Click here to Skip to main content
Click here to Skip to main content

Tagged as

Expression Parsing and Nested Properties

, 25 Feb 2014
Rate this:
Please Sign up or sign in to vote.
Expression Parsing and Nested Properties

Introduction

In one of the projects I was working on, I needed to get property value from property path and property path from expression.

First, let's cover the second case.

With expression in the form of nested property value:

()=>object1.object2.object3.object4

we cannot take simple value of some property, because we have only root object, object1.

Instead, we have to take every value of every object in the middle and last object as value of our desired property.

If we would have expression like this:

()=>object1.object2

and value of root object (object1), we can just cast expression above to MemberExpression type and then retrieve name of the property, from property MemberExpression.Member.Name.

But if we need another property of object3 or even deeper, we need to retrieve another, nested MemberExpression from the expression above.

Without knowledge of depth of our expression, we have to repeat that operation as long as property MemberExpression.Expression has value different than null.

To take MemberExpression from given Expression (which can be of many types: MemberExpression, LambdaExpression, UnaryExpression), we can use the following method:

public static MemberExpression GetMemberExpression(Expression expression)
{
    if (expression is MemberExpression)
    {
        return (MemberExpression)expression;
    }
    else if (expression is LambdaExpression)
    {
        var lambdaExpression = expression as LambdaExpression;
        if (lambdaExpression.Body is MemberExpression)
        {
            return (MemberExpression)lambdaExpression.Body;
        }
        else if (lambdaExpression.Body is UnaryExpression)
        {
            return ((MemberExpression)((UnaryExpression)lambdaExpression.Body).Operand);
        }
    }
    return null;
}

This method will return MemberExpression from any of the above types.

Armed with method like this, we can write loop to retrieve property name for all levels of expression. For example, we can use rarely used do...while loop. Of course, we can use while loop, but this way we can have additional learning experience, using less known language constructs. Smile | :)

public static string GetPropertyPath(Expression expr)
{
    var path = new StringBuilder();
    MemberExpression memberExpression = GetMemberExpression(expr);
    do
    {
        if (path.Length > 0)
        {
            path.Insert(0, ".");
        }
        path.Insert(0, memberExpression.Member.Name);
        memberExpression = GetMemberExpression(memberExpression.Expression);
    }
    while (memberExpression != null);
    return path.ToString();
}

In my code, I placed those two methods in one class called ExpressionOperator and then used them in extension for type Object:

public static string GetPropertyPath<TObj, TRet>(this TObj obj, 
	Expression<Func<TObj, TRet>> expr)
{
    return ExpressionOperator.GetPropertyPath(expr);
}

which can be used like this:

object1.GetPropertyPath(o =>o.object2.object3.object4)

which should return "object2.object3.object4" string. With possibility for returning a property path from any expression, we can now write method that returns value of destination property (last in the expression).

Method like this is even simpler than for returning property path. We just need to find value of every property in the middle of path to the point, when we have our destination property. For that, we can use while loop. Smile | :)

public static object GetPropertyValue(this object obj, string propertyPath)
{
    object propertyValue = null;
    if (propertyPath.IndexOf(".") < 0)
    {
        var objType = obj.GetType();
        propertyValue = objType.GetProperty(propertyPath).GetValue(obj, null);
        return propertyValue;
    }
    var properties = propertyPath.Split('.').ToList();
    var midPropertyValue = obj;
    while (properties.Count > 0)
    {
        var propertyName = properties.First();
        properties.Remove(propertyName);
        propertyValue = midPropertyValue.GetPropertyValue(propertyName);
        midPropertyValue = propertyValue;
    }
    return propertyValue;
}

The above code returns value of property by reflection. Property name is taken by splitting property path into parts separated by '.'. For example, we can use this method in the following way:

object1.GetPropertyValue(o=>o.object2.object3.object4);

This is really simple example. For better usability, you should add validating for root object (if this object has value in the first place) and for any of mid-objects. They did not have to have value too, they could not have value because of lack of initialization of object tree. Also a good idea is to add boolean flag, if we want method to return an error in the above cases or false (lack of success) and null value (value of desired property).

Another way to improve things is to add similar way to retrieve type of nested property or set property value by property path given as string.

In the project I was working on, I used this mechanism to retrieve properties path in C# code, transport it to client (it was a web application so client was a browser) and set property value in JavaScript object, which had the same object tree. In other way, I transported value of changed property at the client side and its path, to apply changes at server side. Very useful to synchronize two different data schemes.

Attached to this article (see download link at the top) is an example console application for retrieving property path and property value of a nested property.

I hope this will help. Smile | :)

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

n.podbielski
Software Developer
Poland Poland
No Biography provided

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Mobile
Web03 | 2.8.140827.1 | Last Updated 25 Feb 2014
Article Copyright 2014 by n.podbielski
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid