Десериализация Json строки
Получаю извне вот такую json строку:
[
{"spray": "250.00","ip": "202.214.237.72"}, //единственная отличающаяся строка
{"access": "444","sum": "100.00","date": "13.07.2020 19:28:49","pass": "0"},
{"access": "444","sum": "50.00","date": "13.07.2020 19:21:00","pass": "1"},
{ /*похожий формат строки, до конца документа json */ }
]
Создал 2 класса
public class Product {
public string spray { get; set; }
public string ip { get; set; }
public string access { get; set; }
public string sum { get; set; }
public string date { get; set; }
public string pass { get; set; }
}
public class Shop {
public List<Product> Product { get; set; }
}
Десериализация, заполняю как массив.
Shop shop = JsonConvert.DeserializeObject<List<Shop>(JsonString);
Как извлечь данные из shop и вывести все в виде таблицы на форму?
Ответы (2 шт):
Проблема со структурой
Давайте визуализируем то, что вы получаете:
Как видите, тут просто массив объектов. Почему тогда вы пытаетесь это засунуть в List<Product> Product, да который еще и в List<Shop>? По вашему тогда JSON должен выглядеть так:
Похоже это на то, что отдает вам сервер или кто там? Я думаю, что нет. По этому, List<Shop> можно смело заменять на Product[], удаляя полностью класс Shop.
Неверный тип данных
Хорошо, со структурой разобрались, давайте теперь поговорим о другом. Почему вы все засовываете в string? Да, я понимаю, это проще и может понятней, но ведь это очень неудобно! Вот вам надо сравнить значение sum и взять те объекты, которые выше скажем 20, как будете это делать с типом string? Правильно, вы будете пытаться перегнать это в нужный тип и уже потом проверять. Так почему не сделать так, чтобы в классе был сразу нужный нам тип?
Тут будет пару подводных камней, ибо по умолчанию десериализатор плохо понимает некоторые типы, например за ip в c# отвечает IPAddress и для того, чтобы в него преобразовать входную строку надо будет сделать отдельный конвертор, например такой:
class IPAddressConverter : JsonConverter
{
public override bool CanConvert(Type objectType) => objectType == typeof(IPAddress);
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => writer.WriteValue(value.ToString());
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) => IPAddress.Parse((string)reader.Value);
}
Также проблема будет и с датой, ибо она у вас имеет не стандартный формат и этот формат вам тоже надо будет задать при помощи конвертора, он простой:
class DateFormatConverter : IsoDateTimeConverter
{
public DateFormatConverter(string format) => DateTimeFormat = format;
}
Данные несколько классов в одном
Далее, почему у вас все в одном месте? Ведь вам отдают два совершенно разных объекта, один отвечающий за ip, а другой за доступ наверно? Может стоит их разделить?
Сделать это можно при помощи конвертера и некого абстрактного класса или интерфейса.
Сделаем пустой интерфейс, который будет просто некой меткой для нужных нам классов:
public interface IJsonData { }Разделим данные на несколько классов, где каждый класс наследуется от интерфейса и реализует только то, что ему необходимо:
public class IpData : IJsonData { public IpData(double spray, IPAddress ip) => (Spray, Ip) = (spray, ip); public double Spray { get; } [JsonConverter(typeof(IPAddressConverter))] public IPAddress Ip { get; } } public class AccessData : IJsonData { public AccessData(int access, double sum, DateTimeOffset date, int pass) => (Access, Sum, Date, Pass) = (access, sum, date, pass); public int Access { get; } public double Sum { get; } [JsonConverter(typeof(DateFormatConverter), "dd.MM.yyyy HH':'mm':'ss")] public DateTimeOffset Date { get; } public int Pass { get; } }Тут заодно и применим конверторы для типов и будем использовать сразу нужные нам типы с удобными именами.
Сделаем конвертор, который будет брать объект, искать например
ipи если он есть, то все преобразовывать в типIpData, иначе вAccessData:public class JsonDataConverter : JsonConverter { public override bool CanConvert(Type objectType) => objectType == typeof(IJsonData); public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new InvalidOperationException("Use default serialization."); public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { var jsonObject = JObject.Load(reader); return jsonObject["ip"] != null ? jsonObject.ToObject<IpData>() : (IJsonData) jsonObject.ToObject<AccessData>(); } }Тут собственно стандартный конвертор, который позволяет применить его только тогда, когда входящий тип равен нашему интерфейсу, обратной конвертации нету. Сама конвертация, как я уже описывал выше, работает так, что мы ищем
ip, если он неNULL, то преобразуем вIpData, иначе вAccessData.Осталось нам десериализовать, добавив 2-м параметром конвертор:
var json = JsonConvert.DeserializeObject<IJsonData[]>(jsonString, new JsonDataConverter());
Результат
Запустив проект, мы можем увидеть, что будет массив из 3-х объектов, где первый отвечает за Ip:
Имея это, мы можем делать уже дальше что хотим, например как вы просили вывести данные (я выведу в консоль):
foreach (var item in json)
{
switch (item)
{
case IpData ipData:
Console.WriteLine($"Ip данные: {ipData.Ip} | {ipData.Spray}");
break;
case AccessData accesData:
Console.WriteLine($"Access данные: {accesData.Date} | {accesData.Sum}");
break;
}
}
Вывод:
Ip данные: 202.214.237.72 | 250
Access данные: 13.07.2020 19:28:49 +03:00 | 100
Access данные: 13.07.2020 19:21:00 +03:00 | 50
Собственно вот и все решение задачи. Удачи в изучении!
Глянуть весь пример можно тут
Предложу свой вариант.
Создадим следующие классы:
public class Data
{
public string spray { get; set; }
public string ip { get; set; }
}
public class Product
{
public string access { get; set; }
public string sum { get; set; }
public string date { get; set; }
public string pass { get; set; }
}
Используем следующий код:
Data data = null;
var products = new List<Product>();
var jsonSerializer = new JsonSerializer();
bool isFirst = true;
using (var textReader = new StreamReader("test.json"))
using (var jsonReader = new JsonTextReader(textReader))
{
while (jsonReader.Read())
{
if (jsonReader.TokenType == JsonToken.StartObject)
{
if (isFirst)
{
data = jsonSerializer.Deserialize<Data>(jsonReader);
isFirst = false;
}
else
{
var product = jsonSerializer.Deserialize<Product>(jsonReader);
products.Add(product);
}
}
}
}
Console.WriteLine(data.spray + " " + data.ip);
foreach (var p in products)
Console.WriteLine(p.access + " " + p.sum + " " + p.date + " " + p.pass);
Логика такова. В json имеется массив объектов. Объекты заключены в фигурные скобки. Все объекты плоские, поэтому каждый из них начинается с открывающей скобки { и внутри нет вложенных скобок. Этот факт и используем.
Когда jsonReader встречает токен JsonToken.StartObject (это и есть {), мы десериализуем объект. Используем булев флаг isFirst для определения первой строки. Из неё заполняем первый класс. Остальные строки десериализуем в другой класс и добавляем в коллекцию.
Работа с потоковыми читателями, наподобие JsonReader, XmlReader и т. п., обычно считается сложной. На мой взгляд, незаслуженно. Если понимать устройство форматов данных (json/xml/csv etc), то их использование элементарно.


