⭐️ Furion v4 版本支持【所有历史版本】无缝升级,一套代码兼容 .NET 5+ ⭐️
Skip to main content

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,只需以下步骤即可:

  1. 安装 Microsoft.AspNetCore.Mvc.NewtonsoftJson 拓展,并在 Startup.cs 中注册
services.AddControllersWithViews()        .AddNewtonsoftJson();
  1. 实现 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.5.12 long 类型序列化时转 string

有时候我们需要将 long 类型序列化时转为 string 类型,防止 JavaScript 出现精度溢出问题,这个时候可以尝试使用以下方法解决:

  • System.Text.Json 方式
services.AddControllersWithViews()        .AddJsonOptions(options =>        {            options.JsonSerializerOptions.Converters.Add(new LongJsonConverter()); // 配置过长的整数类型返回前端会丢失精度的问题        });

创建 LongJsonConverter 类:

public class LongJsonConverter : JsonConverter<long>{    public override long Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)    {        // 这里做处理,前端传入的 long 类型可能为 string 类型,或者 number 类型        return reader.TokenType == JsonTokenType.String                ? long.Parse(reader.GetString())                : reader.GetInt64();    }    public override void Write(Utf8JsonWriter writer, long value, JsonSerializerOptions options)    {        writer.WriteStringValue($"{value}");    }}
  • Newtonsoft.Json 方式
.AddNewtonsoftJson(options =>{     // long类型序列化时转 string ,防止 Javascript 精度溢出     options.SerializerSettings.Converters.Add(new LongJsonConverter());})

创建 LongJsonConverter 类:

public class LongJsonConverter : JsonConverter<long>{    public override void WriteJson(JsonWriter writer, long value, JsonSerializer serializer)    {        serializer.Serialize(writer, value.ToString());    }    public override long ReadJson(JsonReader reader, Type objectType, long existingValue, bool hasExistingValue, JsonSerializer serializer)    {        JToken jt = JValue.ReadFrom(reader);        return jt.Value<long>();    }}

23.6 DataTableDataSetTuple 元组等序列化问题

由于默认 Furion 采用 System.Text.Json 进行序列化,但是不支持复杂类型,如 DataTableDataSetTuple 元组,所以需要更换成 NewtonsoftJson 即可,见 JSON 序列化 - 23.5.1 自定义序列化提供器

23.7 System.Text.JsonNewtonsoft.Json 完整差异化对比

https://docs.microsoft.com/zh-cn/dotnet/standard/serialization/system-text-json-migrate-from-newtonsoft-how-to?pivots=dotnet-5-0

23.8 反馈与建议

与我们交流

给 Furion 提 Issue

演练场