亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

序列化時未忽略在 DefaultValue 中聲明的數組

序列化時未忽略在 DefaultValue 中聲明的數組

C#
三國紛爭 2022-07-10 16:02:04
我正在使用 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 忽略數組嗎?
查看完整描述

1 回答

?
慕的地6264312

TA貢獻1817條經驗 獲得超6個贊

除了您發現的問題之外,您當前的架構還有一些其他問題:


您忽略了記錄在案的建議DefaultValueAttribute:


ADefaultValueAttribute不會導致成員使用屬性值自動初始化。您必須在代碼中設置初始值。


您當前的實現導致具有默認值的所有實例共享對數組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在這里。


如果您決定使用DefaultValueHandling和DefaultValueAttribute用于數組,您將需要一個自定義合約解析器:


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 的預期功能。


查看完整回答
反對 回復 2022-07-10
  • 1 回答
  • 0 關注
  • 99 瀏覽

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號