如何使用 System.Text.Json 自定义字符编码

默认情况下,序列化程序会转义所有非 ASCII 字符。 即,会将它们替换为 \uxxxx,其中 xxxx 为字符的 Unicode 代码。 例如,如果以下 JSON 中的 Summary 属性设置为西里尔文 жарко,则 WeatherForecast 对象会进行序列化,如以下示例中所示:

{
  "Date": "2019-08-01T00:00:00-07:00",
  "TemperatureCelsius": 25,
  "Summary": "\u0436\u0430\u0440\u043A\u043E"
}

序列化语言字符集

若要序列化一种或多种语言的字符集而不进行转义,请在创建 System.Text.Encodings.Web.JavaScriptEncoder 的实例时指定 Unicode 范围,如以下示例中所示:

using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Unicode;
Imports System.Text.Encodings.Web
Imports System.Text.Json
Imports System.Text.Unicode
var options1 = new JsonSerializerOptions
{
    Encoder = JavaScriptEncoder.Create(UnicodeRanges.BasicLatin, UnicodeRanges.Cyrillic),
    WriteIndented = true
};
jsonString = JsonSerializer.Serialize(weatherForecast, options1);
options = New JsonSerializerOptions With {
    .Encoder = JavaScriptEncoder.Create(UnicodeRanges.BasicLatin, UnicodeRanges.Cyrillic),
    .WriteIndented = True
}
jsonString = JsonSerializer.Serialize(weatherForecast1, options)

此代码不转义西里尔文或希腊语字符。 如果 Summary 属性设置为西里尔文 жарко,则 WeatherForecast 对象会进行序列化,如以下示例中所示:

{
  "Date": "2019-08-01T00:00:00-07:00",
  "TemperatureCelsius": 25,
  "Summary": "жарко"
}

默认情况下,编码器使用 BasicLatin 范围进行初始化。

若要序列化所有语言集而不进行转义,请使用 UnicodeRanges.All

序列化特定字符

一种替代方法是指定要允许的单个字符,而不进行转义。 下面的示例仅序列化 жарко 的前两个字符:

using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Unicode;
Imports System.Text.Encodings.Web
Imports System.Text.Json
Imports System.Text.Unicode
var encoderSettings = new TextEncoderSettings();
encoderSettings.AllowCharacters('\u0436', '\u0430');
encoderSettings.AllowRange(UnicodeRanges.BasicLatin);
var options2 = new JsonSerializerOptions
{
    Encoder = JavaScriptEncoder.Create(encoderSettings),
    WriteIndented = true
};
jsonString = JsonSerializer.Serialize(weatherForecast, options2);
Dim encoderSettings As TextEncoderSettings = New TextEncoderSettings
encoderSettings.AllowCharacters(ChrW(&H436), ChrW(&H430))
encoderSettings.AllowRange(UnicodeRanges.BasicLatin)
options = New JsonSerializerOptions With {
    .Encoder = JavaScriptEncoder.Create(encoderSettings),
    .WriteIndented = True
}
jsonString = JsonSerializer.Serialize(weatherForecast1, options)

下面是前面代码生成的 JSON 的示例:

{
  "Date": "2019-08-01T00:00:00-07:00",
  "TemperatureCelsius": 25,
  "Summary": "жа\u0440\u043A\u043E"
}

阻止列表

前面的部分显示了如何指定不希望转义的码位或范围的允许列表。 但是,存在一些全局和编码器特定的阻止列表,它们可覆盖允许列表中的某些码位。 阻止列表中的码位总是进行转义,即使它们包含在允许列表中也是如此。

全局阻止列表

全局阻止列表包括专用字符、控制字符、未定义的码位和某些 Unicode 类别(如 Space_Separator 类别)等内容,但不包含 U+0020 SPACE。 例如,即使指定 Unicode 范围 CJK 符号和标点 (U+3000-U+303F) 作为允许列表,也会对 U+3000 IDEOGRAPHIC SPACE 进行转义。

全局阻止列表是一个实现细节,在 .NET 的每个版本中都有所更改。 不要依赖于全局阻止列表中包含(或未包含)的字符。

编码器特定的阻止列表

对于 HTML 编码器,编码器特定的阻止列表的示例包括 '<''&';对于 JSON 编码器,示例包括 '\';对于 URL 编码器,示例包括 '%'。 例如,HTML 编码器总是会转义与号 ('&'),即使与号在 BasicLatin 范围内,并且所有编码器都默认使用 BasicLatin 进行初始化。

序列化所有字符

要最大程度地减少转义,则可以使用 JavaScriptEncoder.UnsafeRelaxedJsonEscaping,如下例所示:

using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Unicode;
Imports System.Text.Encodings.Web
Imports System.Text.Json
Imports System.Text.Unicode
var options3 = new JsonSerializerOptions
{
    Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
    WriteIndented = true
};
jsonString = JsonSerializer.Serialize(weatherForecast, options3);
options = New JsonSerializerOptions With {
    .Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
    .WriteIndented = True
}
jsonString = JsonSerializer.Serialize(weatherForecast1, options)

注意

与默认编码器相比,UnsafeRelaxedJsonEscaping 编码器在允许字符通过而不进行转义方面更加宽松:

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

仅当知道客户端将生成的有效负载解释为 UTF-8 编码的 JSON 时,才使用不安全编码器。 例如,如果服务器在发送响应标头 Content-Type: application/json; charset=utf-8,则可以使用它。 永远不允许将原始 UnsafeRelaxedJsonEscaping 输出发出到 HTML 页面或 <script> 元素。

请参阅