Keep casing when serializing dictionaries

C#json.net

C# Problem Overview


I have a Web Api project being configured like this:

config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();

However, I want dictionary keys casing to remain unchanged. is there any attribute in Newtonsoft.Json I can use to a class to denote that I want casing to remain unchanged during serialization?

public class SomeViewModel
{
	public Dictionary<string, string> Data { get; set; }	
}

C# Solutions


Solution 1 - C#

There is not an attribute to do this, but you can do it by customizing the resolver.

I see that you are already using a CamelCasePropertyNamesContractResolver. If you derive a new resolver class from that and override the CreateDictionaryContract() method, you can provide a substitute DictionaryKeyResolver function that does not change the key names.

Here is the code you would need:

class CamelCaseExceptDictionaryKeysResolver : CamelCasePropertyNamesContractResolver
{
    protected override JsonDictionaryContract CreateDictionaryContract(Type objectType)
    {
        JsonDictionaryContract contract = base.CreateDictionaryContract(objectType);

        contract.DictionaryKeyResolver = propertyName => propertyName;

        return contract;
    }
}

Demo:

class Program
{
    static void Main(string[] args)
    {
        Foo foo = new Foo
        {
            AnIntegerProperty = 42,
            HTMLString = "<html></html>",
            Dictionary = new Dictionary<string, string>
            {
                { "WHIZbang", "1" },
                { "FOO", "2" },
                { "Bar", "3" },
            }
        };

        JsonSerializerSettings settings = new JsonSerializerSettings
        {
            ContractResolver = new CamelCaseExceptDictionaryKeysResolver(),
            Formatting = Formatting.Indented
        };

        string json = JsonConvert.SerializeObject(foo, settings);
        Console.WriteLine(json);
    }
}

class Foo
{
    public int AnIntegerProperty { get; set; }
    public string HTMLString { get; set; }
    public Dictionary<string, string> Dictionary { get; set; }
}

Here is the output from the above. Notice that all of the class property names are camel-cased, but the dictionary keys have retained their original casing.

{
  "anIntegerProperty": 42,
  "htmlString": "<html></html>",
  "dictionary": {
    "WHIZbang": "1",
    "FOO": "2",
    "Bar": "3"
  }
}

Solution 2 - C#

Json.NET 9.0.1 introduced the NamingStrategy class hierarchy to handle this sort of issue. It extracts the logic for algorithmic remapping of property names from the contract resolver to a separate, lightweight class that allows for control of whether dictionary keys, explicitly specified property names, and extension data names (in 10.0.1) are remapped.

By using DefaultContractResolver and setting NamingStrategy to an instance of CamelCaseNamingStrategy you can generate JSON with camel-cased property names and unmodified dictionary keys by setting it in JsonSerializerSettings.ContractResolver:

var resolver = new DefaultContractResolver
{
    NamingStrategy = new CamelCaseNamingStrategy
    {
        ProcessDictionaryKeys = false,
        OverrideSpecifiedNames = true
    }
};
config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = resolver;

Notes:

  • The current implementation of CamelCasePropertyNamesContractResolver also specifies that .Net members with explicitly specified property names (e.g. ones where JsonPropertyAttribute.PropertyName has been set) should have their names remapped:

     public CamelCasePropertyNamesContractResolver()
     {
         NamingStrategy = new CamelCaseNamingStrategy
         {
             ProcessDictionaryKeys = true,
             OverrideSpecifiedNames = true
         };
     }
    

    The above resolver preserves this behavior. If you don't want this, set OverrideSpecifiedNames = false.

  • Json.NET has several built-in naming strategies including:

    1. CamelCaseNamingStrategy. A camel case naming strategy that contains the name-remapping logic formerly embedded in CamelCasePropertyNamesContractResolver.
    2. SnakeCaseNamingStrategy. A snake case naming strategy.
    3. DefaultNamingStrategy. The default naming strategy. Property names and dictionary keys are unchanged.

    Or, you can create your own by inheriting from the abstract base class NamingStrategy.

  • While it is also possible to modify the NamingStrategy of an instance of CamelCasePropertyNamesContractResolver, since the latter shares contract information globally across all instances of each type, this can lead to unexpected side-effects if your application tries to use multiple instances of CamelCasePropertyNamesContractResolver. No such problem exists with DefaultContractResolver, so it is safer to use when any customization of casing logic is required.

Solution 3 - C#

That is a very nice answer. But why not just override the ResolveDictionaryKey?

class CamelCaseExceptDictionaryResolver : CamelCasePropertyNamesContractResolver
    {
        #region Overrides of DefaultContractResolver

        protected override string ResolveDictionaryKey(string dictionaryKey)
        {
            return dictionaryKey;
        }

        #endregion
    }

Solution 4 - C#

The selected answer is perfect but I guess by the time I'm typing this, the contract resolver must change to something like this because DictionaryKeyResolver doesn't exists anymore :)

public class CamelCaseExceptDictionaryKeysResolver : CamelCasePropertyNamesContractResolver
	{
		protected override JsonDictionaryContract CreateDictionaryContract(Type objectType)
		{
			JsonDictionaryContract contract = base.CreateDictionaryContract(objectType);
			contract.PropertyNameResolver = propertyName => propertyName;
			return contract;
		}
	}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionzafeiris.mView Question on Stackoverflow
Solution 1 - C#Brian RogersView Answer on Stackoverflow
Solution 2 - C#dbcView Answer on Stackoverflow
Solution 3 - C#DmitriyView Answer on Stackoverflow
Solution 4 - C#DinaView Answer on Stackoverflow