.NET Core中的System.Text.Json如何反转义Unicode字符串

90

使用 JsonSerializer.Serialize(obj) 会生成一个带转义字符的字符串,但我想要未转义的版本。例如:

using System;
using System.Text.Json;

public class Program
{
    public static void Main()
    {
        var a = new A{Name = "你好"};
        var s = JsonSerializer.Serialize(a);
        Console.WriteLine(s);
    }
}

class A {
    public string Name {get; set;}
}

将产生一个字符串{"Name":"\u4F60\u597D"},但我想要{"Name":"你好"}

我在https://dotnetfiddle.net/w73vnO创建了一个代码片段
请帮忙。


除了使数据变得不易读取外,默认的转义还会使json的大小增加40%。当您缓存或发送大型json有效负载时,这是一个重要的变化。 - Yogi
4个回答

110

您需要设置JsonSerializer选项,以便不对这些字符串进行编码。

JsonSerializerOptions jso = new JsonSerializerOptions();
jso.Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping;

当您调用Serialize方法时,请传递这些选项。

var s = JsonSerializer.Serialize(a, jso);        

完整代码:

JsonSerializerOptions jso = new JsonSerializerOptions();
jso.Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping;

var a = new A { Name = "你好" };
var s = JsonSerializer.Serialize(a, jso);        
Console.WriteLine(s);

结果:

在此输入图片描述

如果您需要在控制台中打印结果,则可能需要安装其他语言。请参考这里


6
当我发现这个链接时,简直不敢相信自己的眼睛:https://learn.microsoft.com/zh-cn/dotnet/api/system.text.encodings.web.javascriptencoder.unsaferelaxedjsonescaping?view=netcore-3.0这是默认编码器的极其令人惊讶的行为。 - arkod
3
如果可行的话,我建议考虑更安全的替代方案,因为使用此功能可能存在潜在问题。请参阅https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-how-to?view=netcore-3.0#serialize-all-characters。 - ahsonkhan
4
这些文档从未提到为什么要避免对它们进行序列化。为什么在这些字符已经有特定的转义序列(例如双引号"和控制字符)时,还要决定将所有内容编码?! - gregsdennis
4
使用“不安全”的编码方式并不是答案,ahsonkhan的答案是正确的。 - Karrde
我不得不注册StackOverflow才能点赞!我同意这并不安全,但它回答了问题。 - Tono Nam
显示剩余2条评论

47
要更改 JsonSerializer 的转义行为,您可以通过在 JsonSerializerOptions 上设置 Encoder 属性,向 JsonSerializer 传递自定义的 JavascriptEncoder。默认行为考虑到安全性,并且JsonSerializer 会进行过度转义以增强防御深度。如果您只想转义特定非拉丁语言的某些“字母数字”字符,建议使用 Create 工厂方法创建 JavascriptEncoder 而不是使用 UnsafeRelaxedJsonEscaping 编码器。参考链接:https://learn.microsoft.com/en-us/dotnet/api/system.text.json.jsonserializeroptions.encoder?view=netcore-3.0#System_Text_Json_JsonSerializerOptions_Encoder
JsonSerializerOptions options = new JsonSerializerOptions
{
    Encoder = JavaScriptEncoder.Create(UnicodeRanges.BasicLatin, UnicodeRanges.CjkUnifiedIdeographs)
};

var a = new A { Name = "你好" };
var s = JsonSerializer.Serialize(a, options);
Console.WriteLine(s);

这样做可以保持某些安全措施,例如,HTML敏感字符将继续被转义。

我建议谨慎使用System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,因为它仅进行最小限度的转义(这就是其名称中带有“不安全”一词的原因)。如果要创建的JSON写入到UTF-8编码的磁盘文件中,或者作为Web请求的一部分,明确将字符集设置为utf-8(并且不会嵌入到HTML组件中),那么可能可以使用它。

请参阅API文档中的注释部分:https://learn.microsoft.com/en-us/dotnet/api/system.text.encodings.web.javascriptencoder.unsaferelaxedjsonescaping?view=netcore-3.0#remarks

如果您期望/需要所有语言保持未转义状态,也可以考虑指定UnicodeRanges.All。但是这仍然会转义某些容易存在安全漏洞的ASCII字符。

JsonSerializerOptions options = new JsonSerializerOptions
{
    Encoder = JavaScriptEncoder.Create(UnicodeRanges.All)
};

获取更多信息和代码示例,请参阅https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-how-to?view=netcore-3.0#customize-character-encoding

请注意警告


6
@Joey,我知道这个回答来得有点晚,但它应该成为被采纳的答案。 - Ruben Bartelink
1
这是更新后的文档页面链接:https://learn.microsoft.com/zh-cn/dotnet/standard/serialization/system-text-json-character-encoding?view=netcore-3.0 - ahsonkhan
如果你正在创建的JSON被写入到UTF-8编码的磁盘文件中,或者它是Web请求的一部分,该请求明确将字符集设置为utf-8(并且不会潜在地嵌入到HTML组件中),那么使用这个选项可能是可以接受的。[UnsafeRelaxedJsonEscaping] - imsan

13

使用:

JsonSerializerOptions options = new JsonSerializerOptions
{
    Encoder = JavaScriptEncoder.Create(UnicodeRanges.All)
};

2
那就是这样,谢谢。 - mkb

11
你可以使用: System.Text.RegularExpressions.Regex.Unescape(string) 来反转义unicode字符。 https://learn.microsoft.com/en-us/dotnet/api/system.text.regularexpressions.regex.unescape 从原问题中更新的示例:
using System;
using System.Text.Json;

public class Program
{
    public static void Main()
    {
            var a = new A{Name = "你好"};
            var s = JsonSerializer.Serialize(a);
        
            var unescaped = System.Text.RegularExpressions.Regex.Unescape(s);

            Console.WriteLine(s);
            Console.WriteLine(unescaped);
        }
}

class A {
    public string Name {get; set;}
}

输出:

{"Name":"\u4F60\u597D"}
{"Name":"你好"}

这是适用于所有情况的最佳选择。 - code5

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接