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

给出以下 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 序列化程序,并自己为缺少的成员进行映射(同时进行序列化和反序列化)。 我一直在考虑各种可能性:

  • Net 和自定义契约解析器(不知道怎么做)
  • 数据契约序列化程序(只能覆盖已序列化的、正在序列化的)
  • 序列化到动态并执行自定义映射(这可能有效,但似乎工作量很大)
  • 让 product 从 DynamicObject 继承(序列化器使用反射,不调用 trygetmember 和 trysetmember 方法)

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

哦,我不能改变 json 的结果,而且 这个这个也帮不了我。

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

63953 次浏览

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

解决这个问题的一个更简单的选择是使用来自 JSON. NET 的 扩展数据属性

public class MyClass
{
// known field
public decimal TaxRate { get; set; }


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

在项目博客 给你上有一个示例

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

最近我也遇到过类似的问题,所以我觉得我应该参与进来。下面是我想要反序列化的 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 等字段,还填充了 private _ extsionData 字段。
  2. 然后 JSON.NET 调用私有 OnSerialization ()方法,该方法将适当地将数据从 _ extsionData 移动到 Value (或者将其放在地板上,否则——假设您可以记录这个,如果它是您想要知道的东西)。然后,我们将额外的块从 _ extsionData 中移除,这样它就不会消耗内存。
  3. 在序列化时,OnSerialization 方法获得调用,我们将数据移动到 _ extsionData 中,以便保存它。
  4. 当序列化完成后,调用 OnSerialization,我们从 _ tensionData 中删除额外的内容。

我们可以在需要的时候进一步删除和重新创建 _ extsionData 字典,但是我没有看到其中真正的价值,因为我没有使用成吨的这些对象。要做到这一点,我们只需在 OnSerialization 上创建和在 OnSerialization 上删除。在反序列化中,我们可以释放它,而不是清除它。

我在研究一个类似的问题时发现了这篇文章。

这里有一个使用反射的方法。

为了使其更加通用,应该检查属性的类型,而不仅仅是在 PropertyInfo 中使用 ToString ()。SetValue,除非 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);
}