JsonPropertyAttribute ignored on private property in derived class

I have a problem with Json.Net when serializing derived objects that have private properties. Sth like

public class Base
{
   [JsonProperty]
   private string Type { get { return "Base"; } }
}

public class Inherited : Base
{
   [JsonProperty]
   private string Type { get { return "Inherited"; } }
}

      

When I serialize instances Inherited

, the property is Type

always "Base". The only way I've found this to work is if the property is protected or public and overridden in the subclass.

Why does it work? This is mistake?

+3


source to share


1 answer


It looks like this is the intended behavior of Json.NET. From ReflectionUtils.cs :

    private static void GetChildPrivateProperties(IList<PropertyInfo> initialProperties, Type targetType, BindingFlags bindingAttr)
    {
        // fix weirdness with private PropertyInfos only being returned for the current Type
        // find base type properties and add them to result

        // also find base properties that have been hidden by subtype properties with the same name

        while ((targetType = targetType.BaseType()) != null)
        {
            foreach (PropertyInfo propertyInfo in targetType.GetProperties(bindingAttr))
            {
                PropertyInfo subTypeProperty = propertyInfo;

                if (!IsPublic(subTypeProperty))
                {
                    // have to test on name rather than reference because instances are different
                    // depending on the type that GetProperties was called on
                    int index = initialProperties.IndexOf(p => p.Name == subTypeProperty.Name);
                    if (index == -1)
                    {
                        initialProperties.Add(subTypeProperty);
                    }
                    else
                    {
                        PropertyInfo childProperty = initialProperties[index];
                        // don't replace public child with private base
                        if (!IsPublic(childProperty))
                        {
                            // replace nonpublic properties for a child, but gotten from
                            // the parent with the one from the child
                            // the property gotten from the child will have access to private getter/setter
                            initialProperties[index] = subTypeProperty;
                        }

      

This is where a property list for the type is created, and as you can see, there is code that intentionally prefers identically named properties in the base class to the inherited class.

I don't know why Json.NET does this, you can report the problem and ask why. In the meantime, you can use IContractResolver

to prevent this behavior selectively:

[System.AttributeUsage(AttributeTargets.Property)]
public class JsonPreferDerivedPropertyAttribute : System.Attribute
{
}

public class PreferDerivedPropertyContractResolver : DefaultContractResolver
{
    static PropertyInfo GetDerivedPropertyRecursive(Type objectType, Type stopType, PropertyInfo property)
    {
        var parameters = property.GetIndexParameters().Select(info => info.ParameterType).ToArray();
        for (; objectType != null && objectType != stopType; objectType = objectType.BaseType)
        {
            var derivedProperty = objectType.GetProperty(
                property.Name,
                BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, property.PropertyType,
                parameters,
                null);
            if (derivedProperty == null)
                continue;
            if (derivedProperty == property)
                return derivedProperty;  // No override.
            if (derivedProperty.GetCustomAttribute<JsonPreferDerivedPropertyAttribute>() != null)
                return derivedProperty;
        }
        return null;
    }

    protected override List<MemberInfo> GetSerializableMembers(Type objectType)
    {
        var list = base.GetSerializableMembers(objectType);

        for (int i = 0; i < list.Count; i++)
        {
            var property = list[i] as PropertyInfo;
            if (property == null)
                continue;
            if (property.DeclaringType != objectType)
            {
                var derivedProperty = GetDerivedPropertyRecursive(objectType, property.DeclaringType, property);
                if (derivedProperty == null || derivedProperty == property)
                    continue;
                if (derivedProperty != property 
                    && (property.GetGetMethod(true) == null || derivedProperty.GetGetMethod(true) != null)
                    && (property.GetSetMethod(true) == null || derivedProperty.GetSetMethod(true) != null))
                {
                    list[i] = derivedProperty;
                }
            }
        }

        return list;
    }
}

      

I recommend doing this selectively because I don't quite understand why Json.NET does what it does. The above code only overrides the default behavior for derived class properties using a custom attribute JsonPreferDerivedPropertyAttribute

.



And then use it like:

public class Base
{
    [JsonProperty]
    private string Type { get { return "Base"; } }
}

public class Inherited : Base
{
    [JsonProperty]
    [JsonPreferDerivedPropertyAttribute]
    private string Type { get { return "Inherited"; } }
}

public class VeryInherited : Inherited
{
    [JsonProperty]
    public string VeryInheritedProperty { get { return "VeryInherited"; } }
}

public static class TestOverride
{
    public static void Test()
    {
        var inherited = new Inherited();
        var json1 = JsonConvert.SerializeObject(inherited, Formatting.Indented, new JsonSerializerSettings() { ContractResolver = new PreferDerivedPropertyContractResolver() });

        var veryInherited = new VeryInherited();
        var json2 = JsonConvert.SerializeObject(veryInherited, Formatting.Indented, new JsonSerializerSettings() { ContractResolver = new PreferDerivedPropertyContractResolver() });

        Debug.WriteLine(json1);
        Debug.WriteLine(json2);
    }
}

      

And the outputs are:

{
  "Type": "Inherited"
}

      

and

{
  "VeryInheritedProperty": "VeryInherited",
  "Type": "Inherited"
}

      

+2


source







All Articles