Travailler avec la bibliothèque Newtonsoft.Json avec un exemple réel. 2e partie

La première partie de l'article a examiné le mécanisme d'analyse des objets JSON avec une structure changeant dynamiquement. Ces objets ont été convertis en types d'espace de noms newtonsoft.json.linq, puis convertis en structures de langage C #. Dans les commentaires de la première partie, il y avait beaucoup de critiques, pour la plupart justifiées. Dans la deuxième partie, je vais essayer de prendre en compte tous les commentaires et suggestions.



De plus, nous nous concentrerons sur la préparation des classes pour un réglage plus fin de la transformation des données, mais vous devez d'abord revenir à l'analyse JSON elle-même. Je me souviens dans la première partie, les méthodes Parse () et ToObject <T> () des classes JObject et JArray de l'espace de noms newtonsoft.json.linq ont été utilisées:

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

Il convient de noter que dans l'espace de noms newtonsof.json de la classe JsonConvert, il existe une méthode statique DeserializeObject <>, qui vous permet de convertir directement des chaînes en structures C # correspondant aux objets et aux tableaux de notation JSON:

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

Et à l'avenir, cette méthode sera utilisée dans l'article, vous devez donc ajouter en utilisant newtonsoft.json au programme.

En outre, il est possible de réduire davantage le nombre de conversions intermédiaires - après avoir installé la bibliothèque Microsoft.AspNet.WebApi.Client (également disponible via NuGet), les données peuvent être analysées directement à partir du flux à l'aide de la méthode ReadAsAsync:

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

Merci pour la tanière .

Préparer la classe pour la conversion


Retour à notre classe de commande:

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

Permettez-moi de vous rappeler qu'il a été créé sur la base du format proposé par JSON C # Class Generator. Il y a deux points qui peuvent causer des difficultés lorsque vous travaillez avec des objets de cette classe.

Le premier - sous cette forme, les propriétés de notre classe violent les règles de nommage des champs. Eh bien, en plus, il est logique pour un objet de type Order de s'attendre à ce que son identifiant soit appelé OrderID (et non traid_id, comme cela s'est produit dans les exemples de la première partie). Pour associer un élément de structure JSON et une propriété de classe à un nom arbitraire, vous devez ajouter l'attribut JsonProperty avant la propriété:

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

Par conséquent, la valeur correspondant à l'élément «trade_id» sera enregistrée dans la propriété OrderID, «type» dans Type, etc. L'attribut JsonProperty est également nécessaire pour sérialiser / désérialiser les propriétés qui ont des modificateurs privés ou statiques - par défaut, ces propriétés sont ignorées.
Par conséquent, le code de compilation d'une liste de toutes les transactions OrderID pour les paires de devises BTC_USD et ETH_USD peut ressembler à ceci:

 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 deuxième difficulté lorsque vous travaillez avec cette classe sera la propriété Date. Comme vous pouvez le voir, le générateur de classe JSON C # a défini l'élément «date» comme un entier premier. Mais il serait beaucoup plus pratique si la propriété Date de notre classe avait un type spécialement créé pour les dates - DateTime. La procédure à suivre sera décrite ultérieurement.

Caractéristiques du travail avec les dates


La phrase initiale de l' article dans la documentation de newtonsof.json, avec une description de l'utilisation des dates, peut être grossièrement traduite par «DateTime en JSON - c'est difficile». Le problème est que la spécification JSON elle-même ne contient pas d'informations sur la syntaxe à utiliser pour décrire la date et l'heure.

Tout est relativement bon lorsque la date de la chaîne JSON est présentée sous forme de texte et que le format de présentation correspond à l'une des trois options: «Microsoft» (actuellement considéré comme obsolète), «JavaScript» (heure Unix ) et la variante ISO 8601 . Exemples de formats valides:

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

Cependant, dans le cas de l'échange Exmo, tout est un peu plus compliqué. La description de l'API Exchange indique que la date et l'heure sont spécifiées au format Unix (JavaScript). Et, théoriquement, en ajoutant à notre propriété Date de la classe Order une fonction de conversion de format de la classe JavaScriptDateTimeConverter (), nous devrions obtenir la conversion de la date en type 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; } } 

Toutefois, dans ce cas, lorsque vous essayez d'analyser des données dans une variable de type DateTime, une exception Newtonsoft.Json.JsonReaderException est déjà familière dans la première partie de l'article. Cela se produit car la fonction de conversion de la classe JavaScriptDateTimeConverter n'est pas en mesure de convertir des données numériques au type DateTime (cela ne fonctionne que pour les chaînes).



Un moyen possible de sortir de cette situation est d'écrire votre propre classe de conversion de format. En fait, une telle classe existe déjà et peut être utilisée en connectant d'abord l'espace de noms Newtonsoft.Json.Converters (notez que la fonction inverse n'est pas de convertir de DateTime en JSON dans cette 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(); } } 

Il ne reste plus qu'à connecter notre fonction à la propriété Date de la classe Order. Pour ce faire, utilisez l'attribut 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; } } 

Maintenant, notre propriété Date est de type DateTime et nous pouvons, par exemple, créer une liste d'offres au cours des 10 dernières minutes:

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

Texte intégral du programme
 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); } } } 


Substitution d'élément JSON


Plus tôt, nous avons travaillé avec l'équipe des échanges commerciaux. Cette commande renvoie des objets avec les champs suivants:

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

La commande d'échange user_open_orders renvoie une structure très similaire:

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

Par conséquent, il est logique d'adapter la classe Order afin qu'elle puisse convertir non seulement les données de la commande commerciale, mais également les données de la commande user_open_orders.

Les différences sont qu'il existe un nouvel élément de paire contenant le nom de la paire de devises, trade_id est remplacé par order_id (et maintenant c'est une chaîne), et la date est devenue créée et est également une chaîne.

Pour commencer, nous ajoutons la possibilité d'enregistrer le champ order_id et les champs créés dans les champs OrderID et Date correspondants de la classe Order. Pour ce faire, préparez la classe OrderDataContractResolver, dans laquelle les noms de champ seront remplacés pour l'analyse (les espaces de noms System.Reflection et Newtonsoft.Json.Serialization seront requis):

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

De plus, cette classe doit être spécifiée comme paramètre de la méthode DeserializeObject comme suit:

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

En conséquence, une telle structure JSON obtenue en réponse à la commande user_open_orders:

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

sera converti en une telle structure de données:



Veuillez noter que pour que le programme fonctionne correctement, le type du champ OrderID dans la classe Order devait être remplacé par une chaîne.

Texte intégral du programme
 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 }); } } } } 



Comme vous pouvez le voir, lorsque la commande user_open_orders est appelée, la réponse contient le champ «paire», dans le cas de la commande de commerce, les informations sur la paire de devises sont contenues uniquement dans la valeur de clé. Par conséquent, vous devez soit remplir le champ Pair après l'analyse:

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

Ou utilisez l'objet 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); } 

Ce qui conduit finalement à la création de la structure de données suivante:

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


All Articles