JsonPropertyAttribute ignored on private property in derived class

前端 未结 1 1183
無奈伤痛
無奈伤痛 2021-01-20 04:19

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

public class Base
{
   [JsonProperty]
   private str         


        
相关标签:
1条回答
  • 2021-01-20 04:21

    Looks like this is an 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 the list of properties for a type is generated, 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 might want to report an issue and ask why. In the meantime, you can use an 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 entirely understand why Json.NET does what it does. The code above only overrides the default behavior for derived class properties with the custom JsonPreferDerivedPropertyAttribute attribute applied.

    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"
    }
    
    0 讨论(0)
提交回复
热议问题