Json.NET 反序列化或序列化 json 字符串并将属性映射到运行时定义的不同属性名称

Posted

技术标签:

【中文标题】Json.NET 反序列化或序列化 json 字符串并将属性映射到运行时定义的不同属性名称【英文标题】:Json.NET deserialize or serialize json string and map properties to different property names defined at runtime 【发布时间】:2016-11-01 20:27:08 【问题描述】:

我有以下 JSON 字符串:


    "values": 
        "details": 
            "property1": "94",
            "property2": "47",
            "property3": "32",
            "property4": 1
        ,
        count: 4
    
     

我将把它映射到以下模型:

public class Details

    public string property1  get; set; 
    public string property2  get; set; 
    public string property3  get; set; 
    public int property4  get; set; 


public class Values

    public Details details  get; set; 
    public int count  get; set; 


public class RootObject

    public Values values  get; set; 

当像这样反序列化这个 JSON 字符串时,我希望能够在运行时将这些属性名称映射到不同的名称:

JsonConvert.DeserializeObject<RootObject>(jsonString);

例如,在反序列化过程中,我希望将“property1”的名称反序列化为“differen_property_name1”或“differen_property_name2”或“differen_property_name3”。 因为我在运行时选择了新名称(我会将“property1”名称更改为的新名称),所以我不能使用使用 JsonPropertyAttribute 的解决方案,如下所示:

.NET NewtonSoft JSON deserialize map to a different property name

上述问题的一个答案(Jack 的答案)使用 DefaultContractResolver 的继承,但在这种情况下似乎不起作用。

更新

稍后,我需要序列化从反序列化中获得的对象,并将属性映射到不同的属性名称,在运行时定义。 我使用了与 Brian 建议的相同方法进行序列化:

我使用字典来映射我的新属性名称:

var map = new Dictionary<Type, Dictionary<string, string>>

     
        typeof(Details), 
        new Dictionary<string, string>
        
            "property1", "myNewPropertyName1",
            "property2", "myNewPropertyName2",
            "property3", "myNewPropertyName3",
            "property4", "myNewPropertyName4"
        
    
;    

然后我使用 Brian 的 DynamicMappingResolver 像这样序列化对象:

var settings = new JsonSerializerSettings

    ContractResolver = new DynamicMappingResolver(map)
;

var root = JsonConvert.SerializeObject(myObjectInstance, settings);            

【问题讨论】:

因为我想在运行时决定我想为属性赋予哪个新名称,并且使用 JsonPropertyAttribute 你必须使用“硬编码”名称。 一种方法(虽然它可能很难看)是首先反序列化 JSON,然后将反序列化的对象映射到具有新名称的对象。不过,可能还有更优雅的方式。 @Tim,当需要在运行时定义新名称时,如何将反序列化对象映射到具有新名称的对象? 如何定义目标?代码DOM?匿名类型?您会在应用程序中映射到很多很多类吗?我可以想到一个折中的方法,但它可能有助于了解您要做什么。 【参考方案1】:

您可以使用自定义ContractResolver 来执行此操作。基本上,这与将[JsonProperty] 属性放在要映射到不同 JSON 属性名称的每个类成员上的想法相同,除非您通过解析器以编程方式执行此操作。在反序列化之前设置解析器时,您可以将所需映射的字典传递给解析器。

自定义解析器代码如下所示:

class DynamicMappingResolver : DefaultContractResolver

    private Dictionary<Type, Dictionary<string, string>> memberNameToJsonNameMap;

    public DynamicMappingResolver(Dictionary<Type, Dictionary<string, string>> memberNameToJsonNameMap)
    
        this.memberNameToJsonNameMap = memberNameToJsonNameMap;
    

    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    
        JsonProperty prop = base.CreateProperty(member, memberSerialization);
        Dictionary<string, string> dict;
        string jsonName;
        if (memberNameToJsonNameMap.TryGetValue(member.DeclaringType, out dict) && 
            dict.TryGetValue(member.Name, out jsonName))
        
            prop.PropertyName = jsonName;
        
        return prop;
    

要使用解析器,首先构造一个包含您的映射的Dictionary&lt;Type, Dictionary&lt;string, string&gt;&gt;。外部字典的键是要映射其属性的类类型;内部字典是类属性名称到 JSON 属性名称的映射。您只需为名称尚未与 JSON 匹配的属性提供映射。

因此,例如,如果您的 JSON 看起来像这样(注意 details 对象内的属性名称已更改)...


    "values": 
        "details": 
            "foo": "94",
            "bar": "47",
            "baz": "32",
            "quux": 1
        ,
        count: 4
    

...如果您想将其映射到问题中的类,您可以像这样创建字典:

var map = new Dictionary<Type, Dictionary<string, string>>

     
        typeof(Details), 
        new Dictionary<string, string>
        
            "property1", "foo",
            "property2", "bar",
            "property3", "baz",
            "property4", "quux"
        
    
;

最后一步是使用新的解析器实例设置序列化程序设置,为其提供刚刚构建的映射字典,然后将设置传递给JsonConvert.DeserializeObject()

var settings = new JsonSerializerSettings

    ContractResolver = new DynamicMappingResolver(map)
;

var root = JsonConvert.DeserializeObject<RootObject>(json, settings);

这是一个演示:https://dotnetfiddle.net/ULkB0J

【讨论】:

正是我想要的。谢谢!! 很棒的答案。谢谢布赖恩。【参考方案2】:

为什么要一步到位?为什么不反序列化为您的标准对象,然后使用Automapper 动态映射它们?

类似:

Mapper.Initialize(c =>

    c.ReplaceMemberName("property1 ", "differen_property_name1");
);

【讨论】:

【参考方案3】:

如果您不想使用自定义 ContractResolver 来执行此操作。使用[JsonProperty("")] 查找属性名称的不同变体并返回另一个属性,如下所示:

public class Details

    private string _property1;
    private string _property2;

    [JsonProperty("property1")]
    public string prop1 get;set;

    [JsonProperty("foo")]
    public string foo get;set;

    public string getProperty1 
    
        get _property1=prop1??foo;return _property1;
        setprop1=value;foo=value;
    

    [JsonProperty("property2")]
    public string prop2 get;set;

    [JsonProperty("bar")]
    public string bar get;set;

    public string getProperty2
    
        get _property2=prop2??bar;return _property2;
        set prop2=value;bar=value;
    

在这里演示:https://dotnetfiddle.net/V17igc

【讨论】:

【参考方案4】:

我不相信 JSON.net 对您正在寻找的内容有任何支持。如果您不知道格式,则应该将 JSON 反序列化为 JObject,或者如果您知道,则应该使用一些通用格式(例如,如果 JSON 总是显示 property1,您可以使用通用对象来表示它)。

一旦你有了你的通用对象,接下来你需要翻译这些字段。任何不可更改的都可以直接完成,但其他任何事情都需要使用反射。

基本上它涉及获取类型(typeof(Details)obj.GetType()),然后搜索要更新的属性。最后,您应该能够找到 setter 方法并调用它,从您的通用对象中提供原始值。

【讨论】:

以上是关于Json.NET 反序列化或序列化 json 字符串并将属性映射到运行时定义的不同属性名称的主要内容,如果未能解决你的问题,请参考以下文章

尝试使用 JSON.NET 反序列化带有 [] 字符的 JSON

Json.NET在返回json序列化字符串时添加反斜杠

.NET Core - Json.NET反序列化映射

JSON.Net无法在自定义JsonConverter中反序列化json数组

Json.NET 可以对流进行序列化/反序列化吗?

使用字符串、int 数组反序列化 json - .net core C#