解决System.Text.Json中文编码问题:配置UnsafeRelaxedJsonEscaping
1. 项目概述:当JSON输出不再“友好”
在.NET Core 3.0及更高版本的开发中,System.Text.Json作为微软官方力推的高性能JSON序列化库,已经逐渐成为处理JSON数据的首选。它速度快、内存占用低,对于构建高性能API和微服务来说,是一个利器。然而,很多开发者,包括我自己在内,在从经典的Newtonsoft.Json(即Json.NET)迁移过来,或者初次使用时,都踩过一个不大不小的坑:当你满怀期待地将一个包含中文或特殊符号(比如引号、斜杠、Emoji)的对象序列化成JSON字符串时,得到的输出却是一串令人困惑的Unicode转义序列,例如\u4e2d\u6587(代表“中文”)或者\u0022(代表双引号)。在控制台输出或者前端接收时,这看起来就是一堆乱码,严重影响了数据的可读性和调试体验。
这个问题看似简单,却直接关系到开发效率和数据的直观性。想象一下,你正在调试一个返回用户信息的API,日志里看到的用户名全是\u5f20\u4e09,而不是清晰的“张三”,这无疑增加了排查问题的难度。更关键的是,某些下游系统或前端框架可能对这类编码字符串的处理并不一致,从而引发更深层次的兼容性问题。因此,理解System.Text.Json的编码行为,并掌握如何控制它,是每一位.NET开发者在使用这个库时必须掌握的技能。本文将深入拆解其背后的原理,并提供从全局配置到局部控制的完整解决方案,让你输出的JSON既高性能,又“人见人爱”。
2. 核心原理:为什么System.Text.Json要“编码”?
要解决问题,首先要理解问题是如何产生的。System.Text.Json默认对非ASCII字符(包括中文)和某些特殊字符进行转义,这并非一个Bug,而是一个经过深思熟虑的默认安全设计。
2.1 编码与转义的本质区别
这里需要先厘清一个概念:我们常说的“乱码”和“编码”在这个语境下,通常指的是“字符转义”。真正的编码(如UTF-8、GBK)发生在字节层面,而System.Text.Json输出的是字符串,它涉及的是字符串内部的字符表示方式。
默认情况下,System.Text.Json的JsonSerializer使用了一个名为JavaScriptEncoder.UnsafeRelaxedJsonEscaping的编码器(更准确地说,默认是比UnsafeRelaxedJsonEscaping更严格的规则)。这个编码器的设计遵循了JSON规范(RFC 8259)和防范特定安全风险的原则,主要体现在两点:
-
非ASCII字符转义:将所有不在基本拉丁语(Basic Latin)区块(即码点大于
\u007F)的字符转换为\uXXXX的Unicode转义序列。例如,汉字“中”的Unicode码点是U+4E2D,就会被转义为\u4e2d。这样做可以确保生成的JSON文本完全由ASCII字符构成,具有极佳的兼容性。无论接收方的系统使用何种字符编码(即使是古老的只支持ASCII的环境),这份JSON文件在语法上都是绝对安全的,不会因为编码问题导致解析失败。 -
HTML敏感字符转义:默认编码器会转义一些在HTML和XML上下文中具有特殊意义的字符,例如
<、>、&等。这是为了防止JSON被意外嵌入HTML时可能引发的跨站脚本攻击风险。例如,字符串“<script>”会被转义为“\u003Cscript\u003E”。
2.2 与Newtonsoft.Json的默认行为对比
这也是许多开发者感到困惑的根源。Newtonsoft.Json的默认行为要“宽松”得多,它通常直接输出原始字符(如中文),除非你显式配置了转义。这种差异导致了迁移时的“水土不服”。System.Text.Json将“安全”和“兼容性”放在了更高优先级,而Newtonsoft.Json则更注重“可读性”和“开发者习惯”。
注意:
System.Text.Json的严格默认行为,在构建需要面对不可信输入或多种异构环境的高安全性、高可靠性服务时,是一个优势。但在大多数内部系统、前后端分离且字符集统一(如全栈UTF-8)的Web API场景中,这种转义就显得多余且不友好了。
3. 解决方案全景:从全局到局部的控制
明白了原因,解决方案就清晰了:我们需要告诉System.Text.Json,在序列化时使用一个更“宽松”的编码器,允许非ASCII字符和更多符号以原样输出。微软提供了几个不同安全等级的编码器供我们选择。
3.1 可用的编码器选项
System.Text.Json主要通过JsonSerializerOptions类来配置序列化行为,其中Encoder属性是关键。我们可以使用的编码器来自System.Text.Encodings.Web命名空间下的JavaScriptEncoder类:
| 编码器 | 安全性 | 转义行为 | 适用场景 |
|---|---|---|---|
JavaScriptEncoder.Default |
高 | 转义所有非ASCII字符及HTML敏感字符(<, >, &, ', "等)。这是JsonSerializer的默认编码器。 |
最高安全需求,输出环境未知或不可控。 |
JavaScriptEncoder.UnsafeRelaxedJsonEscaping |
中 | 允许非ASCII字符(如中文)原样输出。但仍会转义少数几个在JSON字符串和HTML中都必须转义的字符,如引号(")和反斜杠(\)。这是最常用的解决中文乱码问题的选项。 |
绝大多数Web API场景,前后端均使用UTF-8,且信任数据源。 |
JavaScriptEncoder.Create |
可定制 | 允 |