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

3

14

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.

Scutiform answered 28/4, 2021 at 20:47 Comment(0)
L
11

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.

Leporide answered 28/4, 2021 at 21:27 Comment(0)
E
1

The question is quite old but I would say I have a better solution. The solution below doesn't create an additional dictionary so has lower memory flow.

JsonSerializerOptions JsonSerializerOptions =
    new JsonSerializerOptions
    {
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        PropertyNameCaseInsensitive = false,
        TypeInfoResolver = new DefaultJsonTypeInfoResolver()
    };

string valuesAsJson =
    """
    {
        "key1": "value1",
        "key2": "value2",
        "key3": "value3",
        "Key1": "value3"
    }
    """;

JsonTypeInfo<Dictionary<string, string?>> jsonTypeInfo =
    JsonTypeInfo.CreateJsonTypeInfo<Dictionary<string, string?>>(
        JsonSerializerOptions
    );

jsonTypeInfo.CreateObject = () => new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);

Dictionary<string, string?>? values =
    JsonSerializer.Deserialize<Dictionary<string, string?>>(

        valuesAsJson,
        jsonTypeInfo
    );
Epi answered 13/6 at 9:9 Comment(0)
C
-3

There seems to be an option to achieve this.

JsonSerializerOptions options = new JsonSerializerOptions
{
       PropertyNameCaseInsensitive = true
};

await JsonSerializer.DeserializeAsync<...>(stream, options)

JsonSerializerOptions.PropertyNameCaseInsensitive Property

Cholon answered 4/2, 2023 at 8:19 Comment(1)
This will make the serializer agnostic to the case of property names in the JSON. For example, it could deserialize "SoMeNaMe" into a property on an object called SomeName without any additional property naming convention. However, if it deserializes a dictionary, it will not make lookups on that dictionary case insensitive.Fluoridation

© 2022 - 2024 — McMap. All rights reserved.