Deserialize json с известными и неизвестными полями

Учитывая следующий результат json: По умолчанию json result имеет известный набор полей:

{ "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-результат был сериализован и десериализован в class и с classом со свойствами для известных полей и сопоставил неизвестные поля (для которых нет свойств) к свойству (или нескольким свойствам), подобному словарю, чтобы они могли быть доступ и изменение.

 public class Product { public string id { get; set; } public string name { get; set; } public Dictionary fields { get; set; } } 

Я думаю, мне нужен способ подключиться к сериализатору json и выполнить сопоставление для отсутствующих элементов (как для сериализации, так и для десериализации). Я рассматривал различные возможности:

  • json.net и пользовательских разрешителей контрактов (не могу понять, как это сделать)
  • datacontract serializer (может только переопределять onserialized, onserializing)
  • сериализуйте динамику и выполняйте настраиваемое сопоставление (это может работать, но, похоже, много работы)
  • пусть продукт наследуется от DynamicObject (сериализаторы работают с reflectionм и не вызывают методы trygetmember и trysetmember)

Я использую restsharp, но любой сериализатор может быть подключен.

О, и я не могу изменить результат json, и это не помогло мне.

Обновление: это больше похоже на: http://geekswithblogs.net/DavidHoerster/archive/2011/07/26/json.net-custom-convertersndasha-quick-tour.aspx

Еще более простой вариант решения этой проблемы – использовать JsonExtensionDataAttribute из JSON .NET

 public class MyClass { // known field public decimal TaxRate { get; set; } // extra fields [JsonExtensionData] private IDictionary _extraStuff; } 

Вот пример этого в блоге проекта здесь

UPDATE Обратите внимание, что для этого требуется версия JSON .NET v5 версии 5 и выше

См. https://gist.github.com/LodewijkSioen/5101814

То, что вы искали, было JsonConverter

Это можно решить, хотя мне это не нравится. Я решил это с помощью 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()); 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(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 { {"_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(); } public int id { get; set; } public string name { get; set; } [JsonIgnore] public Dictionary UnknownValues { get; set; } } 

}

Я думал, что брошу свою шляпу на ринг, так как в последнее время у меня была аналогичная проблема. Вот пример 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 _extensionData = new Dictionary(); [JsonIgnore] public readonly Dictionary Values = new Dictionary(); [JsonProperty(".plan", NullValueHandling = NullValueHandling.Ignore)] public Dictionary ByPlan; [JsonProperty(".classAndTier", NullValueHandling = NullValueHandling.Ignore)] public Dictionary ByClassAndTier; [JsonProperty(".bargainingUnit", NullValueHandling = NullValueHandling.Ignore)] public Dictionary 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()); _extensionData.Clear(); } } 

Основная идея такова:

  1. PropertyBagModel при десериализации JSON.NET имеет заполненные поля ByPlan, ByClassAndTier и т. Д., А также заполняется поле private _extensionData.
  2. Затем JSON.NET вызывает частный метод OnDeserialized (), который будет перемещать данные из _extensionData в значения в зависимости от ситуации (или оставить его на пол иначе – возможно, вы могли бы зарегистрировать это, если бы это было то, что вы хотели знать). Затем мы удаляем дополнительный gunk из _extensionData, чтобы он не потреблял память.
  3. При сериализации метод OnSerializing получает вызовы, где мы перемещаем материал в _extensionData, чтобы он сохранялся.
  4. Когда сериализация завершена, OnSerialized вызывается, и мы удаляем лишний материал из _extensionData.

Мы могли бы дополнительно удалять и воссоздавать словарь _extensionData, когда это необходимо, но я не видел в этом реальной ценности, так как я не использую тонны этих объектов. Для этого мы просто создадим OnSerializing и удалим OnSerialized. В OnDeserializing вместо очистки мы можем освободить его.

Я искал аналогичную проблему и нашел этот пост.

Вот как это сделать, используя reflection.

Чтобы сделать его более общим, нужно проверить тип свойства, а не просто использовать ToString () в propertyInfo.SetValue, если только OFC все фактические свойства не являются строками.

Кроме того, имена свойств нижнего регистра не являются стандартными для C #, но при условии, что GetProperty чувствителен к регистру, есть несколько других опций.

 public class Product { private Type _type; public Product() { fields = new Dictionary(); _type = GetType(); } public string id { get; set; } public string name { get; set; } public Dictionary 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); } 
  • Поиск конкретного JToken по имени в иерархии JObject
  • Обнаружен цикл саморегуляции - Возврат данных из WebApi в браузер
  • Сериализация нескольких свойств DateTime в одном classе с использованием разных форматов для каждого из них
  • JSON.Net Обнаружен собственный цикл привязки
  • JSON.net: как десериализовать без использования конструктора по умолчанию?
  • Как я могу зашифровать выбранные свойства при сериализации моих объектов?
  • Как разобрать объект JSON на C #, если я заранее не знаю ключ?
  • Невозможно сериализовать словарь с помощью сложного ключа с помощью Json.net
  • Частные сеттеры в Json.Net
  • Разбор JSON DateTime от JSON Serializer от Newtonsoft
  • .NET NewtonSoft JSON десериализует карту для другого имени свойства
  • Давайте будем гением компьютера.