Search code examples
c#json-deserialization.net-5system.text.json

Deserialize into a case-insensitive dictionary using System.Text.Json


I'm trying to deserialize json into an object with a property of type Dictionary<string,string>. I specify the comprarer for the Dictionary as StringComparer.OrdinalIgnoreCase. Here's this class:

class  DictionaryTest
{
       public Dictionary<string, string> Fields { get; set; }
       public DictionaryTest()
       {
           Fields = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
       }
}

But when the deserialization happens, the comparer is changed to the generic one. Hence, I cannot access the keys of my dictionary in a case-insensitive way.

var points = new Dictionary<string, string>
{
    { "James", "9001" },
    { "Jo", "3474" },
    { "Jess", "11926" }
};

var testObj = new DictionaryTest{Fields = points};           
var dictionaryJsonText =  JsonSerializer.Deserialize<DictionaryTest>(JsonSerializer.Serialize(testObj, options:new JsonSerializerOptions()
{
    IgnoreNullValues = true,
    WriteIndented = false,
    PropertyNamingPolicy = null,
    Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
    DictionaryKeyPolicy = JsonNamingPolicy.CamelCase,
    PropertyNameCaseInsensitive = true
}));

string nameJsonText = "", nameJsonText2="";
//Because of the naming policy specified above, the keys are camelCase. 
//So keys are james, jo and jess
//I expect to be able to access either james, or James as keys. 
dictionaryJsonText?.Fields.TryGetValue("James", out nameJsonText);
dictionaryJsonText?.Fields.TryGetValue("james", out nameJsonText2);
Console.WriteLine($"Name with system.text.json is:  {nameJsonText}");
Console.WriteLine($"Name with system.text.json is:  {nameJsonText2}");
Console.WriteLine($"Comparer is {dictionaryJsonText?.Fields.Comparer}");

enter image description here

enter image description here

So how can I go about deserializing json into a class like the one below and maintain its case-insesitivity? Any suggestions? I'm using .net5. And I should mention that this code would work perfectly fine using Newtonsoft. The comparer will remain as OrdinalIgnoreCase and case-insensitivity is retained.


Solution

  • Currently there isn't a way to do what you want. But, you can implement the functionality yourself.

    You could create a custom JsonConverter for this specific case. For example:

    public sealed class CaseInsensitiveDictionaryConverter<TValue>
        : JsonConverter<Dictionary<string, TValue>>
    {
        public override Dictionary<string, TValue> Read(
            ref Utf8JsonReader reader,
            Type typeToConvert,
            JsonSerializerOptions options)
        {
            var dic = (Dictionary<string, TValue>)JsonSerializer
                .Deserialize(ref reader, typeToConvert, options);
            return new Dictionary<string, TValue>(
                dic, StringComparer.OrdinalIgnoreCase);
        }
    
        public override void Write(
            Utf8JsonWriter writer,
            Dictionary<string, TValue> value,
            JsonSerializerOptions options)
        {
            JsonSerializer.Serialize(
                writer, value, value.GetType(), options);
        }
    }
    

    You can then bind it to the specific property by doing this:

    class DictionaryTest
    {
        [JsonConverter(typeof(CaseInsensitiveDictionaryConverter<string>))]
        public Dictionary<string, string> Fields { get; set; }
            = new Dictionary<string, string>();
    }
    

    And that's it. You can just deserialize as normal:

    var json = JsonSerializer.Serialize(new DictionaryTest
    {
        Fields =
        {
            { "One", "Two" },
            { "Three", "Four" }
        }
    });
    var dictionaryJsonText = JsonSerializer.Deserialize<DictionaryTest>(json);
    

    The above example will produce a dictionary with case-insensitive keys.