写点什么

System.Text.Json 中的字符编码

作者:喵叔
  • 2021 年 11 月 10 日
  • 本文字数:2303 字

    阅读完需:约 8 分钟

默认的 System.Text.Json 序列化的时候会把所有的非 ASCII 的字符进行转义,这就会导致很多时候我们的一些非 ASCII 的字符就会变成 \uxxxx 这样的形式,很多场景下并不太友好,我们可以配置字符编码来解决被转义的问题

首先来看一个简单的示例:

var testObj = new{    Name = "小明",    Age = 10};WriteLine(JsonSerializer.Serialize(testObj));
复制代码

输出结果如下:

{"Name":"\u5C0F\u660E","Age":10}
复制代码

可以看到,我们的中文没有直接显式出来,而是被转义了,这可读性一下子就大打折扣了,接着我们来尝试让他工作

在我们序列化的时候,可以指定一个 JsonSerializeOptions,而这个 JsonSerializeOptions 中有一个 Encoder 我们可以用来配置支持的字符编码,不支持的就会被转义,而默认只支持 ASCII 字符

我们可以配置 Encoder 来支持中文,如下所示:

WriteLine(JsonSerializer.Serialize(testObj, new JsonSerializerOptions(){    Encoder = JavaScriptEncoder.Create(UnicodeRanges.BasicLatin, new UnicodeRange(0x4E00, 8000))}));
WriteLine(JsonSerializer.Serialize(testObj, new JsonSerializerOptions(){ Encoder = JavaScriptEncoder.Create(UnicodeRanges.BasicLatin, UnicodeRanges.CjkUnifiedIdeographs)}));
WriteLine(JsonSerializer.Serialize(testObj, new JsonSerializerOptions(){ Encoder = JavaScriptEncoder.Create(UnicodeRanges.All)}));
复制代码

我们可以通过 JavaScriptEncoder.Create 来自定义支持的字符范围,要指定一个 Unicode 范围

在 System.Text.Unicode.UnicodeRanges 这个静态类中定义了一些静态属性来比较方便的使用各个语言对应的字符集范围,当然如果你明确的知道字符从哪里开始,有多个个字符也可以自定义,如上面的第一种方式,但是这种不是特别推荐,因为你知道的范围并不一定是最准确的而且可能会有变更

推荐还是直接使用 UnicodeRanges 里定义好的字符集,如第二种方式,第二种方式这里的 UnicodeRanges.CjkUnifiedIdeographs 就包含了中文字符,去网上查了一下这个 CjkUnifiedIdeographs 代表中文(Chinese)、日文(Japanese )、韩文(Korean)的字符集合

TIP:中日韩统一表意文字(英语:CJK Unified Ideographs),也称统一汉字、统汉码(英语:Unihan),目的是要把分别来自中文、日文、韩文、越南文、壮文、琉球文中,起源相同、本义相同、形状一样或稍异的表意文字,在 ISO 10646 及万国码标准赋予相同编码。

如果你不确定是哪种字符集或者有全球化的需求,可以直接使用 UnicodeRanges.All 来支持所有的字符,如上面第三种方式

上面的示例输出结果如下:

{"Name":"小\u660E","Age":10}{"Name":"小明","Age":10}{"Name":"小明","Age":10}
复制代码

除此之外还有一个地方可能会需要,对于一些包含 html 标签的文本即使指定了所有字符集也会被转义,这是出于安全考虑。如果觉得不需要转义也可以配置,配置使用 JavaScriptEncoder.UnsafeRelaxedJsonEscaping 即可,示例如下:

var testObj = new{    Name = "小明",    Age = 10,    Description = "<h1>这是标题</h1>"};
WriteLine(JsonSerializer.Serialize(testObj, new JsonSerializerOptions(){ Encoder = JavaScriptEncoder.Create(UnicodeRanges.All)}));
WriteLine(JsonSerializer.Serialize(testObj, new JsonSerializerOptions(){ Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping}));
复制代码

输出结果如下:

{"Name":"小明","Age":10,"Description":"\u003Ch1\u003E这是标题\u003C/h1\u003E"}{"Name":"小明","Age":10,"Description":"<h1>这是标题</h1>"}
复制代码

之前曾经介绍过 JsonLogging .NET5  的一个新特性,可以参考 .net 5.0 中的 JsonConsole , 有一个应用使用了 JsonConsole 来格式化 Console 的日志为 Json,使用默认的配置然后会发现日志中会有很多这种 \uxxxx 的东西,看起来怪怪的。

后来测试发现默认支持的字符集也只是 ASCII 字符,而且有些字符会出于安全考虑也会转义,微软也是支持配置  Encoder 的,不过不是 JsonSerializeOptions, 而是 JsonWriterOptions,我们可以在注册 JsonConsole 的时候配置 Encoder,示例如下:

var builder = WebApplication.CreateBuilder(args);builder.Logging.AddJsonConsole(options =>{    options.JsonWriterOptions = new JsonWriterOptions    {        Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping    };});
复制代码

这里我们直接使用比较宽松的转义规则,因为我们的场景是日志不会直接渲染在页面上,所以个人觉得不必要求的太严格。

在指定 Unicode 字符集范围的时候需要把 UnicodeRanges.BasicLatin 也加上,其他的字符串没有包含基本的 ASCII 字符,否则基本的 ASCII 字符也会被转义

你也可以通过 TextEncoderSettings 来配置只支持的字符,示例如下:

var encoderSettings = new TextEncoderSettings();encoderSettings.AllowCharacters('\u0436', '\u0430');encoderSettings.AllowRange(UnicodeRanges.BasicLatin);var options = new JsonSerializerOptions{    Encoder = JavaScriptEncoder.Create(encoderSettings)};var jsonString = JsonSerializer.Serialize(weatherForecast, options);
复制代码

针对 JavaScriptEncoder.UnsafeRelaxedJsonEscaping 的使用需要注意安全问题

  • 它不转义 HTML 敏感字符,如 <>& 和 '

  • 它不提供任何针对 XSS 或信息泄露攻击(如客户端和服务器在字符集方面不一致所可能导致的攻击)的额外深度防御保护。

不要将原始 UnsafeRelaxedJsonEscaping 序列化的结果用到 HTML 页面或 <script> 元素。

用户头像

喵叔

关注

还未添加个人签名 2020.01.14 加入

还未添加个人简介

评论

发布
暂无评论
System.Text.Json 中的字符编码