Trabajando con la biblioteca Newtonsoft.Json con un ejemplo real. Parte 2

La primera parte del artículo examinó el mecanismo de analizar objetos JSON con una estructura que cambia dinámicamente. Estos objetos se convirtieron en tipos de espacio de nombres newtonsoft.json.linq y luego se convirtieron en estructuras de lenguaje C #. En los comentarios a la primera parte hubo muchas críticas, en su mayor parte justificadas. En la segunda parte intentaré tener en cuenta todos los comentarios y sugerencias.



Además, nos centraremos en preparar clases para un ajuste más preciso de la transformación de datos, pero primero debe volver al análisis JSON. Recuerdo en la primera parte, se utilizaron los métodos Parse () y ToObject <T> () de las clases JObject y JArray del espacio de nombres newtonsoft.json.linq:

HttpClient httpClient = new HttpClient(); string request = "https://api.exmo.com/v1/trades/?pair=BTC_USD,ETH_USD"; HttpResponseMessage response = (await httpClient.GetAsync(request)).EnsureSuccessStatusCode(); string responseBody = await response.Content.ReadAsStringAsync(); JObject jObject = JObject.Parse(responseBody); Dictionary<string, List<Order>> dict = jObject.ToObject<Dictionary<string, List<Order>>>(); 

Cabe señalar que en el espacio de nombres newtonsof.json en la clase JsonConvert hay un método estático DeserializeObject <>, que le permite convertir cadenas directamente a estructuras C # correspondientes a objetos y matrices de notación JSON:

 Dictionary<string, List<Order>> JsonObject = JsonConvert.DeserializeObject<Dictionary<string, List<Order>>>(responseBody); List<string> Json_Array = JsonConvert.DeserializeObject<List<string>>(responseBody); 

Y en el futuro, este método se usará en el artículo, por lo que debe agregar el uso de newtonsoft.json al programa.

Además, es posible reducir aún más el número de conversiones intermedias: después de instalar la biblioteca Microsoft.AspNet.WebApi.Client (también disponible a través de NuGet), los datos se pueden analizar directamente desde la secuencia utilizando el método ReadAsAsync:

 Dictionary<string, List<Order>> JsonObject = await (await httpClient.GetAsync(request)). EnsureSuccessStatusCode().Content. ReadAsAsync<Dictionary<string, List<Order>>>(); 

Gracias por la guarida de propinas.

Preparando la clase para la conversión


Volver a nuestra clase de orden:

  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; } } 

Permítame recordarle que fue creado sobre la base del formato propuesto por JSON C # Class Generator. Hay dos puntos que al trabajar con objetos de esta clase pueden causar dificultades.

El primero: en este formulario, las propiedades de nuestra clase violan las reglas para nombrar campos. Bueno, además, es lógico que un objeto de tipo Order espere que su identificador se llame OrderID (y no traid_id, como sucedió en los ejemplos de la primera parte). Para asociar un elemento de estructura JSON y una propiedad de clase con un nombre arbitrario, debe agregar el atributo JsonProperty antes de la propiedad:

  class Order { [JsonProperty("trade_id")] public int OrderID { get; set; } [JsonProperty("type")] public string Type { get; set; } [JsonProperty("quantity")] public double Quantity { get; set; } [JsonProperty("price")] public double Price { get; set; } [JsonProperty("amount")] public double Amount { get; set; } [JsonProperty("date")] public int Date { get; set; } } 

Como resultado, el valor correspondiente al elemento "trade_id" se registrará en la propiedad OrderID, "type" en Type, etc. El atributo JsonProperty también es necesario para serializar / deserializar propiedades que tienen modificadores privados o estáticos; de forma predeterminada, dichas propiedades se ignoran.
Como resultado, el código para compilar una lista de todas las transacciones de OrderID para los pares de divisas BTC_USD y ETH_USD puede verse así:

 using HttpClient httpClient = new HttpClient(); string request = "https://api.exmo.com/v1/trades/?pair=BTC_USD,ETH_USD"; string responseBody = await (await httpClient.GetAsync(request)). EnsureSuccessStatusCode(). Content.ReadAsStringAsync(); Dictionary<string, List<Order>> PairList = JsonConvert.DeserializeObject<Dictionary<string, List<Order>>>(responseBody); List<int> IDs = new List<int>(); foreach (var pair in PairList) foreach (var order in pair.Value) IDs.Add(order.OrderID); 

La segunda dificultad al trabajar con esta clase será la propiedad Fecha. Como puede ver, el generador de clase JSON C # definió el elemento "fecha" como un entero primo. Pero sería mucho más conveniente si la propiedad Date de nuestra clase tuviera un tipo especialmente creado para fechas: DateTime. Cómo hacerlo se describirá más adelante.

Características de trabajar con fechas


La frase inicial del artículo en la documentación para newtonsof.json, con una descripción del trabajo con fechas, se puede traducir aproximadamente como "DateTime en JSON - es difícil". El problema es que la especificación JSON en sí no contiene información sobre qué sintaxis se debe usar para describir la fecha y la hora.

Todo es relativamente bueno cuando la fecha en la cadena JSON se presenta en forma de texto y el formato de presentación corresponde a una de tres opciones: "Microsoft" (actualmente considerado obsoleto), "JavaScript" (tiempo Unix ) y la variante ISO 8601 . Ejemplos de formatos válidos:

 Dictionary<string, DateTime> d = new Dictionary<string, DateTime> { { "date", DateTime.Now } }; string isoDate = JsonConvert.SerializeObject(d); // {"date":"2019-12-19T14:10:31.3708939+03:00"} JsonSerializerSettings microsoftDateFormatSettings = new JsonSerializerSettings { DateFormatHandling = DateFormatHandling.MicrosoftDateFormat }; string microsoftDate = JsonConvert.SerializeObject(d, microsoftDateFormatSettings); // {"date":"\/Date(1576753831370+0300)\/"} string javascriptDate = JsonConvert.SerializeObject(d, new JavaScriptDateTimeConverter()); // {"date":new Date(1576753831370)} 

Sin embargo, en el caso del intercambio Exmo, todo es un poco más complicado. La descripción de la API de intercambio indica que la fecha y la hora se especifican en formato Unix (JavaScript). Y, teóricamente, agregando a nuestra propiedad Date de la clase Order una función de conversión de formato de la clase JavaScriptDateTimeConverter (), debemos obtener la fecha de conversión al tipo DateTime:

  class Order { [JsonProperty("trade_id")] public int OrderID { get; set; } [JsonProperty("type")] public string Type { get; set; } [JsonProperty("quantity")] public double Quantity { get; set; } [JsonProperty("price")] public double Price { get; set; } [JsonProperty("amount")] public double Amount { get; set; } [JsonProperty("date", ItemConverterType = typeof(JavaScriptDateTimeConverter))] public DateTime Date { get; set; } } 

Sin embargo, en este caso, cuando intenta analizar datos en una variable de tipo DateTime, una excepción Newtonsoft.Json.JsonReaderException ya es familiar desde la primera parte del artículo. Esto sucede porque la función de conversión de la clase JavaScriptDateTimeConverter no puede convertir datos numéricos al tipo DateTime (esto solo funciona para cadenas).



Una posible salida de esta situación es escribir su propia clase de conversión de formato. De hecho, dicha clase ya existe y puede usarse pre-conectando el espacio de nombres Newtonsoft.Json.Converters (tenga en cuenta que la función inversa no es convertir DateTime a JSON en esta clase):

  class UnixTimeToDatetimeConverter : DateTimeConverterBase { private static readonly DateTime _epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { throw new NotImplementedException(); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { if (reader.Value == null) { return null; } return _epoch.AddSeconds(Convert.ToDouble(reader.Value)).ToLocalTime(); } } 

Solo queda conectar nuestra función a la propiedad Date de la clase Order. Para hacer esto, use el atributo JsonConverter:

  class Order { [JsonProperty("trade_id")] public int OrderID { get; set; } [JsonProperty("type")] public string Type { get; set; } [JsonProperty("quantity")] public double Quantity { get; set; } [JsonProperty("price")] public double Price { get; set; } [JsonProperty("amount")] public double Amount { get; set; } [JsonProperty("date")] [JsonConverter(typeof(UnixTimeToDatetimeConverter))] public DateTime Date { get; set; } } 

Ahora nuestra propiedad Date es de tipo DateTime y podemos, por ejemplo, crear una lista de ofertas en los últimos 10 minutos:

 HttpClient httpClient = new HttpClient(); string request = "https://api.exmo.com/v1/trades/?pair=BTC_USD,ETH_USD"; string responseBody = await (await httpClient.GetAsync(request)). EnsureSuccessStatusCode(). Content.ReadAsStringAsync(); Dictionary<string, List<Order>> PairList = JsonConvert.DeserializeObject<Dictionary<string, List<Order>>>(responseBody); List<Order> Last10minuts = new List<Order>(); foreach (var pair in PairList) foreach (var order in pair.Value) if (order.Date > DateTime.Now.AddMinutes(-10)) Last10minuts.Add(order); 

Texto completo del programa.
 using System; using System.Threading.Tasks; using System.Collections.Generic; using System.Net.Http; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Newtonsoft.Json.Serialization; namespace JSONObjects { class Order { [JsonProperty("pair")] public string Pair { get; set; } [JsonProperty("trade_id")] public int OrderID { get; set; } [JsonProperty("type")] public string Type { get; set; } [JsonProperty("quantity")] public double Quantity { get; set; } [JsonProperty("price")] public double Price { get; set; } [JsonProperty("amount")] public double Amount { get; set; } [JsonProperty("date")] [JsonConverter(typeof(UnixTimeToDatetimeConverter))] public DateTime Date { get; set; } } class UnixTimeToDatetimeConverter : DateTimeConverterBase { private static readonly DateTime _epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { throw new NotImplementedException(); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { if (reader.Value == null) { return null; } return _epoch.AddSeconds(Convert.ToDouble(reader.Value)).ToLocalTime(); } } class Program { public static async Task Main(string[] args) { using HttpClient httpClient = new HttpClient(); string request = "https://api.exmo.com/v1/trades/?pair=BTC_USD,ETH_USD"; string responseBody = await (await httpClient.GetAsync(request)). EnsureSuccessStatusCode(). Content.ReadAsStringAsync(); Dictionary<string, List<Order>> PairList = JsonConvert. DeserializeObject<Dictionary<string, List<Order>>>(responseBody); List<Order> Last10minuts = new List<Order>(); foreach (var pair in PairList) foreach (var order in pair.Value) if (order.Date > DateTime.Now.AddMinutes(-10)) Last10minuts.Add(order); } } } 


Sustitución de elementos JSON


Anteriormente trabajamos con el equipo de intercambio de oficios. Este comando devuelve objetos con los siguientes campos:

 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; } } 

El comando de intercambio user_open_orders devuelve una estructura muy similar:

 public class BTCUSD { public string order_id { get; set; } public string created { get; set; } public string type { get; set; } public string pair { get; set; } public string quantity { get; set; } public string price { get; set; } public string amount { get; set; } } 

Por lo tanto, tiene sentido adaptar la clase Order para que pueda convertir no solo los datos del comando trade, sino también los datos del comando user_open_orders.

Las diferencias son que hay un nuevo elemento de par que contiene el nombre del par de divisas, trade_id se reemplaza por order_id (y ahora es una cadena), y la fecha se ha creado y también es una cadena.

Para comenzar, agregamos la capacidad de guardar el order_id y los campos creados en los campos OrderID y Date correspondientes de la clase Order. Para hacer esto, prepare la clase OrderDataContractResolver, en la que los nombres de campo se reemplazarán para el análisis (se requerirán los espacios de nombres System.Reflection y Newtonsoft.Json.Serialization):

  class OrderDataContractResolver : DefaultContractResolver { public static readonly OrderDataContractResolver Instance = new OrderDataContractResolver(); protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) { var property = base.CreateProperty(member, memberSerialization); if (property.DeclaringType == typeof(Order)) { if (property.PropertyName.Equals("trade_id", StringComparison.OrdinalIgnoreCase)) { property.PropertyName = "order_id"; } if (property.PropertyName.Equals("date", StringComparison.OrdinalIgnoreCase)) { property.PropertyName = "created"; } } return property; } } 

Además, esta clase debe especificarse como un parámetro del método DeserializeObject de la siguiente manera:

 Dictionary<string, List<Order>> PairList = JsonConvert.DeserializeObject<Dictionary<string, List<Order>>>(responseBody, new JsonSerializerSettings { ContractResolver = OrderDataContractResolver.Instance }); 

Como resultado, dicha estructura JSON obtenida como respuesta al comando user_open_orders:

 {"BTC_USD":[{"order_id":"4722868563","created":"1577349229","type":"sell","pair":"BTC_USD","quantity":"0.002","price":"8362.2","amount":"16.7244"}]} 

se convertirá a dicha estructura de datos:



Tenga en cuenta que para que el programa funcione correctamente, el tipo del campo OrderID en la clase Order tuvo que ser reemplazado por string.

Texto completo del programa.
 using System; using System.Threading.Tasks; using System.Collections.Generic; using System.Net.Http; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Newtonsoft.Json.Converters; using Newtonsoft.Json.Serialization; using System.Reflection; namespace JSONObjects { class Order { [JsonProperty("pair")] public string Pair { get; set; } [JsonProperty("trade_id")] public string OrderID { get; set; } [JsonProperty("type")] public string Type { get; set; } [JsonProperty("quantity")] public double Quantity { get; set; } [JsonProperty("price")] public double Price { get; set; } [JsonProperty("amount")] public double Amount { get; set; } [JsonProperty("date")] [JsonConverter(typeof(UnixTimeToDatetimeConverter))] public DateTime Date { get; set; } } class OrderDataContractResolver : DefaultContractResolver { public static readonly OrderDataContractResolver Instance = new OrderDataContractResolver(); protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) { var property = base.CreateProperty(member, memberSerialization); if (property.DeclaringType == typeof(Order)) { if (property.PropertyName.Equals("trade_id", StringComparison.OrdinalIgnoreCase)) { property.PropertyName = "order_id"; } if (property.PropertyName.Equals("date", StringComparison.OrdinalIgnoreCase)) { property.PropertyName = "created"; } } return property; } } class UnixTimeToDatetimeConverter : DateTimeConverterBase { private static readonly DateTime _epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { throw new NotImplementedException(); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { if (reader.Value == null) { return null; } return _epoch.AddSeconds(Convert.ToDouble(reader.Value)).ToLocalTime(); } } class Program { public static async Task Main(string[] args) { using HttpClient httpClient = new HttpClient(); string request = "https://api.exmo.com/v1/trades/?pair=BTC_USD,ETH_USD"; string responseBody = await (await httpClient.GetAsync(request)). EnsureSuccessStatusCode(). Content.ReadAsStringAsync(); Dictionary<string, List<Order>> PairList = new Dictionary<string, List<Order>>(); JObject jObject = JObject.Parse(responseBody); foreach (var pair in jObject) { List<Order> orders = new List<Order>(); foreach (var order in pair.Value.ToObject<List<Order>>()) { order.Pair = pair.Key; orders.Add(order); } PairList.Add(pair.Key, orders); } responseBody = "{\"BTC_USD\":[{\"order_id\":\"4722868563\"," + "\"created\":\"1577349229\",\"type\":\"sell\"," + "\"pair\":\"BTC_USD\",\"quantity\":\"0.002\"," + "\"price\":\"8362.2\",\"amount\":\"16.7244\"}]}"; Dictionary<string, List<Order>> PairList1 = JsonConvert.DeserializeObject<Dictionary<string, List<Order>>> (responseBody, new JsonSerializerSettings { ContractResolver = OrderDataContractResolver.Instance }); } } } } 



Como puede ver, cuando se llama al comando user_open_orders, la respuesta contiene el campo "par", en el caso del comando comercial, la información sobre el par de divisas está contenida solo en el valor clave. Por lo tanto, debe completar el campo Emparejar después de analizar:

 foreach (var pair in PairList) foreach (var order in pair.Value) order.Pair = pair.Key; 

O use el objeto JObject:

 Dictionary<string, List<Order>> PairList = new Dictionary<string, List<Order>>(); JObject jObject = JObject.Parse(responseBody); foreach (var pair in jObject) { List<Order> orders = new List<Order>(); foreach (var order in pair.Value.ToObject<List<Order>>()) { order.Pair = pair.Key; orders.Add(order); } PairList.Add(pair.Key, orders); } 

Lo que finalmente lleva a la creación de la siguiente estructura de datos:

Source: https://habr.com/ru/post/482042/


All Articles