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



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

我的问题是,如果数组引用没有改变,那么代码就可以工作,但程序中的其他代码正在改变数组,但保留其中的值。

以下是使用c#interactive显示的问题:

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忽略数组吗?

除了您发现的问题之外,您当前的体系结构还有几个其他问题:

  1. 您忽略了DefaultValueAttribute:的文档建议

    DefaultValueAttribute不会导致成员自动使用属性值进行初始化。您必须在代码中设置初始值

  2. 您当前的实现导致具有默认值的A的所有实例共享对int[3] { 4, 6, 12 }数组的单个全局实例的引用。由于数组实际上不是只读的,这意味着修改A的一个实例将使用默认值修改A的所有其他当前和未来实例:

    var serializerSettings = new JsonSerializerSettings
    {
    DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate,
    };
    var a1 = JsonConvert.DeserializeObject<A>("{}", serializerSettings);
    // The following succeeds
    Assert.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
{
static readonly int[] SomeArrayDefaultValue = new int[] { 4, 6, 12 };
// Disable global settings for NullValueHandling and DefaultValueHandling
[JsonProperty(NullValueHandling = NullValueHandling.Include, DefaultValueHandling = DefaultValueHandling.Include)]
public int[] SomeArray = (int[])SomeArrayDefaultValue.Clone();
public bool ShouldSerializeSomeArray()
{
return !(SomeArray != null && SomeArray.SequenceEqual(SomeArrayDefaultValue));
}
}

在这里演示小提琴#1。

如果您决定将DefaultValueHandlingDefaultValueAttribute用于阵列,那么您将需要一个自定义的合约解析器:

public class ArrayDefaultValueContractResolver : DefaultContractResolver
{
class ArrayDefaultValueProvider : IValueProvider
{
readonly IValueProvider baseProvider;
readonly System.Array defaultValue;
public ArrayDefaultValueProvider(IValueProvider baseProvider, System.Array defaultValue)
{
this.baseProvider = baseProvider;
this.defaultValue = defaultValue;
}
#region IValueProvider Members
public object GetValue(object target)
{
return baseProvider.GetValue(target);
}
public void SetValue(object target, object value)
{
// Make sure the default value is cloned since arrays are not truly read only.
if (value != null && object.ReferenceEquals(value, defaultValue))
value = defaultValue.Clone();
baseProvider.SetValue(target, value);
}
#endregion
}
static void AddArrayDefaultHandling<T>(JsonProperty property)
{
var defaultValue = (T [])property.DefaultValue;
// If the default value has length > 0, clone it when setting it back into the object.
if (defaultValue.Length > 0)
{
property.ValueProvider = new ArrayDefaultValueProvider(property.ValueProvider, defaultValue);
}
// Add a ShouldSerialize method that checks for memberwise array equality.
var valueProvider = property.ValueProvider;
var oldShouldSerialize = property.ShouldSerialize;
Predicate<object> shouldSerialize = target =>
{
var array = (T[])valueProvider.GetValue(target);
return !(array == null || array.SequenceEqual(defaultValue));
};
if (oldShouldSerialize == null)
property.ShouldSerialize = shouldSerialize;
else
property.ShouldSerialize = (target) => shouldSerialize(target) && oldShouldSerialize(target);
}
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
if (property.PropertyType.IsArray && property.DefaultValue != null && property.DefaultValue.GetType() == property.PropertyType
&& property.PropertyType.GetArrayRank() == 1)
{
typeof(ArrayDefaultValueContractResolver)
.GetMethod("AddArrayDefaultHandling", BindingFlags.Static | BindingFlags.NonPublic)
.MakeGenericMethod(property.PropertyType.GetElementType())
.Invoke(null, BindingFlags.Static | BindingFlags.NonPublic, null, new [] { property }, null);
}
return property;
}
}

要使用它,请在某个地方缓存一个静态实例以提高性能,例如

static IContractResolver resolver = new ArrayDefaultValueContractResolver();

并在序列化时将其用作JsonSerializerSettings.ContractResolver

var serializerSettings = new JsonSerializerSettings
{
DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate,
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.的预期功能

相关内容

  • 没有找到相关文章

最新更新