Davide Icardi Davide Icardi - 3 months ago 51
C# Question

Json.Net serialization of IEnumerable with TypeNameHandling=auto

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.

Answer

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