JSON (JavaScript Object Notation) 已經成為跨平台、跨語言的資料交換標準。不論我們開發任何的系統,JSON 幾乎無所不在。對於開發者而言,處理 JSON,不僅影響系統的反應速度,更直接牽動使用者體驗。
這篇文章將透過範例與效能測試,探討在 JSON 的序列化與反序列化的過程中有無使用 Source Generator 的差異。
為什麼該用 Source Generator
序列化與反序列化的過程時常成為效能瓶頸,當系統需要頻繁地將物件轉換成 JSON 字串,或將 JSON 字串還原成物件時,如若效能低落,可能導致延遲增加、伺服器資源消耗過高,甚至影響整體系統的可擴展性。特別是在高流量 API 或即時資料處理場景中,效能的差異非常容易被放大。
過去依賴反射的運作
在 .NET Core 3.0 的時代,微軟推出了 System.Text.Json 用以取代過去非常受歡迎的 Newtonsoft.Json,這個 API 目標是高效能與低記憶體消耗。但是其基本的序列化與反序列化處理依然圍繞著「反射 (Reflection)」這個技術。
使用反射的問題在於框架會在執行期動態檢查型別的屬性與結構,並依據這些資訊生成對應的 JSON 字串或執行個體。這種方式的好處是開發者幾乎不需要額外設定,只要設計一個對應的型別,就能立即進行序列化與反序列化,十分直覺。不過在這便利性帶有隱藏成本,這些成本會來自於以下幾個可能:(1) 型別結構解析的效能消耗 (2)反射過程中產生的物件會增加 GC 的負擔
Source Generator 來了
微軟在 .NET 5 引入了 Source Generator 技術 (.NET 6 有進行過一次改造),這是一種在編譯期就能產生程式碼的機制,其中一個重要的應用就是解決反射帶來的效能瓶頸。為了解決反射的痛點,Source Generator (System.Text.Json.SourceGeneration.JsonSourceGenerator) 會在編譯階段分析程式碼,並自動生成序列化與反序列化所需的邏輯;換句話說,開發者在編譯完成後,會自動產生一組「量身訂做」的程式碼,能直接處理指定型別的 JSON 轉換,而不需要在執行期再去動態解析,這樣的設計除了避免反射所帶來的效能損耗,同時也減少了額外的執行個體產生。
我們在建立專案後可以在【相依性】→【分析器】中看到 System.Text.Json.SourceGeneration.JsonSourceGenerator。

如何使用
先假設資料所需的型別:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public Address Address { get; set; }
}
public class Address
{
public string Street { get; set; }
public string City { get; set; }
} 過去我們會習於用這樣的方式:
static void SerializeWithoutSourceGen()
{
Person person = new Person
{
Name = "Bob",
Age = 25,
Address = new Address
{
Street = "456 Elm St",
City = "Metropolis"
}
};
// 使用原生序列化功能進行序列化
string jsonString = System.Text.Json.JsonSerializer.Serialize(person);
}
static void DeserializeWithoutSourceGen()
{
string jsonString = """ {"Name":"Bob","Age":25,"Address":{"Street":"456 Elm St","City":"Metropolis"}} """;
// 使用原生反序列化功能進行反序列化
Person person = System.Text.Json.JsonSerializer.Deserialize<Person>(jsonString);
}當然,使用 Source Generator 會多一丁點的步驟,但也真的就是一丁點。
首先,要繼承 System.Text.Json.Serialization.JsonSerializerContext Class 建立一個自訂的序列化上下文類別,雖然乍看之下這個 JsonSerializerContext 有幾個看起來有點可怕的成員需要實作,但實際上 Source Generator 會幫你搞定,你只要定義好適當的類別名稱,並且標示哪些類別需要使用這功能就好 (注意,這會是個 partial class):
[JsonSerializable(typeof(Person))]
[JsonSerializable(typeof(Address))]
internal partial class JsonContext : JsonSerializerContext;在序列化/反序列化呼叫的時候,加入 JsonTypeInfo 的引數,為了方便看文章時容易理解,這部分會採用具名引數的方式:
static void SerializeWithSourceGen()
{
Person person = new Person
{
Name = "Bob",
Age = 25,
Address = new Address
{
Street = "456 Elm St",
City = "Metropolis"
}
};
// 使用 source generator 進行序列化
string jsonString = System.Text.Json.JsonSerializer.Serialize(value: person, jsonTypeInfo: JsonContext.Default.Person);
}
static void DeserializeWithSourceGen()
{
string jsonString = """ {"Name":"Bob","Age":25,"Address":{"Street":"456 Elm St","City":"Metropolis"}} """;
// 使用 source generator 進行反序列化
Person person = System.Text.Json.JsonSerializer.Deserialize(json: jsonString, jsonTypeInfo: JsonContext.Default.Person);
}滿簡單的對吧?
Source Generator 其實不僅僅在解決執行時期的效能問題,另外也會解決開發時期的效率。目前除了微軟已經在框架定義好的幾個 Source Generators 以外,有許多第三方程式庫也開始加入了這類的功能;當然,如果可以自己寫 Source Generator 是更棒的。