106

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; }    
}
Soner Gönül
  • 97,193
  • 102
  • 206
  • 364
zafeiris.m
  • 4,339
  • 5
  • 28
  • 41
  • 1
    Have you tried the default resolver? – Matthew Jun 10 '14 at 14:10
  • 1
    @Matthew No, I haven't; can you explain with an example how the code would look like? Note, I still want Camel case serialisation for all my web api requests, I just want custom serialisation for a class (or maybe for any dictionary keys). – zafeiris.m Jun 10 '14 at 21:18

4 Answers4

144

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"
  }
}
James Newton-King
  • 48,174
  • 24
  • 109
  • 130
Brian Rogers
  • 125,747
  • 31
  • 299
  • 300
  • 2
    FYI, PropertyNameResolver is now obsolete. It seems that `contract.DictionaryKeyResolver = key => key;` works just fine. – John Gietzen Aug 05 '15 at 18:57
  • 1
    This is still VERY relevant with anonymous types, especially when we do want camel casing for most of the structure, but do not want the keys inside dictionaries to be camelised. – Chris Schaller Aug 24 '15 at 23:07
  • Absolutely agree with Chris. I have been forced to go through hoops in my JavaScript just because I can't keep dictionaries from being camelCased. Turns out that one line of code will solve this problem (and make my JavaScript a lot simpler)! – Stephen Chung Feb 13 '16 at 09:08
  • @BrianRogers Works great! However, do you know if I can condition using my `DictionaryKeyResolver` only if my Dictionary property has some custom Attribute? – Mugen Feb 14 '16 at 09:34
  • @Mugen Not off the top of my head. I would recommend asking that as a new Question. You can link back to this question if you need to provide context. – Brian Rogers Feb 22 '16 at 15:54
  • Use naming strategy as suggested by dbc: https://stackoverflow.com/a/40732416/475727 – Liero Jul 26 '23 at 16:37
85

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.

dbc
  • 104,963
  • 20
  • 228
  • 340
  • This solution is not working for a property like ``public Dictionary> Values { get; set; }``. It still does camelCase for the inner dictionary keys. – hikalkan Oct 27 '17 at 07:43
  • @hikalkan - while I could not reproduce your exact problem, I was able to find a problem when using multiple instances of `CamelCasePropertyNamesContractResolver`. Basically `NamingStrategy` for the first would influence the contracts generated by the second. That *might* be what you are seeing. Try the new recommendation instead and let me know if if fixes your problem. – dbc Oct 27 '17 at 18:17
  • 1
    Is there a flexible `NamingStrategy`, so that it's capable of parsing both camel case and pascal case? – Shimmy Weitzhandler Dec 06 '17 at 22:52
  • @dbc In the initial code sample, what is `config` supposed to be? – Ryan Lundy Dec 07 '18 at 16:23
  • @RyanLundy - I copied it from the initial question, which showed the following line of code: `config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();`. It looks to be the MVC 4 Web API `HttpConfiguration`, see [How to set custom JsonSerializerSettings for Json.NET in MVC 4 Web API?](https://stackoverflow.com/q/13274625). – dbc Dec 07 '18 at 16:32
14

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
    }
Dmitriy
  • 638
  • 1
  • 6
  • 12
1

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;
        }
    }
Dina
  • 937
  • 9
  • 12
  • 6
    Actually the reverse is true. You must be using an old version of Json.Net. `DictionaryKeyResolver` was added in [version 7.0.1](https://github.com/JamesNK/Newtonsoft.Json/releases/tag/7.0.1), and `PropertyNameResolver` was marked obsolete. – Brian Rogers Nov 23 '16 at 16:11