使用真实示例使用Newtonsoft.Json库。 第二部分

本文的第一部分研究了使用动态变化的结构解析JSON对象的机制。 这些对象被转换为newtonsoft.json.linq命名空间类型,然后转换为C#语言结构。 在第一部分的评论中,有很多批评,其中大部分是合理的。 在第二部分中,我将尝试考虑所有评论和建议。



此外,我们将专注于准备类以更好地调整数据转换,但首先您需要返回JSON解析本身。 我记得在第一部分中,使用了newtonsoft.json.linq命名空间的JObject和JArray类的Parse()和ToObject <T>()方法:

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

应当注意,在JsonConvert类的newtonsof.json命名空间中,有一个静态方法DeserializeObject <>,它允许您将字符串直接转换为与JSON表示法的对象和数组相对应的C#结构:

 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提出的格式创建的。 有两点说明,使用此类的对象可能会造成困难。

首先-以这种形式,我们类的属性违反了命名字段的规则。 好吧,此外,订单类型的对象期望其标识符被称为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属性中,“ Type”记录在Type等中。 对于具有私有或静态修饰符的属性,要序列化/反序列化,JsonProperty属性也是必需的-默认情况下,此类属性将被忽略。
结果,用于编译货币对BTC_USD和ETH_USD的所有OrderID交易的列表的代码可能如下所示:

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

使用此类的第二个困难是Date属性。 如您所见,JSON C#类生成器将“ date”元素定义为素数整数。 但是,如果我们类的Date属性具有专门为日期创建的类型-DateTime,它将更加方便。 稍后将描述如何执行此操作。

处理日期的功能


文章中有关newtonsof.json的文章的最初短语,以及使用日期的描述,可以粗略地翻译为“ JSON中的DateTime-很难”。 问题在于JSON规范本身不包含有关应使用哪种语法来描述日期和时间的信息。

当以文本形式显示JSON字符串中的日期并且显示格式对应于以下三个选项之一时,一切都相对不错:“ Microsoft”(当前被认为已过时),“ JavaScript”( Unix时间)和ISO 8601变体。 有效格式示例:

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

但是,就Exmo交换而言,一切都比较复杂。 exchange API的描述指示日期和时间以Unix(JavaScript)格式指定。 从理论上讲,从JavaScriptDateTimeConverter()类向Order类的Date属性添加格式转换函数,我们应该将日期转换为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(); } } 

仅保留将我们的函数连接到Order类的Date属性。 为此,请使用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元素替代


之前我们与交易交换团队合作。 此命令返回具有以下字段的对象:

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

交换命令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; } } 

因此,调整Order类是有意义的,这样它不仅可以转换交易命令数据,还可以转换user_open_orders命令数据。

不同之处在于,有一个包含货币对名称的新对元素,trade_id被order_id替换(现在是一个字符串),并且日期已经创建并且也是一个字符串。

首先,我们添加了将order_id和创建的字段保存在Order类的相应OrderID和Date字段中的功能。 为此,请准备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"}]} 

将被转换为这样的数据结构:



请注意,为使程序正常运行,必须将Order类中的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命令时,答案包含“ pair”字段,对于贸易命令,有关货币对的信息仅包含在键值中。 因此,您必须在解析后填写“配对”字段:

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

最终导致创建以下数据结构:

Source: https://habr.com/ru/post/zh-CN482042/


All Articles