Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Json.Net serialization of IEnumerable with TypeNameHandling=auto

Tags:

c#

json.net

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.

like image 646
Davide Icardi Avatar asked Jan 07 '16 18:01

Davide Icardi


2 Answers

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
like image 108
Meirion Hughes Avatar answered Sep 24 '22 08:09

Meirion Hughes


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.

like image 39
vendettamit Avatar answered Sep 26 '22 08:09

vendettamit