Search code examples
c#json.netjson-deserialization

NewtonSoft Deserialize Same Object from multiple sources with different property names


Imagine having the following class

public class Person
{
public int Id {get; set;}
[JsonProperty("first_name")]
public string FirstName {get; set;}
[JsonProperty("last_name")]
public string LastName {get; set;}
}

I have two sources of JSON that I'm currently using to JsonConvert.DeserializeObject<Person>(source). This puts me in a pickle. Both sources must have identical property names in order for this to work. For the sake of this exercise, let's assume the followings for source.

source 1:

{
"id" : 1,
"first_name": "Jon",
"last_name": "Doe"
} 

source 2:

{
    "id" : 1,
    "firstName": "Jon",
    "lastName": "Doe"
} 

In this scenario, the second source isn't going to get deserialized into the Person object because the property names don't match.

I'm not sure how to deserialize these two sources to the same object without having the need to create a new object, which I don't want to do.


Solution

  • To solve this using a custom JsonConverter...

    Implement a custom JsonConverter that defines the field name mappings that can be different ...

    public class PersonConverter : JsonConverter
    {
        private Dictionary<string, string> propertyMappings { get; set; }
    
        public PersonConverter()
        {
            this.propertyMappings = new Dictionary<string, string>
            {
                {"firstName","first_name"},
                {"lastName","last_name"},
            };
        }
    
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }
    
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            object instance = Activator.CreateInstance(objectType);
            var props = objectType.GetTypeInfo().DeclaredProperties.ToList();
    
            JObject jo = JObject.Load(reader);
            foreach (JProperty jp in jo.Properties())
            {
                if (!propertyMappings.TryGetValue(jp.Name, out var name))
                        name = jp.Name;
    
                PropertyInfo prop = props.FirstOrDefault(pi =>
                        pi.CanWrite && pi.GetCustomAttribute<JsonPropertyAttribute>().PropertyName == name);
    
                prop?.SetValue(instance, jp.Value.ToObject(prop.PropertyType, serializer));
            }
            return instance;
        }
    
        public override bool CanConvert(Type objectType)
        {
            return objectType.GetTypeInfo().IsClass;
        }
    
        public override bool CanWrite => false;
    }
    

    Add the JsonConverter attribute on your object class, so your custom converter is used during deserializtion...

    [JsonConverter(typeof(PersonConverter))]
    public class RootObject
    {
        [JsonProperty("first_name")]
        public string FirstName{ get; set; }
    
        [JsonProperty("last_name")]
        public string LastName { get; set; }
    }
    

    To use the custom converter...

    string json_first_name = "{\"id\" : 1,\"first_name\": \"Jon\",\"last_name\": \"Doe\"}";
    string json_firstname = "{\"id\" : 1,\"firstName\": \"Jon\",\"lastName\": \"Doe\"}";
    
    var objFirst_Name = JsonConvert.DeserializeObject<RootObject>(json_first_name);
    var objFirstName = JsonConvert.DeserializeObject<RootObject>(json_firstname);