Keep Casing When Serializing Dictionaries

Keep casing when serializing dictionaries

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"
}
}

Force lowercase JSON serialization of dictionary keys while using camel case for class properties

You can solve this by implementing a custom NamingStrategy deriving from the CamelCaseNamingStrategy. It's just a few lines of code:

public class CustomNamingStrategy : CamelCaseNamingStrategy
{
public CustomNamingStrategy()
{
ProcessDictionaryKeys = true;
ProcessExtensionDataNames = true;
OverrideSpecifiedNames = true;
}

public override string GetDictionaryKey(string key)
{
return key.ToLower();
}
}

Then replace the CamelCasePropertyNamesContractResolver in your Web API global configuration with a DefaultContractResolver which uses the new naming strategy:

GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ContractResolver = 
new DefaultContractResolver() { NamingStrategy = new CustomNamingStrategy };

Here is a working demo (console app): https://dotnetfiddle.net/RVRiJY

Note: naming strategies are supported via [JsonProperty] and [JsonObject] attributes in Json.Net, but sadly the dictionary keys don't appear to get processed when you apply the strategy that way. At least it did not seem to work in my testing. Setting the strategy via resolver seems to be the way to go for what you are trying to accomplish.

How to make Json Serialize ignore dictionary keys

The problem doesn't reproduce with just the code in your question as shown in demo fiddle #1 here.

Instead, you must be serializing with some global serializer settings for which JsonSerializerSettings.ContractResolver.NamingStrategy.ProcessDictionaryKeys = true such as CamelCasePropertyNamesContractResolver:

var settings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
};
var json = JsonConvert.SerializeObject(root, Formatting.Indented, settings);

Demo fiddle #2 here.

Assuming that's correct, the reason that [JsonProperty(NamingStrategyType = typeof(SnakeCaseNamingStrategy), NamingStrategyParameters = new object[] { false, false })] does not cause the dictionary keys to be serialized verbatim is that JsonPropertyAttribute.NamingStrategyType only applies to the property name itself (here CustomAttributes) not the property names of property's items. If you wanted to apply a naming strategy to the property's items you would need something like ItemNamingStrategyType -- but JsonPropertyAttribute has no such functionality.

So, what are your options?

  1. You could modify your global naming strategy to serialize dictionary names verbatim as shown in Keep casing when serializing dictionaries.

  2. You could subclass Dictionary<TKey, TValue> and apply [JsonDictionary(NamingStrategyType = typeof(DefaultNamingStrategy))] to it as shown in this answer to Applying JsonDictionary attribute to dictionary:

    [JsonDictionary(NamingStrategyType = typeof(DefaultNamingStrategy))]
    public class VerbatimDictionary<TKey, TValue> : Dictionary<TKey, TValue>
    {
    }

    And then later:

    CustomAttributes = new VerbatimDictionary<string, string>()
    {
    {"Custom Attribute 1", "1"},
    {"CustomAttribute 2", "2"}
    }

    Demo fiddle #3 here.

  3. You could introduce a custom JsonConverter that serializes an IDictionary<TKey, TValue> with the default naming strategy. First, define the following converter:

    public class VerbatimDictionaryConverter<TKey, TValue> : JsonConverter<IDictionary<TKey, TValue>>
    {
    [JsonDictionary(NamingStrategyType = typeof(DefaultNamingStrategy))]
    class VerbatimDictionarySerializationSurrogate : IReadOnlyDictionary<TKey, TValue>
    {
    readonly IDictionary<TKey, TValue> dictionary;

    public VerbatimDictionarySerializationSurrogate(IDictionary<TKey, TValue> dictionary)
    {
    if (dictionary == null)
    throw new ArgumentNullException(nameof(dictionary));
    this.dictionary = dictionary;
    }
    public bool ContainsKey(TKey key) { return dictionary.ContainsKey(key); }
    public bool TryGetValue(TKey key, out TValue value) { return dictionary.TryGetValue(key, out value); }
    public TValue this[TKey key] { get { return dictionary[key]; } }
    public IEnumerable<TKey> Keys { get { return dictionary.Keys; } }
    public IEnumerable<TValue> Values { get { return dictionary.Values; } }
    public int Count { get { return dictionary.Count; } }
    public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() { return dictionary.GetEnumerator(); }
    IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
    }

    public override void WriteJson(JsonWriter writer, IDictionary<TKey, TValue> value, JsonSerializer serializer)
    {
    serializer.Serialize(writer, new VerbatimDictionarySerializationSurrogate(value));
    }

    public override bool CanRead { get { return false; } }

    public override IDictionary<TKey, TValue> ReadJson(JsonReader reader, Type objectType, IDictionary<TKey, TValue> existingValue, bool hasExistingValue, JsonSerializer serializer) { throw new NotImplementedException(); }
    }

    And apply it as follows:

    [JsonConverter(typeof(VerbatimDictionaryConverter<string, string>))]
    public IDictionary<string, string> CustomAttributes { get; set; }

    Demo fiddle #4 here.

Json.net - How to preserve dictionary value references when populating a dictionary?

Your problem is similar to the one from JsonSerializer.CreateDefault().Populate(..) resets my values: you would like to populate a preexisting collection, specifically a Dictionary<int, T> for some T, and populate the preexisting values. Unfortunately, in the case of a dictionary, Json.NET will replace the values rather than populate them, as can be seen in JsonSerializerInternalReader.PopulateDictionary() which simply deserializes the value to the appropriate type, and sets it the dictionary.

To work around this limitation, you can create a custom JsonConverter for Dictionary<TKey, TValue> when TKey is a primitive type and TValue is a complex type which merges the incoming JSON key/value pairs onto the preexisting dictionary. The following converter does the trick:

public class DictionaryMergeConverter : JsonConverter
{
static readonly IContractResolver defaultResolver = JsonSerializer.CreateDefault().ContractResolver;
readonly IContractResolver resolver = defaultResolver;

public override bool CanConvert(Type objectType)
{
var keyValueTypes = objectType.GetDictionaryKeyValueType();
if (keyValueTypes == null)
return false;
var keyContract = resolver.ResolveContract(keyValueTypes[0]);
if (!(keyContract is JsonPrimitiveContract))
return false;
var contract = resolver.ResolveContract(keyValueTypes[1]);
return contract is JsonContainerContract;
// Also possibly check whether keyValueTypes[1] is a read-only collection or dictionary.
}

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.MoveToContentAndAssert().TokenType == JsonToken.Null)
return null;
if (reader.TokenType != JsonToken.StartObject)
throw new JsonSerializationException(string.Format("Unexpected token {0}", reader.TokenType));
IDictionary dictionary = existingValue as IDictionary ?? (IDictionary)serializer.ContractResolver.ResolveContract(objectType).DefaultCreator();
var keyValueTypes = objectType.GetDictionaryKeyValueType();
while (reader.ReadToContentAndAssert().TokenType != JsonToken.EndObject)
{
switch (reader.TokenType)
{
case JsonToken.PropertyName:
var name = (string)reader.Value;
reader.ReadToContentAndAssert();

// TODO: DateTime keys and enums with overridden names.
var key = (keyValueTypes[0] == typeof(string) ? (object)name : Convert.ChangeType(name, keyValueTypes[0], serializer.Culture));
var value = dictionary.Contains(key) ? dictionary[key] : null;

// TODO:
// - JsonConverter active for valueType, either in contract or in serializer.Converters
// - NullValueHandling, ObjectCreationHandling, PreserveReferencesHandling,

if (value == null)
{
value = serializer.Deserialize(reader, keyValueTypes[1]);
}
else
{
serializer.Populate(reader, value);
}
dictionary[key] = value;
break;

default:
throw new JsonSerializationException(string.Format("Unexpected token {0}", reader.TokenType));
}
}

return dictionary;
}

public override bool CanWrite { get { return false; } }

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { throw new NotImplementedException(); }
}

public static partial class JsonExtensions
{
public static JsonReader ReadToContentAndAssert(this JsonReader reader)
{
return reader.ReadAndAssert().MoveToContentAndAssert();
}

public static JsonReader MoveToContentAndAssert(this JsonReader reader)
{
if (reader == null)
throw new ArgumentNullException();
if (reader.TokenType == JsonToken.None) // Skip past beginning of stream.
reader.ReadAndAssert();
while (reader.TokenType == JsonToken.Comment) // Skip past comments.
reader.ReadAndAssert();
return reader;
}

public static JsonReader ReadAndAssert(this JsonReader reader)
{
if (reader == null)
throw new ArgumentNullException();
if (!reader.Read())
throw new JsonReaderException("Unexpected end of JSON stream.");
return reader;
}
}

public static class TypeExtensions
{
public static IEnumerable<Type> BaseTypesAndSelf(this Type type)
{
while (type != null)
{
yield return type;
type = type.BaseType;
}
}

public static Type[] GetDictionaryKeyValueType(this Type type)
{
return type.BaseTypesAndSelf().Where(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Dictionary<,>)).Select(t => t.GetGenericArguments()).FirstOrDefault();
}
}

Having done so, you will encounter a secondary issue: Json.NET will never use a custom converter to populate the root object. To work around this you will need to call JsonConverter.ReadJson() directly, from some utility method:

public static partial class JsonExtensions
{
public static void PopulateObjectWithConverter(string value, object target, JsonSerializerSettings settings)
{
if (target == null || value == null)
throw new ArgumentNullException();
var serializer = JsonSerializer.CreateDefault(settings);
var converter = serializer.Converters.Where(c => c.CanConvert(target.GetType()) && c.CanRead).FirstOrDefault() ?? serializer.ContractResolver.ResolveContract(target.GetType()).Converter;
using (var jsonReader = new JsonTextReader(new StringReader(value)))
{
if (converter == null)
serializer.Populate(jsonReader, target);
else
{
jsonReader.MoveToContentAndAssert();
var newtarget = converter.ReadJson(jsonReader, target.GetType(), target, serializer);
if (newtarget != target)
throw new JsonException(string.Format("Converter {0} allocated a new object rather than populating the existing object {1}.", converter, value));
}
}
}
}

You will now be able to populate your dictionary as follows:

var jsonString = JsonConvert.SerializeObject(to_serialize, Formatting.Indented);

var settings = new JsonSerializerSettings
{
Converters = { new DictionaryMergeConverter() },
};
JsonExtensions.PopulateObjectWithConverter(jsonString, to_serialize, settings);

Notes:

  • PreserveReferencesHandling has no impact on whether dictionary values are populated or replaced. Instead this setting controls whether a serialization graph with multiple references to the same object will maintain its reference topology when round-tripped.

  • In your question you wrote // works ok with list<Model> but in fact this is not correct. When a List<T> is populated the new values are appended to the list, so Assert.AreSame(to_serialize[0], model); passes purely by luck. If you had additionally asserted Assert.AreSame(1, to_serialize.Count) it would have failed.

  • While the converter will work for primitive keys such as string and int it may not work for key types that require JSON-specific conversion such as enum or DateTime.

  • The converter is currently only implemented for Dictionary<TKey, TValue> and takes advantage of the fact that this type implements the non-generic IDictionary interface. It could be extended to other dictionary types such as SortedDictionary<TKey,TValue> if required.

Demo fiddle here.

How to keep casing of properties in json returned by Asp.Net Core controller?

For setting per controller, you could try IResultFilter or passing the JsonSerializerSettings.

For a Controller like below:

        public async Task<ActionResult> Test()
{
return Ok(new Product { Id = 1, Name = "test" });
}

Options1

    public async Task<ActionResult> Test()
{
return new JsonResult(new Product { Id = 1, Name = "tt" },new Newtonsoft.Json.JsonSerializerSettings { ContractResolver = new DefaultContractResolver() });
}

Options2

  1. Custom IResultFilter

    public class JsonSerializeFilter : IResultFilter
    {
    public void OnResultExecuted(ResultExecutedContext context)
    {

    }

    public void OnResultExecuting(ResultExecutingContext context)
    {
    var result = context.Result as ObjectResult;
    var value = JsonConvert.SerializeObject(result.Value);
    context.Result = new ObjectResult(JsonConvert.SerializeObject(
    result.Value,
    new JsonSerializerSettings { ContractResolver = new DefaultContractResolver() }
    ));
    }
    }
  2. Use JsonSerializeFilter

    [TypeFilter(typeof(JsonSerializeFilter))]
    public async Task<ActionResult> Test()
    {
    return Ok(new Product { Id = 1, Name = "tt" });
    }


Related Topics



Leave a reply



Submit