C# 序列化时不忽略DefaultValue中声明的数组

C# 序列化时不忽略DefaultValue中声明的数组,c#,json.net,C#,Json.net,我使用JSON作为配置文件,我想为数组设置一个默认值。我希望序列化的JSON忽略数组,如果它等于DefaultValueAttribute,那么如果我在程序的第二个版本中决定更改DefaultValues,那么将加载新的默认值,而不是原始默认值的未触及副本 我的问题是,如果数组引用没有更改,但程序中的其他代码正在更改数组,但保留数组中的值,那么代码就可以工作。(该程序维护该类的许多克隆,因此无法避免) 下面是使用c#interactive显示的问题: using System.Component

我使用JSON作为配置文件,我想为数组设置一个默认值。我希望序列化的JSON忽略数组,如果它等于
DefaultValueAttribute
,那么如果我在程序的第二个版本中决定更改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]}
如您所见,第一个Serialized对象可以工作,但是如果数组内容相同但不是相同的数组引用,它会将默认值写入json,这是我想要避免的


在这种情况下,我有没有办法让Json.net忽略阵列?

除了您确定的问题之外,您当前的体系结构还存在一些其他问题:

  • 您正在忽略for
    DefaultValueAttribute

    A
    DefaultValueAttribute
    不会导致成员自动初始化为该属性的值。您必须在代码中设置初始值。

  • 您当前的实现会导致具有默认值的
    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 }));
    
    演示小提琴#1

    如果决定对数组使用
    DefaultValueHandling
    DefaultValueAttribute
    ,则需要:

    并在序列化时将其用作:

    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的预期功能是否支持数组值默认值


    嗯,只是个主意。与其直接使用数组作为属性的默认值,不如将其包装在一个自定义类型中,该自定义类型的Equals方法将被重写(不要忘记也重写GetHashCode),以便它按内容比较数组。如果序列化程序将属性值(自定义包装器)与实际字段值(数组)进行比较,则应调用重写的Equals方法,从而使其工作。我希望如此。如前所述,只是一个想法;我还没有测试它是否真的有效或者是否会有任何意外的副作用…谢谢。我知道原因,也知道它可能不是预期的功能。我正在制作一个没有经验的程序员将要使用的系统,因此一个定制的ContractResolver可能是一个不错的选择,我只是不知道如果没有这样的例子该如何进行。非常感谢。
    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();
    
    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) == "{}");