mardi 8 juin 2021

Accessing nested properties and collections using Expression

I have the following classes:

public class Person
{
    [JsonProperty("name")]
    public string Name { get; set; }

    [JsonProperty("age")]
    public int Age { get; set; }

    [JsonProperty("country")]
    public string Country { get; set; }

    [JsonProperty("profession")]
    public Profession Profession { get; set; }

    [JsonProperty("hobbies")]
    public List<string> Hobbies { get; set; }
}

public class Profession
{
    [JsonProperty("name")]
    public string ProfessionName { get; set; }

    [JsonProperty("industry")]
    public string Industry { get; set; }

    [JsonProperty("salary")]
    public string AverageSalary { get; set; }

    [JsonProperty("activities")]
    public List<WorkActivity> WorkActivities { get; set; }
}

public class WorkActivity
{
    [JsonProperty("id")]
    public int Id { get; set; }

    [JsonProperty("rooms")]
    public List<string> Rooms { get; set; }
}

I have the following classes:

public class PropertiesVisitor : ExpressionVisitor
{
    private readonly Expression param;

    public List<string> Names { get; } = new List<string>();

    public PropertiesVisitor(Expression parameter)
    {
        param = parameter;
    }

    protected override Expression VisitMember(MemberExpression node)
    {
        if (node.Expression == param)
        {
            Names.Add(node.Member.GetCustomAttribute<JsonPropertyAttribute>().PropertyName);
        }

        return base.VisitMember(node);
    }
}

And I call it like this:

private static List<string> FindPropertyNames<T>(Expression<Func<T, object>> e)
{
    var visitor = new PropertiesVisitor(e.Parameters[0]);
    visitor.Visit(e);
    return visitor.Names;
}

public static void Main(string[] args)
{
    var names = FindPropertyNames<Person>(x => new { x.Age, x.Country, x.Profession.AverageSalary, x.Hobbies });
    Console.WriteLine(string.Join(" ", names));
}

It works fine, except for one minor detailed - it doesn't check for nested properties. The output is the following: age country profession hobbies. I want it to be age country profession.salary hobbies.

I've been trying to fix the issue using a different approach, but I am unable to do so fully. I've tried the following:

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

public static string GetPropertyPath<T>(Expression<Func<T, object>> expr)
{
    var path = new StringBuilder();
    MemberExpression me = GetMemberExpression(expr);

    do
    {
        if (path.Length > 0)
        {
            path.Insert(0, ".");
        }
        path.Insert(0, me.Member.GetCustomAttribute<JsonPropertyAttribute>().PropertyName);
        me = GetMemberExpression(me.Expression);
    }
    while (me != null);
    return path.ToString();
}

It kind of does the job - but it can't take more than one property.

I call it like this:

var x = GetPropertyPath<Person>(p => p.Profession.AverageSalary);
Console.WriteLine(x);

I want to be able to send multiple properties, as in the first version. Also, I am unsure how to pass the following person.Profession.WorkActivities.Rooms as a parameter, because it is a list. I want to get profession.activities.rooms as output for it.





Aucun commentaire:

Enregistrer un commentaire