Flavien Flavien - 17 days ago 8
C# Question

JSON.NET contract resolved is not applied to inner objects

I am trying to deserialize a complex JSON structure such as this using JSON.NET:

{
"a": 1,
"b": "value",
"c": {
"d": "hello"
}
}


I need to apply a value converter to the
c
property.

I am using a contract resolver this looks like this:

public class CustomPropertyResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
IList<JsonProperty> props = base.CreateProperties(type, memberSerialization);

foreach (JsonProperty prop in props.Where(p => p.PropertyType == typeof(string)))
{
PropertyInfo pi = type.GetProperty(prop.UnderlyingName);
if (pi != null && pi.GetCustomAttribute(typeof(MyCustomAttribute), true) != null)
{
prop.ValueProvider = new MyStringValueProvider(pi);
}
}

return props;
}


This works if the property on which the value provider needs to be applied is at the top level (like
b
), but it doesn't work for a nested value (
d
).
CreateProperties
is simply not being called for the nested object.

How can I make sure the contract resolver is applied to the nested objects as well?

Edit: Here is a complete minimal repro:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;

namespace JsonNetStackOverflow
{
public class Program
{
public static void Main(string[] args)
{
string json = "{'a':'olleh', 'b': { 'c': 'dlrow' } }";

JsonSerializerSettings settings = new JsonSerializerSettings()
{
ContractResolver = new ReversePropertyResolver()
};

ClassA result = JsonConvert.DeserializeObject<ClassA>(json, settings);

Console.WriteLine(result.A);
Console.WriteLine(result.B.C);

Console.ReadLine();
}
}

public class ReverseAttribute : Attribute
{ }

public class ReversePropertyResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
IList<JsonProperty> props = base.CreateProperties(type, memberSerialization);

foreach (JsonProperty prop in props.Where(p => p.PropertyType == typeof(string)))
{
PropertyInfo pi = type.GetProperty(prop.UnderlyingName);
if (pi != null && pi.GetCustomAttribute(typeof(ReverseAttribute), true) != null)
{
prop.ValueProvider = new ReverseValueProvider(pi);
}
}

return props;
}
}

public class ReverseValueProvider : IValueProvider
{
private readonly PropertyInfo targetProperty;

public ReverseValueProvider(PropertyInfo targetProperty)
{
this.targetProperty = targetProperty;
}

public object GetValue(object target)
{
string value = (string)targetProperty.GetValue(target);
return new string(value.Reverse().ToArray());
}

public void SetValue(object target, object value)
{
targetProperty.SetValue(target, new string(((string)value).Reverse().ToArray()));
}
}

public class ClassA
{
public ClassA(string a, ClassB b)
{
this.A = a;
this.B = b;
}

[Reverse]
[JsonProperty("a")]
public string A { get; set; }

[JsonProperty("b")]
public ClassB B { get; set; }
}

public class ClassB
{
public ClassB(string c)
{
this.C = c;
}

[Reverse]
[JsonProperty("c")]
public string C { get; set; }
}
}

dbc dbc
Answer

The reason your ReverseValueProvider.SetValue() method is not called is that your types ClassA and ClassB are being constructed with a parameterized constructor, and the values being passed in are the values you want to reverse:

public class ClassB
{
    public ClassB(string c)
    {
        // You would like c to be reversed but it's not since it's never set via its setter.
        this.C = c;
    }

    [Reverse]
    [JsonProperty("c")]
    public string C { get; set; }
}

Because Json.NET is populating these values via the constructor, it never calls the corresponding property setters, and the strings are never reversed.

To ensure the setters are called (and thus the strings are reversed), you can add private parameterless constructors to your types, and mark them with [JsonConstructor]:

public class ClassA
{
    [JsonConstructor]
    ClassA() {}

    public ClassA(string a, ClassB b)
    {
        this.A = a;
        this.B = b;
    }

    [Reverse]
    [JsonProperty("a")]
    public string A { get; set; }

    [JsonProperty("b")]
    public ClassB B { get; set; }
}

public class ClassB
{
    [JsonConstructor]
    ClassB() { }

    public ClassB(string c)
    {
        this.C = c;
    }

    [Reverse]
    [JsonProperty("c")]
    public string C { get; set; }
}