Extending the DefaultContractResolver to convert ExpandoObject sub properties to PascalCase

1.2k views Asked by At

I am trying to write a custom contract resolver that extends DefaultContractResolver in Newtonsoft.Json.Serialization, with the goal of converting all properties within an ExpandoObject to have PascalCase property names.

My contract:

public class Fruit 
{
    public int Id { get; set; }
    public ExpandoObject FruitProperties { get; set; }
}

I am passing in the following data:

{
  "Id": "1234",
  "FruitProperties" : {
      "colour": "red",
      "Taste": "sweet
   }

  }

}

The result I am expecting is the following:

{
   "Id": "1234",
   "FruitProperties" : {
      "Colour": "red",
      "Taste": "sweet"
    }
}

I have tried overriding ResolvePropertyName, and CreateProperty methods in the DefaultContractResolver with no luck. All of these skip the sub properties within the expando object. Does anyone know what method in the DefaultContractResolver I need to override to convert the sub property names in the Expando to PascalCase?

1

There are 1 answers

1
dbc On

ExpandoObject isn't serialized through reflection so modifying CreateProperty won't work. Rather, it is serialized as an IDictionary<string, object>. Thus you can take advantage of the new NamingStrategy type in Json.NET 9.0.1 to create a custom naming strategy to PascalCase only dictionary keys and nothing else. NamingStrategy has a property NamingStrategy.ProcessDictionaryKeys that, when set to true, causes Json.NET to map dictionary key names:

public class PascalCaseDictionaryKeyNamingStrategy : DefaultNamingStrategy
{
    public PascalCaseDictionaryKeyNamingStrategy() : base() { this.ProcessDictionaryKeys = true; }

    public override string GetDictionaryKey(string key)
    {
        if (ProcessDictionaryKeys && !string.IsNullOrEmpty(key))
        {
            if (char.ToUpperInvariant(key[0]) != key[0])
            {
                var builder = new StringBuilder(key);
                builder[0] = char.ToUpperInvariant(key[0]);
                return builder.ToString();
            }
        }
        return key;
    }
}

Then set it on DefaultContractResolver.NamingStrategy (or on any custom subclass of DefaultContractResolver if you prefer):

var resolver = new DefaultContractResolver { NamingStrategy = new PascalCaseDictionaryKeyNamingStrategy() };
var json = JsonConvert.SerializeObject(fruit, Formatting.Indented, new JsonSerializerSettings { ContractResolver = resolver });

Console.WriteLine(json);

Which outputs:

{
  "Id": 1234,
  "FruitProperties": {
    "Colour": "red",
    "Taste": "sweet"
  }
}