23. JSON 序列化
以下内容仅限 Furion 1.16.0 +
版本使用。
23.1 什么是 JSON
JSON (JavaScript Object Notation, JS 对象标记) 是一种轻量级的数据交换格式。它基于 ECMAScript (w3c 制定的 js 规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。
简单来说,JSON,是一种数据格式,在与后端的数据交互中有较为广泛的应用。
23.2 关于序列化库
目前在 C# 语言中有两个主流的 JSON
序列化操作库:
System.Text.Json
:.NET Core
内置JSON
序列化库,也是Furion
框架默认实现Newtonsoft.Json
:目前使用人数最多的JSON
序列化库,需要安装Microsoft.AspNetCore.Mvc.NewtonsoftJson
拓展包
由于目前 System.Text.Json
相比 Newtonsoft.Json
功能和稳定性有许多不足之处,比如循环引用问题在 System.Text.Json
无解。但在 .NET 6
之后得到解决。
Furion
框架为了解决多种序列化工具配置和用法上的差异问题,抽象出了 IJsonSerializerProvider
接口。
23.3 IJsonSerializerProvider
接口
Furion
框架提供了 IJsonSerializerProvider
接口规范,同时要求实现该接口的实体都必须采用单例模式,该接口定义代码如下:
namespace Furion.JsonSerialization{ /// <summary> /// Json 序列化提供器 /// </summary> public interface IJsonSerializerProvider { /// <summary> /// 序列化对象 /// </summary> /// <param name="value"></param> /// <param name="jsonSerializerOptions"></param> /// <returns></returns> string Serialize(object value, object jsonSerializerOptions = default); /// <summary> /// 反序列化字符串 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="json"></param> /// <param name="jsonSerializerOptions"></param> /// <returns></returns> T Deserialize<T>(string json, object jsonSerializerOptions = default); /// <summary> /// 返回读取全局配置的 JSON 选项 /// </summary> /// <returns></returns> object GetSerializerOptions(); }}
SystemTextJsonSerializerProvider
类是 IJsonSerializerProvider
接口的默认实现,在应用启动时已默认注册。
23.4 如何使用
23.4.1 获取序列化对象
Furion
框架提供了两种方式获取 IJsonSerializerProvider
实例:
- 构造函数注入
IJsonSerializerProvider
- 静态类
JSON.GetJsonSerializer()
方式,查看 JSON 静态类
如:
using Furion.DynamicApiController;using Furion.JsonSerialization;namespace Furion.Application{ public class JsonDemo : IDynamicApiController { private readonly IJsonSerializerProvider _jsonSerializer; private readonly IJsonSerializerProvider _jsonSerializer2; public JsonDemo(IJsonSerializerProvider jsonSerializer) { _jsonSerializer = jsonSerializer; _jsonSerializer2 = JSON.GetJsonSerializer(); } }}
23.4.2 序列化对象
public string GetText(){ return _jsonSerializer.Serialize(new { Id = 1, Name = "Furion" });}
23.4.3 反序列化字符串
public object GetObject(){ var json = "{\"Id\":1,\"Name\":\"Furion\"}"; var obj = _jsonSerializer.Deserialize<object>(json); return obj;}
System.Text.Json
默认反序列化大小写敏感,也就是不完全匹配的属性名称不会自动赋值。这时候我们可以全局配置或单独配置。
- 全局配置
services.AddControllersWithViews() .AddJsonOptions(options => { options.JsonSerializerOptions.PropertyNameCaseInsensitive = true; });
- 单独配置
var obj = _jsonSerializer.Deserialize<object>(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
23.4.4 序列化更多配置
Furion
框架不推荐一个框架中有多种序列化实现类,也就是说使用 System.Text.Json
就不要使用 Newtonsoft.Json
,反之亦然。
如需配置更多选项,只需创建 JsonSerializerOptions
配置对象即可,如:
var json = _jsonSerializer.Serialize(new { Id = 1, Name = "Furion" }, new JsonSerializerOptions { WriteIndented = true });
23.5 高级用法
23.5.1 自定义序列化提供器
正如上文所说,Furion
默认的 IJsonSerializerProvider
实现方式是 System.Text.Json
库,如需替换为 Newtonsoft.Json
,只需以下步骤即可:
- 安装
Microsoft.AspNetCore.Mvc.NewtonsoftJson
拓展,并在Startup.cs
中注册
services.AddControllersWithViews() .AddNewtonsoftJson();
- 实现
IJsonSerializerProvider
提供器
using Furion.DependencyInjection;using Furion.JsonSerialization;using Newtonsoft.Json;namespace Furion.Core{ /// <summary> /// Newtonsoft.Json 实现 /// </summary> public class NewtonsoftJsonSerializerProvider : IJsonSerializerProvider, ISingleton { /// <summary> /// 序列化对象 /// </summary> /// <param name="value"></param> /// <param name="jsonSerializerOptions"></param> /// <returns></returns> public string Serialize(object value, object jsonSerializerOptions = null) { return JsonConvert.SerializeObject(value, (jsonSerializerOptions ?? GetSerializerOptions()) as JsonSerializerSettings); } /// <summary> /// 反序列化字符串 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="json"></param> /// <param name="jsonSerializerOptions"></param> /// <returns></returns> public T Deserialize<T>(string json, object jsonSerializerOptions = null) { return JsonConvert.DeserializeObject<T>(json, (jsonSerializerOptions ?? GetSerializerOptions()) as JsonSerializerSettings); } /// <summary> /// 返回读取全局配置的 JSON 选项 /// </summary> /// <returns></returns> public object GetSerializerOptions() { return App.GetOptions<MvcNewtonsoftJsonOptions>()?.SerializerSettings; } }}
23.5.2 序列化属性名大写(属性原样输出)
System.Text.Json
方式
services.AddControllersWithViews() .AddJsonOptions(options => { options.JsonSerializerOptions.PropertyNamingPolicy = null; // options.JsonSerializerOptions.DictionaryKeyPolicy = null; // 配置 Dictionary 类型序列化输出 });
Newtonsoft.Json
方式
services.AddControllersWithViews() .AddNewtonsoftJson(options => { options.SerializerSettings.ContractResolver = new DefaultContractResolver(); });
采用 Newtonsoft.Json
方式接口返回值能够正常输出,但是 Swagger
界面中的 Example Values
依然显示小写字母开头的属性,这时只需要再添加 System.Text.Json
配置即可,如:
.AddJsonOptions(options => { options.JsonSerializerOptions.PropertyNamingPolicy = null; });
主要原因是 Swagger
拓展包底层依赖了 System.Text.Json
。
23.5.3 时间格式化
System.Text.Json
方式
services.AddControllersWithViews() .AddJsonOptions(options => { options.JsonSerializerOptions.Converters.AddDateFormatString("yyyy-MM-dd HH:mm:ss"); });
如果使用使用了 DateTimeOffset
类型,那么可以设置 .AddDateFormatString("yyyy-MM-dd HH:mm:ss", true)
第二个参数为 true
,自动转换成本地时间。
如果使用了 Mysql
数据库,且使用了 Pomelo.EntityFrameworkCore.MySql
包,那么会出现时区问题,比如少 8 小时,可以尝试配置第二个参数为 true
。
需引用 System.Text.Json
命名空间。
Newtonsoft.Json
方式
services.AddControllersWithViews() .AddNewtonsoftJson(options => { options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss"; });
23.5.4 忽略循环引用
System.Text.Json
方式
services.AddControllersWithViews() .AddJsonOptions(options => { options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles; });
在 .NET 5
中,System.Text.Json
并不支持处理循环引用问题,以上的解决方案仅限用于 .NET 6 Preview 2+
。😂
需引用 System.Text.Json
命名空间。
Newtonsoft.Json
方式
services.AddControllersWithViews() .AddNewtonsoftJson(options => { options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; });
23.5.5 包含成员字段序列化
System.Text.Json
方式
services.AddControllersWithViews() .AddJsonOptions(options => { options.JsonSerializerOptions.IncludeFields = true; });
需引用 System.Text.Json
命名空间。
Newtonsoft.Json
方式
无需配置。
23.5.6 允许尾随逗号
System.Text.Json
方式
services.AddControllersWithViews() .AddJsonOptions(options => { options.JsonSerializerOptions.AllowTrailingCommas = true; });
需引用 System.Text.Json
命名空间。
Newtonsoft.Json
方式
无需配置。
23.5.7 允许注释
System.Text.Json
方式
services.AddControllersWithViews() .AddJsonOptions(options => { options.JsonSerializerOptions.ReadCommentHandling = JsonCommentHandling.Skip; });
需引用 System.Text.Json
命名空间。
Newtonsoft.Json
方式
无需配置。
23.5.8 处理乱码问题
System.Text.Json
方式
services.AddControllersWithViews() .AddJsonOptions(options => { options.JsonSerializerOptions.Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping; });
需引用 System.Text.Json
命名空间。
Newtonsoft.Json
方式
无需配置。
23.5.9 不区分大小写
System.Text.Json
方式
services.AddControllersWithViews() .AddJsonOptions(options => { options.JsonSerializerOptions.PropertyNameCaseInsensitive = true; });
需引用 System.Text.Json
命名空间。
Newtonsoft.Json
方式
这里只列举常用见的序列化配置,如需查看更多配置,可查阅 System.Text.Json 文档
23.5.10 忽略特定属性序列化
有时候我们不希望对象中某个对象被序列化出来或者不想在 Swagger
中显示,这时候只需要在属性贴该特性即可:
[Newtonsoft.Json.JsonIgnore] // 针对 Newtonsoft[System.Text.Json.Serialization.JsonIgnore] // 针对 System.Text.Jsonpublic string PropertyName {get; set;}
23.5.11 动态对象属性名大写问题
有时候使用了动态对象后发现属性名出现了大写情况(首字母),这个时候可以尝试使用以下方法解决:
.AddNewtonsoftJson(options =>{ options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();})
23.6 DataTable
、DataSet
、Tuple
元组等序列化问题
由于默认 Furion
采用 System.Text.Json
进行序列化,但是不支持复杂类型,如 DataTable
、 DataSet
、Tuple
元组,所以需要更换成 NewtonsoftJson
即可,见 JSON 序列化 - 23.5.1 自定义序列化提供器
23.7 System.Text.Json
和 Newtonsoft.Json
完整差异化对比
23.8 反馈与建议
给 Furion 提 Issue。