我正在查询一个 Web API 并获取一个 JSON 作为响应,我正在使用 JSON.NET 解析该响应。生成的 JSON 的结构可能会有所不同。某个节点(例如称为项(可以包含零个、一个或多个子节点(称为项(。看看我的示例代码,你会在 case1、case2 和 case3 下看到三个不同的 JSON 数据。
我正在尝试找到一个简单的解决方案来涵盖每种情况并从第一项(即"标题"和"艺术家"(访问一些数据
如果有多个项目节点,那么我总是只对第一项感兴趣。因此硬编码项[0],因为JSON.Parse()
将在此处返回一个索引对象。
但是,如果只有一个项节点,则JSON.Parse()
返回一个没有任何索引的对象。通过item[0]
访问数据是行不通的。您必须改用item
。
我的问题相对简单:如果只有一个或多个子节点,是否有一种优雅的方式来访问给定父节点的第一个子注释?
我目前的解决方法(10 行额外的代码(看起来很麻烦。相比之下,你将在Powershell中获得相同的功能,只需一行:
$title = @($json.ItemSearchResponse.Items.Item)[0].ItemAttributes.Title
这里@()
用于将单个项目转换为数组,以便在使用索引[0]
时涵盖所有可能的 JSON 情况。但在 C# 中,将 JObject 转换为具有.ToArray()
的数组的工作方式有所不同。
可以将此代码复制+粘贴到 Windows 控制台测试项目中并运行它。通过var json = case2;
切换我的不同 JSON 示例并查看差异。
有什么想法吗?也许是JSONpath?转换为数组/列表/IEnumerable? SelectNodes
还是Select
而不是SelectNode
?
C-Sharp 中的工作示例(需要参考 JSON.NET(
using System;
using System.Linq;
using Newtonsoft.Json.Linq;
namespace testspace
{
class Program
{
public static void Main(string[] args)
{
// JSON example data
// case with one "Item". Access token via "Item" (no index)
JObject case1 = JObject.Parse(@"{
'ItemSearchResponse': {
'Items': {
'TotalResults': '1',
'Item': {
'ASIN': 'B00J6VXXXX',
'ItemAttributes': {
'Creator': 'MyArtist1',
'Genre': 'pop-music',
'ReleaseDate': '2014-06-09',
'Title': 'MyTitle1',
'TrackSequence': '10'
}
}
}
}
}");
// case with multiple "Item"s. Access token via "Item[0]" (with index)
JObject case2 = JObject.Parse(@"{
'ItemSearchResponse': {
'Items': {
'TotalResults': '2',
'Item': [
{
'ASIN': 'B001FAXXXX',
'ItemAttributes': {
'Creator': 'MyArtist1',
'Genre': 'pop-music',
'ReleaseDate': '2007-04-17',
'Title': 'MyTitle1',
'TrackSequence': '7'
}
},
{
'ASIN': 'B00136XXXX',
'ItemAttributes': {
'Binding': 'MP3 Music',
'Creator': 'MyArtist2',
'Genre': 'pop-music',
'ReleaseDate': '2007-04-17',
'Title': 'MyTitle2',
'TrackSequence': '7'
}
}
]
}
}
}");
// case with no "Item"s. Should return empty/null strings when trying to access "item" data, and not throw an error
JObject case3 = JObject.Parse(@"{
'ItemSearchResponse': {
'Items': {
'TotalResults': '0',
}
}
}");
// #######################################################
//switch between different possible json data
var json = case2; // <- switch between "case1", "case2", "case3" to see the difference
//expected result for case1 and case2 should be "MyTitle1"
// but this works only for first case - not for second case
string result1 = (string)json.SelectToken("ItemSearchResponse.Items.Item.ItemAttributes.Title");
Console.WriteLine("try 1: " + result1);
// expected result for case1 and case2 should be "MyTitle1"
// but this works only for second case - not for first case
string result2 = (string)json.SelectToken("ItemSearchResponse.Items.Item[0].ItemAttributes.Title");
Console.WriteLine("try 2: " + result2);
// ugly workaround I'd like to get rid off
string result3 = null;
if (json.SelectToken("ItemSearchResponse.Items.Item") != null ) {
JToken item;
if ((int)json.SelectToken("ItemSearchResponse.Items.TotalResults") == 1) {
item = json.SelectToken("ItemSearchResponse.Items.Item");
} else {
item = json.SelectToken("ItemSearchResponse.Items.Item[0]");
}
result3 = (string)item.SelectToken("ItemAttributes.Title");
// access more data like artist, release-date and so on
}
Console.WriteLine("workaround: " + result3);
// #######################################################
Console.ReadKey(true);
}
}
}
#1 和情况 #3 是相同的,因为情况 #3 没有项目,null 是一个有效的数组 - 没有问题。问题是情况 #2,但这可以通过 JSON.NET 自定义解析器来解决。
我将使对象更简单,使整个代码更短。我正在使用 JSON.NET,因为它是你需要的一切。因此,这是我的 BigObject,其中包含零个、一个或多个项目:
public class Item
{
public int Value { get; set; }
}
public class BigObject
{
[JsonConverter(typeof(ArrayItemConverter))]
public List<Item> Items;
}
请注意我用自定义的ArrayItemConverter所做的装饰:
public class ArrayItemConverter : JsonConverter
{
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)
{
object retVal = (string)null;
if (reader.TokenType == JsonToken.StartObject)
{
Item instance = (Item)serializer.Deserialize<Item>(reader);
retVal = new List<Item>() { instance };
}
else if (reader.TokenType == JsonToken.StartArray)
{
List<Item> list = serializer.Deserialize<List<Item>>(reader);
retVal = list;
}
return retVal;
}
public override bool CanConvert(Type objectType)
{
return true;
}
}
我在这里说的是,如果我检测到一个对象的开始(在 JSON 语法中有"{"(,我将反序列化单个项目并为其创建一个新列表并将其放在那里。
如果我检测到数组的开始(JSON 中的"["(,我会将数组反序列化为列表。
以下是我的测试:
static void Main(string[] args)
{
string case1 = @"{
""Items"": {
""Value"":1
}
}";
string case2 = @"{
""Items"": [
{
""Value"":21
},
{
""Value"":22
},
]
}";
string case3 = @"{
}";
BigObject c1 = JsonConvert.DeserializeObject<BigObject>(case1);
Console.WriteLine("c1 value = {0}", c1.Items[0].Value);
BigObject c2 = JsonConvert.DeserializeObject<BigObject>(case2);
Console.WriteLine("c2 value1 = {0}", c2.Items[0].Value);
Console.WriteLine("c2 value2 = {0}", c2.Items[1].Value);
BigObject c3 = JsonConvert.DeserializeObject<BigObject>(case3);
Console.WriteLine("c3 items = {0}", c3.Items == null ? "null" : "non-null" );
}
控制台输出为:
c1 value = 1
c2 value1 = 21
c2 value2 = 22
c3 items = null