為什麼要在 Newtonsoft.Json、System.Text.Json 稱霸的今天手寫序列化器?作者給出三個理由:簡單(單一類別)、輕量(300 行)、靈活(繼承覆寫單一方法即可擴展型別處理)。本文完整呈現 JsonBuilder 核心架構:以 StringBuilder 為緩衝、虛擬方法對應各基礎型別、反射處理未知物件、字典快取解決循環參考。
public class JsonBuilder {
protected StringBuilder Buff = new StringBuilder(4096);
public string ToJsonString(object obj) {
Buff.Length = 0;
AppendObject(obj);
return Buff.ToString();
}
// 入口:依型別分流
protected void AppendObject(object obj) {
if (obj == null) { Buff.Append("null"); return; }
if (obj is string) { AppendString((string)obj); return; }
if (obj is int) { AppendInt32((int)obj); return; }
// ... 其他基礎型別 ...
// 循環參考偵測
if (!_LoopObject.ContainsKey(obj)) {
_LoopObject.Add(obj, null);
if (obj is IDictionary) AppendJson((IDictionary)obj);
else if (obj is IEnumerable) AppendArray((IEnumerable)obj);
else if (obj is DataSet) AppendDataSet((DataSet)obj);
else if (obj is DataTable) AppendDataTable((DataTable)obj);
else if (obj is DataView) AppendDataView((DataView)obj);
else AppendOther(obj); // 反射處理自訂類別
_LoopObject.Remove(obj);
} else {
Buff.Append("undefined"); // 循環參考標記
}
}
// 可覆寫的虛擬方法們
protected virtual void AppendString(string v) { /* ... */ }
protected virtual void AppendInt32(int v) { AppendNumber(v); }
protected virtual void AppendNumber(IConvertible n) { Buff.Append(n.ToString(NumberFormatInfo.InvariantInfo)); }
protected virtual void AppendOther(object obj) { /* 反射屬性 */ }
// ... 陣列、字典、DataTable 等集合型別 ...
}
遇到 A.B = A 這類循環,第一次進入 AppendObject 將物件加入 _LoopObject,遞迴處理屬性時再次遇到同物件 → 直接輸出 undefined,避免堆疊溢位。這比 Newtonsoft 預設拋例外更優雅,也能區分 null。
public class EnumValueJsonBuilder : JsonBuilder {
protected override void AppendEnum(Enum value) {
Buff.Append(value.GetHashCode()); // 或 Convert.ToInt64(value)
}
}
單一覆寫,全域生效——這就是「虛擬方法表」帶來的擴展力。


// 完整 JsonBuilder 程式碼(含 DateTime、Guid、DataTable、字串逸出等細節)
// 請見原文完整代碼區塊
300 行掌握序列化核心邏輯,既是造輪子練功,也是理解現有庫底層運作的最佳教材。有興趣可再往 Utf8JsonWriter、Source Generator 方向深入。