如何在反序列化JSON时将ObjectCreationHandling.Replace应用于所选属性?

KDe*_*ker 7 c# serialization json json.net

我有一个包含一个List<Tuple<int, int, int>>属性的类,其默认构造函数分配列表并用一些默认值填充它,例如:

public class Configuration
{
    public List<Tuple<int, int, int>> MyThreeTuple { get; set; }

    public Configuration()
    {
        MyThreeTuple = new List<Tuple<int, int, int>>();
        MyThreeTuple.Add(new Tuple<int, int, int>(-100, 20, 501));
        MyThreeTuple.Add(new Tuple<int, int, int>(100, 20, 864));
        MyThreeTuple.Add(new Tuple<int, int, int>(500, 20, 1286));
    }
}
Run Code Online (Sandbox Code Playgroud)

当我使用Json.NET从JSON反序列化此类的实例时,JSON中的值将添加到列表中而不是替换列表中的项,从而导致列表具有太多值.在反序列化列表期间,Json.Net调用属性getter中给出了此问题的解决方案,从而导致重复项.

var settings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace };
var config = JsonConvert.DeserializeObject<Configuration>(jsonString, settings);    
Run Code Online (Sandbox Code Playgroud)

这会导致Json.NET为正在反序列化的所有内容分配新的实例.

但是,这引入了另一个问题:我的类存在于更大的对象图中,并且图中的某些类型没有默认构造函数.它们由包含类中的构造函数构造.如果我使用ObjectCreationHandling = ObjectCreationHandling.Replace,Json.NET无法尝试构造这些类型的实例,但有以下异常:

Unable to find a constructor to use for the type MySpecialType. A class 
should either have a default constructor, one constructor with arguments
or a constructor marked with the JsonConstructor attribute. 
Run Code Online (Sandbox Code Playgroud)

如何ObjectCreationHandling.Replace有选择地应用于对象图中的某些属性,而不是其他属性?

dbc*_*dbc 15

您有一些替代方法可以强制替换列表而不是重复使用:

  1. 您可以向list属性添加一个属性,指示应该替换它而不是重用:

    public class Configuration
    {
        [JsonProperty(ObjectCreationHandling = ObjectCreationHandling.Replace)]
        public List<Tuple<int, int, int>> MyThreeTuple { get; set; }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  2. 您可以使用数组而不是列表,因为数组总是被替换.如果您的列表应始终包含三个项目并且从未调整大小,则这可能有意义:

    public class Configuration
    {
        public Tuple<int, int, int>[] MyThreeTuple { get; set; }
    
        public Configuration()
        {
            MyThreeTuple = new[]
            {
                new Tuple<int, int, int>(-100, 20, 501),
                new Tuple<int, int, int>(100, 20, 864),
                new Tuple<int, int, int>(500, 20, 1286),
            };
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  3. 如果您不希望类定义依赖于Json.NET,则可以JsonConverter在反序列化时创建一个清除列表的自定义:

    public class ConfigurationConverter : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            return typeof(Configuration).IsAssignableFrom(objectType);
        }
    
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            var config = (existingValue as Configuration ?? (Configuration)serializer.ContractResolver.ResolveContract(objectType).DefaultCreator());
            if (config.MyThreeTuple != null)
                config.MyThreeTuple.Clear();
            serializer.Populate(reader, config);
            return config;
        }
    
        public override bool CanWrite { get { return false; } }
    
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

    然后使用以下内容JsonSerializerSettings:

    var settings = new JsonSerializerSettings { Converters = new JsonConverter[] { new ConfigurationConverter() } };
    
    Run Code Online (Sandbox Code Playgroud)
  4. 如果您希望替换所有列表属性而不是重复使用,则可以创建ContractResolver执行此操作的自定义:

    public class ListReplacementContractResolver : DefaultContractResolver
    {
        // As of 7.0.1, Json.NET suggests using a static instance for "stateless" contract resolvers, for performance reasons.
        // http://www.newtonsoft.com/json/help/html/ContractResolver.htm
        // http://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_Serialization_DefaultContractResolver__ctor_1.htm
        // "Use the parameterless constructor and cache instances of the contract resolver within your application for optimal performance."
        static readonly ListReplacementContractResolver instance;
    
        // Using a static constructor enables fairly lazy initialization.  http://csharpindepth.com/Articles/General/Singleton.aspx
        static ListReplacementContractResolver() { instance = new ListReplacementContractResolver(); }
    
        public static ListReplacementContractResolver Instance { get { return instance; } }
    
        protected ListReplacementContractResolver() : base() { }
    
        protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
        {
            var jsonProperty = base.CreateProperty(member, memberSerialization);
            if (jsonProperty.ObjectCreationHandling == null && jsonProperty.PropertyType.GetListType() != null)
                jsonProperty.ObjectCreationHandling = ObjectCreationHandling.Replace;
            return jsonProperty;
        }
    }
    
    public static class TypeExtensions
    {
        public static Type GetListType(this Type type)
        {
            while (type != null)
            {
                if (type.IsGenericType)
                {
                    var genType = type.GetGenericTypeDefinition();
                    if (genType == typeof(List<>))
                        return type.GetGenericArguments()[0];
                }
                type = type.BaseType;
            }
            return null;
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

    然后使用以下设置:

    var settings = new JsonSerializerSettings { ContractResolver = ListReplacementContractResolver.Instance };
    
    Run Code Online (Sandbox Code Playgroud)
  5. 如果集合是get-only(在这种情况下不是),请在填充现有对象时添加项目之前查看Clear集合.