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

时间:2013-03-06 17:19:11

标签: c# json json.net

给出以下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序列化程序(只能覆盖序列化,序列化)
  • 序列化为动态并进行自定义映射(这可能有效,但似乎很多工作)
  • 让产品继承自DynamicObject(序列化程序使用反射,不调用trygetmember和trysetmember方法)

我正在使用restsharp,但可以插入任何序列化程序。

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

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

5 个答案:

答案 0 :(得分:62)

解决此问题的更简单方法是使用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版及更高版本

答案 1 :(得分:10)

答案 2 :(得分:4)

这是你可以解决的一种方式,虽然我不喜欢它。我用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; }
}

}

答案 3 :(得分:0)

我认为我最近会遇到类似问题,因此我会戴上帽子。这是我想要反序列化的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();
    }
}

基本理念是:

  1. JSON.NET反序列化的PropertyBagModel填充了ByPlan,ByClassAndTier等字段,并且还填充了私有_extensionData字段。
  2. 然后JSON.NET调用私有OnDeserialized()方法,并将数据从_extensionData移动到适当的值(或者将其放在地板上,否则 - 如果你想知道的话,你可以记录下来)。然后我们从_extensionData中删除多余的gunk,这样它就不会占用内存。
  3. 在序列化时,OnSerializing方法会调用我们将内容移动到_extensionData中的调用,以便保存它。
  4. 序列化完成后,会调用OnSerialized并从_extensionData中删除多余的东西。
  5. 我们可以在需要时进一步删除并重新创建_extensionData字典,但由于我没有使用大量的这些对象,因此我没有看到真正的值。为此,我们只需在onSerialized上创建OnSerializing并删除。 On OnDeserializing,而不是清除,我们可以释放它。

答案 4 :(得分:0)

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

以下是使用反射进行此操作的方法。

为了使它更通用,应该检查属性的类型,而不是简单地在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);
}