multiple JsonProperty Name assigned to single property
Asked Answered
J

6

94

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.

Junie answered 1/5, 2017 at 3:55 Comment(4)
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: #36234259Tolley
follow the steps here... https://mcmap.net/q/225284/-alternate-property-name-while-deserializingYesman
@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.Junie
@Yesman I have already seen all of example we have on stack overflow. nothing suggest to have two names for one property of nested class :(Junie
C
250

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

Cymogene answered 1/5, 2017 at 6:7 Comment(8)
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.Overlying
@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.Cymogene
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.Alfons
Epic. If only ideas like this could earn as much money as the paperclip.Diplomacy
For me, making the second parameter private did not work when using JsonSerializer.Deserialize<MyObject>(theString); . After I made both public it started working.Mucor
This is useful, yes, but sadly does not apply when deserializing enum types.Prostyle
@Prostyle I'm not sure what you mean. It seems to work fine for me. Maybe you are missing a StringEnumConverter?Cymogene
It seems the syntax has been changed to private string CodeModel2 { set => CodeModel = value; } as of C# version 11.Stimulant
J
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;
    }
Junie answered 1/5, 2017 at 4:34 Comment(0)
D
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

Deel answered 13/10, 2022 at 23:25 Comment(0)
D
0

The solution I applied was: I used a regex to patch the JSON text before deserializing it to eliminate variations (i.e. I transformed name1 and name2 in name, etc.), then applied a standard conversion.

Starting from your JSON I modified a bit your classes by renaming the JsonProperty:

public class Specifications
{
    [JsonProperty("name")]    // << originally was 'name1'
    public string CodeModel { get; set; }
}

public class ClassToDeserialize
{
    [JsonProperty("field")]   // << originally was 'field1'
    public string Vin { get; set; }

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

An then I used this parsing code:

// convert all 'field{n}' and 'name{m}' in 'field' and 'name'
var patchedJson = Regex.Replace(json1, @"(')([a-zA-Z]+)\d+(')", "$1$2$3", RegexOptions.Multiline);
// deserialize the patched json
var data = JsonConvert.DeserializeObject<ClassToDeserialize>(patchedJson);

// convert all 'field{n}' and 'name{m}' in 'field' and 'name'
patchedJson = Regex.Replace(json2, @"(')([a-zA-Z]+)\d+(')", "$1$2$3", RegexOptions.Multiline);
// deserialize the patched json
data = JsonConvert.DeserializeObject<ClassToDeserialize>(patchedJson);

Obviously this trick only works if during the conversion you don't generate different properties with duplicate names.

Also the length of the JSON text might be an issue.

Drift answered 11/1 at 14:45 Comment(0)
F
-2

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")
Fahlband answered 20/1, 2022 at 5:39 Comment(0)
T
-3

You could even do this for more than 2 names.

@JsonProperty("name1")
@JsonAlias({"name2","name3","name4"})
Tenatenable answered 20/6, 2022 at 14:27 Comment(1)
The question is asked for C# language and you have given an answer for Java language. JsonAlias is currently unavailable in C#Mucor

© 2022 - 2024 — McMap. All rights reserved.