Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Different "translated" JsonProperty names

Tags:

c#

json.net

I want to translate keys used in JSON to different languages.

I'm aware this seems like nonsense from a technical perspective when designing interfaces, APIs and so on. Why not use English only in the first place? Well, I didn't write this requirement ;)

/// <summary>serialization language</summary>
public enum Language
{
   /// <summary>English</summary>
   EN,
   /// <summary>German</summary>
   DE
   // some more ...
}

The easiest way to achieve this is probably an attribute:

/// <summary>An Attribute to add different "translations"</summary>
public class TranslatedFieldName : Attribute
{
   public string Name { get; }
   public Language Lang{ get; }
   // actually there might be a dictionary with lang-name pairs later on; but let's keep it simple
   public TranslatedFieldName(string translatedName, Language lang)
   {
       this.Lang = lang;
       this.Name = translatedName;
    }
}

Then I add this attribute to the class I'd like to serialize:

/// <summary>I want to serialize classes of this kind to json in different languages </summary>
public class TranslatableObject
{
    [TranslatedFieldName("deutscher_schluessel", Language.DE)]
    public string english_key;
}

and call JsonConvert.SerializeObject(...)

public void SerializationMethod()
{
    TranslatableObject to = new TranslatableObject()
    {
        english_key = "foo bar"
    };
    string englishJson = JsonConvert.SerializeObject(to); // == { "english_key": "foo bar" }
    string germanJson = JsonConvert.SerializeObject(to, new MultiLangConverter(Language.DE); // == { "deutscher_schluessel": "foo bar" }
}

I don't have a clue how the used MultiLangConverter should actually look like:

public class MultiLangConverter : JsonConverter
{
    private readonly Language _lang,

    public MultiLangConverter() : this(Language.EN) { }

    public MultiLangConverter(Language lang)
    {
        _lang = lang;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException("Out of scope for now");
    }

    public override bool CanRead
    {
        get { return false; } // out of scope for now
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        // what goes here?
    }
}

All the attempts I tried seemed overly complex. Do I actually need a JsonConverter? Or is a ContractResolver better suited?

like image 334
koks der drache Avatar asked Sep 19 '19 14:09

koks der drache


1 Answers

I think you are on the right track with using attributes to represent the translated field names, but I think you will ultimately want to use a custom ContractResolver to handle the translation rather than a JsonConverter, especially if you will need to do translation on multiple model classes (seems likely).

Here is what the resolver would look like:

public class MultiLangResolver : DefaultContractResolver
{
    public Language Language { get; set; }

    public MultiLangResolver(Language lang)
    {
        Language = lang;
    }

    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        JsonProperty prop = base.CreateProperty(member, memberSerialization);

        // See if there is a [TranslatedFieldName] attribute applied to the property 
        // for the requested language
        var att = prop.AttributeProvider.GetAttributes(true)
                                        .OfType<TranslatedFieldNameAttribute>()
                                        .FirstOrDefault(a => a.Lang == Language);

        // if so, change the property name to the one from the attribute
        if (att != null)
        {
            prop.PropertyName = att.Name;
        }
        return prop;
    }
}

And here is how you would use the resolver to serialize:

public static string Serialize(object obj, Language lang)
{
    var settings = new JsonSerializerSettings
    {
        ContractResolver = new MultiLangResolver(lang),
        Formatting = Formatting.Indented
    };
    var json = JsonConvert.SerializeObject(obj, settings);
    return json;
}

One other note: you will want to mark your TranslatedFieldNameAttribute class with an [AttributeUsage] attribute which indicates that it can be used on properties and can be applied multiple times (once for each different language you want to support):

[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = true)]
public class TranslatedFieldNameAttribute : Attribute
{
    ...
}

Working demo: https://dotnetfiddle.net/lIQaJc

like image 101
Brian Rogers Avatar answered Nov 05 '22 14:11

Brian Rogers