從 .NET Core 3.0 開始,System.Text.Json 逐步取代 Newtonsoft Json.NET 成為 .NET 處理 JSON 的官方解決方案。在寫 .NET 6 專案時,我也開始嘗試不引用 Json.NET,改以內建 System.Text.Json.JsonSerializer 處理。JsonSerializer.Serializ()/Deserialize<T>() 與 Json.NET JsonConvert 用法相近,較明顯差異是 System.Text.Json 預設會將中文字元轉成 \uxxxx 編碼,需設定 JsonSerializerOptions.Encoder 調整:參考

除了將 JSON 轉成預先定義的物件,Json.NET 有 JObject、JArray,不需轉為強型別即可讀取複雜的 JSON 結構(參考:小技巧 - Json.NET 免定義物件 LINQ 操作),類似需求在 System.Text.Json 則是透過 JsonDocument、JsonElement 實現,這篇將整理相關使用技巧。

假設有 JSON 內容如下:

{
    "RS": "Root Property String",
    "RI": 123,
    "RB": true,
    "RD": "2021-12-21T00:00:00Z",
    "RA": [
        { "CI": 1, "CT": "A" },
        { "CI": 2, "CT": "B"}
    ],
    "NO": {
        "NS": "Neseted Object String",
        "NA": [ 1, 2, 3 ],
        "NNO": { "NNS": "Nested Nested Object String" }
    }
}

原理是以 JsonDocument.Parse() 將 JSON 解析成 DOM,RootElement 為根元素,用 GetProperty("...") 可存取屬性,傳回型別為 JsonElement,我們可透過 GetInt32()、GetString()、GetBoolean()、GetDateTime() 等方法取值;若屬性為子物件,可繼續用 GetProperty() 取子物件屬性,以此類推。JsonElement.EnumerateArray() 可傳回可列舉 JsonElement 集合以 foreach 方式巡覽或使用 LINQ 操作;JsonElement.EnumerateObject() 則可傳回可列舉 JsonProperty 集合,列出物件所有屬性動態處理。若要依型別差異化處理,可由 JsonElement.ValueKind 偵測元素型別,JsonElement.GetRawText() 為原始 JSON 內容。另外,每個 JsonElement 也可 Deserialize<T>() 成指定的強型別物件。

最後來個綜合示範:

using System.Text.Json;

public class Program
{
    class RAItem
    {
        public int CI { get; set; }
        public string CT { get; set; }
    }
    static void Main(string[] args)
    {
        var json = File.ReadAllText("sample.json");

        var jd = JsonDocument.Parse(json);

        var root = jd.RootElement;
        Console.WriteLine(root.GetProperty("RS").GetString());
        if (root.TryGetProperty("RI", out var ri))
            Console.WriteLine(ri.GetInt32());
        Console.WriteLine(root.GetProperty("RB").GetBoolean());
        Console.WriteLine(root.GetProperty("RD").GetDateTime().ToString("yyyy-MM-dd"));

        foreach (JsonElement elem in root.GetProperty("RA").EnumerateArray())
        {
            Console.Write($"CI={elem.GetProperty("CI").GetInt32()}");
            //JsonElement 可 Deserialize 轉成物件
            var obj = elem.Deserialize<RAItem>();
            Console.WriteLine($" CT={obj.CT}");
        }

        var nestedObject = root.GetProperty("NO");
        Console.WriteLine(nestedObject.GetProperty("NS").GetString());
        Console.WriteLine(string.Join(", ", 
            nestedObject.GetProperty("NA").EnumerateArray()
            .Select(o => o.GetInt32().ToString()).ToArray()
        ));

        // 直接串接 GetProperty() 深入巢狀物件取得屬性
        Console.WriteLine(root.GetProperty("NO").GetProperty("NNO").GetProperty("NNS").GetString());

        // 列舉屬性
        var props = nestedObject.EnumerateObject();
        while (props.MoveNext()) {
            var prop = props.Current;
            Console.WriteLine($"Prop[{prop.Name}]/{prop.Value.ValueKind}/{prop.Value.GetRawText()}");
        }
    }
}

執行結果:

Example of using JsonElement to read complex JSON structure in System.Text.Json.


Comments

# by 小海

讚嘆黑大

# by Chris Wong

實用,謝謝

Post a comment