Rather new to Json.net and tried the following simple example serializing and then deserialing an object getting the error below:
using Microsoft.VisualStudi
Update 2
This is getting reverted back in 11.0.2 for backwards compatibility. Refer to the original answer for a solution.
Update
Reported as Issue #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 will probably be Json.NET version 11.
Original answer
I notice you have marked your Activities
class with [DataContract]
and [DataMember]
:
[DataContract]
public class Activities : IEnumerable<Activity>
{
private List<Activity> _list;
[DataMember]
public List<Activity> List
{
get { return this._list; }
set { this._list = value; }
}
// ...
}
Applying [DataContact]
will cause DataContractJsonSerializer
to serialize an IEnumerable<T>
as a JSON object with properties, rather than as a JSON array. Since Json.NET supports data contract attributes when applied to non-enumerables, you might be thinking that it will respect them on enumerables and collections as well.
However, it appears this is not implemented. If I serialize your class with DataContractJsonSerializer
, I see
{"List":[{"Id":1,"Name":"test1"},{"Id":2,"Name":"test2"}]}
But if I serialize with Json.NET, I see that the [DataContract]
was ignored:
[{"Id":1,"Name":"test1"},{"Id":2,"Name":"test2"}]
Then later it throws an exception during deserialization because it doesn't know how to add members to your IEnumerable<Activity>
class. (It would have been able to add members if your class implemented ICollection<Activity>
, or had a constructor with an IEnumerable<Activity> argument.)
So, should this work? The documentation page Serialization Attributes states:
The DataContractAttribute can be used as substitute for JsonObjectAttribute. The DataContractAttribute will default member serialization to opt-in.
Which implies that Json.NET ought to work the way you expect. You could report an issue about it if you want -- at least the documentation should be clarified.
As a workaround, if you want to force Json.NET to serialize a 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 enumerable classes with [DataContract]
applied, or cannot add a dependency on Json.NET to your models, you can create a custom ContractResolver that checks for the presence of [DataContract]
on enumerable 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
};