Trabalhando com a biblioteca Newtonsoft.Json com um exemplo real. Parte 2

A primeira parte do artigo examinou o mecanismo de análise de objetos JSON com uma estrutura dinamicamente variável. Esses objetos foram convertidos para tipos de espaço para nome newtonsoft.json.linq e depois convertidos em estruturas de linguagem C #. Nos comentários da primeira parte, houve muitas críticas, em grande parte justificadas. Na segunda parte, tentarei levar em consideração todos os comentários e sugestões.



Além disso, focaremos na preparação de classes para um ajuste mais preciso da transformação de dados, mas primeiro você precisa retornar à análise JSON. Recordo que, na primeira parte, foram utilizados os métodos Parse () e ToObject <T> () das classes JObject e JArray do espaço para nome 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>>>(); 

Note-se que no namespace newtonsof.json na classe JsonConvert, existe um método estático DeserializeObject <>, que permite converter seqüências de caracteres diretamente em estruturas C # correspondentes a objetos e matrizes da notação JSON:

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

E, no futuro, esse método será usado no artigo, portanto, você precisa adicionar o newtonsoft.json ao programa.

Além disso, é possível reduzir ainda mais o número de conversões intermediárias - após a instalação da biblioteca Microsoft.AspNet.WebApi.Client (também disponível no NuGet), os dados podem ser analisados ​​diretamente do fluxo usando o método ReadAsAsync:

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

Obrigado pela ponta do covil .

Preparando a turma para conversão


Voltar para a nossa classe 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; } } 

Deixe-me lembrá-lo de que foi criado com base no formato proposto pelo JSON C # Class Generator. Há dois pontos que, ao trabalhar com objetos dessa classe, podem causar dificuldades.

O primeiro - neste formulário, as propriedades da nossa classe violam as regras para nomear campos. Além disso, é lógico que um objeto do tipo Order espere que seu identificador seja chamado OrderID (e não traid_id, como aconteceu nos exemplos da primeira parte). Para associar um elemento de estrutura JSON e uma propriedade de classe a um nome arbitrário, você deve adicionar o atributo JsonProperty antes da propriedade:

  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, o valor correspondente ao elemento "trade_id" será registrado na propriedade CódigoDoPedido, "tipo" em Tipo, etc. O atributo JsonProperty também é necessário para serializar / desserializar propriedades que possuem modificadores privados ou estáticos - por padrão, essas propriedades são ignoradas.
Como resultado, o código para compilar uma lista de todas as transações de OrderID para os pares de moedas BTC_USD e ETH_USD pode se parecer com o seguinte:

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

A segunda dificuldade ao trabalhar com esta classe será a propriedade Date. Como você pode ver, o Gerador de classe JSON C # definiu o elemento "date" como um número inteiro principal. Mas seria muito mais conveniente se a propriedade Date da nossa classe tivesse um tipo criado especialmente para datas - DateTime. Como fazer isso será descrito mais adiante.

Recursos de trabalho com datas


A frase inicial do artigo na documentação para newtonsof.json, com uma descrição do trabalho com datas, pode ser traduzida aproximadamente como "DateTime no JSON - é difícil". O problema é que a própria especificação JSON não contém informações sobre qual sintaxe deve ser usada para descrever a data e a hora.

Tudo é relativamente bom quando a data na string JSON é apresentada em formato de texto e o formato da apresentação corresponde a uma das três opções: “Microsoft” (atualmente considerado obsoleto), “JavaScript” (horário do Unix ) e a variante ISO 8601 . Exemplos 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)} 

No entanto, no caso da troca Exmo, tudo é um pouco mais complicado. A descrição da API do Exchange indica que a data e a hora estão especificadas no formato Unix (JavaScript). E, teoricamente, adicionando à nossa propriedade Date da classe Order uma função de conversão de formato da classe JavaScriptDateTimeConverter (), devemos obter a data convertida para o 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; } } 

No entanto, nesse caso, quando você tenta analisar dados em uma variável do tipo DateTime, uma exceção Newtonsoft.Json.JsonReaderException já é familiar na primeira parte do artigo. Isso acontece porque a função de conversão da classe JavaScriptDateTimeConverter não pode converter dados numéricos no tipo DateTime (isso funciona apenas para seqüências de caracteres).



Uma possível saída dessa situação é escrever sua própria classe de conversão de formato. De fato, essa classe já existe e pode ser usada pré-conectando o espaço para nome Newtonsoft.Json.Converters (observe que a função inversa não é converter de DateTime para JSON nesta classe):

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

Resta apenas conectar nossa função à propriedade Date da classe Order. Para fazer isso, use o 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; } } 

Agora, nossa propriedade Date é do tipo DateTime e podemos, por exemplo, criar uma lista de ofertas nos ú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 do 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); } } } 


Substituição de elemento JSON


Anteriormente, trabalhamos com a equipe de troca de negócios. Este comando retorna objetos com os seguintes 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; } } 

O comando de troca user_open_orders retorna uma estrutura muito semelhante:

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

Portanto, faz sentido adaptar a classe Order para que possa converter não apenas os dados do comando de negociação, mas também os dados do comando user_open_orders.

As diferenças são que existe um novo elemento de par que contém o nome do par de moedas, trade_id é substituído por order_id (e agora é uma string) e a data foi criada e também é uma string.

Para começar, adicionamos a capacidade de salvar o order_id e os campos criados nos campos OrderID e Date correspondentes da classe Order. Para fazer isso, prepare a classe OrderDataContractResolver, na qual os nomes dos campos serão substituídos para análise (os espaços de nomes System.Reflection e Newtonsoft.Json.Serialization serão necessários):

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

Além disso, essa classe deve ser especificada como um parâmetro do método DeserializeObject da seguinte maneira:

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

Como resultado, essa estrutura JSON obtida como resposta ao 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"}]} 

será convertido para essa estrutura de dados:



Observe que, para que o programa funcione corretamente, o tipo do campo OrderID na classe Order teve que ser substituído por string.

Texto completo do 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 você pode ver, quando o comando user_open_orders é chamado, a resposta contém o campo "pair", no caso do comando trade, informações sobre o par de moedas estão contidas apenas no valor da chave. Portanto, você precisará preencher o campo Emparelhar após analisar:

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

Ou use o 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); } 

Em última análise, o que leva à criação da seguinte estrutura de dados:

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


All Articles