2

I have some .NET classes with localized text; i.e. text in English, corresponding text in Spanish, etc. We have a Locale class that looks something like this:

class Locale
{
   int Id;
   string Abbreviation; // e.g. "en"
   string Name;         // e.g. "English"

   static Locale FromAbbreviation(string abbreviation);
}

Localized text is stored in IDictionary properties, something like

class Document
{
   IDictionary<Locale, string> Content;
}

When serializing to JSON, I would like this to be keyed by locale abbreviation, so a serialized Document object would look something like this:

{
   "content": {
      "en": "English content",
      "es": "Spanish content"
   }
}

I need a ContractResolver that converts an IDictionary<Locale, string> object to a Dictionary<string, string> object, using the Locale.Abbreviation property as the key during serialization, and calling Locale.FromAbbreviation() on deserialization to convert the key back to a Locale object.

I have looked at the JSON.NET documentation and various Stackoverflow questions, and there does not seem to be an easy way to do this (at least I can't find it). I did find what looks like a straightforward way to do the same thing using a TypeConverter attribute, but I would rather not take a dependence on Json.NET from my domain classes. Is there a reasonable way to do this using a ContractResolver?

Eric Pohl
  • 2,324
  • 1
  • 21
  • 31

1 Answers1

2

You don't really need a ContractResolver here. You can use a custom JsonConverter to handle the conversion from Dictionary<Locale, string> to your desired JSON and back without needing to use an attribute in your model classes. As long as the converter's CanConvert method is coded correctly to identify the dictionary type that the converter handles, then you can just add the converter to the serializer settings and Json.Net will find it and use it appropriately.

Here is what the converter might look like:

class LocaleDictionaryConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return typeof(IDictionary<Locale, string>).IsAssignableFrom(objectType);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JObject obj = JObject.Load(reader);
        IDictionary<Locale, string> dict = (IDictionary<Locale, string>)existingValue ?? new Dictionary<Locale, string>();
        foreach (var prop in obj.Properties())
        {
            dict.Add(Locale.FromAbbreviation(prop.Name), (string)prop.Value);
        }
        return dict;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        IDictionary<Locale, string> dict = (IDictionary<Locale, string>)value;
        JObject obj = new JObject();
        foreach (var kvp in dict)
        {
            obj.Add(kvp.Key.Abbreviation, kvp.Value);
        }
        obj.WriteTo(writer);
    }
}

And here is how you would use it to serialize:

var settings = new JsonSerializerSettings();
settings.Converters.Add(new LocaleDictionaryConverter());
settings.Formatting = Formatting.Indented;

string json = JsonConvert.SerializeObject(document, settings);

And deserialize:

// same settings as above
var document = JsonConvert.DeserializeObject<Document>(json, settings);

Here is a demo: https://dotnetfiddle.net/f1rXl2

Robert Synoradzki
  • 1,766
  • 14
  • 20
Brian Rogers
  • 125,747
  • 31
  • 299
  • 300
  • This looks perfect; I will give it a whirl. Thanks for the quick response! – Eric Pohl Aug 15 '17 at 15:30
  • The only change I needed to make was to use the existingValue argument in ReadJson() since these types of dictionary references tend to be readonly in our code (see https://stackoverflow.com/a/27904050/2144): IDictionary dict = (IDictionary)existingValue ?? new Dictionary(); – Eric Pohl Aug 15 '17 at 16:02