1 回答

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 的預期功能。
- 1 回答
- 0 關注
- 99 瀏覽
添加回答
舉報