遇到过一个让人头疼的问题:高峰期API响应慢得像老牛拉车,排查后发现JSON序列化竟然占了30%的CPU时间!当时项目里用的是老牌的Newtonsoft.Json,虽然功能强大,但在高并发场景下确实有点吃不消。
后来切换到 System.Text.Json 后,序列化性能提升了 2-3倍,内存分配减少了 40% 左右(基于. NET 6测试环境,10万次序列化操作)。这篇文章咱们就聊聊这个微软官方钦定的JSON库,它不仅仅是"又一个JSON库"那么简单。
读完本文你能收获:
我在本地做了个简单测试(环境:. NET 8, Release模式):

很多同学跟我说过类似的疑虑,咱们得先破除这些误区:
误解一:"功能没Newtonsoft全"
确实,某些特殊场景(比如DataSet序列化)确实支持不够完善,但80%的常规需求都能覆盖,而且微软在持续更新。
误解二:"迁移成本太高"
其实大部分代码改动就是换个命名空间,核心逻辑基本不动。我团队去年迁移了一个20万行的项目,实际改动代码不到300行。
误解三:"只适合新项目"
老项目也能渐进式迁移,两个库可以共存。我见过不少项目是先把性能敏感模块(比如日志、缓存)换成Text.Json,然后逐步扩大范围。
System.Text.Json提供了三种主要方式,每种都有最佳适用场景:
我的建议是:原型阶段用反射,生产环境上Source Generator。就像开车,先学自动挡,熟练了再开手动挡追求极致控制。
这几个配置选项在实战中用得最多,我按使用频率排个序:
csharpvar options = new JsonSerializerOptions
{
// 🔥 使用频率Top1:属性命名策略(前端对接必备)
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
// 🔥 Top2:美化输出(调试神器,生产环境记得关)
WriteIndented = true,
// 🔥 Top3:忽略null值(减少传输体积)
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
// 🔥 Top4:允许尾随逗号(兼容手写JSON)
AllowTrailingCommas = true,
// 🔥 Top5:大小写不敏感(容错处理)
PropertyNameCaseInsensitive = true
};
适用场景: 新手入门、API接口开发、配置文件读写
先看最常见的用法,我刚带的实习生第一天就能上手:
csharpusing System.Text.Json;
using System.Text.Json.Serialization;
// 定义数据模型
public class Product
{
public int Id { get; set; }
// 自定义JSON属性名
[JsonPropertyName("product_name")]
public string Name { get; set; }
// 忽略某个属性
[JsonIgnore]
public string InternalCode { get; set; }
public decimal Price { get; set; }
public DateTime CreatedAt { get; set; }
}
// 序列化示例
var product = new Product
{
Id = 1001,
Name = "机械键盘",
InternalCode = "SECRET-123",
Price = 299.99m,
CreatedAt = DateTime.Now
};
// 方式1:最简单的序列化
string json = JsonSerializer.Serialize(product);
Console.WriteLine(json);
// 方式2:带配置的序列化(推荐)
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true,
Encoder = System.Text. Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping // 中文不转义
};
string formattedJson = JsonSerializer. Serialize(product, options);
Console.WriteLine(formattedJson);
// 反序列化
string jsonData = """
{
"id": 1001,
"product_name": "机械键盘",
"price": 299.99,
"createdAt": "2026-01-22T10:30:00"
}
""";
var deserializedProduct = JsonSerializer.Deserialize<Product>(jsonData, options);
Console.WriteLine($"产品名称:{deserializedProduct.Name}");

踩坑预警:
\uXXXX格式,记得设置JavaScriptEncoder.UnsafeRelaxedJsonEscaping性能数据: 在测试中(序列化1万个Product对象),这种基础用法耗时约45ms,内存分配1.2MB。
适用场景: 特殊格式处理、遗留系统对接、复杂业务逻辑
这是个分水岭,很多开发者卡在这里。我当时第一次写转换器也是查了半天文档,后来总结了个万能模板。
实战案例: 处理前端传来的多种日期格式
csharpusing System.Text.Json;
using System.Text.Json.Serialization;
// 自定义日期转换器
public class FlexibleDateTimeConverter : JsonConverter<DateTime>
{
private readonly string[] _formats = new[]
{
"yyyy-MM-dd HH:mm: ss",
"yyyy/MM/dd HH:mm: ss",
"yyyy-MM-dd",
"yyyyMMdd"
};
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
string dateString = reader.GetString();
// 尝试多种格式解析
foreach (var format in _formats)
{
if (DateTime.TryParseExact(dateString, format, null,
System.Globalization.DateTimeStyles.None, out DateTime result))
{
return result;
}
}
// 兜底方案
return DateTime.Parse(dateString);
}
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
{
// 统一输出格式
writer.WriteStringValue(value.ToString("yyyy-MM-dd HH:mm:ss"));
}
}
// 自定义枚举转换器(转成字符串而非数字)
public class OrderStatus
{
public enum Status
{
Pending = 0,
Processing = 1,
Shipped = 2,
Completed = 3
}
}
// 使用方式
public class Order
{
public int OrderId { get; set; }
// 方式1:在属性上标注
[JsonConverter(typeof(FlexibleDateTimeConverter))]
public DateTime OrderDate { get; set; }
// 方式2:枚举转字符串
[JsonConverter(typeof(JsonStringEnumConverter))]
public OrderStatus. Status Status { get; set; }
public decimal TotalAmount { get; set; }
}
// 测试代码
var order = new Order
{
OrderId = 10001,
OrderDate = DateTime.Now,
Status = OrderStatus.Status.Processing,
TotalAmount = 1888.88m
};
var options = new JsonSerializerOptions
{
WriteIndented = true,
Converters = { new FlexibleDateTimeConverter() } // 全局注册
};
string json = JsonSerializer.Serialize(order, options);
Console.WriteLine(json);
// 反序列化测试(支持多种日期格式)
string testJson = """
{
"OrderId": 10002,
"OrderDate": "2026/01/22 14:30:00",
"Status": "Shipped",
"TotalAmount": 2999.00
}
""";
var parsedOrder = JsonSerializer.Deserialize<Order>(testJson, options);
Console.WriteLine($"订单状态:{parsedOrder.Status}");

真实应用场景: 我们有个对接老系统的项目,对方日期格式五花八门,有时是yyyyMMdd,有时是yyyy/MM/dd,用这个转换器完美解决。
性能影响: 自定义转换器会比默认慢约15-20%,但在可接受范围。测试显示序列化1万个Order对象从45ms增加到54ms。
扩展建议:
JsonConverter<T>封装通用逻辑适用场景: 高并发API、性能敏感服务、云函数/Serverless
这是我个人最推荐的方式,特别是. NET 6+项目。虽然代码稍微多一点,但性能提升是肉眼可见的。
csharpusing System.Text.Json;
using System.Text.Json.Serialization;
// 1. 定义序列化上下文(这是关键)
[JsonSerializable(typeof(ApiResponse))]
[JsonSerializable(typeof(List<Product>))]
[JsonSourceGenerationOptions(
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
WriteIndented = false // 生产环境关闭美化
)]
public partial class AppJsonContext : JsonSerializerContext
{
}
// 2. 数据模型
public class ApiResponse
{
public int Code { get; set; }
public string Message { get; set; }
public List<Product> Data { get; set; }
}
// 3. 使用方式
public class PerformanceDemo
{
public static void Main()
{
var response = new ApiResponse
{
Code = 200,
Message = "success",
Data = new List<Product>
{
new Product { Id = 1, Name = "商品A", Price = 99.9m },
new Product { Id = 2, Name = "商品B", Price = 199.9m }
}
};
// 🚀 使用Source Generator序列化(推荐)
string json = JsonSerializer.Serialize(response, AppJsonContext.Default. ApiResponse);
Console.WriteLine(json);
// 🚀 反序列化
var parsed = JsonSerializer. Deserialize(json, AppJsonContext.Default. ApiResponse);
Console.WriteLine($"返回码:{parsed.Code}");
// 性能对比测试
PerformanceBenchmark();
}
static void PerformanceBenchmark()
{
var testData = new ApiResponse
{
Code = 200,
Message = "test",
Data = Enumerable.Range(1, 1000).Select(i => new Product
{
Id = i,
Name = $"商品{i}",
Price = i * 10m,
CreatedAt = DateTime. Now
}).ToList()
};
var sw = System.Diagnostics.Stopwatch.StartNew();
// 测试1:反射模式
for (int i = 0; i < 1000; i++)
{
_ = JsonSerializer.Serialize(testData);
}
sw.Stop();
Console.WriteLine($"反射模式:{sw.ElapsedMilliseconds}ms");
// 测试2:Source Generator
sw.Restart();
for (int i = 0; i < 1000; i++)
{
_ = JsonSerializer.Serialize(testData, AppJsonContext.Default.ApiResponse);
}
sw. Stop();
Console.WriteLine($"Source Generator:{sw.ElapsedMilliseconds}ms");
}
}

踩坑预警:
进阶技巧: 多个Context隔离
csharp// 按模块拆分Context,避免一个Context太臃肿
[JsonSerializable(typeof(UserInfo))]
[JsonSerializable(typeof(LoginRequest))]
public partial class AuthJsonContext : JsonSerializerContext { }
[JsonSerializable(typeof(Order))]
[JsonSerializable(typeof(OrderDetail))]
public partial class OrderJsonContext : JsonSerializerContext { }
适用场景: 日志处理、数据导出、大JSON文件解析
有次我处理一个500MB的JSON日志文件,用常规方式直接内存爆了。后来发现Text.Json有个流式API,完美解决。
csharpusing System.Text.Json;
public class LogEntry
{
public DateTime Timestamp { get; set; }
public string Level { get; set; }
public string Message { get; set; }
}
public class StreamingJsonDemo
{
// 场景1:流式写入大量数据
public static async Task WriteLogsToFile(string filePath, IEnumerable<LogEntry> logs)
{
using var fileStream = File.Create(filePath);
using var writer = new Utf8JsonWriter(fileStream, new JsonWriterOptions
{
Indented = false // 生产环境关闭美化节省空间
});
writer.WriteStartArray();
foreach (var log in logs)
{
JsonSerializer.Serialize(writer, log);
// 每1000条刷新一次,避免占用太多内存
if (logs.ToList().IndexOf(log) % 1000 == 0)
{
await writer.FlushAsync();
}
}
writer.WriteEndArray();
}
// 场景2:流式读取大文件
public static async Task<List<LogEntry>> ReadLogsFromFile(string filePath)
{
var logs = new List<LogEntry>();
using var fileStream = File.OpenRead(filePath);
using var jsonDoc = await JsonDocument.ParseAsync(fileStream);
foreach (var element in jsonDoc.RootElement. EnumerateArray())
{
var log = new LogEntry
{
Timestamp = element.GetProperty("Timestamp").GetDateTime(),
Level = element.GetProperty("Level").GetString(),
Message = element.GetProperty("Message").GetString()
};
logs.Add(log);
}
return logs;
}
// 场景3:逐行解析(最省内存)
public static async IAsyncEnumerable<LogEntry> StreamReadLogs(string filePath)
{
using var fileStream = File.OpenRead(filePath);
using var reader = new StreamReader(fileStream);
await reader.ReadLineAsync(); // 跳过 '['
string line;
while ((line = await reader.ReadLineAsync()) != null)
{
if (line. Trim() == "]") break;
var cleanLine = line.TrimEnd(',');
var log = JsonSerializer.Deserialize<LogEntry>(cleanLine);
if (log != null)
{
yield return log;
}
}
}
// 性能测试
public static async Task TestPerformance()
{
var testLogs = Enumerable.Range(1, 100000).Select(i => new LogEntry
{
Timestamp = DateTime.Now,
Level = "INFO",
Message = $"Test log message {i}"
}).ToList();
var filePath = "test_logs.json";
// 写入测试
var sw = System.Diagnostics.Stopwatch.StartNew();
await WriteLogsToFile(filePath, testLogs);
sw.Stop();
Console.WriteLine($"写入10万条日志耗时:{sw.ElapsedMilliseconds}ms");
Console.WriteLine($"文件大小:{new FileInfo(filePath).Length / 1024 / 1024}MB");
// 流式读取测试
sw.Restart();
int count = 0;
await foreach (var log in StreamReadLogs(filePath))
{
count++;
}
sw.Stop();
Console.WriteLine($"流式读取{count}条日志耗时:{sw.ElapsedMilliseconds}ms");
Console.WriteLine($"峰值内存占用:约{GC.GetTotalMemory(false) / 1024 / 1024}MB");
}
}
实测效果(处理10万条日志):
适用场景清单:
在项目中用了两年多Text.Json,我总结出这三条铁律:
别每次序列化都new一个JsonSerializerOptions,这玩意儿创建成本不低。我建议这样:
csharppublic static class JsonConfig
{
// 单例模式复用配置
public static readonly JsonSerializerOptions Default = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
PropertyNameCaseInsensitive = true
};
public static readonly JsonSerializerOptions PrettyPrint = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true
};
}
// 使用
string json = JsonSerializer.Serialize(data, JsonConfig.Default);
JSON反序列化是个高危操作,外部数据不可信。我习惯这样包一层:
csharppublic static class SafeJsonHelper
{
public static T TryDeserialize<T>(string json, T defaultValue = default)
{
try
{
return JsonSerializer.Deserialize<T>(json, JsonConfig.Default) ?? defaultValue;
}
catch (JsonException ex)
{
// 记录日志
Console.WriteLine($"JSON解析失败:{ex.Message}");
return defaultValue;
}
}
}
别盲目优化,先测量。我常用这个简单的性能监控装饰器:
csharppublic class JsonPerformanceMonitor
{
public static string SerializeWithMetrics<T>(T value, JsonSerializerOptions options = null)
{
var sw = System.Diagnostics. Stopwatch.StartNew();
long memBefore = GC.GetTotalMemory(false);
string result = JsonSerializer.Serialize(value, options);
sw.Stop();
long memAfter = GC.GetTotalMemory(false);
Console.WriteLine($"序列化耗时:{sw.ElapsedMilliseconds}ms,内存分配:{(memAfter - memBefore) / 1024}KB");
return result;
}
}
话题1: 你在项目中遇到过哪些JSON序列化的奇葩问题?比如时区、精度丢失、循环引用等,欢迎留言分享踩坑经历!
话题2: 如果让你设计一个通用的JSON工具类,你会包含哪些功能?我准备开源一个JsonHelper库,想听听大家的需求。
小挑战: 试试用Text.Json实现一个"部分更新"功能——只序列化值发生变化的属性。提示:可以结合JsonDocument做差异对比。
#CSharp #性能优化 #JSON序列化 #dotNET #后端开发 #系统架构
本文作者:技术老小子
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!