使用已知和未知字段反序列化 json

Posted

技术标签:

【中文标题】使用已知和未知字段反序列化 json【英文标题】:Deserialize json with known and unknown fields 【发布时间】:2013-02-21 15:06:10 【问题描述】:

鉴于以下 json 结果: 默认的 json 结果有一组已知的字段:


    "id": "7908",
    "name": "product name"

但可以使用其他字段(在此示例中为 _unknown_field_name_1_unknown_field_name_2)进行扩展,这些字段在请求结果时名称未知。


    "id": "7908",
    "name": "product name",
    "_unknown_field_name_1": "some value",
    "_unknown_field_name_2": "some value"

我希望 json 结果在具有已知字段属性的类之间进行序列化和反序列化,并将未知字段(没有属性)映射到像字典一样的属性(或多个属性),所以它们可以被访问和修改。

public class Product

    public string id  get; set; 
    public string name  get; set; 
    public Dictionary<string, string> fields  get; set; 

我认为我需要一种方法来插入 json 序列化程序并自己为丢失的成员进行映射(用于序列化和反序列化)。 我一直在寻找各种可能性:

json.net 和自定义合约解析器(不知道怎么做) datacontract 序列化器(只能覆盖 onserialized、onserializing) 序列化为动态并进行自定义映射(这可能有效,但似乎工作量很大) 让产品继承自 DynamicObject(序列化程序使用反射,不调用 trygetmember 和 trysetmember 方法)

我使用的是restsharp,但是任何序列化器都可以插入。

哦,我无法更改 json 结果,this 或 this 也没有帮助我。

更新: 这看起来更像:http://geekswithblogs.net/DavidHoerster/archive/2011/07/26/json.net-custom-convertersndasha-quick-tour.aspx

【问题讨论】:

你不能派生自Dictionary&lt;string, object&gt; json 结果还包含映射到 List 属性的数组。问题中的 json 是更复杂的 json 结果的缩减部分。所以没有。 【参考方案1】:

解决这个问题的一个更简单的选择是使用来自 JSON .NET 的 JsonExtensionDataAttribute

public class MyClass

   // known field
   public decimal TaxRate  get; set; 

   // extra fields
   [JsonExtensionData]
   private IDictionary<string, JToken> _extraStuff;

项目博客here上有一个示例

更新请注意,这需要 JSON .NET v5 版本 5 及更高版本

【讨论】:

我需要尝试一下,但它看起来像是我需要的解决方案。请注意,这仅在 json.net 5 或更高版本中可用。 有什么办法可以省略 JToken 中的一些值?我有一个问题,它添加了一个 id 参数作为 extraField,这使得生成的 json 无效,因为我已经有了一个 id 参数.. 请注意,这不包括复合成员中的未知字段,即如果 TaxRate 是一个复杂对象,它也必须有[JsonExtensionData],否则其中的未知字段仍将被清除。跨度> 获取未知字段的完美/简单解决方案【参考方案2】:

见https://gist.github.com/LodewijkSioen/5101814

您要查找的是自定义JsonConverter

【讨论】:

我想让您的解决方案更通用,并尽可能多地使用 json.net 逻辑,但我需要对 json 输出进行大量控制,因此我必须按照您的建议进行操作.谢谢你的无限智慧:) 这更像是一个答案链接,而不是答案本身。【参考方案3】:

这是一种你可以解决的方法,虽然我不太喜欢它。我使用 Newton/JSON.Net 解决了它。我想您也可以使用 JsonConverter 进行反序列化。

private const string Json = "\"id\":7908,\"name\":\"product name\",\"_unknown_field_name_1\":\"some value\",\"_unknown_field_name_2\":\"some value\"";

    [TestMethod]
    public void TestDeserializeUnknownMembers()
    
        var @object = JObject.Parse(Json);

        var serializer = new Newtonsoft.Json.JsonSerializer();
        serializer.MissingMemberHandling = Newtonsoft.Json.MissingMemberHandling.Error;
        serializer.Error += (sender, eventArgs) =>
            
                var contract = eventArgs.CurrentObject as Contract ?? new Contract();
                contract.UnknownValues.Add(eventArgs.ErrorContext.Member.ToString(), @object[eventArgs.ErrorContext.Member.ToString()].Value<string>());
                eventArgs.ErrorContext.Handled = true;
            ;

        using (MemoryStream memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(Json)))
        using (StreamReader streamReader = new StreamReader(memoryStream))
        using (JsonReader jsonReader = new JsonTextReader(streamReader))
        
            var result = serializer.Deserialize<Contract>(jsonReader);
            Assert.IsTrue(result.UnknownValues.ContainsKey("_unknown_field_name_1"));
            Assert.IsTrue(result.UnknownValues.ContainsKey("_unknown_field_name_2"));
        
    

    [TestMethod]
    public void TestSerializeUnknownMembers()
    
        var deserializedObject = new Contract
        
            id = 7908,
            name = "product name",
            UnknownValues = new Dictionary<string, string>
        
            "_unknown_field_name_1", "some value",
            "_unknown_field_name_2", "some value"
        
        ;

        var json = JsonConvert.SerializeObject(deserializedObject, new DictionaryConverter());
        Console.WriteLine(Json);
        Console.WriteLine(json);
        Assert.AreEqual(Json, json);
    


class DictionaryConverter : JsonConverter

    public DictionaryConverter()
    

    

    public override bool CanConvert(Type objectType)
    
        return objectType == typeof(Contract);
    

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    
        throw new NotImplementedException();
    

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    
        var contract = value as Contract;
        var json = JsonConvert.SerializeObject(value);
        var dictArray = String.Join(",", contract.UnknownValues.Select(pair => "\"" + pair.Key + "\":\"" + pair.Value + "\""));

        json = json.Substring(0, json.Length - 1) + "," + dictArray + "";
        writer.WriteRaw(json);
    


class Contract

    public Contract()
    
        UnknownValues = new Dictionary<string, string>();
    

    public int id  get; set; 
    public string name  get; set; 

    [JsonIgnore]
    public Dictionary<string, string> UnknownValues  get; set; 


【讨论】:

【参考方案4】:

我想我会把帽子扔在戒指上,因为我最近遇到了类似的问题。这是我想要反序列化的 JSON 示例:


    "agencyId": "agency1",
    "overrides": 
        "assumption.discount.rates": "value: 0.07",
        ".plan": 
            "plan1": 
                "assumption.payroll.growth": "value: 0.03",
                "provision.eeContrib.rate": "value: 0.35"
            ,
            "plan2": 
                ".classAndTier": 
                    "misc:tier1": 
                        "provision.eeContrib.rate": "value: 0.4"
                    ,
                    "misc:tier2": 
                        "provision.eeContrib.rate": "value: 0.375"
                    
                
            
        
    

这适用于覆盖在不同级别应用并沿树继承的系统。无论如何,我想要的数据模型可以让我拥有一个带有这些特殊继承规则的属性包。

我最终得到的是以下内容:

public class TestDataModel

    public string AgencyId;
    public int Years;
    public PropertyBagModel Overrides;


public class ParticipantFilterModel

    public string[] ClassAndTier;
    public string[] BargainingUnit;
    public string[] Department;


public class PropertyBagModel

    [JsonExtensionData]
    private readonly Dictionary<string, JToken> _extensionData = new Dictionary<string, JToken>();

    [JsonIgnore]
    public readonly Dictionary<string, string> Values = new Dictionary<string, string>();

    [JsonProperty(".plan", NullValueHandling = NullValueHandling.Ignore)]
    public Dictionary<string, PropertyBagModel> ByPlan;

    [JsonProperty(".classAndTier", NullValueHandling = NullValueHandling.Ignore)]
    public Dictionary<string, PropertyBagModel> ByClassAndTier;

    [JsonProperty(".bargainingUnit", NullValueHandling = NullValueHandling.Ignore)]
    public Dictionary<string, PropertyBagModel> ByBarginingUnit;

    [OnSerializing]
    private void OnSerializing(StreamingContext context)
    
        foreach (var kvp in Values)
            _extensionData.Add(kvp.Key, kvp.Value);
    

    [OnSerialized]
    private void OnSerialized(StreamingContext context)
    
        _extensionData.Clear();
    

    [OnDeserialized]
    private void OnDeserialized(StreamingContext context)
    
        Values.Clear();
        foreach (var kvp in _extensionData.Where(x => x.Value.Type == JTokenType.String))
            Values.Add(kvp.Key, kvp.Value.Value<string>());
        _extensionData.Clear();
    

基本思路是这样的:

    通过 JSON.NET 反序列化的 PropertyBagModel 填充了 ByPlan、ByClassAndTier 等字段,还填充了私有 _extensionData 字段。 然后 JSON.NET 调用私有 OnDeserialized() 方法,这会将数据从 _extensionData 移动到适当的 Values (否则将其放在地板上 - 如果您想知道它,大概可以记录它)。然后我们从 _extensionData 中删除多余的垃圾,这样它就不会消耗内存。 在序列化时,OnSerializing 方法会调用我们将内容移动到 _extensionData 中以便保存。 序列化完成后,将调用 OnSerialized 并从 _extensionData 中删除多余的内容。

我们可以在需要时进一步删除和重新创建 _extensionData 字典,但我没有看到其中的真正价值,因为我没有使用大量这些对象。为此,我们只需在 OnSerializing 上创建并在 OnSerialized 上删除。在 OnDeserializing 上,我们可以释放它,而不是清除它。

【讨论】:

【参考方案5】:

我正在研究一个类似的问题并找到了这篇文章。

这是一种使用反射的方法。

为了使其更通用,应该检查属性的类型,而不是简单地在 propertyInfo.SetValue 中使用 ToString(),除非 OFC 所有实际属性都是字符串。

此外,小写属性名称在 C# 中不是标准的,但鉴于 GetProperty 区分大小写,因此很少有其他选项。

public class Product

    private Type _type;

    public Product()
    
        fields = new Dictionary<string, object>();
        _type = GetType();
    

    public string id  get; set; 
    public string name  get; set; 
    public Dictionary<string, object> fields  get; set; 

    public void SetProperty(string key, object value)
    
        var propertyInfo = _type.GetProperty(key);
        if (null == propertyInfo)
        
            fields.Add(key,value);
            return;
        
        propertyInfo.SetValue(this, value.ToString());
    

...
private const string JsonTest = "\"id\":7908,\"name\":\"product name\",\"_unknown_field_name_1\":\"some value\",\"_unknown_field_name_2\":\"some value\"";

var product = new Product();
var data = JObject.Parse(JsonTest);
foreach (var item in data)

    product.SetProperty(item.Key, item.Value);

【讨论】:

以上是关于使用已知和未知字段反序列化 json的主要内容,如果未能解决你的问题,请参考以下文章

如果枚举映射键为空或未知,则忽略 JSON 反序列化

当对象数量未知时反序列化Json

Jackson 将额外字段反序列化为 Map

使用 Jackson 处理未知的 JSON 属性

使用知道架构的 protobuf-net 反序列化未知对象

使用Newtonsoft.Json.dll序列化和反序列化