【问题标题】:Json.Net serialization of IEnumerable with TypeNameHandling=auto使用 TypeNameHandling=auto 对 IEnumerable 进行 Json.Net 序列化
【发布时间】:2016-08-10 03:56:27
【问题描述】:

根据Json.Net documentation,所有IEnumerable类型都应该序列化为json数组。

所以我期待以下课程:

public class MyClass
{
    public IEnumerable<string> Values { get; set; }
}

被序列化为:

{
    "Values": []
}

问题是当我使用TypeNameHandling=Auto 时,我得到:

{
    "Values": {
        "$type": "System.String[], mscorlib",
        "$values": []
    }
}

我需要TypeNameHandling=Auto 来获取其他属性,但我希望IEnumerable 使用默认序列化。其他类型(例如IList)按预期工作。

这是一个错误还是我遗漏了什么?

这里是重现问题的完整代码:

    [Test]
    public void Newtonsoft_serialize_list_and_enumerable()
    {
        var target = new Newtonsoft.Json.JsonSerializer
        {
            TypeNameHandling = TypeNameHandling.Auto
        };

        var myEvent = new MyClass
        {
            Values = new string[0]
        };

        var builder = new StringWriter();
        target.Serialize(builder, myEvent);
        var json = JObject.Parse(builder.ToString());

        Assert.AreEqual(JTokenType.Array, json["Values"].Type);
    }

    public class MyClass
    {
        public IEnumerable<string> Values { get; set; }
    }

我正在使用 Newtonsoft 7.0.1。

更新: 这是另一个使用更多类型的测试:

    [Test]
    public void Newtonsoft_serialize_list_and_enumerable()
    {
        var target = new Newtonsoft.Json.JsonSerializer
        {
            TypeNameHandling = TypeNameHandling.Auto
        };

        var myEvent = new MyClass
        {
            Values1 = new string[0],
            Values2 = new List<string>(),
            Values3 = new string[0],
            Values4 = new List<string>(),
            Values5 = new string[0]
        };

        var builder = new StringWriter();
        target.Serialize(builder, myEvent);
        var json = builder.ToString();
    }

    public class MyClass
    {
        public IEnumerable<string> Values1 { get; set; }
        public IEnumerable<string> Values2 { get; set; }
        public IList<string> Values3 { get; set; }
        public IList<string> Values4 { get; set; }
        public string[] Values5 { get; set; }
    }

这是结果:

{
    "Values1": {
        "$type": "System.String[], mscorlib",
        "$values": []
    },
    "Values2": [],
    "Values3": {
        "$type": "System.String[], mscorlib",
        "$values": []
    },
    "Values4": [],
    "Values5": []
}

我还是不明白为什么我会根据组合得到不同的结果。

【问题讨论】:

    标签: c# json.net


    【解决方案1】:

    当反序列化为IEnumerableIList 字段时,Json.Net 的默认自动行为是创建一个List 实例。如果您分配 Array 实例,那么将对象恢复到原始实例状态的唯一方法是让 Json.Net 添加 $type 元数据,这就是您所看到的。 即有很多方法可以反序列化为 IEnumerable 字段。

    通过使用List&lt;string&gt; 实例:

    var myEvent = new MyClass
    {
        Values = new List<string>(),
    };
    

    与:

    public class MyClass
    {
       public IEnumerable<string> Values { get; set; }
    }    
    

    结果(序列化时):

    {"Values":["hello"]}
    

    此外,如果您使用显式可构造类型或使用默认 List,则 Json.Net 将使用该类型并省略 $type

    例如:

    string[] Values { get; set; } = new string[0]; // not needed
    IEnumerable<string> Values { get; set; } = new string[0]; //$type needed
    IEnumerable<string> Values { get; set; } = new Stack<string>(); //$type needed
    IEnumerable<string> Values { get; set; } = new List<string>; // not needed
    List<string> Values { get; set; } = new List<string>; // not needed
    
    ObservableCollection<string> Values { get; set; } = 
        new ObservableCollection<string>(); // not needed
    

    【讨论】:

    • 谢谢,我可能会使用这个解决方案。但我认为这是一个错误,因为所有类型的行为都应该相同,相反,如果我使用Listarray,我会得到不同的结果。
    • 对我来说,这是不正确的。如果您将new string[0] 设置为IList 字段,您将获得相同的行为。正如我所指出的,默认的反序列化行为是为EnumerableIList 接口字段创建一个List 实例。如果您指定一个明确的对象类型,它将使用它来代替;但是有很多方法可以实例化IEnumerable。如果您违反默认设置,那么确切知道您使用的实例的唯一方法是添加 $type 元数据
    【解决方案2】:

    这不是错误,但这是预期的行为。您正在全局设置TypeNameHandling。您可以使用如下属性标记属性,而不是全局设置类型名称处理,以避免在设置 TypeNameHandling.Auto 时包含它们:

    [JsonProperty(TypeNameHandling = TypeNameHandling.None)]
    

    这是一个示例:

        public class MyClass
        {
            // Indicate the property not to apply any type handling
            [JsonProperty(TypeNameHandling=TypeNameHandling.None)]
            public IList<string> Values { get; set; }
    
            public string Name { get; set; }
        }
    

    这将为您提供所需的结果,而不会受到全局声明的任何 TypeNameHandling 设置的影响。

    【讨论】:

    • 通常我不喜欢在我的代码中插入序列化属性,但最终这可能是一个很好的解决方案。但我不明白为什么其他类型的行为不同。例如,如果我将属性声明为 IList 并将其设置为 List。我认为这是一个错误...
    • 这是支持正确反序列化派生类型的行为。当您将其声明为全局时,它将应用于所有属性。这就是对 TypeNameHandling 的支持作为属性属性提供的原因。它也可以通过其他方式完成,例如将需要特殊 TypeHandling 的属性标记为 Auto 并且根本不使用全局设置。
    • 是的,我明白了,但为什么 IList 有不同的行为?文档似乎证实了我的期望。
    • 可以分享链接吗?因为我通过在我的机器上运行代码来确认它与 IList 和 IEnumerable 的结果相同。
    • 我已经用更多案例更新了这个问题。我不明白为什么arrayList 之间的行为不同。这里是documentation。最终,当类型不匹配时,我希望总是有一个包装的对象,但事实并非如此。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-29
    • 1970-01-01
    • 1970-01-01
    • 2021-11-10
    相关资源
    最近更新 更多