一个属性有多个JsonProperty名称分配

90

我有两种JSON格式,我希望将它们反序列化成一个类。 我知道我们不能在一个属性上应用两个 [JsonProperty] 属性。

你能否建议一种方法来实现这个目标呢?

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

我希望name1name2都被反序列化成规范类的name1属性。


这似乎是一个设计问题。但如果你仍想这么做,你可以编写一个自定义的JSON转换器,并将这两个名称映射到name1。以下是这样一个转换器的示例:https://dev59.com/5pXfa4cB1Zd3GeqPfHJP#36243575 - Khanh TO
请按照这里的步骤进行操作... https://dev59.com/OGIj5IYBdhLWcg3w4Y2S#19885911 - khaled4vokalz
1
@Khanh TO,是的,我知道这个要求有点奇怪。实际上,我们从两个不同的数据源获取数据,它们都有不同的数据格式。我们试图做的是将它们映射到一个共同的格式。关于 JSON 转换器部分,我没有看到任何示例可以将嵌套类字段映射到两个不同的名称。如果你能帮忙,那就太好了。先谢谢了。 - Vivek Tiwari
@khaled4vokalz 我已经看过我们在 Stack Overflow 上的所有示例了。没有任何建议为嵌套类的一个属性拥有两个名称 :( - Vivek Tiwari
5个回答

241

一种简单的解决方案不需要转换器:只需在您的类中添加第二个私有属性,用[JsonProperty("name2")]标记它,并将其设置为第一个属性:

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

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

演示代码: https://dotnetfiddle.net/z3KJj5


3
非常有帮助,谢谢。我遇到的一个小细节是,我必须为私有属性显式提供JsonProperty属性。通常,“name2”会自动映射到“Name2”,而不需要JsonProperty属性,但在我的情况下却没有。可能只是我们设置的一个怪癖,但以防对其他人有所帮助。 - Steve Cadwallader
10
这句话的意思是:“Json.Net的设计并不会自动序列化或反序列化私有属性,所以使用该属性可以表明你确实希望对其进行序列化/反序列化。”“quirk”在这里的含义是“小毛病”,指的是Json.Net不会默认处理私有属性的特点。 - Brian Rogers
3
我还需要在属性上设置Required属性--默认情况下,JsonProperty属性的Required属性设置为“Required”,而不是“Default”...哈哈。 - Slate
4
史诗般的想法。如果这样的想法能够像回形针一样赚到那么多的钱就好了。 - Frank
1
对我来说,当使用JsonSerializer.Deserialize<MyObject>(theString);时,将第二个参数设置为private并没有起作用。在我将两个参数都设置为public后,它开始工作了。 - Serhat
显示剩余3条评论

2

欺骗自定义JsonConverter对我很有用。 感谢@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;
    }

0

你可以使用JsonConverter来实现。

例如,当你从第三方服务中获取一些数据时,它们经常更改属性名称,然后又返回到之前的属性名称,这时就很有用了。 :D

以下代码展示了如何从多个属性名称反序列化到同一个类属性,并使用[JsonProperty(PropertyName = "EnrollmentStatusEffectiveDateStr")]属性进行修饰。

MediCalFFSPhysician也使用自定义的JsonConverter进行修饰:[JsonConverter(typeof(MediCalFFSPhysicianConverter))]

请注意,_propertyMappings字典包含应映射到属性EnrollmentStatusEffectiveDateStr的可能属性名称:

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

完整代码:

    // 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;
        }
    }
}

输出结果如下:

enter image description here

摘自:将不同的JSON结构反序列化为同一个C#类


-1

我有同样的用例,不过是在Java中。

这个资源对我很有帮助 https://www.baeldung.com/json-multiple-fields-single-java-field

我们可以使用一个

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

在您的使用情况下,您可以这样做

@JsonProperty("name1")
@JsonAlias("name2")

-2

你甚至可以为超过2个名称执行此操作。

@JsonProperty("name1")
@JsonAlias({"name2","name3","name4"})

2
问题是针对 C# 语言提出的,而你给出了 Java 语言的答案。JsonAlias 在 C# 中目前不可用。 - Serhat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接