فحص الجزء الأول من المقالة آلية تحليل كائنات JSON ذات بنية متغيرة ديناميكيًا. تم تحويل هذه الكائنات إلى أنواع مساحة الاسم newtonsoft.json.linq ، ثم تم تحويلها إلى بنيات لغة C #. في التعليقات على الجزء الأول كان هناك الكثير من الانتقادات ، مبررة للجزء الأكبر. في الجزء الثاني ، سأحاول مراعاة جميع التعليقات والاقتراحات.

علاوة على ذلك ، سوف نركز على إعداد الفصول لضبط أفضل لتحويل البيانات ، ولكن عليك أولاً العودة إلى تحليل JSON نفسه. أتذكر في الجزء الأول ، تم استخدام أساليب Parse () و ToObject <T> () لفئتي JObject و JArray في مساحة الاسم 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>>>();
تجدر الإشارة إلى أنه في مساحة الاسم newtonsof.json في فئة JsonConvert ، هناك طريقة ثابتة DeserializeObject <> ، والتي تسمح لك بتحويل السلاسل مباشرة إلى بنيات C # المقابلة للكائنات ومصفوفات تدوين JSON:
Dictionary<string, List<Order>> JsonObject = JsonConvert.DeserializeObject<Dictionary<string, List<Order>>>(responseBody); List<string> Json_Array = JsonConvert.DeserializeObject<List<string>>(responseBody);
وفي المستقبل ، سيتم استخدام هذه الطريقة في المقالة ، لذلك تحتاج إلى إضافة باستخدام newtonsoft.json إلى البرنامج.
بالإضافة إلى ذلك ، من الممكن تقليل عدد التحويلات الوسيطة - بعد تثبيت مكتبة Microsoft.AspNet.WebApi.Client (المتوفرة أيضًا من خلال NuGet) ، يمكن تحليل البيانات مباشرة من الدفق باستخدام الأسلوب ReadAsAsync:
Dictionary<string, List<Order>> JsonObject = await (await httpClient.GetAsync(request)). EnsureSuccessStatusCode().Content. ReadAsAsync<Dictionary<string, List<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; } }
اسمحوا لي أن أذكرك أنه تم إنشاؤه على أساس التنسيق المقترح من قبل JSON C # Class Generator. هناك نقطتان عند العمل مع أشياء من هذه الفئة يمكن أن يسبب صعوبات.
الأول - في هذا النموذج ، تنتهك خصائص صفنا قواعد تسمية الحقول. حسنًا ، بالإضافة إلى ذلك ، من المنطقي أن يتوقع كائن من النوع Order أن معرفه سوف يسمى OrderID (وليس traid_id ، كما حدث في الأمثلة من الجزء الأول). لربط عنصر بنية JSON وخاصية فئة باسم اعتباطي ، يجب عليك إضافة سمة JsonProperty قبل الخاصية:
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; } }
نتيجةً لذلك ، سيتم تسجيل القيمة المطابقة لعنصر "trade_id" في خاصية OrderID أو "النوع" في النوع ، إلخ. تعد سمة JsonProperty ضرورية أيضًا لتسلسل / إلغاء تسلسل الخصائص التي تحتوي على معدلات خاصة أو ثابتة - بشكل افتراضي ، يتم تجاهل هذه الخصائص.
نتيجة لذلك ، قد يبدو رمز تجميع قائمة بجميع معاملات OrderID لأزواج العملات BTC_USD و ETH_USD كما يلي:
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);
ستكون الصعوبة الثانية عند العمل مع هذه الفئة هي خاصية التاريخ. كما ترون ، عرّف JSON C # Class Generator عنصر "التاريخ" بأنه عدد صحيح أولي. ولكن سيكون أكثر ملاءمة إذا كانت الخاصية Date لفئتنا تحتوي على نوع تم إنشاؤه خصيصًا للتواريخ - DateTime. كيفية القيام بذلك سيتم وصفها لاحقا.
ميزات العمل مع التواريخ
يمكن ترجمة العبارة الأولية
للمقال في وثائق newtonsof.json ، مع وصف للعمل مع التواريخ ، تقريبًا باسم "DateTime في JSON - إنه صعب". المشكلة هي أن مواصفات JSON نفسها لا تحتوي على معلومات حول بناء الجملة الذي يجب استخدامه لوصف التاريخ والوقت.
كل شيء جيد نسبيًا عندما يتم تقديم التاريخ في سلسلة JSON في شكل نص ويتوافق تنسيق العرض التقديمي مع أحد الخيارات الثلاثة: "Microsoft" (تعتبر حاليًا قديمة) و "JavaScript" (وقت
Unix ) ومتغير
ISO 8601 . أمثلة للتنسيقات الصحيحة:
Dictionary<string, DateTime> d = new Dictionary<string, DateTime> { { "date", DateTime.Now } }; string isoDate = JsonConvert.SerializeObject(d);
ومع ذلك ، في حالة تبادل Exmo ، كل شيء أكثر تعقيدًا بقليل. يشير وصف واجهة برمجة تطبيقات التبادل إلى أنه تم تحديد التاريخ والوقت بتنسيق Unix (JavaScript). ومن الناحية النظرية ، إضافة إلى خاصية Date الخاصة بنا من فئة Order وظيفة تحويل تنسيق من فئة JavaScriptDateTimeConverter () ، يجب أن نحصل على التاريخ الذي يتم إرساله إلى نوع 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; } }
ومع ذلك ، في هذه الحالة ، عند محاولة تحليل البيانات إلى متغير من نوع DateTime ، هناك استثناء Newtonsoft.Json.JsonReaderException مألوف بالفعل من الجزء الأول من المقالة. يحدث هذا لأن وظيفة التحويل للفئة JavaScriptDateTimeConverter غير قادرة على تحويل البيانات الرقمية إلى نوع DateTime (هذا يعمل فقط للسلاسل).

هناك طريقة ممكنة للخروج من هذا الموقف تتمثل في كتابة فئة تحويل التنسيق الخاصة بك. في الواقع ، هذه الفئة موجودة بالفعل ويمكن استخدامها عن طريق توصيل مساحة اسم Newtonsoft.Json.Converters مسبقًا (لاحظ أن الوظيفة العكسية ليست التحويل من DateTime إلى JSON في هذه الفئة):
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(); } }
يبقى فقط لربط وظيفتنا بخاصية التاريخ لفئة الطلب. للقيام بذلك ، استخدم سمة 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; } }
الآن خاصية Date لدينا هي من نوع DateTime ويمكننا ، على سبيل المثال ، إنشاء قائمة بالصفقات في آخر 10 دقائق:
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);
النص الكامل للبرنامج 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 Element Substitution
في وقت سابق عملنا مع فريق التبادل التجاري. هذا الأمر بإرجاع الكائنات مع الحقول التالية:
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; } }
يقوم الأمر exchange user_open_orders بإرجاع بنية مشابهة جدًا:
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; } }
لذلك ، من المنطقي تكييف فئة الأوامر بحيث لا يمكنها تحويل بيانات أوامر التجارة فقط ، ولكن أيضًا بيانات أوامر user_open_orders.
الاختلافات هي أنه يوجد عنصر زوج جديد يحتوي على اسم زوج العملة ، ويتم استبدال trade_id بـ order_id (وهو الآن سلسلة) ، وأصبح التاريخ عبارة عن سلسلة.
بادئ ذي بدء ، نضيف القدرة على حفظ order_id والحقول التي تم إنشاؤها في حقول معرف الطلب والتاريخ المقابلة من فئة الطلب. للقيام بذلك ، قم بإعداد فئة OrderDataContractResolver ، حيث سيتم استبدال أسماء الحقول للتحليل (ستكون مساحات System.Reflection و 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; } }
علاوة على ذلك ، يجب تحديد هذه الفئة كمعلمة للأسلوب DeserializeObject كما يلي:
Dictionary<string, List<Order>> PairList = JsonConvert.DeserializeObject<Dictionary<string, List<Order>>>(responseBody, new JsonSerializerSettings { ContractResolver = OrderDataContractResolver.Instance });
نتيجة لذلك ، تم الحصول على بنية JSON كرد على الأمر user_open_orders:
{"BTC_USD":[{"order_id":"4722868563","created":"1577349229","type":"sell","pair":"BTC_USD","quantity":"0.002","price":"8362.2","amount":"16.7244"}]}
سيتم تحويلها إلى بنية البيانات هذه:

يرجى ملاحظة أنه لكي يعمل البرنامج بشكل صحيح ، يجب استبدال نوع حقل OrderID في فئة الطلب بسلسلة.
النص الكامل للبرنامج 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 }); } } } }
كما ترون ، عندما يتم استدعاء الأمر user_open_orders ، تحتوي الإجابة على حقل "الزوج" ، في حالة أمر التداول ، يتم تضمين معلومات حول زوج العملة في القيمة الأساسية فقط. لذلك ، سيتعين عليك إما ملء حقل الزوج بعد التحليل:
foreach (var pair in PairList) foreach (var order in pair.Value) order.Pair = pair.Key;
أو استخدم كائن 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); }
مما يؤدي في النهاية إلى إنشاء بنية البيانات التالية:
