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ом со свойствами для известных полей и сопоставил неизвестные поля (для которых нет свойств) к свойству (или нескольким свойствам), подобному словарю, чтобы они могли быть доступ и изменение.
- JsonValueProviderFactory выбрасывает «запрос слишком большой»
- Сериализовать контейнер перечислений в виде строк с помощью JSON.net
- Как добавить дополнительное свойство в сериализованную строку JSON с помощью json.net?
- Могу ли я сериализовать вложенные свойства в мой class за одну операцию с помощью Json.net?
- Deserialize json, который имеет некоторое имя свойства, начиная с числа
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
- Json.NET: десериализация вложенных словарей
- Как изменить имена свойств при сериализации с помощью Json.net?
- Как настроить пользовательские настройки JsonSerializer для Json.NET в веб-API MVC 4?
- Как можно десериализовать дочерний объект с динамическими (числовыми) именами клавиш?
- Как сериализовать словарь как часть его родительского объекта с помощью Json.Net
- Серийный объект Json.NET с корневым именем
- Как реализовать пользовательский JsonConverter в JSON.NET для десериализации списка объектов базового classа?
- Как использовать Json.NET для JSON-моделирования в проекте MVC5?
Еще более простой вариант решения этой проблемы – использовать 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(); } }
Основная идея такова:
- PropertyBagModel при десериализации JSON.NET имеет заполненные поля ByPlan, ByClassAndTier и т. Д., А также заполняется поле private _extensionData.
- Затем JSON.NET вызывает частный метод OnDeserialized (), который будет перемещать данные из _extensionData в значения в зависимости от ситуации (или оставить его на пол иначе – возможно, вы могли бы зарегистрировать это, если бы это было то, что вы хотели знать). Затем мы удаляем дополнительный gunk из _extensionData, чтобы он не потреблял память.
- При сериализации метод OnSerializing получает вызовы, где мы перемещаем материал в _extensionData, чтобы он сохранялся.
- Когда сериализация завершена, 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); }