eugeneK
eugeneK

Reputation: 11146

Dynamic json serialization filtering via reflection

I want to create a dynamic contract resolver for json.net which will exclude fields in runtime. The idea is to pass into constructor something which will exclude certain fields inside CreateProperties override.

So far i came up with passing PropertyInfo[] which relies on Json / Class properties name equality which is not good in long run ( ie. i want to override json property name to something shorter ). Another issue with solution is that i need to pass PropertyInfo[] which is not intuitive in my opinion.

Maybe there is a way to use LINQ expressions to rewrite this class in better way. For example like passing List<Func<T,TOut>> then compiling and extracting parameters via reflection. It will be more dynamic but would not solve the issue with Json / Class property name equality.

Any suggestions, i'm stuck....

public class DynamicContractResolver : DefaultContractResolver
{
    private readonly PropertyInfo[] m_propertiesExclusion;
    public DynamicContractResolver(PropertyInfo[] propertiesExclusion)
    {
        m_propertiesExclusion = propertiesExclusion;
    }

    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    {
        IList<JsonProperty> jsonProperties = base.CreateProperties(type, memberSerialization);

        IEnumerable<string> filteredOutProperties = m_propertiesExclusion.Select(i => i.Name);
        jsonProperties = jsonProperties
            .Where(i => !filteredOutProperties.Contains(i.PropertyName))
            .ToList();

        return jsonProperties;
    }
}

Upvotes: 1

Views: 1393

Answers (1)

Andrew Whitaker
Andrew Whitaker

Reputation: 126082

Here's an implementation that takes any number of Expression<Func<T, object>>s and excludes the properties they reference. The code for extracting property names from expressions was taken from this answer.

public class DynamicContractResolver<T> : DefaultContractResolver
{
    private readonly HashSet<string> propertiesToExclude;

    public DynamicContractResolver(
        params Expression<Func<T, object>>[] propertyExpressions)
    {
        this.propertiesToExclude = new HashSet<string>();

        foreach (Expression<Func<T, object>> expression in propertyExpressions)
        {
            string propertyName = GetPropertyNameFromExpression(expression);

            this.propertiesToExclude.Add(propertyName);
        }
    }

    protected override IList<JsonProperty> CreateProperties(
        Type type, MemberSerialization memberSerialization)
    {
        IList<JsonProperty> jsonProperties =
            base.CreateProperties(type, memberSerialization);

        if (typeof(T).IsAssignableFrom(type))
        {
            jsonProperties = jsonProperties
                .Where(pr => !this.propertiesToExclude.Contains(pr.PropertyName))
                .ToList();
        }

        return jsonProperties;
    }

    // https://stackoverflow.com/a/2916344/497356
    private string GetPropertyNameFromExpression(
        Expression<Func<T, object>> expression)
    {
        MemberExpression body = expression.Body as MemberExpression;

        if (body == null)
        {
            UnaryExpression ubody = (UnaryExpression)expression.Body;
            body = ubody.Operand as MemberExpression;
        }

        return body.Member.Name;
    }
}

Here's an example using it:

var resolver = new DynamicContractResolver<MyClass>(
    mc => mc.MyIntegerProperty,
    mc => mc.MyBoolProperty);

var myClass = new MyClass
{
    MyIntegerProperty = 4,
    MyStringProperty = "HELLO",
    MyBoolProperty = true
};

var settings = new JsonSerializerSettings
{
    ContractResolver = resolver,
    Formatting = Newtonsoft.Json.Formatting.Indented
};

string serialized = JsonConvert.SerializeObject(
    myClass, settings);

Console.WriteLine(serialized);

Upvotes: 3

Related Questions