序列化时未忽略在 DefaultValue 中声明的数组

我正在使用 JSON 作为配置文件,并且我想要一个数组的默认值。我想让序列化的 JSON 忽略数组,如果它等于,DefaultValueAttribute这样如果我决定在程序的第二个版本中更改 DefaultValues,新的默认值将被加载,而不是原始默认值的未触及副本。


我的问题是,如果数组引用没有改变,代码就可以工作,但是程序中的其他代码正在改变数组但保留其中的值。(该程序维护该类的许多克隆,因此无法避免)。


这是使用 c# 交互式显示的问题:


using System.ComponentModel;

using Newtonsoft.Json;


class A

{

    [DefaultValue(new int[] { 4, 6, 12 })]

    public int[] SomeArray;

}


var serializerSettings = new JsonSerializerSettings

{

    DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate,

};

var a = new A();

JsonConvert.PopulateObject("{}", a, serializerSettings);


Console.WriteLine(JsonConvert.SerializeObject(a, serializerSettings));

// Prints {}


a.SomeArray = new int[] { 4, 6, 12 };

Console.WriteLine(JsonConvert.SerializeObject(a, serializerSettings));

// Prints {"SomeArray":[4,6,12]}

如您所见,第一个 SerializeObject 有效,但如果数组内容相同但不是相同的数组引用,它会将默认值写入 json,我想避免这种情况。


在这种情况下,有什么办法可以让 Json.net 忽略数组吗?


三国纷争
浏览 86回答 1
1回答

慕的地6264312

除了您发现的问题之外,您当前的架构还有一些其他问题:您忽略了记录在案的建议DefaultValueAttribute:ADefaultValueAttribute不会导致成员使用属性值自动初始化。您必须在代码中设置初始值。您当前的实现导致具有默认值的所有实例共享对数组A的单个全局实例的引用。int[3] { 4, 6, 12 }由于数组并不是真正只读的,这意味着修改 的一个实例将使用默认值A修改所有其他当前和未来的实例:Avar serializerSettings = new JsonSerializerSettings{&nbsp; &nbsp; DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate,};var a1 = JsonConvert.DeserializeObject<A>("{}", serializerSettings);// The following succeedsAssert.IsTrue(a1.SomeArray.SequenceEqual(new int[] { 4, 6, 12 }));// Sime SomeArray is a globally shared pointer, this will modify all current and future instances of A!a1.SomeArray[0] = -999;var a2 = JsonConvert.DeserializeObject<A>("{}", serializerSettings);// The following now fails!Assert.IsTrue(a2.SomeArray.SequenceEqual(new int[] { 4, 6, 12 }));避免这些问题的最简单方法是根本不使用DefaultValueHandling数组,而是使用条件属性序列化:class A{&nbsp; &nbsp; static readonly int[] SomeArrayDefaultValue = new int[] { 4, 6, 12 };&nbsp; &nbsp; // Disable global settings for NullValueHandling and DefaultValueHandling&nbsp; &nbsp; [JsonProperty(NullValueHandling = NullValueHandling.Include, DefaultValueHandling = DefaultValueHandling.Include)]&nbsp; &nbsp; public int[] SomeArray = (int[])SomeArrayDefaultValue.Clone();&nbsp; &nbsp; public bool ShouldSerializeSomeArray()&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; return !(SomeArray != null && SomeArray.SequenceEqual(SomeArrayDefaultValue));&nbsp; &nbsp; }}演示小提琴#1在这里。如果您决定使用DefaultValueHandling和DefaultValueAttribute用于数组,您将需要一个自定义合约解析器:public class ArrayDefaultValueContractResolver : DefaultContractResolver{&nbsp; &nbsp; class ArrayDefaultValueProvider : IValueProvider&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; readonly IValueProvider baseProvider;&nbsp; &nbsp; &nbsp; &nbsp; readonly System.Array defaultValue;&nbsp; &nbsp; &nbsp; &nbsp; public ArrayDefaultValueProvider(IValueProvider baseProvider, System.Array defaultValue)&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; this.baseProvider = baseProvider;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; this.defaultValue = defaultValue;&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; #region IValueProvider Members&nbsp; &nbsp; &nbsp; &nbsp; public object GetValue(object target)&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return baseProvider.GetValue(target);&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; public void SetValue(object target, object value)&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // Make sure the default value is cloned since arrays are not truly read only.&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (value != null && object.ReferenceEquals(value, defaultValue))&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; value = defaultValue.Clone();&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; baseProvider.SetValue(target, value);&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; #endregion&nbsp; &nbsp; }&nbsp; &nbsp; static void AddArrayDefaultHandling<T>(JsonProperty property)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; var defaultValue = (T [])property.DefaultValue;&nbsp; &nbsp; &nbsp; &nbsp; // If the default value has length > 0, clone it when setting it back into the object.&nbsp; &nbsp; &nbsp; &nbsp; if (defaultValue.Length > 0)&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; property.ValueProvider = new ArrayDefaultValueProvider(property.ValueProvider, defaultValue);&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; // Add a ShouldSerialize method that checks for memberwise array equality.&nbsp; &nbsp; &nbsp; &nbsp; var valueProvider = property.ValueProvider;&nbsp; &nbsp; &nbsp; &nbsp; var oldShouldSerialize = property.ShouldSerialize;&nbsp; &nbsp; &nbsp; &nbsp; Predicate<object> shouldSerialize = target =>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var array = (T[])valueProvider.GetValue(target);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return !(array == null || array.SequenceEqual(defaultValue));&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; };&nbsp; &nbsp; &nbsp; &nbsp; if (oldShouldSerialize == null)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; property.ShouldSerialize = shouldSerialize;&nbsp; &nbsp; &nbsp; &nbsp; else&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; property.ShouldSerialize = (target) => shouldSerialize(target) && oldShouldSerialize(target);&nbsp; &nbsp; }&nbsp; &nbsp; protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; var property = base.CreateProperty(member, memberSerialization);&nbsp; &nbsp; &nbsp; &nbsp; if (property.PropertyType.IsArray && property.DefaultValue != null && property.DefaultValue.GetType() == property.PropertyType&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; && property.PropertyType.GetArrayRank() == 1)&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; typeof(ArrayDefaultValueContractResolver)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .GetMethod("AddArrayDefaultHandling", BindingFlags.Static | BindingFlags.NonPublic)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .MakeGenericMethod(property.PropertyType.GetElementType())&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .Invoke(null, BindingFlags.Static | BindingFlags.NonPublic, null, new [] { property }, null);&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; return property;&nbsp; &nbsp; }}要使用它,请在某处缓存一个静态实例以提高性能,例如static IContractResolver resolver = new ArrayDefaultValueContractResolver();并JsonSerializerSettings.ContractResolver在序列化时使用它:var serializerSettings = new JsonSerializerSettings{&nbsp; &nbsp; DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate,&nbsp; &nbsp; ContractResolver = resolver,};var a = new A();JsonConvert.PopulateObject("{}", a, serializerSettings);Console.WriteLine(JsonConvert.SerializeObject(a, serializerSettings));Assert.IsTrue(JsonConvert.SerializeObject(a, serializerSettings) == "{}");a.SomeArray = new int[] { 4, 6, 12 };Console.WriteLine(JsonConvert.SerializeObject(a, serializerSettings));Assert.IsTrue(JsonConvert.SerializeObject(a, serializerSettings) == "{}");演示小提琴#2在这里。笔记:合约解析器仅适用于排名为 1 的数组。如果需要,您可以将其扩展到多维数组。合约解析器在将默认值数组实例设置为成员时会自动克隆它,以避免上面提到的问题 #2。如果你不想这样,你可以删除ArrayDefaultValueProvider.不清楚是否支持数组值默认值是 Json.NET 的预期功能。
打开App,查看更多内容
随时随地看视频慕课网APP