System.Text.Json can’t serialize Dictionary unless it has a string key

The built-in JSON serializer in .NET Core can’t handle serializing a dictionary unless it has a string key.

var map = new Dictionary<int, string>() { { 1, "hello" }, { 2, "world" } }; Console.Write(System.Text.Json.JsonSerializer.Serialize(map));

When I run this code I get the following exception:

System.NotSupportedException: The collection type ‘System.Collections.Generic.Dictionary`2[System.Int32,System.String]’ is not supported.

It can only serialize dictionaries with string keys. This is bizarre, and another reason to stick with Newtonsoft for the time being. Apparently they’re going to add support for serializing any dictionary in the next version of .NET.

Solution

There are two options for solving this: use Newtonsoft or write a custom converter.

Option 1 – Use Newtonsoft

The simple solution is to use Newtonsoft, because it already supports serialization of any dictionaries.

  • Install the Newtonsoft.Json Nuget package.
Nuget Package Manager - installling the Newtonsoft.Json nuget package
  • Change your code to use Newtonsoft.
var map = new Dictionary<int, string>() { { 1, "hello" }, { 2, "world" } }; Console.Write(Newtonsoft.Json.JsonConvert.SerializeObject(map));

Option 2 – Write a custom dictionary converter

If you don’t want to, or can’t, use Newtonsoft, then you’ll need to write a custom converter.

1 – Implement a custom converter

To create a custom converter you need to inherit from JsonConverter, then override three methods: CanConvert(), Read(), and Write().

I created the following custom dictionary converter that handles dictionaries with any key type, as long as that type is convertible from a string. Note: If your type cannot be converted to string, think about implementing IConvertible.

public class CustomDictionaryJsonConverter<TKey, TValue> : JsonConverter<IDictionary<TKey, TValue>> where TKey : IConvertible { public override bool CanConvert(Type typeToConvert) { /* Only use this converter if * 1. It's a dictionary * 2. The key is not a string */ if (typeToConvert != typeof(Dictionary<TKey, TValue>)) { return false; } else if (typeToConvert.GenericTypeArguments.First() == typeof(string)) { return false; } return true; } public override IDictionary<TKey, TValue> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { //Step 1 - Use built-in serializer to deserialize into a dictionary with string key var dictionaryWithStringKey = (Dictionary<string, TValue>)JsonSerializer.Deserialize(ref reader, typeof(Dictionary<string, TValue>), options); //Step 2 - Convert the dictionary to one that uses the actual key type we want var dictionary = new Dictionary<TKey, TValue>(); foreach (var kvp in dictionaryWithStringKey) { dictionary.Add((TKey)Convert.ChangeType(kvp.Key, typeof(TKey)), kvp.Value); } return dictionary; } public override void Write(Utf8JsonWriter writer, IDictionary<TKey, TValue> value, JsonSerializerOptions options) { //Step 1 - Convert dictionary to a dictionary with string key var dictionary = new Dictionary<string, TValue>(value.Count); foreach (var kvp in value) { dictionary.Add(kvp.Key.ToString(), kvp.Value); } //Step 2 - Use the built-in serializer, because it can handle dictionaries with string keys JsonSerializer.Serialize(writer, dictionary, options); } }

I tried to write this with code readability in mind, instead of trying to make it super generalized. The downside if you have to create an instance of this converter for each specific type of dictionary you are using. Ex: if you need to serialize Dictionary<int, string> and Dictionary<DateTime, string>, you’ll need two instances of the converter.

2 – Use the custom dictionary converter

To use the custom dictionary converter, you need to pass in an instance of the converter to JsonSerializerOptions. Then when you call Deserialize() or Serialize(), it’ll use the custom converter if CanConvert() is true.

  • using System.Text.Json;
static void Main(string[] args) { Dictionary<int, string> map = new Dictionary<int, string>() { { 1, "hello" }, { 2, "world" } }; JsonSerializerOptions options = new JsonSerializerOptions(); options.Converters.Add(new CustomDictionaryJsonConverter<int, string>()); var json = JsonSerializer.Serialize(map, options); Console.WriteLine(json); var deserializedMap = JsonSerializer.Deserialize<Dictionary<int, string>>(json, options); Console.WriteLine(JsonSerializer.Serialize(deserializedMap, options)); }

3 – Run it

When I run this, it correctly serializes and deserializes the Dictionary<int, string>.

Shows results of running the custom dictionary converter.

Leave a Comment