Unity 6中源生成式System.Text.Json仍频繁调用GetTypeInfo导致性能不及Newtonsoft.Json的问题排查
我特别理解你的困惑——明明冲着System.Text.Json源生成的AOT友好、低内存、高性能去的,结果反序列化前的初始化耗时反而比Newtonsoft还高,完全没达到预期,这肯定让人头疼。结合你贴的代码和描述,我来帮你拆解下问题:
一、为什么会频繁调用GetTypeInfo?
这绝对不是源生成的正常表现,说明你的序列化流程没有完全走预生成的代码路径,部分类型的处理fallback到了反射模式。
源生成的核心价值就是预先生成所有需要的序列化/反序列化代码,彻底绕过反射;一旦有未被标记为[JsonSerializable]的类型(包括嵌套类型、泛型参数、接口实现等)出现在你的序列化树中,System.Text.Json就会自动切换到反射兜底逻辑,而反射路径必然会频繁调用GetTypeInfo来读取类型元数据,这就是你看到300ms耗时的根源。
二、具体排查&修复步骤
1. 全面检查所有涉及的序列化类型
你已经标记了MainConcreteClass、ConcreteClass2、ConcreteClass3,但要仔细核对以下场景:
- 嵌套类型/泛型参数:如果
MainConcreteClass里有public List<ConcreteClass4> Items,或者public Dictionary<string, SubNestedClass> Config这类字段,那ConcreteClass4、SubNestedClass甚至List<ConcreteClass4>都需要被添加[JsonSerializable]标记(.NET 7+可以自动处理泛型容器的参数类型,但Unity的环境可能需要显式声明)。 - 接口/多态类型:如果
MainConcreteClass里有接口类型的属性,比如public IBaseClass MyData,哪怕你标记了所有接口的实现类,也需要给IBaseClass添加[JsonSerializable],同时在JsonSourceGenerationOptions里配置多态支持(比如添加PolymorphismOptions = new JsonPolymorphismOptions { TypeInfoResolver = ... }),否则接口类型的处理会触发反射。 - 值类型/特殊类型:比如自定义结构体、枚举,是否也需要标记?大部分基础值类型不需要,但如果是你自定义的复杂值类型,最好也加上
[JsonSerializable]。
2. 确认源生成代码是否正确生成
在Unity项目里,检查Library/ScriptAssemblies或者项目根目录的Generated文件夹(取决于你的Unity版本),找AppJsonContext.g.cs这个自动生成的文件:
- 如果这个文件不存在,说明源生成器根本没运行——大概率是项目有编译错误,或者Unity的源生成配置有问题,先解决所有编译报错再试。
- 如果文件存在,打开看里面的代码:正常的源生成代码会有针对每个标记类型的
JsonTypeInfo实现,比如针对MainConcreteClass,会有类似下面的预生成方法,完全没有反射相关的逻辑:
private static JsonTypeInfo<MainConcreteClass> Create_MainConcreteClass(JsonSerializerOptions options) { // 预生成的序列化/反序列化逻辑,直接操作字段/属性,无反射 }
如果生成的代码里有GetTypeInfo或者Reflection相关的调用,说明源生成没有正确处理某些类型,触发了兜底逻辑。
3. 检查Unity的项目配置
Unity对Roslyn源生成的支持需要几个关键配置:
- 确认Player Settings > Other Settings > Configuration > .NET API Compatibility Level设置为
.NET 6或更高(Unity 6默认应该是这个,但手动核对下,旧版本的.NET兼容级别不支持完整的源生成)。 - 确认Scripting Runtime Version设置为
.NET Framework(不是旧的Mono Runtime),Mono对源生成的支持有限。 - 关闭任何可能干扰源生成的第三方插件:有些代码压缩、混淆插件可能会误删源生成的代码,或者阻止源生成器运行,临时禁用这类插件测试下。
4. 验证源生成上下文的使用是否正确
你的代码里JsonSerializer.Deserialize(response.Data, AppJsonContext.Default.MainConcreteClass)看起来是对的,但要排除以下坑:
- 不要在任何地方修改
AppJsonContext.Default的配置:比如如果有人写了AppJsonContext.Default.Options = new JsonSerializerOptions(),这会直接破坏源生成的预配置,触发反射路径。 - 不要混用源生成上下文和手动创建的
JsonSerializerOptions:比如不要调用JsonSerializer.Deserialize(response.Data, typeof(MainConcreteClass), AppJsonContext.Default.Options),这会绕过预生成的类型信息,走反射。
三、IL2CPP环境的额外优化
因为你用的是Unity的IL2CPP AOT编译,还有两个点要注意:
- 在Player Settings > Other Settings > Optimization里开启Optimize Generated Code,这会让IL2CPP更好地优化源生成的代码,进一步提升性能。
- 如果遇到类型被IL2CPP裁剪的问题(比如某些嵌套类型被误删),可以在项目根目录添加
link.xml文件,显式保留序列化相关的类型:
<linker> <assembly fullname="YourAssemblyName"> <type fullname="MainConcreteClass" preserve="all" /> <type fullname="ConcreteClass2" preserve="all" /> <!-- 其他需要保留的类型 --> </assembly> </linker>
最后验证
当你调整完所有配置后,重新编译项目,再跑性能分析:
- GetTypeInfo的调用应该几乎消失(最多只有极少量的初始化调用,不会每个属性都触发)。
- 反序列化的总耗时应该降到几十毫秒级别,内存占用比Newtonsoft低很多,这才是源生成该有的表现。
内容来源于stack exchange

