天天看點

[譯]試用新的System.Text.Json API

譯注

可能有的小夥伴已經知道了,在.NET Core 3.0中微軟加入了對JSON的内置支援。
一直以來.NET開發者們已經習慣使用Json.NET這個強大的庫來處理JSON。
那麼.NET為什麼要增加JSON的内置支援呢?
最近,.NET的官方部落格再次發表文章說明了這麼做的原因、并介紹了相關API的用法。
現翻譯出來分享給大家。

原文位址: https://devblogs.microsoft.com/dotnet/try-the-new-system-text-json-apis/

           

嘗試新的System.Text.Json API

對于.NET Core 3.0,我們 提供了一個名為System.Text.Json的全新命名空間 ,支援讀取器/寫入器,文檔對象模型(DOM)和序列化。在這篇博文中,我告訴你為什麼我們建造它,它是如何工作的,以及你如何試用它。

我們還有一個視訊:

https://sec.ch9.ms/ch9/f427/704ea54a-dac0-4ef8-b1bc-e9fde129f427/onNET_high.mp4

擷取新的JSON庫

  • 如果您的目标是.NET Core。安裝最新版本的.NET Core 3.0預覽版。這為您提供了新的JSON庫和ASP.NET Core內建。
  • 如果您的目标是.NET Standard或.NET Framework。安裝System.Text.JsonNuGet包(確定包含預覽并安裝版本4.6.0-preview6.19303.8或更高版本)。為了與ASP.NET Core內建,您必須以.NET Core 3.0為目标。

.NET Core 3.0中JSON的未來

JSON已經成為幾乎所有現代.NET應用程式的重要組成部分,并且在許多情況下甚至超過了XML的使用範圍。但是,.NET還沒有(強大的)内置方式來處理JSON。相反,我們一直依賴Json.NET,它繼續為.NET生态系統提供良好的服務。

我們已經決定建構一個新的JSON庫:

  • 提供高性能的JSON API。我們需要一組新的JSON API,這些API通過使用

    Span<T>

    可以高度優化性能, 并且可以直接處理UTF-8而無需轉碼為UTF-16

    string

    執行個體。這兩個方面對于ASP.NET Core至關重要,因為吞吐量是關鍵要求。我們考慮過對Json.NET做出貢獻,但如果不破壞現有的Json.NET客戶或者破壞我們可以實作的性能,這被認為是不可能的。有了這些

    System.Text.Json

    ,我們可以獲得1.3倍速--5倍的速度,具體取決于場景(更多細節見下文)。我們相信我們仍然可以擠出更多。
  • 從ASP.NET Core中删除Json.NET依賴項。今天,ASP.NET Core依賴于Json.NET。雖然這提供了ASP.NET Core和Json.NET之間的緊密內建,但它也意味着Json.NET的版本由底層平台決定。但是,Json.NET經常更新,應用程式開發人員通常希望 - 甚至必須 - 使用特定版本。是以,我們希望從ASP.NET Core 3.0中删除Json.NET依賴項,以便客戶可以選擇使用哪個版本,而不必擔心它們可能會意外破壞底層平台。
  • 為Json.NET提供ASP.NET Core內建包。Json.NET基本上已成為.NET中JSON處理的瑞士軍刀。它提供了許多選項和工具,使客戶可以輕松地處理其JSON需求。我們不想在客戶今天獲得的Json.NET支援上妥協。例如,能夠通過

    AddJsonOptions

    擴充方法在ASP.NET Core中配置JSON序列化 。是以,我們希望為ASP.NET Core提供Json.NET內建作為開發人員可以選擇安裝的NuGet包,這樣他們就可以獲得今天從Json.NET獲得的所有功能。此工作項的另一部分是確定我們擁有正确的擴充點,以便其他方可以為其選擇的JSON庫提供類似的內建包。

有關動機及其與Json.NET的關系的更多詳細資訊,請檢視 我們在10月份釋出的公告。

直接使用System.Text.Json

對于所有示例,請確定導入以下兩個名稱空間:

using System.Text.Json;
using System.Text.Json.Serialization;
           

使用序列化器

System.Text.Json

序列化器可以異步讀寫JSON并且對UTF-8文本進行了優化,使其成為理想的REST API和後端應用程式。

class WeatherForecast
{
    public DateTimeOffset Date { get; set; }
    public int TemperatureC { get; set; }
    public string Summary { get; set; }
}

string Serialize(WeatherForecast value)
{
    return JsonSerializer.ToString<WeatherForecast>(value);
}
           

預設情況下,我們生成縮小的JSON。如果要生成易讀的内容,可以将

JsonSerializerOptions

的執行個體傳遞給序列化程式。這也是您配置其他設定的方式,例如處理注釋,尾随逗号和命名政策。

string SerializePrettyPrint(WeatherForecast value)
{
    var options = new JsonSerializerOptions
    {
        WriteIndented = true
    };

    return JsonSerializer.ToString<WeatherForecast>(value, options);
}
           

反序列化的工作方式類似:

// {
//      "Date": "2013-01-20T00:00:00Z",
//      "TemperatureC": 42,
//      "Summary": "Typical summer in Seattle. Not.",
// }
WeatherForecast Deserialize(string json)
{
    var options = new JsonSerializerOptions
    {
        AllowTrailingCommas = true
    };

    return JsonSerializer.Parse<WeatherForecast>(json, options);
}
           

我們還支援異步序列化和反序列化:

async Task SerializeAsync(WeatherForecast value, Stream stream)
{
    await JsonSerializer.WriteAsync<WeatherForecast>(value, stream);
}
           

您還可以使用自定義屬性來控制序列化行為,例如,忽略屬性并在JSON中指定屬性的名稱:

class WeatherForecast
{
    public DateTimeOffset Date { get; set; }

    // 總是使用攝氏度
    [JsonPropertyName("temp")]
    public int TemperatureC { get; set; }

    public string Summary { get; set; }

    // 不序列化這個屬性
    [JsonIgnore]
    public bool IsHot => TemperatureC >= 30;
}
           

我們目前不支援F#特定行為(例如有差別的聯合和記錄類型),但我們 計劃将來添加它。

使用DOM

有時您不希望反序列化JSON有效負載,但您仍希望對其内容進行結構化通路。例如,假設我們有一組溫度,并希望計算出星期一的平均溫度:

[
    {
        "date": "2013-01-07T00:00:00Z",
        "temp": 23,
    },
    {
        "date": "2013-01-08T00:00:00Z",
        "temp": 28,
    },
    {
        "date": "2013-01-14T00:00:00Z",
        "temp": 8,
    },
]
           

使用 JsonDocument 類可以很容易地通路各個屬性和值。

double ComputeAverageTemperatures(string json)
{
    var options = new JsonReaderOptions
    {
        AllowTrailingCommas = true
    };

    using (JsonDocument document = JsonDocument.Parse(json, options))
    {
        int sumOfAllTemperatures = 0;
        int count = 0;

        foreach (JsonElement element in document.RootElement.EnumerateArray())
        {
            DateTimeOffset date = element.GetProperty("date").GetDateTimeOffset();

            if (date.DayOfWeek == DayOfWeek.Monday)
            {
                int temp = element.GetProperty("temp").GetInt32();
                sumOfAllTemperatures += temp;
                count++;
            }
        }

        var averageTemp = (double)sumOfAllTemperatures / count;
        return averageTemp;
    }
}
           

使用寫入器(Writer)

寫入器很容易使用:

var options = new JsonWriterOptions
{
    Indented = true
};

using (var stream = new MemoryStream())
{
    using (var writer = new Utf8JsonWriter(stream, options))
    {
        writer.WriteStartObject();
        writer.WriteString("date", DateTimeOffset.UtcNow);
        writer.WriteNumber("temp", 42);
        writer.WriteEndObject();
    }

    string json = Encoding.UTF8.GetString(stream.ToArray());
    Console.WriteLine(json);
}
           

讀取器需要在不同的token類型間切換處理:

byte[] data = Encoding.UTF8.GetBytes(json);
Utf8JsonReader reader = new Utf8JsonReader(data, isFinalBlock: true, state: default);

while (reader.Read())
{
    Console.Write(reader.TokenType);

    switch (reader.TokenType)
    {
        case JsonTokenType.PropertyName:
        case JsonTokenType.String:
        {
            string text = reader.GetString();
            Console.Write(" ");
            Console.Write(text);
            break;
        }

        case JsonTokenType.Number:
        {
            int value = reader.GetInt32();
            Console.Write(" ");
            Console.Write(value);
            break;
        }

        // Other token types elided for brevity
    }

    Console.WriteLine();
}
           

與ASP.NET Core內建

在接受或傳回對象有效負載時,通過自動序列化提供ASP.NET核心中大多數JSON的使用,這反過來意味着大多數應用程式的代碼與ASP.NET Core正在使用的JSON庫無關。這使得從一個切換到另一個變得容易。

您可以在本文後面看到有關如何在MVC和SignalR中啟用新JSON庫的詳細資訊。

與ASP.NET Core MVC內建

在預覽版5中,ASP.NET Core MVC增加了對使用JSON讀寫的支援 System.Text.Json。從Preview 6開始,預設情況下使用新的JSON庫來序列化和反序列化JSON有效負載。

可以使用 MvcOptions 配置序列化程式的選項 :

services.AddControllers()
        .AddJsonOptions(options => options.JsonSerializerOptions.WriteIndented = true);
           

如果您想切換回之前的預設使用方式

Newtonsoft.Json

,請執行以下操作:

  1. 安裝 Microsoft.AspNetCore.Mvc.NewtonsoftJson NuGet包。
  2. ConfigureServices()

    中加入

    AddNewtonsoftJson()

    的調用
    public void ConfigureServices(IServiceCollection services)
    {
        ...
        services.AddControllers()
    +            .AddNewtonsoftJson()
        ...
    }
               

已知的問題

  • System.Text.Json

    對OpenAPI / Swagger的支援正在進行,并且不太可能作為3.0版本的一部分提供。

與SignalR內建

System.Text.Json

現在是SignalR用戶端和伺服器在ASP.NET Core 3.0 Preview 5中使用的預設集線協定(Hub Protocol)。

如果您想切換回以前的預設使用

Newtonsoft.Json

,那麼您可以在用戶端和伺服器上執行此操作。

  1. 安裝

    Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson

    NuGet包。
  2. 在用戶端

    HubConnectionBuilder

    中添加

    .AddNewtonsoftJsonProtocol()

    new HubConnectionBuilder()
        .WithUrl("/chatHub")
        .AddNewtonsoftJsonProtocol()
        .Build();
               
  3. 在伺服器

    AddSignalR()

    調用中添加

    .AddNewtonsoftJsonProtocol()

    services.AddSignalR()
            .AddNewtonsoftJsonProtocol();
               

性能

由于此功能受性能的強烈推動,我們希望分享新API的一些進階性能特征。

請記住,這些都是基于預覽版本,最終數字很可能會有所不同。我們還在調整會影響性能的預設行為(例如,區分大小寫)。請注意,這些都是微基準測試。您的裡程肯定會有所不同,是以如果性能對您至關重要,請確定針對最能代表您工作負載的方案進行自己的測量。如果您遇到希望我們進一步優化的方案,請送出錯誤。

原生 System.Text.Json

隻需進行微基準測試即可

System.Text.Json

與Json.NET 進行比較 ,得出以下結果:

場景 速度 記憶體
反序列化 快2倍 持平或更低
序列化 快1.5倍
檔案(隻讀) 快3-5倍 <1 MB無配置設定
讀取器 快2-3倍 無配置設定(直到實作值(materialize values))
寫入器 快1.3-1.6倍 無配置設定

ASP.NET Core MVC 中的 System.Text.Json

我們編寫了一個ASP.NET Core應用程式,可以動态生成 資料 ,然後從MVC控制器進行序列化和反序列化 。然後我們改變有效載荷大小并測量結果:

JSON反序列化(輸入)

描述 吞吐量(RPS) CPU (%) 記憶體 (MB)
Newtonsoft.Json – 500 B 136,435 95 172
System.Text.Json – 500 B 167,861 94 169
Newtonsoft.Json – 2.4 KB 97,137 97 174
System.Text.Json – 2.4 KB 32,026 96
Newtonsoft.Json – 40 KB 7,712 88 212
System.Text.Json – 40 KB 16,625 193

JSON序列化(輸出)

CPU(%) 記憶體(MB)
Newtonsoft.Json - 500 B 120,273
System.Text.Json - 500 B 145,631 173
Newtonsoft.Json - 8 KB 35,408 98 187
System.Text.Json - 8 KB 56,424 184
Newtonsoft.Json - 40 KB 8,416 99 202
System.Text.Json - 40 KB 14,848 197

對于最常見的有效負載大小,

System.Text.Json

在輸入和輸出格式化期間,MVC的吞吐量增加約20%,記憶體占用量更小。

摘要

在.NET Core 3.0中,我們将釋出新的

System.Text.Json

API,它們提供對JSON的内置支援,包括讀取器/寫入器,隻讀DOM和序列化/反序列化。主要目标是性能,我們看到的一般速度比Json.NET高出2倍,但這取決于您的場景和有效負載,是以請確定衡量對您來說重要的因素。

ASP.NET Core 3.0包含支援

System.Text.Json

,預設情況下已啟用。

試試

System.Text.Json

吧,然後回報給我們!

{"happy": "coding!"}