7

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?

Karl Cassar
  • 6,043
  • 10
  • 47
  • 84

2 Answers2

9

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.

ismael.
  • 418
  • 2
  • 7
  • 2
    While that is an option, I have loads of classes and property names so it would be quite cumbersome to do all that code for just 1 property, when I have hundreds. I preferred some cleaner way, something like specifying multiple [JsonProperty] attributes to the same property. – Karl Cassar Mar 14 '18 at 13:45
  • Then the other option is to use the custom ContractResolver in the linked answer. Which basically maps the property to other names using a dictionary. I don't see a way to assign multiple [JsonProperty] attributes to one property. I'm going to try one more thing that might be cleaner than this and update the answer soon. – ismael. Mar 14 '18 at 14:08
  • @KarlCassar updated answer. This uses multiple [JsonProperty] and eliminates the use of the additional property. If you call using Name you will get results with property names Navn or Name. However, considering you have a lot of these, might be best to use the custom ContractResolver and mapping to other property names. – ismael. Mar 14 '18 at 14:37
  • 1
    Perfect, that is very good for what I need. I don't like the fact that the ContractResolver ends up with the mappings in a different location. I like them to be close to source so it's evident what is happening. – Karl Cassar Mar 14 '18 at 15:52
2

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;
    }
}
Sam
  • 1,725
  • 1
  • 17
  • 28
  • 1
    This works great when JsonSerializerSettings are configured with `DefaultValueHandling = DefaultValueHandling.Ignore` But, be warned that setting it to `DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate` can cause some of your values to get overwritten with default when one of the alternate names gets processed: This can be pretty confusing to debug if you're not expecting it. – Alex Hall Nov 11 '21 at 22:44