[C#] 歷遍JSON各層屬性

這個題目是筆者在StackckOverflow上回答問題,後續被其他使用者用評論追問的題目,不過可惜研究出來後,因為解法較複雜,請對方再開一個問題,結果就沒下文了~可能對方已經自己找出答案了吧,為了不浪費,就拿來當這次的題材吧XD

首先介紹一下原題目 How to get list of values by key’s name across Json using C# 題目需求算明確,就是如何一次取得每一層特定屬性名稱的值,JSON範例如下:

{
   "objectId": "123",
   "properties": {
     "objectId": "456"
   },
   "variables": [
     {
       "objectId": "789"
     },
     {
       "objectId": "012"
     }
  ]  
}

需要取得所有objectId的值,並存在一個陣列中,期望輸出是

["123", "456", "789", "012"]

當初為了解這題也是去Google了一下,後來發現到關鍵方法DescendantsAndSelf可以將本身這一層以及底下每一層元素,算是打掉層數,扁平化到同一層的概念,接著再篩選一下屬性名稱取得值就可以了

var root = (JContainer)JToken.Parse(json);
var list = root.DescendantsAndSelf().OfType<JProperty>()
  .Where(p => p.Name == "objectId")
  .Select(p => p.Value.Value<string>());
Console.WriteLine(string.Join(",", list.ToArray()));

這邊算是開胃菜,接著有網友詢問如果要看兩個屬性,且需要成對,objectId屬性要對同一層的objectvalue屬性,若缺其一就忽略或者使用null,期望輸出一個二維陣列,但我覺得做成Dictionary應該會比較適合

{
  "objectId": "123",
  "properties": {
    "objectId": "456",
    "objectvalue": "aaa"
  },
  "variables": [
    {
      "objectId": "789",
      "objectvalue": "bbb"
    },
    {
      "objectId": "012",
      "objectvalue": "ccc"
    }
  ]
}

看起來沒辦法像之前用遍平化的方式處理,因為需要在不同層各判斷是否有成對的屬性,因此決定將每一層歷遍,同時再判斷屬性是否成對,同值將值寫入Dictionary中,解法如下:

private static Dictionary<string, string> result = new Dictionary<string, string>();

private static void Main(string[] args)
{
    var json = "{\"objectId\":\"123\",\"properties\":{\"objectId\":\"456\",\"objectvalue\":\"aaa\"},\"variables\":[{\"objectId\":\"789\",\"objectvalue\":\"bbb\"},{\"objectId\":\"012\",\"objectvalue\":\"ccc\"}]}";
    var root = JObject.Parse(json);
  
    //取得這一層所有的屬性
    var propertyList = root.Properties();
    handleIdValue(propertyList);

    Console.WriteLine("Keys: " + string.Join(",", result.Keys)); //456, 789, 012
    Console.WriteLine("Values: " + string.Join(",", result.Values)); //aaa, bbb, ccc
}


private static void handleIdValue(IEnumerable<JProperty> propertyList)
{
    //依據每一層判斷是否有成對的屬性,再依照屬性型別深入,確定成對後寫入result中
    var haveId = false;
    var id = "";
    foreach (var property in propertyList)
    {
        //若值是字串,判斷成對名稱
        if (property.Value.Type == JTokenType.String)
        {
            if (property.Name == "objectId")
            {
                id = property.Value.Value<string>();
                haveId = true;
            }
            if (haveId && property.Name == "objectvalue")
            {
                result.Add(id, property.Value.Value<string>());
            }
        }
        //若是陣列,依序將每一個物件取得屬性,遞迴處理
        else if (property.Value.Type == JTokenType.Array)
        {
            var array = property.Value.Value<JArray>();
            foreach (var p in array)
            {
                if (p.Type == JTokenType.Object)
                {
                    handleIdValue(p.Value<JObject>().Properties());
                }
            }
        }
        //若是物件,取得屬性,遞迴處理
        else if (property.Value.Type == JTokenType.Object)
        {
            handleIdValue(property.Value.Value<JObject>().Properties());
        }
    }
}

依照這個解法可以達到題目的要求,不過陣列那邊,如果有多維陣列的話,就需要將處理陣列的部分也分離一個functiont出來,同樣用遞迴處理。藉由這次的解題,也讓我對JSON.NET有更深入的認識,平常頂多就是序列化跟反序列化而已,很少需要客製的分析JSON字串

每次在解stackoverflow的題目時,都還蠻緊張的,因為新題目大家都在搶,稍微慢一點,可能就已經選出答案了,但後來比較釋懷了,好的答案比較重要,也有長尾效應,如果真的有幫助,最後還是會獲得不少認同分數的~

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com 標誌

您的留言將使用 WordPress.com 帳號。 登出 /  變更 )

Google photo

您的留言將使用 Google 帳號。 登出 /  變更 )

Twitter picture

您的留言將使用 Twitter 帳號。 登出 /  變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 /  變更 )

連結到 %s

Create a website or blog at WordPress.com

向上 ↑

%d 位部落客按了讚: