為什麼需要新的JSON API? 為什麼需要新的JSON API? JSON.NET 大家都用過,老版本的ASP.NET Core也依賴於JSON.NET。 JSON.NET 大家都用過,老版本的ASP.NET Core也依賴於JSON.NET。 然而這個依賴就會引起一些版本問題:例如ASP.NET ...
為什麼需要新的JSON API?
JSON.NET 大家都用過,老版本的ASP.NET Core也依賴於JSON.NET。
然而這個依賴就會引起一些版本問題:例如ASP.NET Core某個版本需要使用JSON.NET v10,而另一個庫需要使用JSON.NET v11;或者JSON.NET 出現了一個新版本,而ASP.NET Core還不能支持這個版本,而您卻想使用該版本。
System.Text.Json
隨著NET Core 3.0的出現,出現了System.Text.Json命名空間和它下麵一些用於處理JSON的類。
特點
這個內置JSON API具有與生俱來的高性能、低分配的特點:
JSON.NET 使用.NET 裡面的字元串作為基本數據類型,其實也就是UTF16,而.NET Core中新的JSON API直接使用數據原始的UTF8格式。
新的JSON API基於Span<byte>這個數據類型來進行操作JSON數據,從而具有低分配的特點,這就可以極大的改善吞吐量和記憶體使用情況。
但是新的JSON API的特性還不那麼豐富,有一些JSON.NET具有的特性都還不支持。
例子
隨便找了一個JSON示例文件:
針對這個文件,需要修改一下它的屬性:
Utf8JsonReader
先使用 Utf8JsonReader 來讀取JSON文件。
Utf8JsonReader 並不會讀取文件或者stream,它會讀取Span數據類型。
直接上代碼:
Main方法裡面,我們使用File.ReadAllBytes從sample.json文件讀取數格式為byte[],然後通過AsSpan這個擴展方法將其轉化為Span<byte>數據類型,然後把它傳遞到 Utf8JsonReader 的構造函數來創建一個JSON的reader。
接下來使用while迴圈對JSON數據的每個Token進行讀取,每次執行Read()方法時,reader就會移動到JSON數據裡面的下一個Token那裡。
Token分成幾種類型,GetTokenInfo方法就是判斷一下Token的類型,並返回一些描述性信息,這裡面應該是包含了所有的類型。這裡面使用到了C# 8 的 switch 表達式。
運行程式
結果如下:
可以看到sample.json文件裡面的每個Token都被正確的顯示了。
JsonDocument類
JsonDocument是基於Utf8JsonReader 構建的。JsonDocument 可分析 JSON 數據並生成只讀文檔對象模型 (DOM),可對模型進行查詢,以支持隨機訪問和枚舉。使用 JsonDocument 分析常規 JSON 有效負載並訪問其所有成員比使用 Json.NET 快 2-3 倍,且為合理大小(即 < 1 MB)的數據所分配的量非常少。
JsonDocument可以處理Span,也可以處理Stream。
例子:
這裡我通過File.OpenRead把json文件轉化為stream。然後使用JsonDocument.Parse方法把stream解析成JSON文檔對象模型。
註意,這裡我使用了C# 8的using var語法,這個以後再說。
下麵我們開始從這個JSON文檔對象模型的根節點開始遍歷,也就是RootElement:
然後通過root這個JsonElement類型的對象的GetProperty方法來獲得相應的屬性,而且這個方法可以連串使用:
最後一行使用GetString方法來獲得該屬性的字元串值。
然後我們可以寫一個遞歸調用的方法來遍歷整個模型的每個屬性:
這個方法接受JsonElement類型的對象,然後對該元素的屬性進行迴圈。
如果當前屬性是另一個對象,那麼就繼續遞歸調用這個方法;
否則就輸出原始的文本。
最後調用該方法:
輸出結果為:
與json文件的內容匹配。
Utf8JsonWriter類
下麵研究一下如何寫入json文件。這裡需要使用Utf8JsonWriter類。
直接看代碼:
這個類需要傳遞的參數類型是Stream或者Buffer,也就是向Stream或Buffer裡面寫入數據。
那麼就提供一個buffer:
下麵單獨寫一個方法,來生成json數據:
參數類型是Utf8JsonWriter。通過智能提示可以看到它提供了很多用於寫入不同類型數據的方法。
寫JSON對象
現在我想寫一個json對象,那麼就從WriteStartObject()開始,然後以WriteEndObject()結束:
這樣的話,實際上我已經擁有了一個合法的json文檔。
寫屬性和值
可以分開寫屬性和值:
也可以同時把屬性和值寫出來:
顯示JSON數據
我先寫這些內容,然後在Main方法裡面調用一下:
首先需要告訴writer把它的內容flush給buffer,使用這個buffer我們可以獲得writer的輸出,這樣的話就會得到一個byte數組,然後把這個byte數組轉化為字元串,這樣就可以在控制台顯示它了:
運行一下看看效果:
沒啥太大的問題,就是格式不好看。
對輸出進行格式化
.NET Core提供了一個JsonWriterOptions類,它可以對Writer進行一些設置。
這裡對輸出進行了縮進,最後把這個options傳遞給Utf8JsonWriter的構造函數即可。
再次運行:
現在好看多了。
JsonSerializer
前面幾節的內容可能稍微有點底層,我們大部分時候可能只需要對C#的類進行串列化或者將JSON數據反串列化成C#類,在.NET Core 3.0裡面,我們可以使用JsonSerializer這個類來做這些事情。
例子:
還是使用之前用到的json數據:
然後我們需要建建立兩個類,對應這個文件:
反串列化
可以使用JsonSerializer類的Deserialize()方法對json數據反串列化。這個方法支持三種類型的輸入參數,分別是:
-
JSON數據的字元串
-
Utf8JsonReader
-
ReadOnlySpan<byte>,它裡面包含JSON數據
為了簡單一點,我直接把json文件讀取成字元串,然後傳給Deserialize方法:
然後我試圖列印出反串列化之後的一些屬性數據。但是這不會成功。因為JSON文件裡面數據的大小寫命名規範使用的是camel casing(簡單理解為首字母是小寫的),而預設情況下Deserializer會尋找Pascal casing這種規範(簡單理解為每個單詞的首字母都是大寫的)的屬性名。
格式化
為解決這個問題,就需要使用JsonSerializerOptions類:
建立該類的一個實例,設置PropertyNamingPolicy為CamelCase,然後把這個實例傳遞給Deserialize方法的第二個參數。
運行看結果:
這次就沒有問題了。
串列化
JsonSerializer也支持串列化,也就是把C#數據轉化為JSON數據:
這裡使用了相同的options。
運行結果: