2

Why are instances of this class not being serialized properly?

[JsonObject]
public class CharCollection : List<char> {

  [JsonConstructor]
  public CharCollection(string key) {
    Key = key;      
  }

  public string Key { get; }    

}

However, when serialized using:

JsonConvert.SerializeObject(new CharCollection("CarriageReturn"){'\r'});

The actual characters are not stored, only the Key property and other info... where are the actual chars?

{"Key":"CarriageReturn","Capacity":4,"Count":1}
Rafael Sanchez
  • 394
  • 7
  • 20
  • 2
    If you look at https://msdn.microsoft.com/en-us/library/6sh2ey19(v=vs.110).aspx the only public properties are `Count`, `Capacity` and `Item[int32]`. The last of them it doesn't make sense for it to be able to serialize (should the serializer try every possible int32 looking for data? No, that'd be crazy) and it has successfully serialized the other two fields. Also it is generally not the right thing to do to inherit from `List` there are exceptions but usually having an object with a `List` makes more sense than inheriting from `List` – Chris Jun 30 '17 at 14:08
  • I want to add a label to the list, because later on I'll add many of these CharCollection objects into a List, which I will then query. I suspect this must be possible to serialize?? – Rafael Sanchez Jun 30 '17 at 14:16
  • -As someone suggested-, using Dictionary> seems at first sight like a possible choice (haven't tested it!), however, I want answers as to why a class that inherits from List doesn't get the actual values serialized – Rafael Sanchez Jun 30 '17 at 14:22
  • Sorry but list gets serialized as array in json [] and arrays don't support extra properties in JSON? – Filip Cordas Jun 30 '17 at 14:24
  • A useful further reading on my comment to not inherit from `List`: https://stackoverflow.com/questions/21692193/why-not-inherit-from-listt – Chris Jun 30 '17 at 14:40
  • Out of interest do you actually want an answer to the question "where are the actual chars?" or do you actually want to know how you can output a json document with a key and a list of characters? The first is what you asked but I suspect the second is what you want. You do seem to be very hung up in comments about your specific implementation though so not sure if you really do just want to know the technical details of why it didn't work... – Chris Jun 30 '17 at 14:48

2 Answers2

2

You are likely better off with a class/structure that looks similar to this:

public class CharCollection
{
    public CharCollection(string key, IEnumerable<char> characters = null) 
    {
        Key = key;      
        Characters = new List<char>();

        if(characters != null)
        {
            Characters.AddRange(characters);
        }
    }

    public List<char> Characters { get; set; }

    public string Key { get; }   

    public int Count { get { return Characters.Count; } }

    public int Capacity { get { return Characters.Capacity; } } 

}

Edit

Based off your comment it seems like a Dictionary<string, List<char>> is what you would need.

Dictionary<string, List<char>> dict= new Dictionary<string, List<char>>();

dict.Add("CarriageReturn", new List<char>() { '\r' });

This will give you json that looks like this:

{ "CarriageReturn":["\r"] }

Fiddle here

maccettura
  • 10,514
  • 3
  • 28
  • 35
  • Why am I better off with this? I don't care about Count and Capacity by the way :) ... I have managed to serialize custom classes containing lists of other custom classes, so I don't quite understand why a simple class that extends list and just adds a string property cannot be serialized properly.. Surely it's possible? What am I missing? – Rafael Sanchez Jun 30 '17 at 14:35
  • Your question was unclear, I saw your Json and assumed you wanted those values. If you have no need for Count or Capacity then use a `Dictionary>`, its designed specifically for key/value collections. – maccettura Jun 30 '17 at 14:37
  • My question was clear! Still I get no answers as to how I can successfully serialize my class. Imagine I want to add a second property, then suddenly Dictionary isn't suitable anymore – Rafael Sanchez Jun 30 '17 at 14:41
  • @maccettura: You should add more descriptive text to your answer though to explain exactly why this is better than the OP's suggestion. Also you have a rogue `:` in your first line of code. – Chris Jun 30 '17 at 14:41
  • 1
    @RafaelSanchez: I gave you pretty good reasons why it wouldn't work in a comment on the question itself. Also If you want to add more fields then its not really a List any more so why are you inheriting from list? The CharCollection as in this answer will allow you to add a second property. Or third. Or more. The OP had a class that used multiple properties and when you said you didn't need them he suggested a simpler alternative. You can't then complain if you in fact now want more properties - you can instead just go back to the original answer. – Chris Jun 30 '17 at 14:43
  • @RafaelSanchez second property to what? If you want a structure where a key is used to access data then a Dictionary is exactly what you want. If you want more than just a character list, then create a custom class. – maccettura Jun 30 '17 at 14:44
  • @maccettura Thanks for the feedback. Second property as in Key and an additional Key2. I want to keep my options open, that's why I'm reluctant to use a Dictionary. Creating a custom class, as you suggest, is exactly what I have done – Rafael Sanchez Jun 30 '17 at 14:50
  • So you intend to access your data by two separate keys? Even then you wouldn't need to change from a Dictionary, your new "key" would just be `{key1}_{key2}` with the underscore being some delimiter. This is how Azure Tables work and likely how other No-Sql databases work as well. – maccettura Jun 30 '17 at 14:53
  • @Chris Thanks for your comments. I see the point of setting the List as a public property inside the custom class instead of extending List. However, I think it's neat if my object is a list itself, instead of having to access one of its properties to be able to access the chars – Rafael Sanchez Jun 30 '17 at 14:54
  • 1
    @RafaelSanchez: That may be but the fact is it doesn't work. You either get to have it serialize the list as a JSON array or as a JSON object. You have explicitly chosen the latter by using the `[JsonObject]` attribute but as you have seen this then loses the list properties (I assume you are aware that removing that attribute will serialize the list data but not your custom property). So you *can't* do what you want and instead need to do something like this. It doesn't matter how neat it might be, the fact is the Json Serializer just doesn't work like this. – Chris Jun 30 '17 at 14:58
  • @Chris Oh well I didn't know that! I added JsonObject because I was copying from previous code... Never assume anything haha – Rafael Sanchez Jun 30 '17 at 15:02
0

I agree with @maccettura that his solution is the best, but if you want to customize the writer you can do it.

[JsonConverter(typeof(CharArraySerializer))]
        public class CharArray : List<char>
        {
            public string Key { get; }

            public CharArray(string key)
            {
                Key = key;
            }

            public CharArray(string key,List<char> list):base(list)
            {
                Key = key;
            }
        }

        public class CharArraySerializer : JsonConverter
        {
            public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
            {
                var array = value as CharArray;

                writer.WriteStartObject();
                writer.WritePropertyName("key");
                writer.WriteValue(array.Key);

                writer.WritePropertyName("data");
                serializer.Serialize(writer, array.ToArray());

                writer.WriteEndObject();
            }

            public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
            {
                 var jsonObject = JObject.Load(reader);   

                 var key = jsonObject.Value<string>("key");

                 var array = jsonObject.Value<JArray>("data").Select(t=>t.Value<char>()).ToList();

                 return new CharArray(key,array);
            }

            public override bool CanConvert(Type objectType)
            {
                return typeof(CharArray).IsAssignableFrom(objectType);
            }
        }

Your json will look like this

{"key":"Key","data":["a","b"]} 

You might want to look into the property names to use proper casing but this works for what you wanted I think.

Filip Cordas
  • 2,531
  • 1
  • 12
  • 23