Страницы

Поиск по вопросам

суббота, 15 июня 2019 г.

Десериализация json-строки в Словарь (Dictionary)


{ "rates_scores_stats": [{ "name": 10, "value": 3545 }, { "name": 9, "value": 1004 }, { "name": 8, "value": 820 }, { "name": 7, "value": 493 }, { "name": 6, "value": 218 }, { "name": 5, "value": 138 }, { "name": 4, "value": 80 }, { "name": 3, "value": 41 }, { "name": 2, "value": 26 }, { "name": 1, "value": 83 } ], "rates_statuses_stats": [{ "name": "Запланировано", "value": 2506 }, { "name": "Смотрю", "value": 7861 }, { "name": "Просмотрено", "value": 1947 }, { "name": "Отложено", "value": 1443 }, { "name": "Брошено", "value": 1358 } ] }
Есть JSON описанный выше. Для десериализации я использую Newtonsoft.Json. В частности, метод JsonConvert.DeserializeObject(json)
Я знаю, что для этого json можно описать типичный класс следующим образом

Тоже самое текстом: http://pastexen.com/code.php?file=aMvORa6As3.cs
Но мне надо превратить это не в коллекцию вспомогательных классов или же коллекцию KeyValuePair. Мне нужен именно словарь, чтобы я мог обратиться к такому объекту следующим образом:
var result = JsonConvert.DeserializeObject(json); var val = result.rates_scores_stats[10]; // val = 3545 var val1 = result.rates_statuses_stats["Смотрю"]; // val1 = 7861
Можно ли как-нибудь сериализовать это в словарь? Может с помощью get/set как-то извернуться?
P.S.: Я знаю, что в шарпе есть Linq, который позволит сделать что-то типо result.FirstOrDefault(x.value => x.name == "10"), но я пишу переносимую библиотеку и хочу упростить доступ к данным =/


Ответ

Например, можно десериализовать в JObject, и потом сконвертировать данные вручную:
var obj = JObject.Parse(json); var rates_scores_stats = ((JArray)obj["rates_scores_stats"]).ToDictionary(entry => (int)entry["name"], entry => (int)entry["value"]); var rates_statuses_stats = ((JArray)obj["rates_statuses_stats"]).ToDictionary(entry => (string)entry["name"], entry => (int)entry["value"]); var result = new MyClass() { rates_scores_stats = rates_scores_stats, rates_statuses_stats = rates_statuses_stats };

Альтернативный ответ здесь. Немного модифицировав его, можно получить менее «ручное» решение.
Создадим отдельный конвертер (спёрт и модифицирован из ответа по ссылке):
public class JsonGenericDictionaryOrArrayConverter : JsonConverter { // поскольку у вас в JSON'е не key/value, а name/value, нужен специальный класс // для десериализации одного элемента массива class NameValuePair { public N name { get; set; } public V value { get; set; } }
public override bool CanConvert(Type objectType) { return GetDictionaryKeyValueTypes(objectType).Count() == 1; }
public override bool CanWrite { get { return false; } }
object ReadJsonGeneric( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { var tokenType = reader.TokenType;
var dict = existingValue as IDictionary; if (dict == null) { var contract = serializer.ContractResolver.ResolveContract(objectType); dict = (IDictionary)contract.DefaultCreator(); }
if (tokenType == JsonToken.StartArray) { var pairs = new JsonSerializer() .Deserialize[]>(reader); if (pairs == null) return existingValue; foreach (var pair in pairs) dict.Add(pair.name, pair.value); } else if (tokenType == JsonToken.StartObject) { // Using "Populate()" avoids infinite recursion. // https://github.com/JamesNK/Newtonsoft.Json/blob/ // ee170dc5510bb3ffd35fc1b0d986f34e33c51ab9/Src/Newtonsoft.Json/Converters/ // CustomCreationConverter.cs serializer.Populate(reader, dict); } return dict; }
public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { // Throws an exception if not exactly one. var keyValueTypes = GetDictionaryKeyValueTypes(objectType).Single();
var method = GetType().GetMethod( "ReadJsonGeneric", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public); var genericMethod = method.MakeGenericMethod( new[] { keyValueTypes.Key, keyValueTypes.Value }); return genericMethod.Invoke( this, new object[] { reader, objectType, existingValue, serializer }); }
public override void WriteJson( JsonWriter writer, object value, JsonSerializer serializer) { throw new NotSupportedException(); }
static IEnumerable> GetDictionaryKeyValueTypes(Type type) { foreach (Type intType in GetInterfacesAndSelf(type)) { if (intType.IsGenericType && intType.GetGenericTypeDefinition() == typeof(IDictionary<,>)) { var args = intType.GetGenericArguments(); if (args.Length == 2) yield return new KeyValuePair(args[0], args[1]); } } }
static IEnumerable GetInterfacesAndSelf(Type type) { if (type == null) throw new ArgumentNullException(); if (type.IsInterface) return new[] { type }.Concat(type.GetInterfaces()); else return type.GetInterfaces(); } }
Имея этот вспомогательный класс, можно десериализовать просто так:
var settings = new JsonSerializerSettings { Converters = { new JsonGenericDictionaryOrArrayConverter() } }; var result = JsonConvert.DeserializeObject(jsonString, settings);

Обновление: Для portable library (.NET 4.5.1 + Windows Universal 8.1 + Windows Phone 8.1) у меня скомпилировалось такое:
public class JsonGenericDictionaryOrArrayConverter : JsonConverter { // поскольку у вас в JSON'е не key/value, а name/value, нужен специальный класс // для десериализации одного элемента массива class NameValuePair { public N name { get; set; } public V value { get; set; } }
public override bool CanConvert(Type objectType) { return GetDictionaryKeyValueTypes(objectType).Count() == 1; }
public override bool CanWrite { get { return false; } }
public object ReadJsonGeneric( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { var tokenType = reader.TokenType;
var dict = existingValue as IDictionary; if (dict == null) { var contract = serializer.ContractResolver.ResolveContract(objectType); dict = (IDictionary)contract.DefaultCreator(); }
if (tokenType == JsonToken.StartArray) { var pairs = new JsonSerializer() .Deserialize[]>(reader); if (pairs == null) return existingValue; foreach (var pair in pairs) dict.Add(pair.name, pair.value); } else if (tokenType == JsonToken.StartObject) { // Using "Populate()" avoids infinite recursion. // https://github.com/JamesNK/Newtonsoft.Json/blob/ // ee170dc5510bb3ffd35fc1b0d986f34e33c51ab9/Src/Newtonsoft.Json/Converters/ // CustomCreationConverter.cs serializer.Populate(reader, dict); } return dict; }
public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { // Throws an exception if not exactly one. var keyValueTypes = GetDictionaryKeyValueTypes(objectType).Single();
var method = GetType().GetTypeInfo().GetDeclaredMethod("ReadJsonGeneric"); var genericMethod = method.MakeGenericMethod( new[] { keyValueTypes.Key, keyValueTypes.Value }); return genericMethod.Invoke( this, new object[] { reader, objectType, existingValue, serializer }); }
public override void WriteJson( JsonWriter writer, object value, JsonSerializer serializer) { throw new NotSupportedException(); }
static IEnumerable> GetDictionaryKeyValueTypes(Type type) { foreach (Type intType in GetInterfacesAndSelf(type)) { if (intType.GetTypeInfo().IsGenericType && intType.GetGenericTypeDefinition() == typeof(IDictionary<,>)) { var args = intType.GetTypeInfo().GenericTypeArguments; // вроде бы именно GenericTypeArguments, а не Parameters if (args.Length == 2) yield return new KeyValuePair(args[0], args[1]); } } }
static IEnumerable GetInterfacesAndSelf(Type type) { if (type == null) throw new ArgumentNullException(); if (type.GetTypeInfo().IsInterface) return new[] { type }.Concat(type.GetTypeInfo().ImplementedInterfaces); else return type.GetTypeInfo().ImplementedInterfaces; } }

Комментариев нет:

Отправить комментарий