c# System.Text.Json 精講

sogeisetsu 發表於 2021-11-29
C#

本文內容來自我寫的開源電子書《WoW C#》,現在正在編寫中,可以去WOW-Csharp/學習路徑總結.md at master · sogeisetsu/WOW-Csharp (github.com)來檢視編寫進度。預計2021年年底會完成編寫,2022年2月之前會完成所有的校對和轉制電子書工作,爭取能夠在2022年將此書上架亞馬遜。編寫此書的目的是因為目前.NET市場相對低迷,很多優秀的書都是基於.NET framework框架編寫的,與現在的.NET 6相差太大,正規的.NET 5學習教程現在幾乎只有MSDN,可是MSDN雖然準確優美但是太過瑣碎,沒有過閱讀開發文件的同學容易一頭霧水,於是,我就編寫了基於.NET 5的《WoW C#》。本人水平有限,歡迎大家去本書的開源倉庫sogeisetsu/WOW-Csharp關注、批評、建議和指導。

Json解析

json是一種類似於通過鍵值對來儲存資料的格式,在對資料庫進行操作的時候,通常會把類資料轉為json格式,然後儲存在資料庫裡面,使用的時候再將json轉為類的例項化物件。java的springboot框架的一整套解決方案裡面可以通過mybatis和fastjson完成這個操作。在web的前後端資料傳輸中,一般也是用json作為資料的載體,JavaScript有著對json比較完備的支援。

Json格式概述

  • 基礎

    1. 概念: JavaScript Object Notation JavaScript物件表示法
    • json現在多用於儲存和交換文字資訊的語法

    • 進行資料的傳輸

    • JSON 比 XML 更小、更快,更易解析。

    1. 語法:

    2. 基本規則

    -  資料在名稱/值對中:json資料是由鍵值對構成的
    
    -  鍵用引號(單雙都行)引起來,也可以不使用引號
    
    -  值得取值型別:
    
      1. 數字(整數或浮點數)
    
      2. 字串(在雙引號中)
    
      3. 邏輯值(true 或 false)
    
      4. 陣列(在方括號中)	{"persons":[{},{}]}
    
      5. 物件(在花括號中) {"address":{"province":"陝西"....}}
    
      6. null
    
    -  資料由逗號分隔:多個鍵值對由逗號分隔
    
    -  花括號儲存物件:使用{}定義json 格式
    
    -  方括號儲存陣列:[]
    
    1. JavaScript獲取資料:
  1. json物件.鍵名

  2. json物件["鍵名"]

  3. 陣列物件[索引]

  4. 遍歷img

解析

使用 C# 對 JSON 進行序列化和反序列化 - .NET | Microsoft Docs

會用到兩個名詞,序列化和反序列化,其中序列化是指將例項物件轉換成json格式的字串,反序列化則是逆向前面序列化的過程。

在序列化的過程中,預設情況下會只序列化公共讀寫的屬性,可以通過System.Text.Json.SerializationJsonInclude特性或者JsonSerializerOptionsIncludeFields屬性來包含公有欄位。通過System.Text.Json.SerializationJsonInclude特性可以來自定義可以序列化的非公共屬性訪問器(即屬性的訪問修飾符為public,但是set訪問器和get訪問器的任意一方為非public)。這可能對使用慣了java的人來說不適應,事實上這是一種很合理的序列化要求,預設狀況下,序列化器會序列化物件中的所有可讀屬性,反序列化所有可寫屬性,這種方式尊重了訪問修飾符的作用。也可用開源的Newtonsoft.Json來序列化非公有屬性。現在很多程式語言(包括.NET)能通過反射來獲取私有屬性本身就是不合理的,從.NET core能明顯的感覺到.NET團隊出於安全的考慮在限制反射的使用。

需要用到的namespace

using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Unicode;
  • System.Text.JsonJsonSerializer.Serialize方法可以進行序列化
  • System.Text.JsonJsonSerializer.Deserialize方法可以進行反序列化
  • System.Text.Json.Serialization可以要序列化的類新增必要的特性,比如JsonPropertyName為屬性序列化時重新命名,再比如JsonInclude來定義序列化時要包含的欄位。
  • System.Text.Encodings.WebSystem.Text.Unicode來讓特定的字符集在序列化的時候能夠正常序列化而不是被轉義成為 \uxxxx,其中 xxxx 為字元的 Unicode 程式碼。事實上,預設情況下,序列化程式會轉義所有非 ASCII 字元。

序列化

只將例項化物件轉變成json字串,假設有一個例項化物件weatherForecast,序列化方式如下:

string jsonString = JsonSerializer.Serialize(weatherForecast);

反序列化

指將json字串序列化成例項化物件,書接前文,方式如下:

weatherForecast = JsonSerializer.Deserialize<WeatherForecastWithPOCOs>(jsonString);

JsonSerializerOptions

可以通過JsonSerializerOptions來指定諸如是否整齊列印和忽略Null值屬性等資訊。使用方式為將JsonSerializerOptions例項化之後再當作JsonSerializer.SerializeJsonSerializer.Deserialize的引數。

關於JsonSerializerOptions的屬性可以檢視如何使用 System.Text.Json 例項化 JsonSerializerOptions | Microsoft Docs

先例項化一個JsonSerializerOptions物件,在初始化器裡面定義各種屬性

JsonSerializerOptions jsonSerializerOptions = new JsonSerializerOptions()
{
    // 整齊列印
    WriteIndented = true,
    // 忽略值為Null的屬性
    IgnoreNullValues = true,
    // 設定Json字串支援的編碼,預設情況下,序列化程式會轉義所有非 ASCII 字元。 即,會將它們替換為 \uxxxx,其中 xxxx 為字元的 Unicode
    // 程式碼。 可以通過設定Encoder來讓生成的josn字串不轉義指定的字符集而進行序列化 下面指定了基礎拉丁字母和中日韓統一表意文字的基礎Unicode 塊
    // (U+4E00-U+9FCC)。 基本涵蓋了除使用西裡爾字母以外所有西方國家的文字和亞洲中日韓越的文字
    Encoder = JavaScriptEncoder.Create(UnicodeRanges.BasicLatin, UnicodeRanges.CjkUnifiedIdeographs),
    // 反序列化不區分大小寫
    PropertyNameCaseInsensitive = true,
    // 駝峰命名
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,

    // 對字典的鍵進行駝峰命名
    DictionaryKeyPolicy = JsonNamingPolicy.CamelCase,
    // 序列化的時候忽略null值屬性
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
    // 忽略只讀屬性,因為只讀屬性只能序列化而不能反序列化,所以在以json為儲存資料的介質的時候,序列化只讀屬性意義不大
    IgnoreReadOnlyFields = true,
    // 不允許結尾有逗號的不標準json
    AllowTrailingCommas = false,
    // 不允許有註釋的不標準json
    ReadCommentHandling = JsonCommentHandling.Disallow,
    // 允許在反序列化的時候原本應為數字的字串(帶引號的數字)轉為數字
    NumberHandling = JsonNumberHandling.AllowReadingFromString,
    // 處理迴圈引用型別,比如Book類裡面有一個屬性也是Book類
    ReferenceHandler = ReferenceHandler.Preserve
};

然後在序列化和反序列化的時候jsonSerializerOptions物件當作引數傳給JsonSerializer.SerializeJsonSerializer.Deserialize

string jsonBookA = JsonSerializer.Serialize(bookA, jsonSerializerOptions);
// 反序列化
BookA bookA1 = JsonSerializer.Deserialize<BookA>(jsonBookA, jsonSerializerOptions);

JsonSerializerOptions 常用屬性概述

作用 值型別
WriteIndented 整齊列印,將此值設定為true後序列化的json字串在列印的時候會進行自動縮排和換行。預設為false。 bool
IgnoreNullValues 忽略值為Null的屬性。預設為false。 bool
Encoder 設定Json字串支援的編碼,預設情況下,序列化程式會轉義所有非 ASCII 字元。 即,會將它們替換為 \uxxxx,其中 xxxx 為字元的 Unicode程式碼。 可以通過設定Encoder來讓生成的josn字串不轉義指定的字符集而進行序列化。可設定為Encoder = JavaScriptEncoder.Create(UnicodeRanges.BasicLatin, UnicodeRanges.CjkUnifiedIdeographs)來包含除使用西裡爾字母以外所有西方國家的文字和亞洲中日韓越的文字 JavaScriptEncoder
PropertyNameCaseInsensitive 反序列化不區分鍵的大小寫。預設為false。 bool
PropertyNamingPolicy 序列化時屬性的命名方式,常用的為JsonNamingPolicy.CamelCase設定成小寫字母開頭的駝峰命名。 JsonNamingPolicy
DictionaryKeyPolicy 序列化時對字典的string鍵進行小寫字母開頭的駝峰駝峰命名。 JsonNamingPolicy
DefaultIgnoreCondition 指定一個條件,用於確定何時在序列化或反序列化過程中忽略具有預設值的屬性。 預設值為 Never。常用值為JsonIgnoreCondition.WhenWritingDefault來忽略預設值屬性。 JsonIgnoreCondition
IgnoreReadOnlyFields 序列化時忽略只讀屬性,因為只讀屬性只能序列化而不能反序列化,所以在以json為儲存資料的介質的時候,序列化只讀屬性意義不大。預設為false。 bool
AllowTrailingCommas 反序列化時,允許結尾有逗號的不標準json,預設為false。 bool
ReadCommentHandling 反序列化時,允許有註釋的不標準json,預設為false。 bool
NumberHandling 使用NumberHandling = JsonNumberHandling.AllowReadingFromString可允許在反序列化的時候原本應為數字的字串(帶引號的數字)轉為數字 JsonNumberHandling
ReferenceHandler 配置在讀取和寫入 JSON 時如何處理物件引用。使用ReferenceHandler = ReferenceHandler.Preserve仍然會在序列化和反序列化的時候保留引用並處理迴圈引用。 ReferenceHandler
IncludeFields 確定是否在序列化和反序列化期間處理欄位。 預設值為 false bool

System.Text.Json.Serialization 特性

可以為將要序列化和被反序列化而生成的類的屬性和欄位新增特性。

JsonInclude 包含特定public欄位和非公共屬性訪問器

在序列化或反序列化時,使用 JsonSerializerOptions.IncludeFields 全域性設定或 [JsonInclude] 特性來包含欄位(必須是public),當應用於某個屬性時,指示非公共的 getter 和 setter 可用於序列化和反序列化。 不支援非公共屬性。

demo:

/// <summary>
/// 時間戳
/// </summary>
[JsonInclude]
public long timestamp = DateTimeOffset.Now.ToUnixTimeMilliseconds();

/// <summary>
/// 書的名稱
/// </summary>
[JsonInclude]
public string Name { private get; set; } = "《書名》";

JsonPropertyName 自定義屬性名稱

若要設定單個屬性的名稱,請使用 [JsonPropertyName] 特性。

此特性設定的屬性名稱:

  • 同時適用於兩個方向(序列化和反序列化)。
  • 優先於屬性命名策略。

demo:

/// <summary>
/// 作者
/// </summary>
[JsonPropertyName("作者")]
public string Author
{
    get { return _author; }
    set { _author = value; }
}

JsonIgnore 忽略單個屬性

阻止對屬性進行序列化或反序列化。

demo:

/// <summary>
/// 書的出版商
/// </summary>
[JsonIgnore]
public string OutCompany { get => _outCompany; set => _outCompany = value; }

JsonExtensionData 處理溢位 JSON

反序列化時,可能會在 JSON 中收到不是由目標型別的屬性表示的資料。可以將這些無法由目標型別的屬性表示的資料儲存在一個Dictionary<string, JsonElement>字典裡面,方式如下:

/// <summary>
/// 儲存反序列化時候的溢位資料
/// </summary>
[JsonExtensionData]
public Dictionary<string, JsonElement> ExtensionData { get; set; }

筆者的選擇

在筆者的開發經驗當中,json用的最多的就是前後端資料傳輸和資料庫儲存資料。對jsonSerializerOptions往往會選擇這幾個選項:

JsonSerializerOptions jsonSerializerOptions = new JsonSerializerOptions()
{
    // 整齊列印
    WriteIndented = true,
    // 忽略值為Null的屬性
    IgnoreNullValues = true,
    // 設定Json字串支援的編碼,預設情況下,序列化程式會轉義所有非 ASCII 字元。 即,會將它們替換為 \uxxxx,其中 xxxx 為字元的 Unicode
    // 程式碼。 可以通過設定Encoder來讓生成的josn字串不轉義指定的字符集而進行序列化 下面指定了基礎拉丁字母和中日韓統一表意文字的基礎Unicode 塊
    // (U+4E00-U+9FCC)。 基本涵蓋了除使用西裡爾字母以外所有西方國家的文字和亞洲中日韓越的文字
    Encoder = JavaScriptEncoder.Create(UnicodeRanges.BasicLatin, UnicodeRanges.CjkUnifiedIdeographs, UnicodeRanges.CjkSymbolsandPunctuation),
    // 反序列化不區分大小寫
    PropertyNameCaseInsensitive = true,
    // 駝峰命名
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    // 對字典的鍵進行駝峰命名
    DictionaryKeyPolicy = JsonNamingPolicy.CamelCase,
    // 忽略只讀屬性,因為只讀屬性只能序列化而不能反序列化,所以在以json為儲存資料的介質的時候,序列化只讀屬性意義不大
    IgnoreReadOnlyFields = true,
    // 允許在反序列化的時候原本應為數字的字串(帶引號的數字)轉為數字
    NumberHandling = JsonNumberHandling.AllowReadingFromString
};

儘量不使用JsonPropertyName特性,對有可能會用到json反序列化的類一定會用到JsonExtensionData特性來儲存可能存在的溢位資料。JsonIgnoreJsonInclude會廣泛的使用而不用JsonSerializerOptionsIncludeFields來序列化所有欄位。

LICENSE

已將所有引用其他文章之內容清楚明白地標註,其他部分皆為作者勞動成果。對作者勞動成果做以下宣告:

copyright © 2021 蘇月晟,版權所有。

知識共享許可協議


作品蘇月晟採用知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議進行許可。