小编典典

反序列化IEnumerable 应用[DataContract]的方法不起作用

json

并不是Json.net的新手,而是尝试了以下简单示例,将其序列化然后反序列化,得到以下错误:

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();
        }
    }
}

然后我得到以下错误:

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.

我究竟做错了什么?


阅读 292

收藏
2020-07-27

共1个答案

小编典典

更新2

为了向后兼容,在11.0.2中将其还原。请参阅原始答案以获取解决方案。

更新资料

报告为
问题#1598:DataContractAttribute不会导致IEnumerable的JSon对象序列化,
并已在commit中修复e9e2d00。它应该在10.0.3之后的下一发行版中,它可能是Json.NET版本11。

原始答案

我注意到您Activities[DataContract]和标记了您的班级[DataMember]

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

应用[DataContact]将导致DataContractJsonSerializer将一个序列化为IEnumerable<T>带有属性的JSON对象,而不是JSON数组。由于Json.NET
在应用于非枚举时支持数据协定属性,因此您可能会认为它也会在枚举和集合中尊重它们。

但是,看来这没有实现。如果我使用序列化您的课程DataContractJsonSerializer,我会看到

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

但是,如果我使用Json.NET进行序列化,则会发现[DataContract]忽略了:

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

然后,稍后它将在反序列化期间引发异常,因为它不知道如何向IEnumerable<Activity>类中添加成员。(如果您的类已实现ICollection<Activity>,或者具有带IEnumerable<Activity>参数的构造函数,则可以添加成员。)

那么,这应该工作吗?文档页面“
序列化属性”指出:

DataContractAttribute可以替代JsonObjectAttribute。DataContractAttribute将默认成员序列化以选择加入。

这意味着Json.NET应该按照您期望的方式工作。如果需要,可以报告有关此问题的信息-
至少应弄清楚文档。

解决方法是
,如果要强制Json.NET将集合序列化为对象,则需要使用[JsonObject]

[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.
}

如果您[DataContract]应用了许多可枚举的类,或者无法在模型中添加对Json.NET的依赖关系,则可以创建一个自定义项ContractResolver,以检查[DataContract]on枚举类的存在并将它们序列化为对象:

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);
    }
}

然后使用以下设置:

var serializerSettings = new JsonSerializerSettings()
{
    DateFormatHandling = DateFormatHandling.IsoDateFormat,
    DateParseHandling = Newtonsoft.Json.DateParseHandling.DateTimeOffset,
    ContractResolver = DataContractForCollectionsResolver.Instance
};
2020-07-27