【问题标题】:Double included object while serializing collection class序列化集合类时双重包含对象
【发布时间】:2017-03-17 04:15:09
【问题描述】:

我正在尝试序列化 Poll 类,它看起来像这样:

class Poll
{
    (...) //methods
    public AnswersCollection answers { get; set; }
    public TagsCollection tags { get; set; }
    public string question { get; set; }
}

如您所见,我有“TagsCollection”和“AnswersCollection”,它们看起来很相似,所以我只展示其中一个。

class AnswersCollection
{
    (...) //methods
    public List<Answer> answers { get; set; }
}

最后,Answer 课。

 class Answer
{
    (...) //methods
    public string name { get; set; }
    public uint voteQuantity { get; set; }
}

所有类都有默认的公共构造函数(不带参数),因此 JSON.NET 没有任何序列化问题。

问题在于 AnswersCollection(即封装),正因为如此,JSON 输出看起来像这样:

{
    "answers":{
        "answers":[
            {
                "name":"Foo",
                "voteQuantity":45
            },
            {
                "name":"Bar",
                "voteQuantity":30
            }
        ]
    },
    "tags":{
        "tags":[
            {
                "name":"FooTag",
                "id":5
            },
            {
                "name":"BarTag",
                "id":4
            }
        ]
    },
    "question":"Question?"
}

如您所见,问题出在“answers”之类的结构上:{ "answers": [(...)] }

是否可以选择将其序列化为“answers”之类的结构:[(...)] 没有第二个“answers”标签? 我尝试使用“isReference”之类的属性,但没有成功。

【问题讨论】:

  • 你看过 Datacontracts 吗? stackoverflow.com/questions/16943176/…
  • 第一个 answers 是 Poll 类中的 var 名称——第二个 answers 是 AnswersCollection 类中的 var 名称——你得到它们是因为你正在序列化 Poll 类—— - 没有设置可以改变这一点的标志......由于你的类的结构,就是这样
  • @Bret 我知道,我想避免只包含变量或类的名称,而不忽略它的内容,因为所有 Collection 类都封装了一些方法。

标签: c# json serialization json.net


【解决方案1】:

序列化实际上完全符合预期。

我知道它不会完全回答你最初的问题,即是否有办法让 Json.Net 做你想做的事,但你最好的选择是为你的AnswersCollection 拥有继承而不是组合。

既然名字暗示这个类是答案的集合,为什么不让它继承自 List&lt;Answer&gt; 而不是拥有这种类型的属性呢?

如果你真的不能改变你的对象的结构,你可以走很长的路,实现你的自己的 JsonConverter 有一个绝对控制您的属性将如何被序列化/反序列化。

您必须将JsonConverterAttribute 应用于Poll.answers 属性以告诉Json.Net 使用您的自定义序列化程序。

但是,如果可以避免的话,我强烈建议不要采用这种方法。

题外话: 您应该考虑使用 CamelCasePropertyNamesContractResolver 来告诉您的序列化程序在序列化您的属性时使用 CamelCasing,因此您不必在您的属性本身上使用 camelCasing:如果您想遵循通用命名,answers 应该拼写为 Answers实践。

【讨论】:

    【解决方案2】:

    如果您不需要在序列化过程中往返(换句话说,您只需要序列化而不是反序列化),那么获得所需结果的一种简单方法是让您的集合类实现 @ 987654322@这样的:

    class AnswersCollection : IEnumerable<Answer>
    {
        public List<Answer> answers { get; set; }
    
        public IEnumerator<Answer> GetEnumerator()
        {
            return answers != null ? answers.GetEnumerator() : new List<Answer>().GetEnumerator();
        }
    
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }
    

    这是因为 Json.Net 在序列化过程中自动将实现 IEnumerable 的类视为数组。

    如果您还需要反序列化,那么您需要更进一步并执行以下操作之一:

    1. 添加一个接受IEnumerable&lt;T&gt; 的构造函数,如下面的cmets 中@dbc 所建议的那样;
    2. 让你的班级实现ICollection&lt;T&gt;,或者
    3. 就像@Fabio Salvalai 在他的回答中建议的那样,让您的 Collection 类继承自 List&lt;T&gt;

    如果您不想更改类,另一种选择是实现自定义JsonConverter 来处理自定义序列化/反序列化。转换器可能看起来像这样:

    class CollectionConverter<TCollection, TItem> : JsonConverter where TCollection : new()
    {
        private string ListPropertyName { get; set; }
    
        public CollectionConverter(string listPropertyName)
        {
            ListPropertyName = listPropertyName;
        }
    
        public override bool CanConvert(Type objectType)
        {
            return objectType == typeof(TCollection);
        }
    
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            List<TItem> list = (List<TItem>)typeof(TCollection).GetProperty(ListPropertyName).GetValue(value, null);
            JArray array = JArray.FromObject(list);
            array.WriteTo(writer);
        }
    
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            JArray array = JArray.Load(reader);
            List<TItem> list = array.ToObject<List<TItem>>();
            TCollection collection = new TCollection();
            typeof(TCollection).GetProperty(ListPropertyName).SetValue(collection, list);
            return collection;
        }
    }
    

    要使用转换器,您需要将 [JsonConverter] 属性添加到 Poll 类中的 Collection 属性,如下所示:

    class Poll
    {
        [JsonConverter(typeof(CollectionConverter<AnswersCollection, Answer>), "answers")]
        public AnswersCollection answers { get; set; }
    
        [JsonConverter(typeof(CollectionConverter<TagsCollection, Tag>), "tags")]
        public TagsCollection tags { get; set; }
    
        public string question { get; set; }
    }
    

    然后像往常一样序列化和反序列化。

    【讨论】:

    • 实际上,对于AnswersCollection : IEnumerable&lt;Answer&gt;,您只需要一个带有IEnumerable&lt;Answer&gt; answers 的构造函数,它就可以工作。见dotnetfiddle.net/jSoQ5i
    • @dbc 很好,我没有考虑过,但它有效。我会将该信息添加到我的答案中。谢谢。
    猜你喜欢
    • 1970-01-01
    • 2019-01-26
    • 1970-01-01
    • 1970-01-01
    • 2017-07-21
    • 1970-01-01
    • 2011-01-01
    • 2014-07-10
    • 1970-01-01
    相关资源
    最近更新 更多