Arbeiten mit der Newtonsoft.Json-Bibliothek anhand eines realen Beispiels. Teil 2

Im ersten Teil des Artikels wurde der Mechanismus zum Parsen von JSON-Objekten mit einer sich dynamisch ändernden Struktur untersucht. Diese Objekte wurden in newtonsoft.json.linq-Namespace-Typen umgewandelt und anschließend in C # -Sprachenstrukturen konvertiert. In den Kommentaren zum ersten Teil gab es viel Kritik, die größtenteils gerechtfertigt war. Im zweiten Teil werde ich versuchen, alle Kommentare und Vorschläge zu berücksichtigen.



Darüber hinaus konzentrieren wir uns auf die Vorbereitung von Klassen zur Feinabstimmung der Datentransformation. Zunächst müssen Sie jedoch zum JSON-Parsing selbst zurückkehren. Ich erinnere mich, dass im ersten Teil die Methoden Parse () und ToObject <T> () der Klassen JObject und JArray des Namespace newtonsoft.json.linq verwendet wurden:

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

Es ist zu beachten, dass es im Namespace newtonsof.json in der Klasse JsonConvert eine statische Methode DeserializeObject <> gibt, mit der Sie Zeichenfolgen direkt in C # -Strukturen konvertieren können, die Objekten und Arrays der JSON-Notation entsprechen:

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

In Zukunft wird diese Methode in diesem Artikel verwendet. Sie müssen also newtonsoft.json zum Programm hinzufügen.

Darüber hinaus ist es möglich, die Anzahl der Zwischenkonvertierungen weiter zu reduzieren. Nach der Installation der Microsoft.AspNet.WebApi.Client-Bibliothek (auch über NuGet verfügbar) können Daten mithilfe der ReadAsAsync-Methode direkt aus dem Stream analysiert werden:

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

Danke für den Tipp Lair .

Vorbereiten der Klasse für die Konvertierung


Zurück zu unserer Auftragsklasse:

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

Ich möchte Sie daran erinnern, dass es auf der Grundlage des von JSON C # Class Generator vorgeschlagenen Formats erstellt wurde. Es gibt zwei Punkte, die beim Arbeiten mit Objekten dieser Klasse Schwierigkeiten verursachen können.

Die erste - in dieser Form verstoßen die Eigenschaften unserer Klasse gegen die Regeln für die Benennung von Feldern. Außerdem ist es logisch, dass ein Objekt vom Typ Order erwartet, dass sein Bezeichner OrderID heißt (und nicht traid_id, wie in den Beispielen aus dem ersten Teil). Um ein JSON-Strukturelement und eine Klasseneigenschaft einem beliebigen Namen zuzuordnen, müssen Sie das JsonProperty-Attribut vor der Eigenschaft hinzufügen:

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

Infolgedessen wird der dem Element "trade_id" entsprechende Wert in der Eigenschaft "OrderID", "type" in "Type" usw. aufgezeichnet. Das JsonProperty-Attribut ist auch zum Serialisieren / Deserialisieren von Eigenschaften mit privaten oder statischen Modifizierern erforderlich. Standardmäßig werden solche Eigenschaften ignoriert.
Folglich kann der Code zum Erstellen einer Liste aller OrderID-Transaktionen für die Währungspaare BTC_USD und ETH_USD folgendermaßen aussehen:

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

Die zweite Schwierigkeit bei der Arbeit mit dieser Klasse ist die Eigenschaft Date. Wie Sie sehen, hat der JSON C # -Klassengenerator das Element "date" als Primzahl definiert. Es wäre jedoch viel praktischer, wenn die Date-Eigenschaft unserer Klasse einen speziell für Datumsangaben erstellten Typ hätte - DateTime. Wie dies gemacht wird, wird später beschrieben.

Funktionen zum Arbeiten mit Datumsangaben


Der ursprüngliche Satz des Artikels in der Dokumentation für newtonsof.json mit einer Beschreibung der Arbeit mit Datumsangaben kann grob als "DateTime in JSON - es ist schwer" übersetzt werden. Das Problem ist, dass die JSON-Spezifikation selbst keine Informationen darüber enthält, welche Syntax zur Beschreibung von Datum und Uhrzeit verwendet werden soll.

Alles ist relativ gut, wenn das Datum in der JSON-Zeichenfolge in Textform dargestellt wird und das Darstellungsformat einer von drei Optionen entspricht: "Microsoft" (derzeit als veraltet betrachtet), "JavaScript" ( Unix- Zeit) und die ISO 8601- Variante. Beispiele für gültige Formate:

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

Beim Exmo-Tausch ist jedoch alles etwas komplizierter. Die Beschreibung der Exchange-API gibt an, dass Datum und Uhrzeit im Unix-Format (JavaScript) angegeben sind. Und theoretisch sollten wir, wenn wir unserer Date-Eigenschaft der Order-Klasse eine Formatkonvertierungsfunktion aus der JavaScriptDateTimeConverter () -Klasse hinzufügen, das Datum in den DateTime-Typ umwandeln:

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

Wenn Sie jedoch in diesem Fall versuchen, Daten in eine Variable vom Typ DateTime zu analysieren, ist eine Ausnahme Newtonsoft.Json.JsonReaderException bereits aus dem ersten Teil des Artikels bekannt. Dies liegt daran, dass die Konvertierungsfunktion der JavaScriptDateTimeConverter-Klasse keine numerischen Daten in den DateTime-Typ konvertieren kann (dies funktioniert nur für Zeichenfolgen).



Ein möglicher Ausweg aus dieser Situation ist das Schreiben einer eigenen Formatkonvertierungsklasse. Tatsächlich ist eine solche Klasse bereits vorhanden und kann verwendet werden, indem der Namespace Newtonsoft.Json.Converters vorab verbunden wird (beachten Sie, dass die Umkehrfunktion in dieser Klasse keine Konvertierung von DateTime nach JSON vornimmt):

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

Es bleibt nur, unsere Funktion mit der Date-Eigenschaft der Order-Klasse zu verbinden. Verwenden Sie dazu das JsonConverter-Attribut:

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

Jetzt hat unsere Date-Eigenschaft den Typ DateTime und wir können beispielsweise eine Liste von Deals in den letzten 10 Minuten erstellen:

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

Volltext des Programms
 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); } } } 


JSON-Elementsubstitution


Zuvor haben wir mit dem Trades Exchange Team zusammengearbeitet. Dieser Befehl gibt Objekte mit den folgenden Feldern zurück:

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

Der Austauschbefehl user_open_orders gibt eine sehr ähnliche Struktur zurück:

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

Daher ist es sinnvoll, die Order-Klasse so anzupassen, dass sie nicht nur die Handelsbefehlsdaten, sondern auch die user_open_orders-Befehlsdaten konvertieren kann.

Der Unterschied besteht darin, dass es ein neues Paarelement gibt, das den Namen des Währungspaares enthält. Trade_id wird durch order_id ersetzt (und jetzt ist es eine Zeichenfolge). Das Datum wurde erstellt und ist auch eine Zeichenfolge.

Zunächst fügen wir die Möglichkeit hinzu, die order_id und die erstellten Felder in den entsprechenden OrderID- und Date-Feldern der Order-Klasse zu speichern. Bereiten Sie dazu die OrderDataContractResolver-Klasse vor, in der die Feldnamen für das Parsen ersetzt werden (die Namespaces System.Reflection und Newtonsoft.Json.Serialization sind erforderlich):

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

Außerdem muss diese Klasse wie folgt als Parameter der DeserializeObject-Methode angegeben werden:

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

Als Ergebnis wird eine solche JSON-Struktur als Antwort auf den Befehl user_open_orders abgerufen:

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

wird in eine solche Datenstruktur konvertiert:



Beachten Sie, dass der Typ des OrderID-Felds in der Order-Klasse durch eine Zeichenfolge ersetzt werden musste, damit das Programm ordnungsgemäß funktioniert.

Volltext des Programms
 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 }); } } } } 



Wie Sie sehen, enthält die Antwort beim Aufrufen des Befehls user_open_orders das Feld "pair" (Paar). Im Fall des Handelsbefehls sind Informationen zum Währungspaar nur im Schlüsselwert enthalten. Daher müssen Sie nach dem Parsen entweder das Feld Pair ausfüllen:

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

Oder verwenden Sie das JObject-Objekt:

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

Was letztendlich zur Erstellung der folgenden Datenstruktur führt:

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


All Articles