当子节点计数可以更改时,获取给定父节点的第一个子节点

本文关键字:子节点 获取 第一个 父节点 | 更新日期: 2023-09-27 18:32:59

我正在查询一个 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