neuecc / Utf8Json

Definitely Fastest and Zero Allocation JSON Serializer for C#(NET, .NET Core, Unity, Xamarin).
MIT License
2.36k stars 267 forks source link

Cannot deserialize a class that inherits Dictionary with int as its key #184

Open DamianSuess opened 4 years ago

DamianSuess commented 4 years ago

Unable to Deserialize a class that inherits a dictionary when using int as its key. i.e. Dictionary<int, T>. The use case for this is pulling info from a database and storing the identity in the key and it's object data in the value. Below I'm using a string as the value for a simple demonstration.

Instances which do serialize and deserialize are:

Output

{"1":"Item1-data","2":"Item2-data"}

Exception thrown:

'System.ArgumentException' in System.Private.CoreLib.dll An exception of type 'System.ArgumentException' occurred in System.Private.CoreLib.dll but was not handled in user code The value "1" is not of type "System.Int32" and cannot be used in this generic collection.

Test Class

using System.Collections.Generic;
namespace Test.JsonEngines
{
  public class DictIntKey : Dictionary<int, string>
  {
    // No properties here, just helper methods
  }
}

Unit Test - Fails

    [TestMethod]
    public void Utf8DictIntKeyQuickTest()
    {
      // Assign
      var obj1 = new DictIntKey() { StringA = "Helo", IntB = 34 };
      obj1.Add(1, "Item1-data");
      obj1.Add(2, "Item2-data");

      // Object to string
      string json1 = Utf8Json.JsonSerializer.ToJsonString(obj1);
      Debug.WriteLine($"Utf8 - Before Output:\r\n{json1}");

      // Throws exception when converting string to object
      var obj2 = Utf8Json.JsonSerializer.Deserialize<DictIntKey>(json1);
      string json2 = Utf8Json.JsonSerializer.ToJsonString(obj2);

      Debug.WriteLine($"Utf8 - After Output:\r\n{json2}");
      Assert.AreEqual(json1, json2);
    }

Output before exception

Utf8 - Before Output:
{"1":"Item1-data","2":"Item2-data"}

Unit Test - Passes

This snip below shows that you can use a Dictionary<int, T>.

    [TestMethod]
    public void Utf8DictIntKeySimpleTest()
    {
      // Assign
      var obj1 = new Dictionary<int, string>();
      obj1.Add(1, "Item1-data");
      obj1.Add(2, "Item2-data");

      string json1 = Utf8Json.JsonSerializer.ToJsonString(obj1);
      var obj2 = Utf8Json.JsonSerializer.Deserialize<Dictionary<int, string>>(json1);
      string json2 = Utf8Json.JsonSerializer.ToJsonString(obj2);

      Debug.WriteLine($"Utf8 - Before Output:\r\n{json1}");
      Debug.WriteLine($"Utf8 - After Output:\r\n{json2}");
      Assert.AreEqual(json1, json2);
    }

Output:

Utf8 - Before Output:
{"1":"Item1-data","2":"Item2-data"}
Utf8 - After Output:
{"1":"Item1-data","2":"Item2-data"}
cemheren commented 4 years ago

As a workaround you might be able to add a formatter like this:

public class CustomDictionaryFormatter : IJsonFormatter<DictIntKey>
    {
        public DictIntKey Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
        {
            return new DictIntKey(formatterResolver.GetFormatterWithVerify<Dictionary<int, string>>().Deserialize(ref reader, formatterResolver));
        }

        public void Serialize(ref JsonWriter writer, DictIntKey value, IJsonFormatterResolver formatterResolver)
        {
            formatterResolver.GetFormatterWithVerify<Dictionary<int, string>>().Serialize(ref writer, value, formatterResolver);
        }
    }

Although this is sub optimal since it crates the dictionary twice. Better option would be to write it from scratch.