繁体   English   中英

当 class 具有列表的“快速”访问属性时,Newtonsoft json 存在反序列化错误

[英]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的内容两次。

详细来说,反序列化过程中的事件顺序如下:

  1. 由于"Children"属性恰好在 JSON 中排在第一位,因此Children数组已完全反序列化。

  2. 此时, FirstChild现在返回一个Child实例而不是抛出System.InvalidOperationException: Sequence contains no elements异常。

  3. 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM