How to correctly Serialize Dictionary using [JsonExtensionData] with JsonNamingPolicy.CamelCase?

Viewed 55

I need to use JsonNamingPolicy.CamelCase to serialize my object.

But when I use Dictionary with [JsonExtensionData] attribute, it seems something not work correctly.

Example Code.

public class Test
{
    public Dictionary<string, object> Labels { get; set; } = new();

    [JsonExtensionData]
    public Dictionary<string, object> MetaInfo { get; set; } = new();

    public Dictionary<string, object>? Nullable { get; set; } = null;
}

public class TestContent
{
    public string? Value { get; set; }
}

System.Text.Json

var options = new JsonSerializerOptions
{
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    DictionaryKeyPolicy = JsonNamingPolicy.CamelCase,
};

var test = new Test
{
    Labels = new Dictionary<string, object>
    {
        { "Test1", new TestContent{ Value = "TestContent1" } }, 
        { "Test2", new TestContent { Value = null } },
    },
    MetaInfo = new Dictionary<string, object>
    {
        { "Test1", new TestContent{ Value = "TestContent1" } },
        { "Test2", new TestContent { Value = null } },
    }
};

var result = JsonSerializer.Serialize(test, options);
System.Console.WriteLine(result);

Expected output

  • Observe that the properties are named "test1" and "test2".
{
    "labels":{
        "test1":{
            "value": "TestContent1"
        },
        "test2":{
        }
    },
    "test1":{
        "value": "TestContent1"
    },
    "test2":{
    }
}

Actual output

  • Observe that the properties are named "Test1" and "Test2" instead.
{
    "labels":{
        "test1":{
            "value": "TestContent1"
        },
        "test2":{
            
        }
    },
    "Test1":{
        "value": "TestContent1"
    },
    "Test2":{
        
    }
}

You can see the JsonNamingPolicy.CamelCase work correct on labels, but work failed on Test1 and Test2. Am I missing some settings?

When I change to Newtonsoft.Json it works fine:

var contractResolver = new DefaultContractResolver
{
    NamingStrategy = new CamelCaseNamingStrategy
    {
        ProcessDictionaryKeys = true,
        ProcessExtensionDataNames = true,
        OverrideSpecifiedNames = false,
    }
};

var settings = new JsonSerializerSettings
{
    ContractResolver = contractResolver,
    NullValueHandling = NullValueHandling.Ignore,
};

var result = JsonConvert.SerializeObject(test, settings);       
System.Console.WriteLine(result);
// {"labels":{"test1":{"value":"TestContent1"},"test2":{}},"test1":{"value":"TestContent1"},"test2":{}}
1 Answers

Seeming as it is a mutable dictionary, just edit the keys, like so:

public static void RenameJsonExtensionDataPropertiesToCamelCase<T>( T objectValue )
{
    var jsonExtensionPropertyInfo = typeof(T)
        .GetProperties()
        .Select( p => ( pi: p, attrib: p.GetCustomAttribute<System.Text.Json.Serialization.JsonExtensionDataAttribute>() ) )
        .Where( t => t.attrib != null )
        .SingleOrDefault();

    if( jsonExtensionPropertyInfo.attrib is null ) return;

    PropertyInfo pi = jsonExtensionPropertyInfo.pi;
    
    Object? dictObj = pi.GetValue( objectValue );
    if( dictObj is Dictionary<String,Object?> dict )
    {
        RenameImpl( dict );
    }
    else if( dictObj is Dictionary<String,JsonElement?> jsDict )
    {
        RenameImpl( jsDict );
    }
    else
    {
        throw new NotImplementedException( "TODO?" );
    }
    
    //
    
    static void RenameImpl<TValue>( Dictionary<String,TValue> dict )
    {
        List<String> pascalCaseKeys = dict.Keys
            .Where( k => k.Length > 0 && Char.IsUpper( k[0] ) )
            .ToList();

        foreach( String pascalCaseKey in pascalCaseKeys )
        {
            String camelCaseKey = Char.ToLowerInvariant( pascalCaseKey[0] ) + pascalCaseKey.Substring( startIndex: 1 );
            dict[ camelCaseKey ] = dict[ pascalCaseKey ];
            dict.Remove( pascalCaseKey );
        }
    }
}

...then just call RenameJsonExtensionDataPropertiesToCamelCase right before you serialize:

var options = new JsonSerializerOptions
{
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
    PropertyNamingPolicy   = JsonNamingPolicy.CamelCase,
    DictionaryKeyPolicy    = JsonNamingPolicy.CamelCase,
};

var test = new Test
{
    Labels = new Dictionary<String,Object?>
    {
        { "Test1", new TestContent{ Value = "TestContent1" } }, 
        { "Test2", new TestContent { Value = null } },
    },
    MetaInfo = new Dictionary<String,Object?>
    {
        { "Test1", new TestContent{ Value = "TestContent1" } },
        { "Test2", new TestContent { Value = null } },
    }
};

RenameJsonExtensionDataPropertiesToCamelCase( test ); // <-- Here.

var result = JsonSerializer.Serialize(test, options);

System.Console.WriteLine(result);

Screenshot proof:

(Interestingly, after renaming Test1 and Test2 to test1 and test2 (respectively) .NET seems to serialize the keys in a different order).

enter image description here

Related