使用.Net6中的System.Text.Json遇到几个常见问题及解决方案
前言
以前.NetCore是不内置JSON库的,所以大家都用Newtonsoft
的JSON库,而且也确实挺好用的,不过既然官方出了标准库,那更方便更值得我们多用用,至少不用每次都nuget安装Newtonsoft.Json
库了。
不过日常开发使用中会有一些问题,本文记录一下解决方法,欢迎交流~
字符编码问题
默认的 System.Text.Json
序列化的时候会把所有的非 ASCII 的字符进行转义,这就会导致很多时候我们的一些非 ASCII 的字符就会变成 \uxxxx
这样的形式,很多场景下并不太友好,我们可以配置字符编码来解决被转义的问题。
例子:
-
var testObj=new {
-
Name = "测试",
-
Value = 123
-
};
-
-
var json = JsonSerializer.Serialize(testObj);
-
Console.WriteLine(json);
输出
{"Name":"\u6D4B\u8BD5","Value":123}
在我们序列化的时候,可以指定一个 JsonSerializeOptions
,而这个 JsonSerializeOptions
中有一个 Encoder 我们可以用来配置支持的字符编码,不支持的就会被转义,而默认只支持 ASCII 字符。
所以解决方法如下:
-
var json = JsonSerializer.Serialize(testObj, new JsonSerializerOptions()
-
{
-
Encoder = JavaScriptEncoder.Create(UnicodeRanges.All)
-
})
-
Console.WriteLine(json);
输出结果
{"Name":"测试","Value":123}
字符转义问题
对于一些包含 html 标签的文本即使指定了所有字符集也会被转义,这是出于安全考虑。如果觉得不需要转义也可以配置,配置使用 JavaScriptEncoder.UnsafeRelaxedJsonEscaping
即可。
示例代码
-
var testObj = new {
-
Name = "测试",
-
Value = 123,
-
Code = "<p>test</p>"
-
};
-
-
var json = JsonSerializer.Serialize(testObj, new JsonSerializerOptions {
-
Encoder = JavaScriptEncoder.Create(UnicodeRanges.All)
-
});
-
Console.WriteLine(json);
输出
{"Name":"测试","Value":123,"Code":"\u003Cp\u003Etest\u003C/p\u003E"}
可以看到HTML代码被转义了,这很明显就不行
解决方法
-
var json = JsonSerializer.Serialize(testObj, new JsonSerializerOptions {
-
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
-
});
输出结果
{"Name":"测试","Value":123,"Code":"<p>test</p>"}
搞定!
对象套娃递归问题
这个问题在我之前的一篇文章中有详细说到:Asp-Net-Core开发笔记:接口返回json对象出现套娃递归问题
当时我是用Newtonsoft.Json
来解决的,不过当我把这篇文章发布到博客园之后,有大佬指出.NetCore
标准库System.Text.Json
中也有解决这个问题的方法,于是我这里也来记录一下~
首先建立几个实体类
-
internal class EntityBase {
-
public string Id { get; set; }
-
}
-
internal class CrawlTask : EntityBase {
-
/// <summary>
-
/// 爬虫名称
-
/// </summary>
-
public string Name { get; set; }
-
-
/// <summary>
-
/// 创建这个爬虫的用户
-
/// </summary>
-
public User User { get; set; }
-
-
/// <summary>
-
/// 用户ID
-
/// </summary>
-
public string? UserId { get; set; }
-
}
-
internal class User : EntityBase {
-
/// <summary>
-
/// 用户名
-
/// </summary>
-
public string Name { get; set; }
-
-
/// <summary>
-
/// 用户创建的爬虫
-
/// </summary>
-
public List<CrawlTask> CrawlTasks { get; set; }
-
}
然后用模拟数据来重现问题
-
//模拟数据
-
var crawlTask = new CrawlTask { Name = "爬虫名称", UserId= "0f3d4b2f-3b4e-4d08-8f4c-0009a316f041" };
-
var user = new User { Name = "用户名", CrawlTasks = new List<CrawlTask> { crawlTask } };
-
crawlTask.User = user;
-
-
// 输出
-
var json2 = JsonSerializer.Serialize(crawlTask);
-
Console.WriteLine(json2);
输出结果,直接报错
-
Unhandled exception. System.Text.Json.JsonException: A possible object cycle was detected. This can either be due to a cycle or if the object depth is larger tha
-
n the maximum allowed depth of 64. Consider using ReferenceHandler.Preserve on JsonSerializerOptions to support cycles. Path: $.User.CrawlTasks.User.CrawlTasks.U
-
ser.CrawlTasks.User.CrawlTasks.User.CrawlTasks.User.CrawlTasks.User.CrawlTasks.User.CrawlTasks.User.CrawlTasks.User.CrawlTasks.User.CrawlTasks.User.CrawlTasks.Us
-
er.CrawlTasks.User.CrawlTasks.User.CrawlTasks.User.CrawlTasks.User.CrawlTasks.User.CrawlTasks.User.CrawlTasks.User.CrawlTasks.User.CrawlTasks.Name.
-
...
我们都知道了这是对象的套娃递归问题了
所以接下来直接上解决方法
-
var json2 = JsonSerializer.Serialize(crawlTask,new JsonSerializerOptions {
-
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
-
WriteIndented = true,
-
ReferenceHandler = ReferenceHandler.IgnoreCycles
-
});
-
Console.WriteLine(json2);
ReferenceHandler.IgnoreCycles
方式是.Net6新增加的,可以实现和Newtonsoft.Json
里ReferenceLoopHandling.Ignore
差不多的效果。
最终输出效果如下
-
{
-
"Name": "爬虫名称",
-
"User": {
-
"Name": "用户名",
-
"CrawlTasks": [
-
null
-
],
-
"Id": null
-
},
-
"UserId": "0f3d4b2f-3b4e-4d08-8f4c-0009a316f041",
-
"Id": null
-
}
可以看到导致套娃递归的属性变成了null
不过这个和Newtonsoft.Json
实现的效果还是有点差异的
在我之前的文章里,Newtonsoft.Json
实现的效果是
-
{
-
"name": "test crawl123",
-
"user": {
-
"name": "string",
-
"crawlTasks": null,
-
"id": "0f3d4b2f-3b4e-4d08-8f4c-0009a316f041"
-
},
-
"userId": "0f3d4b2f-3b4e-4d08-8f4c-0009a316f041",
-
"id": "4d52d83b-f3ec-47c6-ab26-e241c09c14d1"
-
}
可以看到的是,crawlTask.user.crawlTasks
这个属性有差别,System.Text.Json
是一个数组,然后里面有一个null
对象,而Newtonsoft.Json
是把这个属性直接置为null
相比之下,我更喜欢Newtonsoft.Json
的实现,因为在前端解析的时候可以很清晰的得到一个空对象,而不是装着空对象的数组(有点绕口……
后记
说实话,JSON处理还是Python这类动态语言比较方便
像上面那些问题,Python加个ensure_ascii
参数就行(虽然C#也不难)
比如
-
import json
-
-
test_obj = {
-
"name": "测试",
-
"value": 123,
-
"code": "<p>test</p>"
-
}
-
print(json.dumps(test_obj, ensure_ascii=False))
有时我还喜欢加个indent
参数,这样输出来的JSON字符串更好看
json.dumps(test_obj, ensure_ascii=False, indent=2)
输出结果
-
{
-
"Name": "测试",
-
"Value": 123,
-
"Code": "<p>test</p>"
-