同事問到,由某 WebAPI 接回 JSON 格式的多筆資料,是否一定要定義強型別物件並搭配 JsonConvert.DeserializeObject<T[]> 才能用 LINQ 進行查詢整理?

答案是不用,有更省事的寫法。

如果只打算取用一兩欄位,且接入後會馬上轉成 Dictionary<T, T>、List<T> 等形式,為此額外宣告類別純屬浪費,不符合精實軟體開發精神。Json.NET 的 JObject LINQ 支援,在這種情境特別管用。

後面會應用以下觀念,印象模糊的同學可先複習:

假設資料源 JSON 長這樣:

[
  {
    "Name": "Jefrey",
    "RegDate": "2018-01-01T00:00:00Z",
    "Scores": [ 1, 9, 4, 2 ]
  },
  {
    "Name": "Darkthread",
    "RegDate": "2018-07-01T00:00:00Z",
    "Scores": [ 1, 2, 3, 4, 5 ]
  },
  {
    "Name": "Fox",
    "RegDate": "2019-01-01T00:00:00Z",
    "Scores": [ 9, 4, 8, 7 ]
  }
]

最終目標是接收 JSON 解析並先找出註冊日期在 2018/6/30 之後的人員,並計算其 Scores 平均值。

廢話不多說,直接看程式碼,說明寫在註解裡。

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace JsonLinq
{
    class Program
    {
        static void Main(string[] args)
        {
            //不宣告強型別物件直接轉成JObject操作,並視為dynamic操作
            //https://blog.darkthread.net/blog/json-net-and-dynamic/
            dynamic[] data = JsonConvert.DeserializeObject<JObject[]>(
                File.ReadAllText("data.json"));

            //取得第一個元素物件
            var x = data.First();
            //印出屬性,由輸出結果其型別如同 string, DateTime 及 int[]
            Console.WriteLine($@"
====== Object ======
Name: {x.Name}
RegDate: {x.RegDate}
Scores: {x.Scores}");
            //但事實不然,其真正型別為 JValue 或 JArray,應用時需轉型
            Console.WriteLine($@"
====== Type ======
Name ({x.Name.GetType()}) 
RegDate ({x.RegDate.GetType()})
Scores ({x.Scores.GetType()})

");

            //以下來個 LINQ 範例,我們要找出 2018/6/30 後註冊者的分數平均
            //結果以Dictionary<string, float>型別回傳
            var d = new DateTime(2018, 6, 30);

            var dict = data
                //日期JValue可直接轉型為DateTime
                .Where(o => ((DateTime) o.RegDate).CompareTo(d) > 0)
                .ToDictionary(
                    o => (string) o.Name, //字串JValue也可直接轉型
                    o =>
                    {
                        //陣列先轉JArray再用Select轉成int[]
                        var scores = (o.Scores as JArray).Select(s => (int)s).ToArray();
                        return Math.Round(scores.Sum() * 1.0f / scores.Length, 1);
                    });

            Console.WriteLine("****** Result ******");
            Console.WriteLine(JsonConvert.SerializeObject(dict, Formatting.Indented));
            Console.ReadLine();
        }
    }
}

執行結果如下:

====== Object ======
Name: Jefrey
RegDate: 2018/1/1 上午 12:00:00
Scores: [
  1,
  9,
  4,
  2
]

====== Type ======
Name (Newtonsoft.Json.Linq.JValue)
RegDate (Newtonsoft.Json.Linq.JValue)
Scores (Newtonsoft.Json.Linq.JArray)


****** Result ******
{
  "Darkthread": 3.0,
  "Fox": 7.0
}

學會這招,未來遇到接收 JSON 要立刻轉換形式的應用情境,就不用再花時間定義資料型別囉~

Tips of using Json.NET JObject feature to execute LINQ function on JSON without declaring strong type data entity.


Comments

Be the first to post a comment

Post a comment