IEnumerable <T> deamination using [DataContract] does not work

Rather new to Json.net and tried the following simple example of serialization and then deserialization of the object that received the error below:

using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.Text; using System.Collections; namespace Timehunter.Base.ServicesTests { /// <summary> /// Summary description for JsonError /// </summary> [TestClass] public class JsonError { [TestMethod] public void TestMethod1() { JsonSerializerSettings serializerSettings = new JsonSerializerSettings() { DateFormatHandling = DateFormatHandling.IsoDateFormat, DateParseHandling = Newtonsoft.Json.DateParseHandling.DateTimeOffset }; Act.Activities acts = new Act.Activities(); acts.Add(new Act.Activity() { Id = 1, Name = "test1" }); acts.Add(new Act.Activity() { Id = 2, Name = "test2" }); string json = Newtonsoft.Json.JsonConvert.SerializeObject(acts, serializerSettings); Timehunter.Base.Act.Activities target = Newtonsoft.Json.JsonConvert.DeserializeObject<Timehunter.Base.Act.Activities>(json, serializerSettings); Assert.AreEqual("test1", target.List[0].Name, "Name of first activity"); } } } namespace Timehunter.Base { [DataContract] public class Activity { private int _id; private string _name; [DataMember] public int Id { get { return this._id; } set { this._id = value; } } [DataMember] public string Name { get { return this._name; } set { this._name = value; } } public Activity() { this._id = new int(); this._name = string.Empty; } } [DataContract] public class Activities : IEnumerable<Activity> { private List<Activity> _list; [DataMember] public List<Activity> List { get { return this._list; } set { this._list = value; } } public Activities() { this._list = new List<Activity>(); } public void Add(Activity item) { this._list.Add(item); } public bool Remove(Activity item) { return this._list.Remove(item); } public int Count() { return this._list.Count; } public IEnumerator<Activity> GetEnumerator() { return this._list.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } } 

And then I get the following error:

 Test Name: TestMethod1 Test FullName: Timehunter.Base.ServicesTests.JsonError.TestMethod1 Test Source: C:\Users\hawi.HAWCONS\Documents\Visual Studio 2015\Projects\Timehunter.Data\Timehunter.Base.ServicesTests\JsonError.cs : line 67 Test Outcome: Failed Test Duration: 0:00:00,2038359 Result StackTrace: at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateNewList(JsonReader reader, JsonArrayContract contract, Boolean& createdFromNonDefaultCreator) at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateList(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, Object existingValue, String id) at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue) at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent) at Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType) at Newtonsoft.Json.JsonConvert.DeserializeObject(String value, Type type, JsonSerializerSettings settings) at Newtonsoft.Json.JsonConvert.DeserializeObject[T](String value, JsonSerializerSettings settings) at Timehunter.Base.ServicesTests.JsonError.TestMethod1() in C:\Users\hawi.HAWCONS\Documents\Visual Studio 2015\Projects\Timehunter.Data\Timehunter.Base.ServicesTests\JsonError.cs:line 79 Result Message: Test method Timehunter.Base.ServicesTests.JsonError.TestMethod1 threw exception: Newtonsoft.Json.JsonSerializationException: Cannot create and populate list type Timehunter.Base.Act.Activities. Path '', line 1, position 1. 

What am I doing wrong?

+3
source share
1 answer

Update 2

This returns to 11.0.2 for backward compatibility. Refer to the original answer for a solution.

Update

Marked as Problem # 1598: DataContractAttribute does not cause JSon object serialization for IEnumerable and fixed in commit e9e2d00 . It should be in the next release after 10.0.3 , which is likely to be version 11 of Json.NET.

Original answer

I noticed that you noted your Activities [DataContract] and [DataMember] class:

 [DataContract] public class Activities : IEnumerable<Activity> { private List<Activity> _list; [DataMember] public List<Activity> List { get { return this._list; } set { this._list = value; } } // ... } 

Using [DataContact] will cause the DataContractJsonSerializer serialize IEnumerable<T> as a JSON object with properties, and not as a JSON array. Since Json.NET supports data attribute attributes when applied to non-enumerable numbers, you might think that it will respect them also for enumerations and collections.

However, this does not seem to be implemented. If I serialize your class using a DataContractJsonSerializer , I see

 {"List":[{"Id":1,"Name":"test1"},{"Id":2,"Name":"test2"}]} 

But if I serialize with Json.NET, I see that [DataContract] been ignored:

 [{"Id":1,"Name":"test1"},{"Id":2,"Name":"test2"}] 

It then throws an exception during deserialization because it does not know how to add members to the IEnumerable<Activity> class. (It could add members if your class implemented ICollection<Activity> , or had a constructor with IEnumerable<Activity> .)

So, should this work? Documentation Page Serialization Attributes :

DataContractAttribute can be used as a substitute for JsonObjectAttribute. DataContractAttribute will use default membership serialization for rejection.

This means that Json.NET should work as you expect. You can report this if you want - at least the documentation should be clarified.

As a workaround , if you want to force Json.NET to serialize the collection as an object, you need to use [JsonObject] instead:

 [DataContract] [JsonObject(MemberSerialization = MemberSerialization.OptIn)] public class Activities : IEnumerable<Activity> { private List<Activity> _list; [DataMember] [JsonProperty] public List<Activity> List { get { return this._list; } set { this._list = value; } } // Remainder unchanged. } 

If you have many enumerated classes using [DataContract] or you cannot add a Json.NET dependency to your models, you can create a custom ContractResolver that checks for the presence of [DataContract] on the enumerated classes and serializes them as objects:

 public class DataContractForCollectionsResolver : DefaultContractResolver { // As of 7.0.1, Json.NET suggests using a static instance for "stateless" contract resolvers, for performance reasons. // http://www.newtonsoft.com/json/help/html/ContractResolver.htm // http://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_Serialization_DefaultContractResolver__ctor_1.htm // "Use the parameterless constructor and cache instances of the contract resolver within your application for optimal performance." static DataContractForCollectionsResolver instance; static DataContractForCollectionsResolver() { instance = new DataContractForCollectionsResolver(); } public static DataContractForCollectionsResolver Instance { get { return instance; } } protected DataContractForCollectionsResolver() : base() { } protected override JsonContract CreateContract(Type objectType) { var t = (Nullable.GetUnderlyingType(objectType) ?? objectType); if (!t.IsPrimitive && t != typeof(string) && !t.IsArray && typeof(IEnumerable).IsAssignableFrom(t) && !t.GetCustomAttributes(typeof(JsonContainerAttribute),true).Any()) { if (t.GetCustomAttributes(typeof(DataContractAttribute),true).Any()) return base.CreateObjectContract(objectType); } return base.CreateContract(objectType); } } 

Then use the following settings:

 var serializerSettings = new JsonSerializerSettings() { DateFormatHandling = DateFormatHandling.IsoDateFormat, DateParseHandling = Newtonsoft.Json.DateParseHandling.DateTimeOffset, ContractResolver = DataContractForCollectionsResolver.Instance }; 
+4
source

All Articles