• 使用.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.JsonReferenceLoopHandling.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>"
    }
    
  • 相关阅读:
    vue_组件化开发
    C++ / C# 访问网络共享文件夹
    PetaLinux 设置操作系统内存
    linux 不用./ 直接执行程序
    Visual Studio Code 开发环境搭建 —— C# 扩展插件
    Visual Studio Code 调试项目时传参
    PetaLinux 安装
    Ubuntu 报 "xxx is not in the sudoers file.This incident will be reported" 错误解决方法
    常用 Linux 命令
    搭建 Git 服务器(Ubuntu 系统)
  • 原文地址:https://www.cnblogs.com/deali/p/16044527.html
Copyright © 2020-2023  润新知