Deserializing an IEnumerable<T> with [DataContract] applied does not work

穿精又带淫゛_ 提交于 2019-11-28 02:06:53
dbc

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