共用方式為


從 Newtonsoft.Json 移轉至 System.Text.Json

本文顯示如何從 Newtonsoft.Json 移轉至 System.Text.Json

System.Text.Json 命名空間提供序列化為 JavaScript 物件標記法 (JSON) 以及從 JSON 還原序列化的功能。 此 System.Text.Json 程式庫包含在 .NET Core 3.1 及更新版本的執行階段中。 對於其他目標 Framework,請安裝 System.Text.Json NuGet 套件。 該套件支援下列版本:

  • .NET Standard 2.0 及更新版本
  • .NET framework 4.6.2 及更新版本
  • .NET Core 2.0、2.1 與 2.2

System.Text.Json 主要著重於效能、安全性與標準合規性。 其預設行為有一些關鍵差異,且具有 Newtonsoft.Json 的同位功能。 在某些案例中,System.Text.Json 目前沒有內建功能,但具有建議的因應措施。 除此以外的其他案例,建議的因應措施並不適用。

System.Text.Json 團隊正在投入資源,以新增最常收到要求的功能。 若應用程式相依於某個遺漏的功能,請考慮在 dotnet/runtime GitHub 存放庫中提出問題,以了解是否可以新增此案例的支援。

本文大部分是關於如何使用 JsonSerializer API 的內容,但也包含如何使用 JsonDocument (表示文件物件模型,或稱 DOM)、Utf8JsonReaderUtf8JsonWriter 類型的指導。

在 Visual Basic 中,您無法使用 Utf8JsonReader,這也表示您無法撰寫自訂轉換器。 此處顯示的大部分因應措施都需要您撰寫自訂轉換器。 您可以使用 C# 撰寫自訂轉換器,並在 Visual Basic 專案中加以註冊。 如需詳細資訊,請參閱 Visual Basic 支援

差異資料表

下列資料表列出 Newtonsoft.Json 功能與 System.Text.Json 對應項。 對應項的分類如下:

  • ✔️ 內建功能即支援。 從 System.Text.Json 取得類似的行為,可能需要使用屬性或全域選項。
  • ⚠ 不支援,但有因應措施。 因應措施為自訂轉換器,這可能無法提供具有 Newtonsoft.Json 功能的完整同位項目。 針對其中某些部分,範例程式碼會以範例的形式提供。 若您依賴這些 Newtonsoft.Json 功能,則移轉會需要修改 .NET 物件模型或其他程式碼變更。
  • ❌ 不支援,且因應措施不適用或沒有因應措施。 若您依賴這些 Newtonsoft.Json 功能,則無法在沒有重大變更的情況下進行移轉。
Newtonsoft.Json 功能 System.Text.Json equivalent
根據預設,不區分大小寫的還原序列化 ✔️ PropertyNameCaseInsensitive 全域設定
駝峰式大小寫屬性名稱 ✔️ PropertyNamingPolicy 全域設定
蛇形命名法屬性名稱 ✔️ 蛇形命名原則
最小字元逸出 ✔️ 嚴格的字元逸出規定,可設定
NullValueHandling.Ignore 全域設定 ✔️ DefaultIgnoreCondition 全域選項
允許註解 ✔️ ReadCommentHandling 全域設定
允許尾端逗號 ✔️ AllowTrailingCommas 全域設定
自訂轉換器註冊 ✔️ 優先順序不同
預設的深度上限為 64,可設定 ✔️ 預設的深度上限為 64,可設定
PreserveReferencesHandling 全域設定 ✔️ ReferenceHandling 全域設定
將引號中的數字序列化或還原序列化 ✔️ NumberHandling 全域設定、[JsonNumberHandling] 屬性
還原序列化為不可變類別和結構 ✔️ JsonConstructor、C# 9 記錄
支援欄位 ✔️ IncludeFields 全域設定、[JsonInclude] 屬性
DefaultValueHandling 全域設定 ✔️ DefaultIgnoreCondition 全域設定
[JsonProperty] 上的 NullValueHandling 設定 ✔️ JsonIgnore 屬性
[JsonProperty] 上的 DefaultValueHandling 設定 ✔️ JsonIgnore 屬性
使用非字串索引鍵將 Dictionary 還原序列化 ✔️ 支援
支援非公用屬性 setter 與 getter ✔️ JsonInclude 屬性
[JsonConstructor] 屬性 ✔️ [JsonConstructor] 屬性
ReferenceLoopHandling 全域設定 ✔️ ReferenceHandling 全域設定
回撥 ✔️ 回呼
NAN、Infinity、-Infinity ✔️ 支援
[JsonProperty] 屬性上的 Required 設定 ✔️ [JsonRequired] 屬性與 C# 必要修飾詞
DefaultContractResolver 可忽略屬性 ✔️ DefaultJsonTypeInfoResolver 類別
多型序列化 ✔️ [JsonDerivedType] 屬性
多型還原序列化 ✔️ [JsonDerivedType] 屬性上的類型鑑別子
還原序列化字串列舉值 ✔️ 還原序列化字串列舉值
MissingMemberHandling 全域設定 ✔️ 處理遺漏的成員
在沒有 setter 的情況下填入屬性 ✔️ 在沒有 setter 的情況下填入屬性
ObjectCreationHandling 全域設定 ✔️ 重複使用而非取代屬性
支援廣泛的類型 ⚠️ 部分類型需要自訂轉換器
將推斷的類型還原序列化為 object 屬性 ️⚠ 不支援、因應措施、範例
將 JSON null 常值還原序列化為不可為 Null 的實值型別 ️⚠ 不支援、因應措施、範例
DateTimeZoneHandlingDateFormatString 設定 ️⚠ 不支援、因應措施、範例
JsonConvert.PopulateObject 方法 ⚠️ 不支援、因應措施
支援 System.Runtime.Serialization 屬性 ️⚠ 不支援、因應措施、範例
JsonObjectAttribute ⚠️ 不支援、因應措施
允許沒有引號的屬性名稱 設計不支援
允許字串值周圍的單引號 設計不支援
允許字串屬性的非字串 JSON 值 設計不支援
TypeNameHandling.All 全域設定 設計不支援
支援 JsonPath 查詢 不支援
可設定的限制 不支援

這不是 Newtonsoft.Json 功能的完整清單。 此清單包含 GitHub 問題StackOverflow 文章中要求的許多案例。 若您針對此處列出的其中一個案例實作因應措施,但目前沒有範例程式碼,且您想要共用解決方案,請在此頁面底部的 [意見反應] 區段中選取 [此頁面]。 這會在此文件的 GitHub 存放庫中建立問題,並列在此頁面的 [意見反應] 區段中。

預設行為的差異

根據預設,System.Text.Json 為嚴格,並會避免代表呼叫者進行任何猜測或解譯,以強調具決定性行為。 程式庫刻意以此種方式設計,以獲得效能與安全性。 根據預設,Newtonsoft.Json 很彈性。 這般設計的基本差異存在於下列預設行為的許多特定差異中。

不區分大小寫的還原序列化

根據預設,在還原序列化期間,Newtonsoft.Json 會執行不區分大小寫的屬性名稱比對。 System.Text.Json 預設會區分大小寫,因為其執行完全相符的比對,所以可提供更佳的效能。 如需如何執行不區分大小寫比對的資訊,請參閱不區分大小寫的屬性比對

若您正在藉由使用 ASP.NET Core 來間接使用 System.Text.Json,則不需要執行任何動作,即可取得類似 Newtonsoft.Json 的行為。 當 ASP.NET Core 使用 System.Text.Json 時,其會指定使用駝峰式大小寫屬性名稱與不區分大小寫比對的設定。

根據預設,ASP.NET Core 也會啟用還原序列化的引號數字

最小字元逸出

在序列化期間,Newtonsoft.Json 對於讓字元在不逸出的情況下通過,標準相對寬鬆。 也就是說,其不會以 \uxxxx 取代字元,其中 xxxx 是字元的字碼指標。 其會在字元之前發出 \ 來進行逸出 (例如," 變成 \")。 根據預設,System.Text.Json 會逸出更多字元,以便針對跨網站指令碼 (XSS) 或資訊洩漏攻擊提供深層防禦保護,並使用六個字元順序來執行此動作。 根據預設,System.Text.Json 會逸出所有非 ASCII 字元;因此,若您在 Newtonsoft.Json 中使用 StringEscapeHandling.EscapeNonAscii,則不需要執行任何動作。 根據預設,System.Text.Json 也會逸出 HTML 敏感性字元。 如需如何覆寫預設 System.Text.Json 行為的資訊,請參閱自訂字元編碼

註解

根據預設,在還原序列化期間,Newtonsoft.Json 會忽略 JSON 中的註解。 因為 RFC 8259 規格不包含註解的例外狀況,所以 System.Text.Json 預設會擲回這些例外狀況。 如需如何允許註解的資訊,請參閱允許註解與尾端逗號

尾端逗號

根據預設,在還原序列化期間,Newtonsoft.Json 會忽略尾端逗號。 其也會忽略多個尾端逗號 (例如 [{"Color":"Red"},{"Color":"Green"},,])。 因為 RFC 8259 規格不允許尾端逗號的例外狀況,所以 System.Text.Json 預設會擲回這些例外狀況。 如需如何使 System.Text.Json 接受尾端逗號的資訊,請參閱允許註解與尾端逗號。 不允許多個尾端逗號。

轉換器註冊優先順序

自訂轉換器的 Newtonsoft.Json 註冊優先順序如下:

  • 屬性 (property) 上的屬性 (attribute)
  • 類型上的屬性
  • 轉換器 (英文) 集合

此順序表示 Converters 集合中的自訂轉換器,是由在類型層級中套用屬性所註冊的轉換器所覆寫。 這兩個註冊都會由屬性 (property) 層級的屬性 (attribute) 所覆寫。

自訂轉換器的 System.Text.Json 註冊優先順序有所不同:

  • 屬性 (property) 上的屬性 (attribute)
  • Converters 集合
  • 類型上的屬性

此處差異在於 Converters 集合中的自訂轉換器會覆寫類型層級的屬性。 此優先順序背後的意圖是讓執行階段變更覆寫設計階段選擇。 無法變更優先順序。

如需自訂轉換器註冊的詳細資訊,請參閱註冊自訂轉換器

深度上限

根據預設,Newtonsoft.Json 的最新版本深度上限為 64。 System.Text.Json 也有 64 的預設限制,且可藉由設定 JsonSerializerOptions.MaxDepth 來設定。

若您藉由使用 ASP.NET Core 來間接使用 System.Text.Json,則預設的深度上限為 32。 預設值與模型繫結相同,且是在 JsonOptions 類別 (英文) 中設定。

JSON 字串 (屬性名稱與字串值)

在還原序列化期間,Newtonsoft.Json 接受以雙引號、單引號或沒有引號括住的屬性名稱。 其會接受以雙引號或單引號括住的字串值。 例如,Newtonsoft.Json 會接受下列 JSON:

{
  "name1": "value",
  'name2': "value",
  name3: 'value'
}

因為 RFC 8259 規格需要置於雙引號內的屬性名稱與字串值,且是唯一視為有效 JSON 的格式,所以 System.Text.Json 只接受該格式。

以單引號括住的值會產生具有下列訊息的 JsonException

''' is an invalid start of a value.

字串屬性的非字串值

Newtonsoft.Json 會接受非字串值 (例如數字或常值 truefalse),以便還原序列化為類型字串的屬性。 下列是成功將 Newtonsoft.Json 還原序列化為下列類別的 JSON 範例:

{
  "String1": 1,
  "String2": true,
  "String3": false
}
public class ExampleClass
{
    public string String1 { get; set; }
    public string String2 { get; set; }
    public string String3 { get; set; }
}

System.Text.Json 不會將非字串值還原序列化為字串屬性。 針對字串欄位收到的非字串值,會產生 JsonException 並顯示下列訊息:

The JSON value could not be converted to System.String.

使用 JsonSerializer 的案例

內建功能不支援下列部分案例,但可能會有因應措施。 因應措施為自訂轉換器,這可能無法提供具有 Newtonsoft.Json 功能的完整同位項目。 針對其中某些部分,範例程式碼會以範例的形式提供。 若您依賴這些 Newtonsoft.Json 功能,則移轉會需要修改 .NET 物件模型或其他程式碼變更。

在下列部分案例中,因應措施不適用或沒有因應措施。 若您依賴這些 Newtonsoft.Json 功能,則無法在沒有重大變更的情況下進行移轉。

允許或撰寫引號中的數字

Newtonsoft.Json 可將 JSON 字串所代表的數字 (以引號括住) 序列化或還原序列化。 例如,其可以接受:{"DegreesCelsius":"23"},而不是 {"DegreesCelsius":23}。 若要在 System.Text.Json 中啟用該行為,請將 JsonSerializerOptions.NumberHandling 設定為 WriteAsStringAllowReadingFromString,或使用 [JsonNumberHandling] 屬性。

若您正在藉由使用 ASP.NET Core 來間接使用 System.Text.Json,則不需要執行任何動作,即可取得類似 Newtonsoft.Json 的行為。 ASP.NET Core 使用 System.Text.Json 時,會指定 Web 預設值,且 Web 預設值允許引號數字。

如需詳細資訊,請參閱允許或撰寫引號中的數字

指定還原序列化時要使用的建構函式

Newtonsoft.Json [JsonConstructor] 屬性可讓您指定要在還原序列化為 POCO 時呼叫的建構函式。

System.Text.Json 也有 [JsonConstructor] 屬性。 如需詳細資訊,請參閱不可變的類型與記錄

有條件地忽略屬性

Newtonsoft.Json 有數種方式可以有條件地忽略序列化或還原序列化的屬性:

  • DefaultContractResolver 可讓您根據任意準則來選取要包含或忽略的屬性。
  • JsonSerializerSettings 上的 NullValueHandlingDefaultValueHandling 設定,可讓您指定應忽略的所有 null 值或預設值屬性。
  • [JsonProperty] 屬性 (attribute) 上的 NullValueHandlingDefaultValueHandling 設定,可讓您指定當設定為 Null 或預設值時應該忽略的個別屬性 (property)。

System.Text.Json 會提供下列在序列化時忽略屬性或欄位的方式:

此外,在 .NET 7 及更新版本中,您可以自訂 JSON 合約,以根據任意準則忽略屬性。 如需詳細資訊,請參閱自訂合約

公用與非公用欄位

Newtonsoft.Json 可將欄位與屬性序列化及還原序列化。

System.Text.Json 中,使用 JsonSerializerOptions.IncludeFields 全域設定或 [JsonInclude] 屬性,在序列化或還原序列化時包含公用欄位。 如需範例,請參閱包含欄位

保留物件參考與處理迴圈

根據預設,Newtonsoft.Json 會依據值進行序列化。 例如,若物件包含兩個屬性,且屬性中包含相同 Person 物件的參考,則該 Person 物件的屬性值就會在 JSON 中重複。

Newtonsoft.JsonJsonSerializerSettings 上具有 PreserveReferencesHandling 設定,可讓您以傳址方式序列化:

  • 識別碼中繼資料會新增至為第一個 Person 物件所建立的 JSON。
  • 為第二個 Person 物件建立的 JSON 包含該識別碼的參考,而不是屬性值。

Newtonsoft.Json 也有一個 ReferenceLoopHandling 設定,可讓您忽略循環參考,而不是擲回例外狀況。

若要在 System.Text.Json 中保留參考及處理循環參考,請將 JsonSerializerOptions.ReferenceHandler 設定為 PreserveReferenceHandler.Preserve 設定相當於 Newtonsoft.Json 中的 PreserveReferencesHandling = PreserveReferencesHandling.All

ReferenceHandler.IgnoreCycles 選項具有類似於 Newtonsoft.JsonReferenceLoopHandling.Ignore 的行為。 其中一個差異在於,System.Text.Json 實作會以 null JSON 權杖取代參考迴圈,而不是忽略物件參考。 如需詳細資訊,請參閱忽略循環參考

如同 Newtonsoft.JsonReferenceResolverSystem.Text.Json.Serialization.ReferenceResolver 類別會定義在序列化與還原序列化上保留參考的行為。 建立衍生類別以指定自訂行為。 如需範例,請參閱 GuidReferenceResolver (英文)。

不支援某些相關的 Newtonsoft.Json 功能:

如需詳細資訊,請參閱保留參考及處理循環參考

具有非字串索引鍵的字典

Newtonsoft.JsonSystem.Text.Json 都支援 Dictionary<TKey, TValue> 類型的集合。 然而,在 System.Text.Json 中,TKey 必須是基本類型,而不是自訂類型。 如需詳細資訊,請參閱支援的索引鍵類型

警告

還原序列化為 Dictionary<TKey, TValue>,其中 TKey 類型為 string 以外的任何項目可能會造成取用應用程式中的安全性弱點。 如需詳細資訊,請參閱 dotnet/runtime#4761 (英文)。

沒有內建支援的類型

System.Text.Json 不提供下列類型的內建支援:

自訂轉換器可以針對沒有內建支援的類型進行實作。

多型序列化

Newtonsoft.Json 會自動執行多型序列化。 自 .NET 7 起,System.Text.Json 支援透過 JsonDerivedTypeAttribute 屬性進行多型序列化。 如需詳細資訊,請參閱序列化衍生類別的屬性

多型還原序列化

Newtonsoft.Json 具有 TypeNameHandling 設定,會在序列化時將類型名稱中繼資料新增至 JSON。 其會在還原序列化時,使用中繼資料執行多型還原序列化。 自 .NET 7 起,System.Text.Json 依賴類型鑑別子資訊來執行多型還原序列化。 此中繼資料會以 JSON 發出,然後在還原序列化期間用於判斷是否要還原序列化為基底類型或衍生類型。 如需詳細資訊,請參閱序列化衍生類別的屬性

若要支援多型還原序列化,請建立如同如何撰寫自訂轉換器中範例的轉換器。

還原序列化字串列舉值

根據預設,System.Text.Json 不支援還原序列化字串列舉值,而 Newtonsoft.Json 則支援。 例如,下列程式碼會擲回 JsonException

string json = "{ \"Text\": \"Hello\", \"Enum\": \"Two\" }";
var _ = JsonSerializer.Deserialize<MyObj>(json); // Throws exception.

class MyObj
{
    public string Text { get; set; } = "";
    public MyEnum Enum { get; set; }
}

enum MyEnum
{
    One,
    Two,
    Three
}

不過,您可以使用 JsonStringEnumConverter 轉換器來啟用字串列舉值的還原序列化。 如需詳細資訊,請參閱以字串表示列舉

還原序列化物件屬性

Newtonsoft.Json 還原序列化為 Object 時,其會:

  • 推斷 JSON 承載中基本值的類型 (非 null),並以 Boxed 物件形式傳回預存 stringlongdoublebooleanDateTime。 「基本值」為單一 JSON 值,例如 JSON 數字、字串、truefalsenull
  • 傳回 JObjectJArray,以用於 JSON 承載中的複雜值。 「複雜值」是大括號內的 JSON 機碼值組集合 ({}),或括號中的值清單 ([])。 大括號或括號中的屬性與值可以有額外的屬性和值。
  • 當承載具有 null JSON 常值時,會傳回 Null 參考。

每當還原序列化為 Object 時,System.Text.Json 會儲存基本值與複雜值的 Boxed JsonElement,例如:

  • object 屬性。
  • object 字典值。
  • object 陣列值。
  • object

然而,當承載本身中有 null JSON 常值時,System.Text.Json 會將 null 視為與 Newtonsoft.Json 相同,並傳回 Null 參考。

若要實作 object 屬性的型別推斷,請建立如同如何撰寫自訂轉換器中範例的轉換器。

將 Null 還原序列化為不可為 Null 的類型

Newtonsoft.Json 在下列案例中不會擲回例外狀況:

  • NullValueHandling 設定為 Ignore,且
  • 在還原序列化期間,JSON 包含不可為 Null 之實值型別的 null 值。

在相同的案例中,System.Text.Json 會擲回例外狀況。 (System.Text.Json 中的對應 Null 處理設定為 JsonSerializerOptions.IgnoreNullValues = true。)

若您擁有目標類型,則最佳因應措施是讓有問題的屬性變成可為 Null (例如,將 int 變更為 int?)。

另一個因應措施是建立類型的轉換器,例如下列處理 DateTimeOffset 類型 null 值的範例:

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

namespace SystemTextJsonSamples
{
    public class DateTimeOffsetNullHandlingConverter : JsonConverter<DateTimeOffset>
    {
        public override DateTimeOffset Read(
            ref Utf8JsonReader reader,
            Type typeToConvert,
            JsonSerializerOptions options) =>
            reader.TokenType == JsonTokenType.Null
                ? default
                : reader.GetDateTimeOffset();

        public override void Write(
            Utf8JsonWriter writer,
            DateTimeOffset dateTimeValue,
            JsonSerializerOptions options) =>
            writer.WriteStringValue(dateTimeValue);
    }
}

使用屬性 (property) 上的屬性 (attribute),或將轉換器新增Converters 集合,來註冊此自訂轉換器。

注意:上述轉換器處理 null 值的方式與 Newtonsoft.Json 處理用於指定預設值之 POCO 的方法不同。 例如,假設下列程式碼表示您的目標物件:

public class WeatherForecastWithDefault
{
    public WeatherForecastWithDefault()
    {
        Date = DateTimeOffset.Parse("2001-01-01");
        Summary = "No summary";
    }
    public DateTimeOffset Date { get; set; }
    public int TemperatureCelsius { get; set; }
    public string Summary { get; set; }
}

且假設下列 JSON 是使用上述轉換器來還原序列化:

{
  "Date": null,
  "TemperatureCelsius": 25,
  "Summary": null
}

還原序列化之後,Date 屬性會有 1/1/0001 (default(DateTimeOffset)),也就是覆寫建構函式中設定的值。 假設有相同的 POCO 與 JSON,Newtonsoft.Json 還原序列化會在 Date 屬性中保留 1/1/2001。

還原序列化為不可變類別和結構

因為 Newtonsoft.Json 可以使用具有參數的建構函式,所以可以還原序列化為不可變類別和結構。

System.Text.Json 中,使用 [JsonConstructor] 屬性來指定使用參數化建構函式。 C# 9 中的記錄也為不可變,且其支援作為還原序列化目標。 如需詳細資訊,請參閱不可變的類型與記錄

必要屬性

Newtonsoft.Json 中,您可以藉由在 [JsonProperty] 屬性 (attribute) 上設定 Required 來指定屬性 (property) 為必要項目。 若 JSON 中未收到標記為必要屬性的值,則 Newtonsoft.Json 會擲回例外狀況。

自 .NET 7 起,您可以使用 C# required 修飾詞或 JsonRequiredAttribute 必要屬性 (property) 上的屬性 (attribute)。 若 JSON 承載未包含標記屬性的值,則 System.Text.Json 會擲回例外狀況。 如需詳細資訊,請參閱必要權限

指定日期格式

Newtonsoft.Json 提供數種方式來控制 DateTimeDateTimeOffset 類型屬性的序列化及還原序列化方式:

  • DateTimeZoneHandling 設定可用於將所有 DateTime 值序列化為 UTC 日期。
  • DateFormatString 設定與 DateTime 轉換器可用於自訂日期字串的格式。

System.Text.Json 支援 ISO 8601-1:2019,包括 RFC 3339 設定檔。 此格式受到廣泛採用,是一個明確的格式,可精確地進行來回行程。 若要使用任何其他格式,請建立自訂轉換器。 例如,下列轉換器會將使用包含或不包含時區位移之 UNIX Epoch 格式的 JSON 序列化及還原序列化 (例如 /Date(1590863400000-0700)//Date(1590863400000)/ 等值):

sealed class UnixEpochDateTimeOffsetConverter : JsonConverter<DateTimeOffset>
{
    static readonly DateTimeOffset s_epoch = new(1970, 1, 1, 0, 0, 0, TimeSpan.Zero);
    static readonly Regex s_regex = new("^/Date\\(([+-]*\\d+)([+-])(\\d{2})(\\d{2})\\)/$", RegexOptions.CultureInvariant);

    public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        string formatted = reader.GetString()!;
        Match match = s_regex.Match(formatted);

        if (
                !match.Success
                || !long.TryParse(match.Groups[1].Value, System.Globalization.NumberStyles.Integer, CultureInfo.InvariantCulture, out long unixTime)
                || !int.TryParse(match.Groups[3].Value, System.Globalization.NumberStyles.Integer, CultureInfo.InvariantCulture, out int hours)
                || !int.TryParse(match.Groups[4].Value, System.Globalization.NumberStyles.Integer, CultureInfo.InvariantCulture, out int minutes))
        {
            throw new JsonException();
        }

        int sign = match.Groups[2].Value[0] == '+' ? 1 : -1;
        TimeSpan utcOffset = new(hours * sign, minutes * sign, 0);

        return s_epoch.AddMilliseconds(unixTime).ToOffset(utcOffset);
    }

    public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options)
    {
        long unixTime = Convert.ToInt64((value - s_epoch).TotalMilliseconds);
        TimeSpan utcOffset = value.Offset;

        string formatted = string.Create(CultureInfo.InvariantCulture, $"/Date({unixTime}{(utcOffset >= TimeSpan.Zero ? "+" : "-")}{utcOffset:hhmm})/");

        writer.WriteStringValue(formatted);
    }
}
sealed class UnixEpochDateTimeConverter : JsonConverter<DateTime>
{
    static readonly DateTime s_epoch = new(1970, 1, 1, 0, 0, 0);
    static readonly Regex s_regex = new("^/Date\\(([+-]*\\d+)\\)/$", RegexOptions.CultureInvariant);

    public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        string formatted = reader.GetString()!;
        Match match = s_regex.Match(formatted);

        if (
                !match.Success
                || !long.TryParse(match.Groups[1].Value, System.Globalization.NumberStyles.Integer, CultureInfo.InvariantCulture, out long unixTime))
        {
            throw new JsonException();
        }

        return s_epoch.AddMilliseconds(unixTime);
    }

    public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
    {
        long unixTime = Convert.ToInt64((value - s_epoch).TotalMilliseconds);

        string formatted = string.Create(CultureInfo.InvariantCulture, $"/Date({unixTime})/");
        writer.WriteStringValue(formatted);
    }
}

如需詳細資訊,請參閱 System.Text.Json 中的 DateTime 與 DateTimeOffset 支援

回撥

Newtonsoft.Json 可讓您在序列化或還原序列化流程中的數個時間點執行自訂程式碼:

  • OnDeserializing (開始將物件還原序列化時)
  • OnDeserialized (完成將物件還原序列化時)
  • OnSerializing (開始將物件序列化時)
  • OnSerialized (完成將物件序列化時)

System.Text.Json 會在序列化與還原序列化期間公開相同的通知。 若要使用這些通知,請從 System.Text.Json.Serialization 命名空間實作下列一或多個介面:

下列範例會檢查 null 屬性,並在序列化與還原序列化的開頭與結尾撰寫訊息:

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

namespace Callbacks
{
    public class WeatherForecast : 
        IJsonOnDeserializing, IJsonOnDeserialized, 
        IJsonOnSerializing, IJsonOnSerialized
    {
        public DateTime Date { get; set; }
        public int TemperatureCelsius { get; set; }
        public string? Summary { get; set; }

        void IJsonOnDeserializing.OnDeserializing() => Console.WriteLine("\nBegin deserializing");
        void IJsonOnDeserialized.OnDeserialized()
        {
            Validate();
            Console.WriteLine("Finished deserializing");
        }
        void IJsonOnSerializing.OnSerializing()
        {
            Console.WriteLine("Begin serializing");
            Validate();
        }
        void IJsonOnSerialized.OnSerialized() => Console.WriteLine("Finished serializing");

        private void Validate()
        {
            if (Summary is null)
            {
                Console.WriteLine("The 'Summary' property is 'null'.");
            }
        }
    }

    public class Program
    {
        public static void Main()
        {
            var weatherForecast = new WeatherForecast
            {
                Date = DateTime.Parse("2019-08-01"),
                TemperatureCelsius = 25,
            };

            string jsonString = JsonSerializer.Serialize(weatherForecast);
            Console.WriteLine(jsonString);

            weatherForecast = JsonSerializer.Deserialize<WeatherForecast>(jsonString);
            Console.WriteLine($"Date={weatherForecast?.Date}");
            Console.WriteLine($"TemperatureCelsius={weatherForecast?.TemperatureCelsius}");
            Console.WriteLine($"Summary={weatherForecast?.Summary}");
        }
    }
}
// output:
//Begin serializing
//The 'Summary' property is 'null'.
//Finished serializing
//{"Date":"2019-08-01T00:00:00","TemperatureCelsius":25,"Summary":null}

//Begin deserializing
//The 'Summary' property is 'null'.
//Finished deserializing
//Date=8/1/2019 12:00:00 AM
//TemperatureCelsius = 25
//Summary=

OnDeserializing 程式碼無法存取新的 POCO 執行個體。 若要在還原序列化開始時操作新的 POCO 執行個體,請將該程式碼放在 POCO 建構函式中。

非公用屬性 setter 與 getter

Newtonsoft.Json 可以透過 JsonProperty 屬性 (attribute) 使用私用與內部屬性 (property) setter 與 getter。

System.Text.Json 可以透過 [JsonInclude] 屬性 (attribute) 支援私用與內部屬性 (property) setter 與 getter。 如需範例程式碼,請參閱非公用屬性存取子

填入現有的物件

Newtonsoft.Json 中的 JsonConvert.PopulateObject 方法會將 JSON 文件還原序列化為類別的現有執行個體,而不是建立新的執行個體。 System.Text.Json 一律使用預設公用無參數建構函式來建立目標類型的新執行個體。 自訂轉換器可還原序列化為現有的執行個體。

重複使用而非取代屬性

從 .NET 8 開始,System.Text.Json 支援重複使用初始化的屬性,而不是取代它們。 行為具有一些差異,您可以在 API 提案中閱讀。

如需詳細資訊,請參閱填入初始化的屬性

在沒有 setter 的情況下填入屬性

從 .NET 8 開始,System.Text.Json 支援填入屬性,包括沒有 setter 的屬性。 如需詳細資訊,請參閱填入初始化的屬性

蛇形命名法原則

System.Text.Json 包含蛇形蛇形命名法的內建命名原則。 不過,針對某些輸入與 Newtonsoft.Json 具有行為差異。 下表顯示使用 JsonNamingPolicy.SnakeCaseLower 原則轉換輸入時的一些差異。

輸入 Newtonsoft.Json 結果 System.Text.Json 結果
"AB1" "a_b1" "ab1"
"SHA512Managed" "sh_a512_managed" "sha512_managed"
"abc123DEF456" "abc123_de_f456" "abc123_def456"
"KEBAB-CASE" "keba_b-_case" "kebab-case"

System.Runtime.Serialization 屬性

System.Runtime.Serialization 屬性,例如 DataContractAttributeDataMemberAttributeIgnoreDataMemberAttribute,可讓您定義資料合約。 「 資料合約 」(Data Contract) 是服務與用戶端之間的正式合約,其中會抽象地描述要交換的資料。 資料合約會精確地定義要序列化哪些屬性以進行交換。

System.Text.Json 沒有這些屬性的內建支援。 不過,從 .NET 7 開始,您可以使用自訂類型解析程式來新增支援。 如需範例,請參閱 ZCS.DataContractResolver

八進位數字

Newtonsoft.Json 會將前置字元為零的數字視為八進位數字。 因為 RFC 8259 規格不允許前置字元為零,所以 System.Text.Json 不允許前置字元為零。

處理遺漏的成員

如果要還原序列化的 JSON 包含目標類型中遺漏的屬性,則 Newtonsoft.Json 可以設定為擲回例外狀況。 根據預設,除非您使用 [JsonExtensionData] 屬性,否則 System.Text.Json 會忽略 JSON 中的額外屬性。

在 .NET 8 和更新版本中,您可以使用下列其中一種方法來設定是否跳過或不允許未對應 JSON 屬性的喜好設定:

JsonObjectAttribute

Newtonsoft.Json 具有可在類型層級JsonObjectAttribute套用的屬性 ,以控制哪些成員已序列化、如何處理 null 值,以及是否需要所有成員。 System.Text.Json 沒有可在類型上套用的對等屬性。 對於某些行為,例如 null 值處理,您可以在全域 JsonSerializerOptions 或每個屬性上個別設定相同的行為。

請考慮使用 Newtonsoft.Json.JsonObjectAttribute 來指定應忽略所有 null 屬性的下列範例:

[JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)]
public class Person { ... }

在 System.Text.Json 中,您可以設定所有類型和屬性的行為:

JsonSerializerOptions options = new()
{
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};

string json = JsonSerializer.Serialize<Person>(person, options);

或者,您可以個別設定每個屬性的行為:

public class Person
{
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    public string? Name { get; set; }

    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    public int? Age { get; set; }
}

接下來,請考慮下列使用 Newtonsoft.Json.JsonObjectAttribute 來指定 JSON 中必須存在所有成員屬性的範例:

[JsonObject(ItemRequired = Required.Always)]
public class Person { ... }

您可以將 C# required 修飾元或 JsonRequiredAttribute 新增至每個屬性,以達到 System.Text.Json 中相同的行為。 如需詳細資訊,請參閱必要權限

public class Person
{
    [JsonRequired]
    public string? Name { get; set; }

    public required int? Age { get; set; }
}

TraceWriter

Newtonsoft.Json 可讓您使用 TraceWriter 進行偵錯,以檢視序列化或還原序列化所產生的記錄。 System.Text.Json 不會執行記錄。

JsonDocument 及 JsonElement 與 JToken (例如 JObject、JArray) 的比較

System.Text.Json.JsonDocument 提供從現有 JSON 承載剖析及建置唯讀文件物件模組 (DOM) 的能力。 DOM 提供 JSON 承載中的資料隨機存取。 撰寫承載的 JSON 元素可以透過 JsonElement 類型存取。 此 JsonElement 類型提供 API,可將 JSON 文字轉換成一般 .NET 類型。 JsonDocument 會公開 RootElement 屬性。

自 .NET 6 起,您可以使用 System.Text.Json.Nodes 命名空間中的 JsonNode 類型與其他類型,從現有的 JSON 承載剖析及建置可變的 DOM。 如需詳細資訊,請參閱使用 JsonNode

JsonDocument 為 IDisposable

JsonDocument 會將資料的記憶體內部檢視建置到集區緩衝區中。 因此,JsonDocument 類型不同於 Newtonsoft.JsonJObjectJArray,其會實作 IDisposable,且必須用於 using 區塊中。 如需詳細資訊,請參閱 JsonDocument 為 IDisposable

JsonDocument 為唯讀

System.Text.Json DOM 無法新增、移除或修改 JSON 元素。 此設計方式是為了取得效能,並減少剖析常見 JSON 承載大小的配置 (也就是 < 1 MB)。

JsonElement 為聯集 struct

JsonDocument 會將 RootElement 公開為類型 JsonElement 的屬性,這是包含任何 JSON 元素的聯集結構類型。 Newtonsoft.Json 會使用專用階層式類型,例如 JObjectJArrayJToken 等等。 JsonElement 是您可以搜尋及列舉的內容,且您可以使用 JsonElement 將 JSON 元素具體化為 .NET 類型。

自 .NET 6 起,您可以使用 JsonNode 類型,以及對應至 JObjectJArrayJTokenSystem.Text.Json.Nodes 命名空間中的類型。 如需詳細資訊,請參閱使用 JsonNode

如何搜尋 JsonDocument 與 JsonElement 以尋找子元素

因為 Newtonsoft.Json 中的 JObjectJArray 在某些字典中為查閱,所以將其用於搜尋 JSON 權杖會相對快速。 相較之下,因為 JsonElement 搜尋需要循序搜尋屬性,所以相對緩慢 (例如使用 TryGetProperty 時)。 System.Text.Json 的設計目的是將初始剖析時間降至最低,而不是查閱時間。 如需詳細資訊,請參閱如何搜尋 JsonDocument 與 JsonElement 以尋找子元素

Utf8JsonReader 與JsonTextReader

System.Text.Json.Utf8JsonReader 是一個高效能、低配置且順向的 UTF-8 編碼 JSON 文字,並會從 ReadOnlySpan<byte>ReadOnlySequence<byte> 讀取。 Utf8JsonReader 是一個低階類型,可用於建置自訂剖析器與還原序列化程式。

Utf8JsonReader 為 ref struct

Newtonsoft.Json 中的 JsonTextReader 是類別。 Utf8JsonReader 類型的差異在於其為「參考結構」。 如需詳細資訊,請參閱 utf8JsonReader 的 ref 結構限制

將 null 值讀入可為 Null 的實值型別

Newtonsoft.Json 會提供可傳回 Nullable<T> 的 API (例如 ReadAsBoolean),其藉由傳回 bool? 來為您處理 Null TokenType。 內建 System.Text.Json API 只會傳回不可為 Null 的實值型別。 如需詳細資訊,請參閱將 null 值讀入可為 Null 的實值型別

用於讀取 JSON 的多目標

若必須繼續針對特定目標 Framework 來使用 Newtonsoft.Json,您可使用多個目標並擁有兩個實作。 然而,這並不簡單,且需要一些 #ifdefs 與來源複製。 盡可能多共用程式碼的其中一種方式,即建立 ref struct 包裝函式來含括 Utf8JsonReaderNewtonsoft.Json.JsonTextReader。 此包裝函式會整合公用介面區,同時隔離行為差異。 這可讓您隔離類型建構的大部分變更,以及使用傳址方式傳遞新類型。 這是 Microsoft.Extensions.DependencyModel (英文) 程式庫遵循的模式:

Utf8JsonWriter 與JsonTextWriter

System.Text.Json.Utf8JsonWriter 是一種高效能方式,可從常見的 .NET 類型 (例如 StringInt32DateTime) 撰寫 UTF-8 編碼的 JSON 文字。 寫入器是一個低階類型,可用於建置自訂序列化程式。

撰寫原始值

Newtonsoft.JsonWriteRawValue 方法會撰寫具有預期值的原始 JSON。 System.Text.Json 具有直接對等項目:Utf8JsonWriter.WriteRawValue。 如需詳細資訊,請參閱撰寫原始 JSON

自訂 JSON 格式

JsonTextWriter 包含下列設定,其中 Utf8JsonWriter 沒有對應項:

  • QuoteChar (英文) - 指定要用於括住字串值的字元。 Utf8JsonWriter 一律使用雙引號。
  • QuoteName (英文) - 指定是否要以引號括住屬性名稱。 Utf8JsonWriter 一律使用引號括住。

從 .NET 9 開始,您可以自訂縮排字元和大小,讓 Utf8JsonWriter 使用 JsonWriterOptions 結構公開的選項:

JsonTextWriter 包含下列設定,其中 Utf8JsonWriter 沒有對應項:

  • Indentation (英文) - 指定要縮排的字元數。 Utf8JsonWriter 一律縮排 2 個字元。
  • IndentChar (英文) - 指定要用於縮排的字元。 Utf8JsonWriter 一律使用空白字元。
  • QuoteChar (英文) - 指定要用於括住字串值的字元。 Utf8JsonWriter 一律使用雙引號。
  • QuoteName (英文) - 指定是否要以引號括住屬性名稱。 Utf8JsonWriter 一律使用引號括住。

沒有任何因應措施可讓您透過這些方式自訂 Utf8JsonWriter 所產生的 JSON。

撰寫 Timespan、URI 或 char 值

JsonTextWriter 提供 TimeSpan (英文)、URI (英文) 與 char (英文) 值的 WriteValue 方法。 Utf8JsonWriter 沒有對等的方法。 作為替代,請將這些值格式化為字串 (例如呼叫 ToString()),並呼叫 WriteStringValue

用於寫入 JSON 的多目標

若必須繼續針對特定目標 Framework 來使用 Newtonsoft.Json,您可使用多個目標並擁有兩個實作。 然而,這並不簡單,且需要一些 #ifdefs 與來源複製。 盡可能多共用程式碼的其中一種方式,即建立包裝函式來含括 Utf8JsonWriterNewtonsoft.Json.JsonTextWriter。 此包裝函式會整合公用介面區,同時隔離行為差異。 這可讓您隔離類型建構的大部分變更。 Microsoft.Extensions.DependencyModel (英文) 程式庫如下:

不支援 TypeNameHandling.All

System.Text.Json 中排除 TypeNameHandling.All 對等功能的決定是刻意為之。 允許 JSON 承載來指定自己的類型資訊,是 Web 應用程式中常見的弱點來源。 具體來說,使用 TypeNameHandling.All 設定 Newtonsoft.Json 可讓遠端用戶端在 JSON 承載本身內嵌整個可執行應用程式,以便在還原序列化期間,讓 Web 應用程式擷取並執行內嵌程式碼。 如需詳細資訊,請參閱 Friday the 13th JSON attacks PowerPointFriday the 13th JSON attacks details

不支援 JSON 路徑查詢

JsonDocument DOM 不支援使用 JSON 路徑 (英文) 進行查詢。

JsonNode DOM 中,每個 JsonNode 執行個體都有一個傳回該節點路徑的 GetPath 方法。 但沒有任何內建 API 可處理以 JSON 路徑查詢字串為基礎的查詢。

如需詳細資訊,請參閱 dotnet/runtime #31068 GitHub 問題 (英文)。

部分限制無法設定

System.Text.Json 會設定部分值無法變更的限制,例如字元中 (166 MB) 與 Base 64 中 (125 MB) 的權杖大小上限。 如需詳細資訊,請參閱原始程式碼中的 JsonConstants 與 GitHub 問題 dotnet/runtime #39953 (英文)。

NAN、Infinity、-Infinity

Newtonsoft 會剖析 NaNInfinity-Infinity JSON 字串權杖。 搭配 System.Text.Json 使用 JsonNumberHandling.AllowNamedFloatingPointLiterals。 如需如何使用此設定的資訊,請參閱允許或撰寫引號中的數字

使用 GitHub Copilot 移轉

您可以從 GitHub Copilot 取得程式碼撰寫說明,以在 IDE 中轉換程式碼。

如果您使用的是 Visual Studio 2022 版本 17.8 或更新版本,您可以嘗試在 Visual Studio 中使用由 AI 驅動的 GitHub Copilot,以協助將 Newtonsoft.Json 程式碼移轉到使用 System.Text.Json。 將您的問題和要轉換的程式碼提交到 Copilot 聊天視窗中,範例如下:

注意

GitHub Copilot 是由 AI 驅動的,因此可能會有意外和錯誤的情況發生。 任何生成的代碼或建議都務必要驗證。 有關 GitHub Copilot 的一般使用、產品影響、人為監督和隱私權的更多資訊,請參閱 [GitHub Copilot 常見問題解答]

Copilot Chat 的範例提示

convert the following code to use System.Text.Json
Product product = new Product();

product.Name = "Apple";
product.ExpiryDate = new DateTime(2024, 08, 08);
product.Price = 3.99M;
product.Sizes = new string[] { "Small", "Medium", "Large" };

string output = JsonConvert.SerializeObject(product);
Console.WriteLine(output);

Copilot Chat 回應的範例

To convert the code to use System.Text.Json, you can use the JsonSerializer class. Here's the updated code:
using System;
using System.Text.Json;

public class Product
{
    public string Name { get; set; }
    public DateTime ExpiryDate { get; set; }
    public decimal Price { get; set; }
    public string[] Sizes { get; set; }
}

public class Program
{
    public static void Main()
    {
        Product product = new Product();

        product.Name = "Apple";
        product.ExpiryDate = new DateTime(2024, 08, 08);
        product.Price = 3.99M;
        product.Sizes = new string[] { "Small", "Medium", "Large" };

        string output = JsonSerializer.Serialize(product);
        Console.WriteLine(output);
    }
}
This code uses System.Text.Json.JsonSerializer.Serialize to convert the Product object to a JSON string.

請注意,您的結果可能會與這些範例回應中顯示的內容有所不同。 AI 模型不具決定性,這代表在詢問相同問題時,它們可能會傳回不同的回答。 這可能是由於隨著時間而進行的額外學習和調整、語言變化、上下文變化 (例如您的聊天記錄) 等因素造成的。

顯示如何在 Visual Studio 中,使用 GitHub Copilot Chat 從 newtonsoft 移轉的動畫螢幕擷取畫面

您可以使用聊天功能,如 [斜線命令、參考和執行緒],來設定意圖,並獲得具有範圍上下文的更佳答案。 例如,如果您的程式碼檔案 filename 在 IDE 中打開,您可以在提示中使用「轉換 #filename 以使用 System.Text.Json」來參考該檔案,以便在 Copilot Chat 中進行操作。 或者,您可以在聊天視窗中或內嵌聊天中,使用「轉換 @workspace 以使用 System.Text.Json」來參考解決方案

其他資源