c# System.Text.Json 精講

本文內容來自我寫的開源電子書《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 國際許可協議進行許可。

Tags: