[英]Newtonsoft json has a deserialization bug when class has 'quick' access property for list
我有父 class 包含子 POCO 的集合以及提供对集合的第一个成员的快速访问的属性。 子 POCO 类型又包含另一个子集合。 当我使用 Json.NET 向父级舍入时,第一个子级集合的内容会重复。
可以做些什么来防止这种情况发生?
最小可重复性如下:
using System;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Linq;
namespace MyApp // Note: actual namespace depends on the project name.
{
internal class Program
{
static void Main(string[] args)
{
var parent = new Parent
{
Children = new List<Child>
{
new Child
{
Children = new List<GrandChild>
{
new GrandChild { Value = "I am a grand child" },
}
},
new Child(),
}
};
var serializedParent = JsonConvert.SerializeObject(parent);
var deserializedParent = JsonConvert.DeserializeObject<Parent>(serializedParent);
// Expected: 1, Actual: 2
Console.WriteLine(deserializedParent.Children[0].Children.Count);
}
}
public class Parent
{
public List<Child> Children { get; set; } = new List<Child>();
public Child FirstChild => Children.First();
}
public class Child
{
public List<GrandChild> Children { get; set; } = new List<GrandChild>();
}
public class GrandChild
{
public string Value { get; set; }
}
}
演示在这里摆弄一个失败的断言。
您的问题是 Json.NET 在预分配时将填充仅获取属性。 在FirstChild
的情况下,您需要避免这种情况,因为这样做,Json.NET 最终会反序列化FirstChild.Children
的内容两次。
详细来说,反序列化过程中的事件顺序如下:
由于"Children"
属性恰好在 JSON 中排在第一位,因此Children
数组已完全反序列化。
此时, FirstChild
现在返回一个Child
实例而不是抛出System.InvalidOperationException: Sequence contains no elements
异常。
Json.NET 现在在 JSON 中遇到"FirstChild"
属性,将其绑定到Parent.FirstChild
c# 属性。 由于它是非空的,它会填充内容,这意味着FirstChild.Children
被填充两次,如Json.net 反序列化列表给出重复项中所述。
那么,您的解决方法是什么?
首先,如果您不需要FirstChild
中的 FirstChild,您可以简单地用[JsonIgnore]
标记它,这将防止它被序列化或反序列化:
public class Parent
{
public List<Child> Children { get; set; } = new List<Child>();
[JsonIgnore]
public Child FirstChild => Children.First();
}
这也将防止当Children
为空时序列化失败。 演示小提琴在这里。
其次,如果您的 JSON 中必须存在FirstChild
,则可以将JsonConverter
附加到在ReadJson()
中跳过传入的 JSON 并简单地返回现有值的属性:
public class Parent
{
public List<Child> Children { get; set; } = new List<Child>();
[JsonConverter(typeof(SerializeOnlyJsonConverter))]
public Child FirstChild => Children.First();
public bool ShouldSerializeFirstChild() => Children != null && Children.Count > 0;
}
public class SerializeOnlyJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType) => throw new NotImplementedException("This converter should only be applied directly to properties");
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
reader.Skip();
return existingValue;
}
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new NotImplementedException();
}
您还需要添加一个ShouldSerialize
方法以防止当Children
为 null 或为空时序列化失败。 或者你可以修复FirstChild
永远不会抛出异常:
public Child FirstChild => Children?.FirstOrDefault();
演示小提琴#2在这里。
第三,您可以使用Pavlo Lissov GetOnlyContractResolver
这个答案中的 GetOnlyContractResolver 序列化来序列化属性,但不要反序列化 Json.Net 中的属性并将[GetOnlyJsonProperty]
应用于FirstChild
public class Parent
{
public List<Child> Children { get; set; } = new List<Child>();
[GetOnlyJsonProperty]
public Child FirstChild => Children.First();
public bool ShouldSerializeFirstChild() => Children != null && Children.Count > 0;
}
[System.AttributeUsage(System.AttributeTargets.Property, AllowMultiple = false)]
public class GetOnlyJsonPropertyAttribute : Attribute
{
}
public class GetOnlyContractResolver : DefaultContractResolver
{
// From this answer https://stackoverflow.com/a/56885301/3744182
// By https://stackoverflow.com/users/7027460/pavlo-lissov
// To https://stackoverflow.com/questions/31731320/serialize-property-but-do-not-deserialize-property-in-json-net
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
if (property != null)
{
var attributes = property.AttributeProvider.GetAttributes(typeof(GetOnlyJsonPropertyAttribute), true);
if (attributes != null && attributes.Count > 0)
property.ShouldDeserialize = (a) => false;
}
return property;
}
}
然后序列化如下:
IContractResolver resolver = new GetOnlyContractResolver(); // In production code this should be cached statically to improve performance
var settings = new JsonSerializerSettings { ContractResolver = resolver };
var serializedParent = JsonConvert.SerializeObject(parent, settings);
var deserializedParent = JsonConvert.DeserializeObject<Parent>(serializedParent, settings);
var reserializedParent = JsonConvert.SerializeObject(deserializedParent, settings);
演示小提琴#3在这里。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.