Home > OS >  NewtonSoft Deserialize Same Object from multiple sources with different property names
NewtonSoft Deserialize Same Object from multiple sources with different property names

Time:03-19

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.

CodePudding user response:

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);

CodePudding user response:

Create some setter only proxy properties (which you just ignore everywhere else, as the naming implies):

public class Person
{
  public int Id {get; set;}
  [JsonProperty("firstName")]
  public string FirstName {get; set;}
  [JsonProperty("lastName")]
  public string LastName {get; set;}
    
  [JsonProperty("first_name")]
  public string __Ignore1 { set { FirstName = value; } }
  [JsonProperty("last_name")]
  public string __Ignore2 { set { LastName = value; } }
}

Fiddle demo here.

I tried using NamingStrategy, but seems that only can tweak things the other way. I see no harm in this little hack - by being set only, they don't serialize.

CodePudding user response:

You do not necessarily need to create a new mapping. There are probably many ways to do it, a clever hack is the one proposed by @TheSoftwareJedi, here is another solution:

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

private static void PrintDeserializedObject(string obj, DefaultContractResolver resolver) 
{
    var person = JsonConvert.DeserializeObject<Person>(obj, new JsonSerializerSettings
    {
        ContractResolver = resolver,
        
    });
    Console.WriteLine("{0}.{1}.{2}", person.Id, person.FirstName, person.LastName);
}

public static void Main()
{
    var firstObj = @"{
        ""id"" : 1,
        ""first_name"": ""Jon"",
        ""last_name"": ""Doe""
        }";
    
    var secondObj = @"{
        ""id"" : 1,
        ""firstName"": ""Jon"",
        ""lastName"": ""Doe""
        }"; 
    
    DefaultContractResolver snakeResolver = new DefaultContractResolver
    {
        NamingStrategy = new SnakeCaseNamingStrategy()
    };

    PrintDeserializedObject(firstObj, snakeResolver);
    PrintDeserializedObject(secondObj, new CamelCasePropertyNamesContractResolver());
}

The idea is to use a ContractResolver and the NamingStrategy. As for the camel case, there is a built-in CamelCasePropertyNamesContractResolver. Instead, for the snake case, you have to create a new DefaultContractResolver with the naming strategy set to SnakeCaseNamingStrategy.

Note that in this case, you may get rid of JsonPropertyAttributes, at least I have not found a way to override them from the JsonSerializerSettings.

Try the fiddle.

  • Related