Если Вы читаете данную статью, значит, скорее всего, Вы в курсе что такое JSON и картинка ниже Вам знакома. Но в любом случае советую посетить эту страничку, если Вы там еще не были, а так же перед прочтением желательно ознакомиться с общими принципами работы с протоколом JSON на языке C#, например по этой ссылке.
Хочу отметить, что данная заметка не претендует на какую-то полноту раскрытия темы. Цель данного текста – структурировать и сохранить те наработки, которые я использовал при работе с библиотекой Newtonsoft.Json.
Постановка задачи
По большому счету, в рамках статьи не так важно каким образом были получены исходные данные, для парсинга, однако данное пояснение наверняка облегчит восприятие материала. Итак, основная задача состоит в том, чтобы реализовать функции API криптобиржи EXMO. Для простоты будем в основном работать с Публичным интерфейсом (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. Если загрузить эти данные в JSON C# Class Generator, нам будет предложена следующая структура класса для десериализации:
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; }
}
Очевидно, что для каждой комбинации валютных пар классы не подготовишь, а значит нужно как-то выкручиваться.
Надо признать, что я то же сначала изобрел велосипед и реализовал парсинг самостоятельно. Несмотря на то, что код абсолютно рабочий, это отличный пример как НЕЛЬЗЯ делать.
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 — неупорядоченный набор пар ключ/значение. Объект начинается с '{' (открывающей фигурной скобки) и заканчивается '}' (закрывающей фигурной скобкой). Каждое имя сопровождается: (двоеточием), пары ключ/значение разделяются запятой.
Из структуры, предложенной нам ранее 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.
Нажимаем правой кнопкой мыши на Решение (Solution) в Обозревателе решений (Solution explorer) и выбираем пункт «Управлением пакетами NuGet для решения...» («Mange NuGet Packages for solution...»).
Далее жмем «Обзор» («Browse») и в строке поиска вводим newtosoft.json. Ставим галочку напротив нашего решения и нажимаем «Установить» («Install»).
Пространство имен newtosoft.json.linq, после установки библиотеки, становится доступно для подключения к проекту:
using Newtonsoft.Json.Linq;
Для представления данных в newtosoft.json.linq используется абстрактный класс JToken, от которого наследуются классы JValue (для представления простых значений) и JContainer (для представления структур). Структуры в свою очередь могут представлять из себя JArray (массив), JConstructor (конструктор), JObject (объект), либо JProperty (свойство).
В классах 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):
Теперь списки сделок нам доступны по ключам «BTC_USD» и «USD_ETH». Например сделки по валютной паре «BTC_USD»:
Далее нам только останется перевести полученные данные в удобный для работы тип, например 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. Если попытаться преобразовать его в объект 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, сообщающее нам, что преобразуемые данные не являются объектом.
Массивы должны быть преобразованы в специальный тип 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 — хорошо подойдут примеры из документации раз и два.
Пример использования для данных с биржи:
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>>>();
}
}
}
Наиболее полную и подробную информацию о библиотеке всегда можно почерпнуть из официальной документации.
Автор: Дмитрий Куликов