如何更优解析API返回的不一致JSON持仓数据结构?
处理Web服务返回JSON结构不一致的问题
对接的Web服务positions端点GET请求返回的JSON存在结构不一致:
- 多持仓场景:
positions字段包含position数组 - 单持仓场景:
positions字段直接是单个持仓对象
已实现基础解析逻辑,以下是行业通用的反序列化技巧及POCO落地方案:
示例返回结构
多持仓返回
{ "positions": { "position": [ { "cost_basis": 207.01, "date_acquired": "2018-08-08T14:41:11.405Z", "id": 130089, "quantity": 1.00000000, "symbol": "AAPL" }, { "cost_basis": 1870.70, "date_acquired": "2018-08-08T14:42:00.774Z", "id": 130090, "quantity": 1.00000000, "symbol": "AMZN" } ] } }
单持仓返回
{ "positions": { "cost_basis": 1870.70, "date_acquired": "2018-08-08T14:42:00.774Z", "id": 130090, "quantity": 1.00000000, "symbol": "AMZN" } }
当前解析代码
using (var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, $"v1/accounts/{tradierHost.TradierAccount}/positions")) using (HttpResponseMessage response = await this.tradierClient.SendAsync(httpRequestMessage)) { // Make sure we executed with no errors. response.EnsureSuccessStatusCode(); var jObject = JObject.Parse(await response.Content.ReadAsStringAsync()); IEnumerable<BrokerPosition> brokerPositions = null; if (jObject["positions"] is JObject positionsObject) { // Update one position. if (positionsObject["position"] is JObject positionObject) { var brokerPositionList = new List<BrokerPosition>(); brokerPositionList.Add(positionObject.ToObject<BrokerPosition>()); } // Update many positions. if (positionsObject["position"] is JArray positionArray) { brokerPositions = positionArray.ToObject<IEnumerable<BrokerPosition>>(); } } }
通用反序列化技巧与POCO方案
1. 自定义JsonConverter(Newtonsoft.Json)
核心思路是写一个通用转换器,自动识别字段是单个对象还是数组,统一转为集合类型,避免手动判断JToken类型。
步骤1:定义POCO类
public class BrokerPosition { [JsonProperty("cost_basis")] public decimal CostBasis { get; set; } [JsonProperty("date_acquired")] public DateTime DateAcquired { get; set; } public int Id { get; set; } public decimal Quantity { get; set; } public string Symbol { get; set; } } public class PositionsWrapper { [JsonProperty("positions")] [JsonConverter(typeof(SingleOrArrayConverter<BrokerPosition>))] public List<BrokerPosition> Positions { get; set; } }
步骤2:实现通用转换器
public class SingleOrArrayConverter<T> : JsonConverter { public override bool CanConvert(Type objectType) { return objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeof(List<>); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { var token = JToken.Load(reader); // 处理嵌套的position字段(多持仓场景) if (token.Type == JTokenType.Object && token["position"] != null) { token = token["position"]; } if (token.Type == JTokenType.Array) { return token.ToObject<List<T>>(); } // 单持仓场景直接转成单个元素的集合 return new List<T> { token.ToObject<T>() }; } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { // 如果不需要序列化回这种结构,可留空或抛出异常 throw new NotImplementedException("此转换器仅用于反序列化"); } }
步骤3:简化后的解析代码
using (var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, $"v1/accounts/{tradierHost.TradierAccount}/positions")) using (HttpResponseMessage response = await this.tradierClient.SendAsync(httpRequestMessage)) { response.EnsureSuccessStatusCode(); var jsonContent = await response.Content.ReadAsStringAsync(); // 直接反序列化为POCO,转换器自动处理单/多持仓逻辑 var result = JsonConvert.DeserializeObject<PositionsWrapper>(jsonContent, new SingleOrArrayConverter<BrokerPosition>()); IEnumerable<BrokerPosition> brokerPositions = result.Positions; }
2. 其他替代方案
- System.Text.Json的自定义转换器:如果使用.NET Core/.NET 5+的System.Text.Json,可实现
JsonConverter<List<T>>,逻辑和Newtonsoft.Json版本类似。 - 预处理JSON字符串:在反序列化前,把单个对象包装成数组(比如替换
"positions":{...}为"positions":{"position":[...]}),但这种方式不够优雅,维护性差,不推荐。
内容的提问来源于stack exchange,提问作者user365462
相关产品推荐
相关产品推荐

