Reputation: 511
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
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 JToken
s 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 uint
s 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