Json.Net serialization of IEnumerable with TypeNameHandling=auto

核能气质少年 提交于 2021-02-08 13:11:19

问题


According to Json.Net documentation all IEnumerable types should be serialized as json array.

So I expect the following class:

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

to be serialized as:

{
    "Values": []
}

The problem is that when I use TypeNameHandling=Auto I get:

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

I need TypeNameHandling=Auto for other properties but I expect IEnumerable to use the default serialization. Other types (IList for example) works as expected.

It is a bug or I missing something?

Here the full code to reproduce the problem:

    [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; }
    }

I'm using Newtonsoft 7.0.1.

UPDATE: Here another test using more types:

    [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; }
    }

And this is the results:

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

Again I don't understand why I get different results depending on the combination.


回答1:


The default automatic behaviour of Json.Net, when deserializing into an IEnumerable or IList field, is to create a List instance. If you assign an Array instance, then the only way to restore your object to it original instance state is for Json.Net to add the $type meta data, which is what you are seeing. i.e. there are many ways to deserialize into an IEnumerable field.

By using a List<string> instance:

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

with:

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

results in (when serialized):

{"Values":["hello"]}

Also, if you use an explicit constructable type or use the default List, then Json.Net will use that and omit $type;

For instance:

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



回答2:


This is not a bug but this is expected behavior. You are setting the TypeNameHandling globally. Instead of globally setting the type name handling you can mark properties with attribute like below to avoid them being included for setting TypeNameHandling.Auto:

[JsonProperty(TypeNameHandling = TypeNameHandling.None)]

Here's a sample:

    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; }
    }

This will give you desired result without getting affected by any TypeNameHandling setting declared globally.



来源:https://stackoverflow.com/questions/34662966/json-net-serialization-of-ienumerable-with-typenamehandling-auto

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!