Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Json.Net cannot serialize property of a class derived from dictionary

I have a class that is derived from Dictionary. Also the class itself has a ClientId property. I want to de-serialize the collection of this class into JSON string using Json.Net So followed the documentation here

public interface IClientSettings: IDictionary<string, string>
{        
    string ClientId { get; set; }        
}

public class ClientSettings : Dictionary<string, string>, IClientSettings
{        
    public string ClientId { get; set; }
}

and then I am de-serializing list into string

        var list = new List<IClientSettings>();

        var client1 = new ClientSettings();
        client1.ClientId = "Client1";
        client1.Add("key1", "value1");
        client1.Add("key2", "value2");
        client1.Add("key3", "value3");

        var client2 = new ClientSettings();
        client1.ClientId = "Client2";
        client2.Add("key1", "value1");
        client2.Add("key2", "value2");
        client2.Add("key3", "value3");

        list.Add(client1);
        list.Add(client2);

        string json = JsonConvert.SerializeObject(list, Formatting.Indented);
        Console.WriteLine(json);

However this does not serialize Clientid property. Below is the output.

[
  {
    "key1": "value1",
    "key2": "value2",
    "key3": "value3"
  },
  {
    "key1": "value1",
    "key2": "value2",
    "key3": "value3"
  }
]

I am not sure what I am missing here. I have also found suggestion here that suggest to do custom serialization, is that the only option I have?

like image 930
LP13 Avatar asked Oct 29 '22 21:10

LP13


1 Answers

Yes, you are correct, you will need to do some sort of custom serialization. Json.NET will either serialize the key/value pairs of the dictionary as a JSON object (using JsonDictionaryContract), or the properties of the dictionary as a JSON object (using JsonObjectContract) if you mark the type with [JsonObject] - but not both. I suspect doing both was not implemented to thereby avoid the possibility of run-time name clashes when the dictionary contains a key with the same name as a property, e.g.:

var client3 = new ClientSettings();
client3.ClientId = "Client1";
client3["ClientId"] = "Conflicting Value";

According to the IETF standard,

When the names within an object are not unique, the behavior of software that receives such an object is unpredictable.

Thus this is a situation best avoided.

One possible implementation is as follows:

[JsonObject(MemberSerialization = MemberSerialization.OptIn)]
public class ClientSettings : Dictionary<string, string>, IClientSettings
{
    [JsonProperty]
    public string ClientId { get; set; }

    [JsonProperty]
    IDictionary<string, string> Items { get { return new DictionaryWrapper<string, string>(this); } }
}

Using

public class DictionaryWrapper<TKey, TValue> : IDictionary<TKey, TValue>
{
    readonly IDictionary<TKey, TValue> dictionary;

    public DictionaryWrapper(IDictionary<TKey, TValue> dictionary)
    {
        if (dictionary == null)
            throw new ArgumentNullException("dictionary");
        this.dictionary = dictionary;
    }

    #region IDictionary<TKey,TValue> Members

    public void Add(TKey key, TValue value) { dictionary.Add(key, value); }

    public bool ContainsKey(TKey key) { return dictionary.ContainsKey(key); }

    public ICollection<TKey> Keys { get { return dictionary.Keys; } }

    public bool Remove(TKey key) { return dictionary.Remove(key); }

    public bool TryGetValue(TKey key, out TValue value) { return dictionary.TryGetValue(key, out value); }

    public ICollection<TValue> Values { get { return dictionary.Values; } }

    public TValue this[TKey key]
    {
        get { return dictionary[key]; }
        set { dictionary[key] = value; }
    }

    #endregion

    #region ICollection<KeyValuePair<TKey,TValue>> Members

    public void Add(KeyValuePair<TKey, TValue> item) { dictionary.Add(item); }

    public void Clear() { dictionary.Clear(); }

    public bool Contains(KeyValuePair<TKey, TValue> item) { return dictionary.Contains(item); }

    public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) { dictionary.CopyTo(array, arrayIndex); }

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

    public bool IsReadOnly { get { return dictionary.IsReadOnly; } }

    public bool Remove(KeyValuePair<TKey, TValue> item) { return dictionary.Remove(item); }

    #endregion

    #region IEnumerable<KeyValuePair<TKey,TValue>> Members

    public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() { return dictionary.GetEnumerator(); }

    #endregion

    #region IEnumerable Members

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); }

    #endregion
}

MemberSerialization.OptIn is used to prevent base class properties such as Count, Comparer, Keys and Values from being serialized.

With this, your JSON will look like:

[
  {
    "ClientId": "Client2",
    "Items": {
      "key1": "value1",
      "key2": "value2",
      "key3": "value3"
    }
  }
]
like image 102
dbc Avatar answered Nov 09 '22 12:11

dbc