0

I'm trying to serialize class Poll, which looks like that:

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

As you can see, I have "TagsCollection" and "AnswersCollection", which both look pretty similiar, so I'll show only one of them.

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

And, finnaly, Answer class.

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

All clases have default public constructor (without parameters) so JSON.NET doesn't have any problems with serialization.

Problem is with AnswersCollection (which is encapsulation), because of it, JSON output looks like that:

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

As you can see, the problem is with structures like "answers":{ "answers": [(...)] }

Is there option to serialize it to structures like "answers" :[(...)] without second "answers" tag? I tried to use properties like "isReference" but it didn't worked.

Elas
  • 212
  • 2
  • 16
  • Have you looked at Datacontracts? http://stackoverflow.com/questions/16943176/how-to-deserialize-xml-using-datacontractserializer – Hespen Nov 03 '16 at 14:23
  • The first `answers` is the var name wtihin the Poll class -- the second `answers` is the var name within the AnswersCollection class -- you get them both because you are serializing the Poll class -- there is no flag to set that would change that ... it is that way because of the structure of your classes – Bret Nov 03 '16 at 14:26
  • @Bret I know that and I would like to avoid including only name of variable or class, without ignoring content of it, because all Collection classess encapsulates some methods. – Elas Nov 03 '16 at 14:36

2 Answers2

2

The serialization is actually doing exactly what is expected of it.

I know it will not exactly answer your original question as to whether there is a way to ask Json.Net to do what you want, but your best option here would be to have inheritence instead of composition for your AnswersCollection.

Since the name suggests the class is a collection of answers, why not make it inherit from List<Answer> instead of having a property of this type ?

If you really can't change the structure of your object, you could go the long-hard way, and implement your own JsonConverter to have an absolute control of how your properties are going to be serialized / deserialized.

You would have to apply the JsonConverterAttribute to the Poll.answers property to tell Json.Net to use your custom serializer.

However, I would highly recommend not taking that approach, if you can avoid it.

Off-topic as a side note: you should consider using a CamelCasePropertyNamesContractResolver to tell your serializer to use CamelCasing when serializing your properties, so you don't have to use camelCasing on your property itself: answers should be spelled Answers if you want to follow common naming practices.

Fabio Salvalai
  • 2,479
  • 17
  • 30
2

If you don't need to go round-trip with your serialization (in other words, you just need to serialize but not deserialize), then one easy way to get the result you want is to make your collection classes implement IEnumerable<T> like this:

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

This works because Json.Net automatically treats classes that implement IEnumerable as arrays during serialization.

If you need to deserialize as well, then you'll need to go a bit further and do one of the following:

  1. Add a constructor that accepts an IEnumerable<T>, as suggested by @dbc in the comments below;
  2. Make your class implement ICollection<T>, or
  3. Just make your Collection classes inherit from List<T> as @Fabio Salvalai suggested in his answer.

Another alternative, if you don't want to change your classes, is to implement a custom JsonConverter to handle the custom serialization/deserialization. The converter might look something like this:

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

To use the converter, you would need to add [JsonConverter] attributes to the Collection properties in your Poll class like this:

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

Then just serialize and deserialize as usual.

Brian Rogers
  • 125,747
  • 31
  • 299
  • 300
  • 1
    Actually, for `AnswersCollection : IEnumerable`, all you need is a constructor that takes an `IEnumerable answers`, and it will just work. See https://dotnetfiddle.net/jSoQ5i. – dbc Nov 03 '16 at 21:09
  • @dbc Nice, I had not considered that, but it works. I will add that info to my answer. Thanks. – Brian Rogers Nov 03 '16 at 21:13