Search code examples
c#json.netjavascriptserializer

Serialization when inheriting from Dictionary


I'm using System.Web.Script.Serialization.JavaScriptSerializer to serialize / deserialize a class that extends Dictionary.

The problem is, my custom properties are not being serialized. Here is my class:

public class Test : Dictionary<string, object> {
    public ushort Id { get; set; }
    public string Name { get; set; }
}

And my code:

var jss = new JavaScriptSerializer();

var test = new Test {
    Id = 123,
    Name = "test"
};

var json = jss.Serialize(test);

The result in json is an empty json {}

I do not want to depend on Newtonsoft or JSON.Net or any other library.

ADDITIONAL INFO

I just noticed some, hm, peculiarities, when using both dynamic and object:

  • JavaScriptSerializer defaults any number value to int.

  • Also, Newtonsoft defaults any number to long.

That can cause casting exceptions in a class using property indexer (as suggested in the accepted answer), for example:

public class Test : Dictionary<string, dynamic> {
    public ushort Id { get => this[nameof(Id)]; set => this[nameof(Id)] = value; }
}

The Id property getter will try to implicitly convert int to ushort, which will fail.

ADDITIONAL INFO 2

I just found out so many weird behaviors with Newtonsoft:

I added these attributes to solve the 'long to ushort' problem:

[JsonObject(MemberSerialization.OptIn)]
public class Test : Dictionary<string, dynamic> {
    [JsonProperty]
    public ushort Id { get => this[nameof(Id)]; set => this[nameof(Id)] = value; }
}

The above works! But when the property is a reference type:

[JsonObject(MemberSerialization.OptIn)]
public class Test : Dictionary<string, dynamic> {
    [JsonProperty]
    public ushort Id { get => this[nameof(Id)]; set => this[nameof(Id)] = value; }
    [JsonProperty]
    public Test Child { get => this[nameof(Child)]; set => this[nameof(Child)] = value; }
}

It tries to get the property before serializing it, resulting in a 'key not found exception'. I can't see why it tries to get the property only when it's a reference type, seems like a bug to me...

So you have to do something like this:

public Test Child { get => this.ContainsKey(index) ? this[nameof(Child)] : null; ... }

Solution

  • Just to summarize the comments:

    To use composition, you would just need to modify your test object like this:

    public class Test
    {
        public ushort Id { get; set; }
        public string Name { get; set; }
        public Dictionary<string, object> Items { get; set; } = new Dictionary<string, object> {};
    }
    

    Then the following code would work fine:

    var jss = new JavaScriptSerializer();
    var test = new Test
    {
        Id = 123,
        Name = "test",
    };
    test.Items.Add("A", 1);
    var json = jss.Serialize(test);
    

    The output would just be:

    {"Id":123,"Name":"test","Items":{"A":1}}

    UPDATE: Property Indexer

    You could add a default indexer to your class so that the following code would work:

    test["A"] = 1;
    var result = test["A"];
    

    Here is the code to add for the default indexer:

    public object this[string key]
    {
        get { return this.Items[key]; }
        set { this.Items[key] = value; }
    }
    

    You could extend this into implementing IDictionary I suppose, but I imagine just working with the composition should be easiest.