C# 使用已知和未知字段反序列化 json
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/15253875/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Deserialize json with known and unknown fields
提问by nickvane
Given following json result: The default json result has a known set of fields:
给定以下 json 结果:默认 json 结果具有一组已知字段:
{
"id": "7908",
"name": "product name"
}
But can be extended with additional fields (in this example _unknown_field_name_1
and _unknown_field_name_2
) of which the names are not known when requesting the result.
但是可以使用其他字段(在本例中_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"
}
I would like the json result to be serialized and deserialized to and from a class with properties for the known fields and map the unknown fields (for which there are no properties) to a property (or multiple properties) like a dictionary so they can be accessed and modified.
我希望将 json 结果序列化和反序列化为一个具有已知字段属性的类,并将未知字段(没有属性)映射到一个属性(或多个属性),如字典,以便它们可以访问和修改。
public class Product
{
public string id { get; set; }
public string name { get; set; }
public Dictionary<string, string> fields { get; set; }
}
I think I need a way to plug into a json serializer and do the mapping for the missing members myself (both for serialize and deserialize). I have been looking at various possibilities:
我想我需要一种方法来插入 json 序列化程序并自己为丢失的成员进行映射(用于序列化和反序列化)。我一直在寻找各种可能性:
- json.net and custom contract resolvers (can't figure out how to do it)
- datacontract serializer (can only override onserialized, onserializing)
- serialize to dynamic and do custom mapping (this might work, but seems a lot of work)
- let product inheriting from DynamicObject (serializers work with reflection and do not invoke the trygetmember and trysetmember methods)
- json.net 和自定义合同解析器(不知道怎么做)
- datacontract 序列化程序(只能覆盖 onserialized、onserializing)
- 序列化为动态并进行自定义映射(这可能有效,但似乎有很多工作)
- 让产品从 DynamicObject 继承(序列化程序使用反射,不调用 trygetmember 和 trysetmember 方法)
I'm using restsharp, but any serializer can be plugged in.
我正在使用restsharp,但可以插入任何序列化程序。
Oh, and I cannot change the json result, and thisor thisdidn't help me either.
Update:This looks more like it: http://geekswithblogs.net/DavidHoerster/archive/2011/07/26/json.net-custom-convertersndasha-quick-tour.aspx
更新:这看起来更像:http: //geekswithblogs.net/DavidHoerster/archive/2011/07/26/json.net-custom-convertersndasha-quick-tour.aspx
采纳答案by cecilphillip
An even easier option to tackling this problem would be to use the JsonExtensionDataAttributefrom JSON .NET
解决这个问题的一个更简单的选择是使用JSON .NET 中的JsonExtensionDataAttribute
public class MyClass
{
// known field
public decimal TaxRate { get; set; }
// extra fields
[JsonExtensionData]
private IDictionary<string, JToken> _extraStuff;
}
There's a sample of this on the project blog here
此处的项目博客上有一个示例
UPDATEPlease note this requires JSON .NET v5 release 5 and above
更新请注意,这需要 JSON .NET v5 版本 5 及更高版本
回答by Lodewijk
See https://gist.github.com/LodewijkSioen/5101814
见https://gist.github.com/LodewijkSioen/5101814
What you were looking for was a custom JsonConverter
您要找的是定制 JsonConverter
回答by Jordy Langen
This is a way you could solve it, although I don't like it that much. I solved it using Newton/JSON.Net. I suppose you could use the JsonConverter for deserialization aswell.
这是一种您可以解决它的方法,尽管我不太喜欢它。我使用 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; }
}
}
回答by Kelly L
I thought I'd throw my hat in the ring since I had a similar problem recently. Here's an example of the JSON I wanted to deserialize:
我想我会把我的帽子扔进戒指,因为我最近遇到了类似的问题。这是我想要反序列化的 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"
}
}
}
}
}
}
This is for a system where overrides apply at different levels and are inherited down the tree. In any case, the data model I wanted was something that would allow me to have a property bag with these special inheritance rules also supplied.
这适用于覆盖应用在不同级别并沿树向下继承的系统。无论如何,我想要的数据模型允许我拥有一个包含这些特殊继承规则的属性包。
What I ended up with was the following:
我最终得到的是以下内容:
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();
}
}
The basic idea is this:
基本思想是这样的:
- The PropertyBagModel on deserialization by JSON.NET has the ByPlan, ByClassAndTier, etc. fields populated and also has the private _extensionData field populated.
- Then JSON.NET calls the private OnDeserialized() method and that will move the data from _extensionData to Values as appropriate (or drop it on the floor otherwise - presumably you could log this if it was something you wanted to know). We then remove the extra gunk from _extensionData so it doesn't consume memory.
- On serialization, the OnSerializing method gets calls where we move stuff into _extensionData so it gets saved.
- When serialization has finished, OnSerialized gets called and we remove the extra stuff from _extensionData.
- JSON.NET 反序列化的 PropertyBagModel 填充了 ByPlan、ByClassAndTier 等字段,还填充了私有 _extensionData 字段。
- 然后 JSON.NET 调用私有的 OnDeserialized() 方法,这会将数据从 _extensionData 适当地移动到 Values (或者将其放在地板上否则 - 如果这是您想知道的事情,大概您可以记录下来)。然后我们从 _extensionData 中删除额外的垃圾,这样它就不会消耗内存。
- 在序列化时, OnSerializing 方法会调用我们将内容移入 _extensionData 以便保存的调用。
- 序列化完成后,OnSerialized 被调用,我们从 _extensionData 中删除额外的内容。
We could further delete and recreate the _extensionData Dictionary when needed but I didn't see a real value in this as I'm not using tons of these objects. To do this we'd just create on OnSerializing and delete on OnSerialized. On OnDeserializing, instead of clearing, we could free it.
我们可以在需要时进一步删除和重新创建 _extensionData 字典,但我没有看到真正的价值,因为我没有使用大量这些对象。为此,我们只需在 OnSerializing 上创建并在 OnSerialized 上删除。在 OnDeserializing 上,我们可以释放它而不是清除它。
回答by pasx
I was looking into a similar issue and found this post.
我正在研究类似的问题并找到了这篇文章。
Here is a way to do it using reflection.
这是一种使用反射的方法。
To make it more generic, one should check the type of the property instead of simply using ToString() in propertyInfo.SetValue, unless OFC all the actual properties are strings.
为了使它更通用,应该检查属性的类型,而不是简单地在 propertyInfo.SetValue 中使用 ToString(),除非 OFC 所有实际属性都是字符串。
Also, lowercase property names is not standard in C# but given that GetProperty is case sensitive there are few other options.
此外,小写属性名称在 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);
}