Appearance
JSON 序列化:最常见的问题所在
在 .NET AOT (Ahead-of-Time) 编译模式下,JSON 处理是开发者最容易遇到的“陷阱”。问题的根源在于,常规的 JSON 序列化操作在默认情况下严重依赖于运行时的反射 (Reflection)。
问题根源:反射与代码裁剪 (Trimming) 的冲突
当你调用标准的 System.Text.Json.JsonSerializer.Serialize(object) 或 Deserialize<T>(jsonString) 方法时,JsonSerializer 在内部会执行以下操作:
- 类型发现:通过反射检查对象
T的类型。 - 成员扫描:查找并获取
T类型的所有公共属性 (properties) 和字段 (fields)。 - 值操作:动态地调用这些属性的
get和set方法来读取或写入数据。
然而,AOT 编译的一个关键步骤是 裁剪 (Trimming)。编译器会分析你的代码,移除所有它认为“未被直接引用”的部分,以最大程度地减小最终生成的可执行文件体积。
这就导致了一个致命的冲突: 如果你代码中只通过泛型参数 T 的形式使用了某个类,比如 JsonSerializer.Deserialize<MyPoco>(json),AOT 编译器无法在编译时“看到”任何直接调用 MyPoco 构造函数或其属性的代码。因此,它会做出一个“合理”的推断:MyPoco 的构造函数和属性都是无用代码,并将它们从最终的程序中裁剪掉。
结果就是在运行时,JsonSerializer 尝试通过反射去寻找那些已经被移除的构造函数和属性,最终导致:
- 反序列化失败:可能抛出异常,或者更隐蔽地返回一个所有属性都为
null或默认值的空对象。 - 序列化失败:可能生成一个空的 JSON 对象
{},因为找不到任何可供序列化的属性。
✅ 解决方案:拥抱源码生成器 (Source Generator)
为了从根本上解决这一问题,.NET 团队提供了官方的解决方案:JSON 源码生成器 (JSON Source Generator)。
它的核心思想非常清晰:将原本在运行时通过反射完成的工作,全部提前到编译时来完成。
源码生成器就像一个编译插件,它会在编译期间:
- 扫描你的代码,找到你明确标记需要序列化的类型。
- 为这些类型自动生成高效、无反射的序列化和反序列化逻辑代码。
- 将这些生成的 C# 代码与你的项目代码一起编译成本机代码。
这样一来,运行时就不再需要任何反射,因为所有关于如何处理特定类型(如 MyPoco)的指令都已经是静态的、预先编译好的代码了。
如何正确操作:JsonSerializerContext 指南
在 AOT 项目中,正确处理 JSON 的步骤如下:
创建
JsonSerializerContext你需要定义一个partial类,让它继承自System.Text.Json.Serialization.JsonSerializerContext。然后,使用[JsonSerializable]特性来“注册”所有你计划要序列化或反序列化的数据模型。csharpusing System.Text.Json.Serialization; namespace MyAotApp; // 1. [必需] 定义一个 partial 类继承 JsonSerializerContext。 // 2. [必需] 为每一个需要序列化的 POCO (Plain Old C# Object) 添加 [JsonSerializable] 特性。 [JsonSerializable(typeof(WeatherForecast))] [JsonSerializable(typeof(UserSession))] [JsonSerializable(typeof(ApiErrorResponse))] // ... internal partial class AppJsonSerializerContext : JsonSerializerContext { // 这个类是空的,它的另一部分代码将由 Source Generator 在编译时自动创建。 }partial关键字:这是必需的,它允许源码生成器在另一个自动生成的文件中为AppJsonSerializerContext“补充”上序列化逻辑代码。[JsonSerializable]特性:这是你给编译器的明确指令,告诉它:“请为WeatherForecast这个类型保留所有成员,并为它生成序列化代码。”
调用
JsonSerializer时传入 Context 在进行序列化或反序列化时,必须调用那些接受JsonSerializerContext或JsonTypeInfo<T>参数的JsonSerializer重载方法。csharpvar jsonString = "{\"date\":\"2024-01-01T00:00:00\",\"temperatureC\":25}"; var myForecast = new WeatherForecast { /* ... */ }; // --- 错误的方式 (在AOT中会失败) --- // var forecast = JsonSerializer.Deserialize<WeatherForecast>(jsonString); // var json = JsonSerializer.Serialize(myForecast); // --- ✅ 正确的方式 (使用源码生成器,AOT安全) --- // 反序列化 var forecast = JsonSerializer.Deserialize( jsonString, AppJsonSerializerContext.Default.WeatherForecast // 传入生成的类型信息 ); // 序列化 var json = JsonSerializer.Serialize( myForecast, AppJsonSerializerContext.Default.WeatherForecast // 同样传入类型信息 ); // 对于需要指定类型的泛型方法,也可以这样做: var forecast2 = JsonSerializer.Deserialize<WeatherForecast>( jsonString, AppJsonSerializerContext.Default // 传入Context实例 ); var json2 = JsonSerializer.Serialize( myForecast, AppJsonSerializerContext.Default // 传入Context实例 );