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

时间:2016-02-03 13:49:25

标签: c# json json.net

我正在查询Web API并获取JSON作为我用JSON.NET解析的响应。生成的JSON的结构可能会有所不同。某个节点(在示例中称为项目)可以包含零个,一个或多个子节点(称为项目)。看看我的示例代码,您将在case1,case2和case3下看到三个不同的JSON数据。

我试图找到一个简单的解决方案来涵盖每个案例并从第一个项目中访问一些数据(即"标题"和#34;艺术家")

如果有多个 item 节点,那么我总是只对第一个项目感兴趣。因此硬编码的项[0] 因为JSON.Parse()将返回索引对象。

但是如果只有一个 item 节点,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? SelectNodesSelect代替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 个答案:

答案 0 :(得分:2)

情况#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语法中有&#39; {&#39;),我将反序列化单个Item并为其创建一个新List并将其放入那里。

如果我检测到数组的开头(&#39; [&#39;在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