Converting Dictionaries in System.Text.Json
Tags: csharp, dotnet
Published: 2020-02-26 05:02:30

Like many out there, I'm a long-time consumer of `Newtonsoft.Json`. It's simple and powerful, so it surprised me to learn that .NET Core 3 would be introducing its own JSON library. What surprised me more was learning that the man behind `Newtonsoft.Json` himself would be involved with designing this new library. This new library, `System.Text.Json`, was built with a specific purpose in mind: performance with JSON-related functionality in ASP.NET Core. It promises reduced allocations and higher throughput.

The design of this new library is somewhat off-putting for many. Right out of the gate, I wondered what happened to `JObject`, `JArray`, and `JToken`. They're just... gone... with no real replacement. The new library has `JsonElement`, but it's largely a read-only component. It is only created as part of a large parse operation; I can't just create JSON things and glue them together like I could with Newtonsoft.

I spent more time with it and slowly grew accustomed to its design philosophy. I no longer miss `JObject` because it was a thin wrapper over things I already have in .NET anyway. What is a JSON object really? It's just a `Dictionary<string, object>`.

```csharp
var obj = new Dictionary<string, object>
{
    ["id"] = 43,
    ["title"] = "Western Union",
    ["isEnabled"] = true,
    ["tags"] = new string[] {"things", "stuff"}
};

var json = JsonSerializer.Serialize(obj);
```

Cool. Simple enough. I can also serialize custom objects with arbitrary properties to accomplish the same things. With this in mind, I kind of see the old Newtonsoft classes as crutches more than anything else. They offer feel-good measures for directly crafting JSON objects and arrays. (And I'm not faulting anyone for sticking with that paradigm. It's pretty easy to read.)

However, I was caught off guard when I went to deserialize a dictionary.

```csharp
var raw = "{\"49fc2162-744a-4a42-b685-ea1e30ce2a2f\": 99}";
var d = JsonSerializer.Deserialize<Dictionary<Guid, int>>(raw);
```

This code here throws a `NotSupportedException`. It points out that having a non-string for the `TKey` is simply unacceptable. This... is a problem. I work with `Guid` extensively (at home and at work) and _often_ use it as a dictionary key. I'm generally not OK with having to convert JSON to a string dictionary first and then to a non-string dictionary as a second step. It seems I am [not alone](https://github.com/dotnet/corefx/issues/40120) in experiencing frustration at this.

However, as much as I love to sit and complain about others' hard work, I decided to just do something about it. The documentation for creating your own JSON converters is [pretty good](https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-converters-how-to). So, I set out to make my own, and I am pleased with the result so far. Now I can do this:

```csharp
var options = new JsonSerializerOptions();
options.Converters.Add(DictionaryJsonConverterFactory.Default);

var raw = "{\"49fc2162-744a-4a42-b685-ea1e30ce2a2f\": 99}";
var d = JsonSerializer.Deserialize<Dictionary<Guid, int>>(raw, options);
```

Back in business! If you are interested in my implementation, the code is all [here](https://github.com/TheBuzzSaw/KellySharp/tree/master/KellySharp/DictionaryJsonConverter) with a small explanation [here](https://github.com/TheBuzzSaw/KellySharp/blob/master/KellySharp/DictionaryJsonConverter/Usage.md). It's public domain. Knock yourself out.