Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mapping multiple property names to the same field in Newtonsoft.JSON

Tags:

json

json.net

I have two components in a distributed system, which send messages which are serialized/deserialized using Newtonsoft.JSON (JSON.Net).

The message properties are currently sent in Norwegian, and I am looking to translate the codebase to English. Since there is a change that some messages would have been sent in Norwegian, and handled by a component which has been upgraded to the English version, it needs to be able to support both.

I would like that on deserialization, both the 'Norwegian' property name as well as English would map to the same property. For example:

For example, take 'name' in English or 'navn' in Norwegian.

public class Message
{
     [JsonProperty("Navn")]
     public string Name { get; set;}
}

The problem with the above is that it would map only from Navn => Name. I would like it to map both Navn and Name to Name.

Is this available in Newtonsoft.JSON, without much custom coding?

like image 444
Karl Cassar Avatar asked Mar 13 '18 10:03

Karl Cassar


2 Answers

You could use a custom ContractResolver in this answer:

Json.NET deserialize or serialize json string and map properties to different property names defined at runtime

Or

Use [JsonProperty("")] to look for different variations of the property name and return with one of the properties like this:

public class Message
{
   private string _name;

   [JsonProperty("Navn" )]
   public string NorwegianName { get; set; }

   [JsonProperty("Name")]
   public string Name { 
      get { return _name ?? NorwegianName; } 
      set { _name = value; } }
}

This will return the name with JSON property name: Navn or Name.

like image 191
ismael. Avatar answered Sep 29 '22 02:09

ismael.


I had a need to do this and after some experimentation I've found that a resolver that returns multiple JsonProperties for the same MemberInfo is working great. It also serializes with a single specified name but can read from any one name. I'm using this migrate names, its NOT expecting to find more than one of the possible names in the source json.

public class TestClass
{
    [FallbackJsonProperty("fn", "firstName", "first_name")]
    public string FirstName {get;set;}
}

public class ThingResolver : CamelCasePropertyNamesContractResolver
{
    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    {
        var typeMembers = GetSerializableMembers(type);
        var properties = new List<JsonProperty>();

        foreach (var member in typeMembers)
        {
            var property = CreateProperty(member, memberSerialization);
            properties.Add(property);
            
            var fallbackAttribute = member.GetCustomAttribute<FallbackJsonProperty>();

            if (fallbackAttribute == null)
            {
                continue;
            }

            property.PropertyName = fallbackAttribute.PreferredName;
            
            foreach (var alternateName in fallbackAttribute.FallbackReadNames)
            {
                var fallbackProperty = CreateProperty(member, memberSerialization);
                fallbackProperty.PropertyName = alternateName;
                fallbackProperty.ShouldSerialize = (x) => false;
                properties.Add(fallbackProperty);
            }
        }

        return properties;
    }
}

[AttributeUsage(AttributeTargets.Property)]
public class FallbackJsonProperty : Attribute
{
    public string PreferredName { get; }
    public string[] FallbackReadNames { get; }

    public FallbackJsonProperty(string preferredName, params string[] fallbackReadNames)
    {
        PreferredName = preferredName;
        FallbackReadNames = fallbackReadNames;
    }
}
like image 26
Sam Avatar answered Sep 29 '22 01:09

Sam