- PVSM.RU - https://www.pvsm.ru -

Работа с библиотекой Newtonsoft.Json на реальном примере. Часть 1

Если Вы читаете данную статью, значит, скорее всего, Вы в курсе что такое JSON и картинка ниже Вам знакома. Но в любом случае советую посетить эту страничку [1], если Вы там еще не были, а так же перед прочтением желательно ознакомиться с общими принципами работы с протоколом JSON на языке C#, например по этой ссылке [2].

Работа с библиотекой Newtonsoft.Json на реальном примере. Часть 1 - 1

Хочу отметить, что данная заметка не претендует на какую-то полноту раскрытия темы. Цель данного текста – структурировать и сохранить те наработки, которые я использовал при работе с библиотекой Newtonsoft.Json.

Постановка задачи

По большому счету, в рамках статьи не так важно каким образом были получены исходные данные, для парсинга, однако данное пояснение наверняка облегчит восприятие материала. Итак, основная задача состоит в том, чтобы реализовать функции API криптобиржи EXMO [3]. Для простоты будем в основном работать с Публичным интерфейсом (Public API). В данном случае запрос информации будет выглядеть как обычный адрес странички сайта. Для отправки запросов используется класс WebClient пространства имен System.Net:

var wb = new WebClient();
string request = "https://api.exmo.com/v1/trades/?pair=BTC_USD";
string answer = wb.DownloadString(request);

Результат работы данной программы можно увидеть пройдя по ссылке https://api.exmo.com/v1/trades/?pair=BTC_USD [4]. Если загрузить эти данные в JSON C# Class Generator [5], нам будет предложена следующая структура класса для десериализации:

    public class BTCUSD
    {
        public int trade_id { get; set; }
        public string type { get; set; }
        public string quantity { get; set; }
        public string price { get; set; }
        public string amount { get; set; }
        public int date { get; set; }
    }
    public class RootObject
    {
        public List<BTCUSD> BTC_USD { get; set; }
    }

Надо отметить, что сама функция “trades” криптобиржи позволяет запрашивать информацию сразу по нескольким валютным парам. И в случае запроса

string request = "https://api.exmo.com/v1/trades/?pair=BTC_USD,ETH_USD";

Структура класса будет выглядеть уже следующим образом:

    public class BTCUSD
    {
        public int trade_id { get; set; }
        public string type { get; set; }
        public string quantity { get; set; }
        public string price { get; set; }
        public string amount { get; set; }
        public int date { get; set; }
    }
    public class ETHUSD
    {
        public int trade_id { get; set; }
        public string type { get; set; }
        public string quantity { get; set; }
        public string price { get; set; }
        public string amount { get; set; }
        public int date { get; set; }
    }
    public class RootObject
    {
        public List<BTCUSD> BTC_USD { get; set; }
        public List<ETHUSD> ETH_USD { get; set; }
    }

Очевидно, что для каждой комбинации валютных пар классы не подготовишь, а значит нужно как-то выкручиваться.

Как делать не нужно.

Удивительно, но хороших статей по работе с протоколом JSON в русскоязычном интернете найти практически невозможно. Как следствие, те реализации EXMO API, которые можно найти на GitHub`е так или иначе содержат манипуляции с исходной структурой данных с использованием spit`ов, trim`ов и тому подобных непотребств.

Надо признать, что я то же сначала изобрел велосипед и реализовал парсинг самостоятельно. Несмотря на то, что код абсолютно рабочий, это отличный пример как НЕЛЬЗЯ делать.

LinkedList<OrderInform> loi = new LinkedList<OrderInform>();
try
{
  string[] json = answer.Split(new char[] { '{', '}' });
  foreach (var item in json)
  {
    if (item.Length > 20)
    {
      string[] trade = item.Split(new char[] { ',' });
      if (trade.Length > 5)
      {
        string t_id = trade[0].Split(new char[] { ':' })[1].Trim('"');
        string t_type = trade[1].Split(new char[] { ':' })[1].Trim('"');
        string t_quantity = trade[2].Split(new char[] { ':' })[1].Trim('"')
          .Replace(".", ",");
        string t_price = trade[3].Split(new char[] { ':' })[1].Trim('"')
          .Replace(".", ",");
        string t_amount = trade[4].Split(new char[] { ':' })[1].Trim('"')
          .Replace(".", ",");
        string t_date_time = trade[5].Split(new char[] { ':' })[1].Trim('"');
        loi.AddLast(new OrderInform(pair, Convert.ToInt32(t_id), t_type,
          Convert.ToDouble(t_quantity), Convert.ToDouble(t_price), 
          Convert.ToDouble(t_amount),
          (new DateTime(1970, 1, 1, 0, 0, 0, 0))
          .AddSeconds(Convert.ToInt32(t_date_time))));
      }
    }
  }
  return loi;
} 
catch (Exception ex)
{
  return new LinkedList<OrderInform>();
}

Объекты JSON (класс JObject)

Объект JSON — неупорядоченный набор пар ключ/значение. Объект начинается с '{' (открывающей фигурной скобки) и заканчивается '}' (закрывающей фигурной скобкой). Каждое имя сопровождается: (двоеточием), пары ключ/значение разделяются запятой.

Работа с библиотекой Newtonsoft.Json на реальном примере. Часть 1 - 2

Из структуры, предложенной нам ранее JSON C# Class Generator`ом видно, что для каждой валютной пары у нас есть 6 постоянных полей. Обернем их в отдельный класс и назовем его Order.

    class Order
    {
        public int trade_id { get; set; }
        public string type { get; set; }
        public double quantity { get; set; }
        public double price { get; set; }
        public double amount { get; set; }
        public int date { get; set; }
    }

Для того, чтобы суметь «выцепить» набор этих полей из динамически меняющейся структуры данных биржи, необходимо поподробней разобраться с типами данных доступными в библиотеке newtosoft.json. А точнее в пространстве имен newtonsoft.json.linq.

Для работы с библиотекой ее необходимо установить.

Чтобы установить библиотеку в Visual Studio можно просто выполнить поиск в NuGet.
Нажимаем правой кнопкой мыши на Решение (Solution) в Обозревателе решений (Solution explorer) и выбираем пункт «Управлением пакетами NuGet для решения...» («Mange NuGet Packages for solution...»).

Работа с библиотекой Newtonsoft.Json на реальном примере. Часть 1 - 3

Далее жмем «Обзор» («Browse») и в строке поиска вводим newtosoft.json. Ставим галочку напротив нашего решения и нажимаем «Установить» («Install»).

Работа с библиотекой Newtonsoft.Json на реальном примере. Часть 1 - 4

Пространство имен newtosoft.json.linq, после установки библиотеки, становится доступно для подключения к проекту:

using Newtonsoft.Json.Linq;

Для представления данных в newtosoft.json.linq используется абстрактный класс JToken [6], от которого наследуются классы JValue [7] (для представления простых значений) и JContainer [8] (для представления структур). Структуры в свою очередь могут представлять из себя JArray [9] (массив), JConstructor [10] (конструктор), JObject [11] (объект), либо JProperty [12] (свойство).

В классах JArray и JObject имеется метод Parse, позволяющей преобразовывать JSON данные из обычной строки в соответствующие объекты. Так, если мы воспользуемся методом JObject.Parse(String) для структуры данных функции trades биржи Exmo:

var wb = new WebClient();
string request = "https://api.exmo.com/v1/trades/?pair=BTC_USD,ETH_USD";
string answer = wb.DownloadString(request);

JObject jObject = JObject.Parse(answer);

мы увидим структуру из двух элементов, каждый из которых представляет пару ключ-значение. Ключом в данном случае будет название валютной пары (тип String), а значением — список сделок (тип JToken):

Работа с библиотекой Newtonsoft.Json на реальном примере. Часть 1 - 5

Теперь списки сделок нам доступны по ключам «BTC_USD» и «USD_ETH». Например сделки по валютной паре «BTC_USD»:

Работа с библиотекой Newtonsoft.Json на реальном примере. Часть 1 - 6

Далее нам только останется перевести полученные данные в удобный для работы тип, например List<Order>, используя метод ToObject<>():

var wb = new WebClient();
string request = "https://api.exmo.com/v1/trades/?pair=BTC_USD,ETH_USD";
string answer = wb.DownloadString(request);
JObject jObject = JObject.Parse(answer);

JToken list = jObject["BTC_USD"];
List<Order> trades = list.ToObject<List<Order>>();

либо всю структуру преобразовать в тип Dictionary<string, List<Order>>:

string request = "https://api.exmo.com/v1/trades/?pair=BTC_USD,ETH_USD";
var wb = new WebClient();
string answer = wb.DownloadString(request);
JObject jObject = JObject.Parse(answer);

Dictionary<string, List<Order>> trades = new Dictionary<string, List<Order>>();
foreach (KeyValuePair<string, JToken> obj in jObject)
  trades.Add(obj.Key, obj.Value.ToObject<List<Order>>());

Массивы JSON (класс JArray)

Массив JSON — упорядоченная коллекция значений. Массив начинается с '[' (открывающей квадратной скобки) и заканчивается ']' (закрывающей квадратной скобкой). Значения разделены запятой.

Как выглядит массив JSON можно увидеть пройдя по ссылке https://api.exmo.com/v1/currency [13]. Если попытаться преобразовать его в объект JObject, так как мы делали с предыдущим запросом:

var wb = new WebClient();
string request = "https://api.exmo.com/v1/currency";
string answer = wb.DownloadString(request);

JObject jObject = JObject.Parse(answer);

мы получим исключение Newtonsoft.Json.JsonReaderException, сообщающее нам, что преобразуемые данные не являются объектом.

Работа с библиотекой Newtonsoft.Json на реальном примере. Часть 1 - 7

Массивы должны быть преобразованы в специальный тип JArray, который, так же как JObject, наследуется от JContainer.

var wb = new WebClient();
string request = "https://api.exmo.com/v1/currency";
string answer = wb.DownloadString(request);

JArray jArray = JArray.Parse(answer);

В случае, когда при парсинге, исходная структура заранее неизвестна, либо требуется возможность обработки обоих типов данных — в качестве типа преобразуемого объекта можно указать JContainer, или же JToken, тогда во время парсинга данные будут неявно преобразованы к нужному типу.

var wb = new WebClient();

string request = "https://api.exmo.com/v1/currency";
string answer = wb.DownloadString(request);
JToken jArray = JToken.Parse(answer);
//  Неявное преобразование в JArray
List<string> list = jArray.ToObject<List<string>>();

request = "https://api.exmo.com/v1/trades/?pair=BTC_USD,ETH_USD";
answer = wb.DownloadString(request);
JToken jObject = JToken.Parse(answer);
//  Неявное преобразование в JObject
Dictionary<string, List<Order>> dict = 
    jObject.ToObject<Dictionary<string, List<Order>>>();

Значения JSON (класс JValue)

Согласно описанию формата JSON, значение может быть строкой в двойных кавычках, числом, true, false, null, объектом или массивом. Эти структуры могут быть вложенными. В реализации newotnsoft.json значением могут быть только объекты простых типов. Попытка преобразовать объект JArray в объект JValue приведет к исключению «System.InvalidCastException».

В качестве ознакомительного материала по работе с конкретными значениями (класс JValue) в библиотеке newtonsoft.json — хорошо подойдут примеры из документации раз [14] и два [15].

Пример использования для данных с биржи:

            
var wb = new WebClient();

string request = "https://api.exmo.com/v1/currency";
string answer = wb.DownloadString(request);
JArray jArray = JArray.Parse(answer);
foreach (JValue value in jArray)
{
    string s = (string)value;
}

Во второй части статьи я более детально опишу возможности подготовки класса для парсинга данных, а также некоторые особенности работы с типом данных DateTime.

Полный текст программы.

using System.Collections.Generic;
using System.Net;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace JSONObjects
{
    class Order
    {
        public int trade_id { get; set; }
        public string type { get; set; }
        public double quantity { get; set; }
        public double price { get; set; }
        public double amount { get; set; }
        public int date { get; set; }
    }
    class Program
    {
        static void Main(string[] args)
        {
            var wb = new WebClient();

            string request = "https://api.exmo.com/v1/currency";
            string answer = wb.DownloadString(request);
            JToken jArray = JToken.Parse(answer);
            //  Неявное преобразование в JArray
            List<string> list = jArray.ToObject<List<string>>();

            request = "https://api.exmo.com/v1/trades/?pair=BTC_USD,ETH_USD";
            answer = wb.DownloadString(request);
            JToken jObject = JToken.Parse(answer);
            //  Неявное преобразование в JObject
            Dictionary<string, List<Order>> dict =
                jObject.ToObject<Dictionary<string, List<Order>>>();
        }
    }
}

Наиболее полную и подробную информацию о библиотеке всегда можно почерпнуть из официальной документации [16].

Автор: Дмитрий Куликов

Источник [17]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/c-2/340862

Ссылки в тексте:

[1] страничку: https://www.json.org/json-ru

[2] по этой ссылке: https://habr.com/ru/post/176087/

[3] EXMO: https://exmo.me/ru/api

[4] https://api.exmo.com/v1/trades/?pair=BTC_USD: https://api.exmo.com/v1/trades/?pair=BTC_USD

[5] JSON C# Class Generator: http://json2csharp.com/

[6] JToken: https://www.newtonsoft.com/JSON/help/html/T_Newtonsoft_Json_Linq_JToken.htm

[7] JValue: https://www.newtonsoft.com/JSON/help/html/T_Newtonsoft_Json_Linq_JValue.htm

[8] JContainer: https://www.newtonsoft.com/JSON/help/html/T_Newtonsoft_Json_Linq_JContainer.htm

[9] JArray: https://www.newtonsoft.com/JSON/help/html/T_Newtonsoft_Json_Linq_JArray.htm

[10] JConstructor: https://www.newtonsoft.com/JSON/help/html/T_Newtonsoft_Json_Linq_JConstructor.htm

[11] JObject: https://www.newtonsoft.com/JSON/help/html/T_Newtonsoft_Json_Linq_JObject.htm

[12] JProperty: https://www.newtonsoft.com/JSON/help/html/T_Newtonsoft_Json_Linq_JProperty.htm

[13] https://api.exmo.com/v1/currency: https://api.exmo.com/v1/currency

[14] раз: https://www.newtonsoft.com/JSON/help/html/JValueCast.htm

[15] два: https://www.newtonsoft.com/JSON/help/html/JValueValue.htm

[16] официальной документации: https://www.newtonsoft.com/JSON/help/html/Introduction.htm

[17] Источник: https://habr.com/ru/post/481514/?utm_source=habrahabr&utm_medium=rss&utm_campaign=481514