在 ASP.NET WebForms / MVC 早期專案中,若未引入 Newtonsoft.Json (Json.NET),常需自行實作 JSON 序列化。本文提供一個涵蓋 List、DataTable、DataSet、DataReader、單一物件、匿名型別、自訂類型 的完整序列化工具類,採用 `StringBuilder` 高效拼接,支援特殊字元轉義與日期/布林型別格式化。
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.ComponentModel;
using System.Globalization;
public static class ConvertJson
{
#region 私有輔助方法
///
/// JSON 特殊字元轉義 (RFC 8259)
///
private static string EscapeJsonString(string input)
{
if (string.IsNullOrEmpty(input)) return "\"\"";
var sb = new StringBuilder(input.Length + 10);
foreach (char c in input)
{
switch (c)
{
case '"': sb.Append("\\\""); break;
case '\\': sb.Append("\\\\"); break;
case '/': sb.Append("\\/"); break;
case '\b': sb.Append("\\b"); break;
case '\f': sb.Append("\\f"); break;
case '\n': sb.Append("\\n"); break;
case '\r': sb.Append("\\r"); break;
case '\t': sb.Append("\\t"); break;
default:
// 控制字元 (U+0000 ~ U+001F) 轉 \uXXXX
if (c < ' ') sb.AppendFormat("\\u{0:X4}", (int)c);
else sb.Append(c);
break;
}
}
return "\"" + sb.ToString() + "\"";
}
///
/// 依型別格式化值
///
private static string FormatValue(object value, Type type)
{
if (value == null || value == DBNull.Value) return "null";
string str = value.ToString();
if (type == typeof(string) || type == typeof(char))
return EscapeJsonString(str);
if (type == typeof(DateTime))
return EscapeJsonString(Convert.ToDateTime(value).ToString("yyyy-MM-ddTHH:mm:ss"));
if (type == typeof(DateTimeOffset))
return EscapeJsonString(Convert.ToDateTimeOffset(value).ToString("yyyy-MM-ddTHH:mm:sszzz"));
if (type == typeof(bool))
return str.ToLower(); // true/false
if (type == typeof(Guid))
return EscapeJsonString(str);
// 數值型別直接輸出
if (type.IsPrimitive || type == typeof(decimal) || type == typeof(double) || type == typeof(float))
return str;
// 其餘複雜型別嘗試遞迴序列化
return SerializeObject(value);
}
#endregion
#region 公開序列化方法
/// List<T> 序列化
public static string ListToJson<T>(IList<T> list, string jsonName = null)
{
if (list == null || list.Count == 0) return "{\"" + (jsonName ?? "list") + "\":[]}";
var sb = new StringBuilder();
string name = jsonName ?? typeof(T).Name;
sb.Append("{\"" + name + "\":[");
for (int i = 0; i < list.Count; i++)
{
sb.Append(SerializeObject(list[i]));
if (i < list.Count - 1) sb.Append(",");
}
sb.Append("]}");
return sb.ToString();
}
/// 單一物件序列化 (含匿名型別)
public static string ToJson(object obj)
{
if (obj == null) return "null";
return SerializeObject(obj);
}
/// IEnumerable 序列化 (陣列、集合)
public static string ToJson(IEnumerable enumerable)
{
if (enumerable == null) return "[]";
var sb = new StringBuilder("[");
bool first = true;
foreach (var item in enumerable)
{
if (!first) sb.Append(",");
sb.Append(SerializeObject(item));
first = false;
}
sb.Append("]");
return sb.ToString();
}
/// DataSet 序列化
public static string ToJson(DataSet dataSet)
{
if (dataSet == null) return "{}";
var sb = new StringBuilder("{");
bool firstTable = true;
foreach (DataTable table in dataSet.Tables)
{
if (!firstTable) sb.Append(",");
sb.Append("\"" + table.TableName + "\":" + ToJson(table));
firstTable = false;
}
sb.Append("}");
return sb.ToString();
}
/// DataTable 序列化
public static string ToJson(DataTable dt, string jsonName = null)
{
if (dt == null) return "{\"" + (jsonName ?? "table") + "\":[]}";
var sb = new StringBuilder();
string name = jsonName ?? dt.TableName;
sb.Append("{\"" + name + "\":[");
for (int i = 0; i < dt.Rows.Count; i++)
{
sb.Append("{");
for (int j = 0; j < dt.Columns.Count; j++)
{
var col = dt.Columns[j];
var val = dt.Rows[i][j];
sb.Append("\"" + col.ColumnName + "\":" + FormatValue(val, col.DataType));
if (j < dt.Columns.Count - 1) sb.Append(",");
}
sb.Append("}");
if (i < dt.Rows.Count - 1) sb.Append(",");
}
sb.Append("]}");
return sb.ToString();
}
/// DataReader 序列化 (逐列流式,記憶體友善)
public static string ToJson(DbDataReader reader)
{
if (reader == null) return "[]";
var sb = new StringBuilder("[");
bool firstRow = true;
while (reader.Read())
{
if (!firstRow) sb.Append(",");
sb.Append("{");
for (int i = 0; i < reader.FieldCount; i++)
{
string name = reader.GetName(i);
Type type = reader.GetFieldType(i);
object val = reader[i];
sb.Append("\"" + name + "\":" + FormatValue(val, type));
if (i < reader.FieldCount - 1) sb.Append(",");
}
sb.Append("}");
firstRow = false;
}
reader.Close(); // 關閉 Reader
sb.Append("]");
return sb.ToString();
}
#endregion
#region 核心遞迴序列化邏輯
private static string SerializeObject(object obj)
{
if (obj == null) return "null";
Type type = obj.GetType();
// 基本型別與字串
if (type.IsPrimitive || type == typeof(string) || type == typeof(decimal) ||
type == typeof(DateTime) || type == typeof(DateTimeOffset) || type == typeof(Guid) ||
type == typeof(TimeSpan))
{
return FormatValue(obj, type);
}
// 列舉
if (type.IsEnum)
return FormatValue(Convert.ToInt32(obj), typeof(int));
// 陣列/集合
if (obj is IEnumerable enumerable && !(obj is string))
return ToJson(enumerable);
// 一般物件 (含匿名型別) - 反射屬性
var props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
var sb = new StringBuilder("{");
bool first = true;
foreach (var prop in props)
{
if (!prop.CanRead) continue;
if (prop.GetIndexParameters().Length > 0) continue; // 跳過索引器
object value = prop.GetValue(obj, null);
if (!first) sb.Append(",");
sb.Append("\"" + prop.Name + "\":" + FormatValue(value, prop.PropertyType));
first = false;
}
sb.Append("}");
return sb.ToString();
}
#endregion
}
var users = new List<User> {
new User { Id = 1, Name = "張三", Birth = new DateTime(1990,1,1), Active = true },
new User { Id = 2, Name = "李四", Birth = new DateTime(1995,5,5), Active = false }
};
string json = ConvertJson.ListToJson(users, "users");
// {"users":[{"Id":1,"Name":"張三","Birth":"1990-01-01T00:00:00","Active":true},...]}
DataTable dt = GetDataTable(); // 從資料庫填充
string json = ConvertJson.ToJson(dt, "products");
using (var conn = new SqlConnection(cs))
using (var cmd = new SqlCommand("SELECT * FROM LargeTable", conn))
{
conn.Open();
using (var reader = cmd.ExecuteReader())
{
// 直接輸出至 Response,避免記憶體累積
Response.ContentType = "application/json";
Response.Write(ConvertJson.ToJson(reader));
}
}
var data = new {
Code = 200,
Msg = "Success",
Data = new { UserId = 100, Token = "abc123" },
Tags = new[] { "tag1", "tag2" }
};
string json = ConvertJson.ToJson(data);
// {"Code":200,"Msg":"Success","Data":{"UserId":100,"Token":"abc123"},"Tags":["tag1","tag2"]}
若需序列化非標準型別 (如 `CustomData`),可實作 `ICustomJsonSerializable` 介面:
public interface ICustomJsonSerializable
{
string ToJson();
}
public class CustomData : ICustomJsonSerializable
{
public string Value { get; set; }
public string ToJson() => "\"" + EscapeJsonString(Value) + "\"";
}
// 在 SerializeObject 中加入判斷
if (obj is ICustomJsonSerializable custom)
return custom.ToJson();
| 場景 | 建議 |
|---|---|
| 新專案 / .NET 4.5+ | 直接使用 Newtonsoft.Json (Json.NET) 或 System.Text.Json (.NET Core 3.0+),效能、功能、維護性全面優於手寫實作 |
| 舊專案維護 (.NET 2.0/3.5) | 本工具類可直接使用,無外部依賴 |
| 極高效能需求 | 使用 `System.Text.Json` (零分配、SIMD 加速) 或 `Utf8Json` |
| DataReader 大量資料 | 本工具 `ToJson(DbDataReader)` 採流式寫入,記憶體佔用恆定,優於先 Load DataTable |
這個 `ConvertJson` 工具類提供了無依賴、全型別覆蓋的 JSON 序列化能力,適合無法引入第三方套件的舊版 ASP.NET 專案。核心設計:StringBuilder 高效拼接、遞迴反射屬性、型別導向格式化、DataReader 流式輸出。現代專案請直接遷移至 `System.Text.Json` 或 `Newtonsoft.Json`。