System.Text.Json 中的字符编码

Posted dotNET跨平台

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了System.Text.Json 中的字符编码相关的知识,希望对你有一定的参考价值。

System.Text.Json 中的字符编码

Intro

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

Sample

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

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)的字符集合

中日韩统一表意文字(英语: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>"}

Console Logging

之前曾经介绍过 JsonLogging .NET5  的一个新特性,可以参考 .net 5.0 中的 JsonConsoleJsonConsole 来格式化 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
    };
});

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

加了上面的配置之后的日志如下:

这样的日志看起来就舒服多了,可读性也会更好一些

More

在指定 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> 元素

References

  • https://github.com/WeihanLi/SparkTodo/blob/master/SparkTodo.API/Program.cs#L22

  • https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-character-encoding?WT.mc_id=DT-MVP-5004222

  • https://github.com/WeihanLi/SamplesInPractice/blob/master/JsonSample/SystemTextJsonSample/EncoderSample.cs

  • https://docs.microsoft.com/zh-cn/dotnet/standard/serialization/system-text-json-character-encoding?WT.mc_id=DT-MVP-5004222

  • https://en.wikipedia.org/wiki/CJK_Unified_Ideographs

  • .net 5.0 中的 JsonConsole

以上是关于System.Text.Json 中的字符编码的主要内容,如果未能解决你的问题,请参考以下文章

System.Text.Json - 将嵌套对象反序列化为字符串

.net core中关于System.Text.Json的使用

使用 System.Text.Json 有条件地将对象序列化为单个字符串

使用 .NET core 3.0/System.text.Json 解析 JSON 文件

如何使用 System.Text.Json 将 json 字符串或流反序列化到 Dictionary<string,string>

将 Newtonsoft.Json 代码迁移到 System.Text.Json