序列化集合类时双包含对象

本文关键字:包含 对象 集合类 序列化 | 更新日期: 2023-09-27 18:16:13

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

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

如您所见,我有"TagsCollection"answers"AnswersCollection",它们看起来非常相似,所以我只展示其中一个。

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

最后,回答课堂。

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

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

问题是与AnswersCollection(这是封装),因为它,JSON输出看起来像:

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

正如你所看到的,问题在于像"answerswers":{"answerswers":[(…)]}这样的结构

是否有选项序列化它的结构,如"答案":[(…)]没有第二个"答案"标签?我尝试使用属性,如"isReference",但它没有工作。

序列化集合类时双包含对象

序列化实际上正在做期望的事情。

我知道它不会完全回答你原来的问题,是否有一种方法来问Json。Net可以做你想做的事情,但是你最好的选择是为你的AnswersCollection使用继承而不是组合。

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

如果你真的不能改变对象的结构,你可以走一条漫长而艰难的路,实现你的自己的 JsonConverter来绝对控制你的属性将如何被序列化/反序列化。

你必须应用JsonConverterAttribute到Poll.answers属性来告诉Json。. Net使用自定义序列化器。

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

离题:你应该考虑使用CamelCasePropertyNamesContractResolver来告诉你的序列化器在序列化你的属性时使用CamelCasePropertyNamesContractResolver,这样你就不必在你的属性本身上使用CamelCasePropertyNamesContractResolver:如果你想遵循常见的命名实践,answers应该拼写为Answers

如果您不需要来回序列化(换句话说,您只需要序列化而不需要反序列化),那么获得您想要的结果的一种简单方法是使您的集合类实现IEnumerable<T>,如下所示:

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<T>的构造函数,正如下面注释中的@dbc所建议的那样;
  2. 让你的类实现ICollection<T>,或者
  3. 就像@Fabio Salvalai在他的回答中建议的那样,让你的Collection类继承List<T>

另一个选择,如果你不想改变你的类,是实现一个自定义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;
    }
}

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

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

那么就像往常一样序列化和反序列化。