0

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; ... }
Jason Aller
  • 3,541
  • 28
  • 38
  • 38
Pedro Henrique
  • 680
  • 7
  • 22
  • Possible duplicate of https://stackoverflow.com/questions/31099396/serialize-class-that-inherits-dictionary-is-not-serializing-properties – Frank Modica Oct 18 '18 at 17:00
  • You might want to consider using composition instead of inheritance. (Have a dictionary property instead) – juharr Oct 18 '18 at 17:03
  • Short of re-implementing most of JavaScriptObjectDeserializer (https://referencesource.microsoft.com/#system.web.extensions/Script/Serialization/JavaScriptObjectDeserializer.cs,2f8d1f9fbf43dbfa), not sure you can. MSDN docs suggest using JSON.NET instead of this approach. Only [IgnoreJson] is supported (no [JsonProperty] or anything available) – Jason W Oct 18 '18 at 17:03
  • Even [JavaScriptSerializer's API docs page](https://learn.microsoft.com/en-us/dotnet/api/system.web.script.serialization.javascriptserializer?view=netframework-4.7.2) recommends just using JSON.Net. But, looking around at the docs, you may need to implement your own `JavaScriptConverter` as there doesn't appear to be an inverse of the [ScriptIgnoreAttribute](https://learn.microsoft.com/en-us/dotnet/api/system.web.script.serialization.scriptignoreattribute?view=netframework-4.7.2). – AntiTcb Oct 18 '18 at 17:04

1 Answers1

0

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.

Jason W
  • 13,026
  • 3
  • 31
  • 62
  • Oddly enough, JavaScriptSrializer defaults any number to int, and Newtonsoft defaults any number to long. That can mess up something like that property indexer. I've updated my question. – Pedro Henrique Oct 18 '18 at 17:36