【问题标题】:Deserializing an IEnumerable<T> with [DataContract] applied does not work应用 [DataContract] 反序列化 IEnumerable<T> 不起作用
【发布时间】:2016-03-03 17:22:05
【问题描述】:

对 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.

我做错了什么?

【问题讨论】:

    标签: c# json json.net datacontract


    【解决方案1】:

    更新 2

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

    更新

    报告为 Issue #1598: DataContractAttribute does not cause JSon object serialization for IEnumerable 并在提交 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] 将导致DataContractJsonSerializerIEnumerable&lt;T&gt; 序列化为具有属性的JSON 对象,而不是JSON 数组。由于 Json.NET supports data contract attributes 应用于不可枚举时,您可能会认为它也会在可枚举和集合上尊重它们。

    但是,这似乎没有实现。如果我用DataContractJsonSerializer 序列化你的课程,我明白了

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

    但如果我使用 Json.NET 进行序列化,我会看到 [DataContract] 被忽略:

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

    然后它在反序列化期间抛出异常,因为它不知道如何将成员添加到您的 IEnumerable&lt;Activity&gt; 类。 (如果您的类实现了ICollection&lt;Activity&gt;had a constructor with an IEnumerable&lt;Activity&gt; argument,它将能够添加成员。)

    那么,这应该有效吗?文档页面Serialization Attributes 声明:

    DataContractAttribute 可以用作 JsonObjectAttribute 的替代品。 DataContractAttribute 将默认成员序列化为选择加入。

    这意味着 Json.NET 应该按照您期望的方式工作。如果你愿意,你可以report an issue 讨论它——至少应该澄清文档。

    作为一种解决方法,如果您想强制 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 的依赖添加到您的模型中,您可以创建一个custom ContractResolver 来检查可枚举类上是否存在[DataContract] 并将它们序列化作为对象:

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

    【讨论】:

    • Thanx 但我必须稍微更改 CreateContract 方法才能让它为我工作:'code' ... && !t.GetCustomAttributes(typeof(JsonContainerAttribute),true).Any ()) { if (t.GetCustomAttributes(typeof(DataContractAttribute),true).Any()) return base.CreateObjectContract(objectType); ...
    • @hawi - 按照您的建议更新答案。看起来像 Json.NET treats DataContract as inherited even though the data contract serializers do not.
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多