上一篇文章提到從 Newtonsoft.Json 遷移到 System.Text.Json 的實際經驗,這篇專注在兩者的功能與行為差異,讓你在選型時有具體依據。

歷史背景

Newtonsoft.Json(又稱 Json.NET)由 James Newton-King 於 2006 年首次發布,長期是 .NET 生態系的預設 JSON 工具。ASP.NET Core 2.x 的 MVC 框架預設使用它進行模型序列化,直到 ASP.NET Core 3.0 才改為 System.Text.Json。

System.Text.Json 隨 .NET Core 3.0(2019 年)一起推出,由微軟直接整合進執行時期,設計目標是效能與低配置開銷——代價是功能集比 Newtonsoft.Json 精簡。

效能

根據 BenchmarkDotNet 的公開測試,System.Text.Json 的序列化吞吐量約比 Newtonsoft.Json 快 20–35%。記憶體配置差距因資料形狀差異很大:小物件可能只差一點,大批次 JSON 串流場景下 System.Text.Json 因避免中間字串配置,記憶體佔用明顯更低。不建議直接套用任何網路上的數字——用 BenchmarkDotNet 在自己的資料結構上實測最準。

效能差距較明顯的情境:

  • 高吞吐量 Web API(大量小物件反覆序列化)
  • 串流處理大型 JSON 檔案(Utf8JsonReader / Utf8JsonWriter

效能差距不明顯的情境:

  • 一次性批次工作
  • JSON 結構複雜、需要大量自訂轉換邏輯(轉換成本蓋過引擎差距)

功能差異

循環參考處理

這是兩者行為差距最大的地方之一。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Newtonsoft.Json:可設定忽略循環參考
var settings = new JsonSerializerSettings
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};
var json = JsonConvert.SerializeObject(myObject, settings);

// System.Text.Json:預設遇到循環參考直接拋例外
// 需明確設定才能處理
var options = new JsonSerializerOptions
{
ReferenceHandler = ReferenceHandler.IgnoreCycles // .NET 6+
};
var json = JsonSerializer.Serialize(myObject, options);

如果你的物件圖有循環參考(例如 EF Core 的導覽屬性雙向關聯),遷移時這個差異最容易炸。

多型 / 派生型別序列化

Newtonsoft.Json 透過 TypeNameHandling 或自訂 JsonConverter 長期支援多型序列化。System.Text.Json 要到 .NET 7 才內建 [JsonDerivedType] 屬性:

1
2
3
4
// System.Text.Json(.NET 7+)
[JsonDerivedType(typeof(Dog), typeDiscriminator: "dog")]
[JsonDerivedType(typeof(Cat), typeDiscriminator: "cat")]
public class Animal { public string Name { get; set; } }

.NET 6 以下若需要多型序列化,只能自己實作 JsonConverter<T>,或繼續用 Newtonsoft.Json。

Source Generator 支援

System.Text.Json 從 .NET 6 起支援 Source Generator 模式,在編譯期產生序列化程式碼,完全避開執行期反射:

1
2
3
4
5
[JsonSerializable(typeof(MyModel))]
internal partial class MyJsonContext : JsonSerializerContext { }

// 使用時傳入 context,完全無反射
var json = JsonSerializer.Serialize(myModel, MyJsonContext.Default.MyModel);

這對 AOT 編譯(Native AOT)和啟動效能要求高的場景幾乎是必選。Newtonsoft.Json 目前不支援 Source Generator,AOT 相容性也較差。

自訂命名規則

1
2
3
4
5
6
7
8
9
10
11
12
13
// System.Text.Json
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
var json = JsonSerializer.Serialize(myObject, options);

// Newtonsoft.Json
var settings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
var json = JsonConvert.SerializeObject(myObject, settings);

動態物件處理

1
2
3
4
5
6
7
8
9
10
11
// System.Text.Json(.NET 6+ JsonNode API)
using System.Text.Json.Nodes;
var jsonNode = JsonNode.Parse(jsonString);
// 注意:若 property 不存在,jsonNode["property"] 回傳 null
// 直接呼叫 GetValue<T>() 會拋 NullReferenceException
var value = jsonNode["property"]?.GetValue<string>();

// Newtonsoft.Json(JObject)
using Newtonsoft.Json.Linq;
var jObject = JObject.Parse(jsonString);
var value = jObject["property"]?.Value<string>(); // 同樣需要 null 檢查

自訂型別轉換

兩者都有泛型版本的 Converter,型別安全性相當:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// System.Text.Json
public class CustomConverter : JsonConverter<CustomType>
{
public override CustomType Read(ref Utf8JsonReader reader,
Type typeToConvert, JsonSerializerOptions options)
{
// 讀取邏輯
}

public override void Write(Utf8JsonWriter writer,
CustomType value, JsonSerializerOptions options)
{
// 寫入邏輯
}
}

// Newtonsoft.Json(泛型版,現代寫法)
public class CustomConverter : JsonConverter<CustomType>
{
public override CustomType ReadJson(JsonReader reader,
Type objectType, CustomType existingValue,
bool hasExistingValue, JsonSerializer serializer)
{
// 讀取邏輯
}

public override void WriteJson(JsonWriter writer,
CustomType value, JsonSerializer serializer)
{
// 寫入邏輯
}
}

非同步支援

System.Text.Json 設計上就以串流為核心,JsonSerializer.DeserializeAsync 直接接受 Stream,不需要先讀成字串。Newtonsoft.Json 也有 JsonTextReader 搭配 StreamReader 的非同步用法,但 API 層次不同,使用起來較繁瑣。高吞吐量 API 若在意配置開銷,這個差距值得測試。

遷移注意事項

從 Newtonsoft.Json 換到 System.Text.Json,常見踩坑點:

  • 屬性名稱大小寫:Newtonsoft 預設 case-insensitive 反序列化,STJ 預設 case-sensitive,要加 PropertyNameCaseInsensitive = true
  • [JsonProperty] 屬性:Newtonsoft 的標記在 STJ 無效,要換成 [JsonPropertyName]
  • [JsonIgnore] 條件:STJ 的 [JsonIgnore(Condition = ...)] 語義和 Newtonsoft 不完全相同
  • 循環參考:如前述,Newtonsoft 預設不報錯,STJ 預設拋例外
  • object 型別反序列化:Newtonsoft 遇到 object 屬性會推斷成 JObject/JArray,STJ 推斷成 JsonElement
  • DateTime 格式:兩者對非標準日期格式的容忍度不同,手動指定格式是最保險的做法

選型建議

新專案用 System.Text.Json,理由很直接:效能更好、是 .NET 執行時期的一部分、Native AOT 相容、長期方向明確。

以下情境考慮繼續用 Newtonsoft.Json 或延後遷移:

  • 現有程式碼大量依賴 JObject/JToken 動態操作
  • 需要 ReferenceLoopHandling.Ignore 且無法修改物件圖
  • 用到 Newtonsoft 特有的 JsonConverter 生態(如 Newtonsoft.Json.Schema
  • 目標框架低於 .NET 6,或需要相容 .NET Framework

兩者並存也是選項:ASP.NET Core 可以只在特定端點或服務層換成 STJ,其餘保留 Newtonsoft.Json,不需要一次全換。