89

I have two format of JSON which I want to Deserialize to one class. I know we can't apply two [JsonProperty] attribute to one property.

Can you please suggest me a way to achieve this?

string json1 = @"
    {
        'field1': '123456789012345',
        'specifications': {
            'name1': 'HFE'
        }
    }";

string json2 = @"
    {
        'field1': '123456789012345',
        'specifications': {
            'name2': 'HFE'
        }
    }";

public class Specifications
{
    [JsonProperty("name1")]
    public string CodeModel { get; set; }
}

public class ClassToDeserialize
{
    [JsonProperty("field1")]
    public string Vin { get; set; }

    [JsonProperty("specification")]
    public Specifications Specifications { get; set; }        
}

I want name1 and name2 both to be deserialize to name1 property of specification class.

PoLáKoSz
  • 355
  • 1
  • 6
  • 7
Vivek Tiwari
  • 961
  • 1
  • 8
  • 7
  • this seems like a design problem. But if you want to do it anyway, you could write a custom json converter and map the 2 names to name1 there. Here is an example of such a converter: http://stackoverflow.com/questions/36233759/web-api-2-custom-data-type-json-serialization/36243575#36243575 – Khanh TO May 01 '17 at 04:00
  • follow the steps here... http://stackoverflow.com/a/19885911/2445471 – khaled4vokalz May 01 '17 at 04:03
  • 1
    @Khanh TO Yes I know this is a bit strange requirement. actually we are getting data from two diff sources and both have diff format of data. what we are trying to do is to map it to a common format. coming to json converter part I didn't see any example where nested class fields could be mapped to two different names. it would be great if you could help. thanks in advance. – Vivek Tiwari May 01 '17 at 04:05
  • @khaled4vokalz I have already seen all of example we have on stack overflow. nothing suggest to have two names for one property of nested class :( – Vivek Tiwari May 01 '17 at 04:08

5 Answers5

238

A simple solution which does not require a converter: just add a second, private property to your class, mark it with [JsonProperty("name2")], and have it set the first property:

public class Specifications
{
    [JsonProperty("name1")]
    public string CodeModel { get; set; }

    [JsonProperty("name2")]
    private string CodeModel2 { set { CodeModel = value; } }
}

Fiddle: https://dotnetfiddle.net/z3KJj5

Brian Rogers
  • 125,747
  • 31
  • 299
  • 300
  • 3
    Very helpful, thanks. Small nuance I encountered is that I had to explicitly provide the JsonProperty attribute for private properties. Usually "name2" would automatically map to "Name2" without the JsonProperty attribute but it did not for me in this case. May have just been a quirk of our settings, but just in case it helps someone else. – Steve Cadwallader May 23 '18 at 13:06
  • 10
    @SteveCadwallader It's not a quirk. By design, Json.Net does not automatically serialize or deserialize private properties. Using the attribute signals that you *do* want it serialized/deserialized. – Brian Rogers May 23 '18 at 14:50
  • 3
    I also needed to set the Required property on the attribute -- by default, JsonProperty attribute's Required is set to "Required" rather than "Default"... haha. – Slate Jun 18 '20 at 18:35
  • 4
    Epic. If only ideas like this could earn as much money as the paperclip. – Frank Apr 18 '22 at 12:46
  • 1
    For me, making the second parameter `private` did not work when using `JsonSerializer.Deserialize(theString);` . After I made both `public` it started working. – Serhat Jan 11 '23 at 14:40
  • This is useful, yes, but sadly does not apply when deserializing `enum` types. – alelom Feb 05 '23 at 17:24
  • @alelom I'm not sure what you mean. It seems to [work fine for me](https://dotnetfiddle.net/j5ZbUn). Maybe you are missing a `StringEnumConverter`? – Brian Rogers Feb 07 '23 at 03:44
  • It seems the syntax has been changed to `private string CodeModel2 { set => CodeModel = value; }` as of C# version 11. – PaulMag Jul 11 '23 at 08:32
2

Tricking custom JsonConverter worked for me. Thanks @khaled4vokalz, @Khanh TO

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        object instance = objectType.GetConstructor(Type.EmptyTypes).Invoke(null);
        PropertyInfo[] props = objectType.GetProperties();

        JObject jo = JObject.Load(reader);
        foreach (JProperty jp in jo.Properties())
        {
            if (string.Equals(jp.Name, "name1", StringComparison.OrdinalIgnoreCase) || string.Equals(jp.Name, "name2", StringComparison.OrdinalIgnoreCase))
            {
                PropertyInfo prop = props.FirstOrDefault(pi =>
                pi.CanWrite && string.Equals(pi.Name, "CodeModel", StringComparison.OrdinalIgnoreCase));

                if (prop != null)
                    prop.SetValue(instance, jp.Value.ToObject(prop.PropertyType, serializer));
            }
        }

        return instance;
    }
Vivek Tiwari
  • 961
  • 1
  • 8
  • 7
0

You can do it using a JsonConverter.

It's useful for example when you consume some data from 3rd party services and they keep changing property names and then going back to previous property names. :D

The following code shows how to deserialize from multiple property names to the same class property decorated with a [JsonProperty(PropertyName = "EnrollmentStatusEffectiveDateStr")] attribute.

The class MediCalFFSPhysician is also decorated with the custom JsonConverter: [JsonConverter(typeof(MediCalFFSPhysicianConverter))]

Note that _propertyMappings dictionary holds the possible property names that should be mapped to the property EnrollmentStatusEffectiveDateStr:

private readonly Dictionary<string, string> _propertyMappings = new()
{
    {"Enrollment_Status_Effective_Dat", "EnrollmentStatusEffectiveDateStr"},
    {"Enrollment_Status_Effective_Date", "EnrollmentStatusEffectiveDateStr"},
    {"USER_Enrollment_Status_Effectiv", "EnrollmentStatusEffectiveDateStr"}
};

Full code:

    // See https://aka.ms/new-console-template for more information
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using System.Reflection;
using System.Text.Json;

internal class JSONDeserialization
{
    private static void Main(string[] args)
    {
        var jsonPayload1 = $"{{\"Enrollment_Status_Effective_Dat\":\"2022/10/13 19:00:00+00\"}}";
        var jsonPayload2 = $"{{\"Enrollment_Status_Effective_Date\":\"2022-10-13 20:00:00+00\"}}";
        var jsonPayload3 = $"{{\"USER_Enrollment_Status_Effectiv\":\"2022-10-13 21:00:00+00\"}}";

        var deserialized1 = JsonConvert.DeserializeObject<MediCalFFSPhysician>(jsonPayload1);
        var deserialized2 = JsonConvert.DeserializeObject<MediCalFFSPhysician>(jsonPayload2);
        var deserialized3 = JsonConvert.DeserializeObject<MediCalFFSPhysician>(jsonPayload3);

        Console.WriteLine(deserialized1.Dump());
        Console.WriteLine(deserialized2.Dump());
        Console.WriteLine(deserialized3.Dump());

        Console.ReadKey();
    }
}

public class MediCalFFSPhysicianConverter : JsonConverter
{
    private readonly Dictionary<string, string> _propertyMappings = new()
    {
        {"Enrollment_Status_Effective_Dat", "EnrollmentStatusEffectiveDateStr"},
        {"Enrollment_Status_Effective_Date", "EnrollmentStatusEffectiveDateStr"},
        {"USER_Enrollment_Status_Effectiv", "EnrollmentStatusEffectiveDateStr"}
    };

    public override bool CanWrite => false;

    public override void WriteJson(JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    public override bool CanConvert(Type objectType)
    {
        return objectType.GetTypeInfo().IsClass;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.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;
    }
}

[JsonConverter(typeof(MediCalFFSPhysicianConverter))]
public class MediCalFFSPhysician
{
    [JsonProperty(PropertyName = "EnrollmentStatusEffectiveDateStr")]
    public string EnrollmentStatusEffectiveDateStr { get; set; }
}

public static class ObjectExtensions
{
    public static string Dump(this object obj)
    {
        try
        {
            return System.Text.Json.JsonSerializer.Serialize(obj, new JsonSerializerOptions { WriteIndented = true });
        }
        catch (Exception)
        {
            return string.Empty;
        }
    }
}

The output is this:

enter image description here

Adapted from: Deserializing different JSON structures to the same C# class

Leniel Maccaferri
  • 100,159
  • 46
  • 371
  • 480
-1

I had the same use case, though in Java.

Resource that helped https://www.baeldung.com/json-multiple-fields-single-java-field

We can use a

@JsonProperty("main_label_to_serialize_and_deserialize")
@JsonAlias("Alternate_label_if_found_in_json_will_be_deserialized")

In your use case you could do

@JsonProperty("name1")
@JsonAlias("name2")
Peter Csala
  • 17,736
  • 16
  • 35
  • 75
-2

You could even do this for more than 2 names.

@JsonProperty("name1")
@JsonAlias({"name2","name3","name4"})
gongqin
  • 65
  • 1
  • 8
  • 2
    The question is asked for C# language and you have given an answer for Java language. JsonAlias is currently unavailable in C# – Serhat Jan 11 '23 at 14:45