MrAkroMenToS
MrAkroMenToS

Reputation: 511

Json.NET deserializing Dictionary<uint, T> & performing conversion

I have a complex JSON object in which I have a dictionary that looks like this:

{
...
"SomeDictionary": {
   "0x010": {
      "a": "b"
   },
   "0x020": {
      "a": "b"
   },
   "0x030": {
      "a": "b"
   }
}
...
}

in this dictionary the keys are represented as hexadecimal numbers, I'd like to read them into a Dictionary<uint, T> (where T is the type of the Values of the Dictionary).

This solution solves it by creating a converter for the whole dictionary: https://stackoverflow.com/a/7010231/3396104 but I'll have multiple of these dictionaries and I don't want to specify multiple converters for each case (not even with generics)

This solution solves it by type converters: JSON.NET not utilizing TypeConverter for Dictionary key but I don't want to wrap my dictionary key into a class.

Current (simplest) solution:

public record class ComplexObjectDTO(
    ...
    Dictionary<string, MyType> SomeDictionary
    ...
)

Then when converting the DTO into a Domain object I wrote this little helper function:

private static Dictionary<uint, T> ToUintDictionary<T>(Dictionary<string, T> source) =>
    source.ToDictionary(
        x =>
        {
            var str = x.Key;
            if (!str.StartsWith("0x"))
                throw new Exception($"Expected hexadecimal number, got {str} instead");
            return Convert.ToUInt32(str, 16);
        },
        x => x.Value
    );

This is fine because I have a DTO object anyways, but I'm still looking for other solutions.

Upvotes: 2

Views: 278

Answers (1)

Flydog57
Flydog57

Reputation: 7111

You can create a type converter for the class you are trying to deserialize. As I pointed out in my comment, you don't want to deserialize directly into a dictionary, you want to deserialize into a class. So...

Let's start with some usings (they aren't all obvious)

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Globalization;

Then the JSON string to deserialize:

static string TheJson = @"
    {
      ""SomeDictionary"": {
        ""0x010"": 12,
        ""0x020"": ""A String"",
        ""0x030"": {
          ""Key"": ""Value""
        }
      }
    }";

And a POCO class to deserialize it into:

public class SomeClass
{
    public Dictionary<uint, object> SomeDictionary { get; set; }
}

Now we need to create a type converter for SomeClass. You only ask about deserializing, so I'm only going to handle that:

public class UintDictionaryConverter : JsonConverter<SomeClass>
{
    public override SomeClass? ReadJson(JsonReader reader, Type objectType, SomeClass? existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        var token = JToken.Load(reader);
        if (!token.Children().Any())
        {
            return null;
        }
        var childToken = token.Children().First();
        if (!childToken.Children().Any())
        {
            return null;
        }
        var dictionaryToken = childToken.Children().First();
        var dictionaryAsObject = dictionaryToken.ToObject<Dictionary<string, object>>();

        var result = new SomeClass { SomeDictionary = new() };
        foreach (var item in dictionaryAsObject)
        {
            var s = item.Key.StartsWith("0x") ? item.Key.Substring(2) : item.Key;
            if (uint.TryParse(s, NumberStyles.AllowHexSpecifier | NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var number))
            {
                result.SomeDictionary[number] = item.Value;
            }
            
        }
        return result;
    }

    public override void WriteJson(JsonWriter writer, SomeClass? value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    public override bool CanRead => true;
    public override bool CanWrite => false;
}

It uses JTokens to walk through the JSON looking for just the dictionary part. Then it effectively deserializes it into a Dictionary<string, object>. Once it has that, it parses the string keys into uints and builds up the SomeDictionary dictionary in the object it's rehydrating.

Using this is simple:

var converter = new UintDictionaryConverter();
var result = JsonConvert.DeserializeObject<SomeClass>(TheJson, converter);

I'll leave getting it null-safe to you

===

As a Note: "Obviously" should be evaluated in the eyes of the reader. I didn't say "deserialize them into a Dictionary<uint, T>, I said to deserialize them into a class that contains one". You'd be surprised how many folks don't see that. Also, if you reference the "dictionary key problem", you may want to define it or post a link to it.

You should also include code that compiles (i.e., not including "...SomeObject...")

Upvotes: 2

Related Questions