.NET 8 PublishAot编译下Newtonsoft.Json反序列化构造函数找不到问题
我使用以下代码进行JSON反序列化:
string json = File.ReadAllText($"./assets/JSON/{mapName}.json"); Console.WriteLine(json); var gridMapData = JsonConvert.DeserializeObject<GridMapData>(json);
Release模式下代码运行正常,但开启PublishAot原生编译后(项目配置如下),抛出Newtonsoft.Json.JsonSerializationException: Unable to find a constructor.异常。已确认JSON内容读取正常,且JSON结构与反序列化类未做任何改动。
项目配置:
<PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net8.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> <PublishAot>true</PublishAot> <InvariantGlobalization>true</InvariantGlobalization> <Platforms>AnyCPU;x64;x86</Platforms> <ApplicationManifest>app.manifest</ApplicationManifest> <AllowUnsafeBlocks>True</AllowUnsafeBlocks> </PropertyGroup>
我尝试过修改GridMapData类,包括添加带参构造函数、无参构造函数、标记[JsonConstructor]属性,但均未解决问题:
public class GridMapData { public int width; public int height; public Dictionary<string, int> cells; // 尝试过的代码(已注释) //public GridMapData(int Width, int Height, Dictionary<string, int> Cells) //{ // this.width = Width; // this.height = Height; // this.cells = Cells; //} //public GridMapData() { } //[JsonConstructor] //public GridMapData() { } }
核心原因
PublishAOT是.NET的原生AOT编译技术,会在编译阶段执行静态代码分析,将未被直接引用的类型、成员、构造函数等从最终程序中裁剪。Newtonsoft.Json依赖反射动态查找构造函数,这类反射调用无法被AOT的静态分析识别,导致无参构造函数被意外裁剪,最终抛出找不到构造函数的异常。
具体解决步骤
1. 保留类的公共无参构造函数
确保GridMapData类拥有明确的公共无参构造函数,这是Newtonsoft.Json反序列化的基础要求:
public class GridMapData { public int width; public int height; public Dictionary<string, int> cells; // 必须保留的公共无参构造函数 public GridMapData() { } }
2. 添加AOT编译保留规则(Runtime Directives)
在项目中创建rd.xml文件,告诉AOT编译器不要裁剪GridMapData的构造函数和成员:
<Directives xmlns="http://schemas.microsoft.com/netfx/2013/01/metadata"> <Application> <Assembly Name="你的项目程序集名称" Dynamic="Required All"> <Type Name="GridMapData" Dynamic="Required All"> <Method Name=".ctor" Dynamic="Required All"/> <!-- 保留无参构造函数 --> <Field Name="width" Dynamic="Required All"/> <Field Name="height" Dynamic="Required All"/> <Field Name="cells" Dynamic="Required All"/> </Type> </Assembly> </Application> </Directives>
将你的项目程序集名称替换为实际的项目名称(通常与项目文件夹名一致),然后在项目文件中引入该文件:
<ItemGroup> <RdXmlFile Include="rd.xml" /> </ItemGroup>
3. 替代方案:改用System.Text.Json(推荐)
Newtonsoft.Json对AOT的支持有限,.NET 8的System.Text.Json原生兼容AOT编译,无需额外配置(只要类符合AOT要求)。替换为以下代码即可:
using System.Text.Json; string json = File.ReadAllText($"./assets/JSON/{mapName}.json"); var gridMapData = JsonSerializer.Deserialize<GridMapData>(json);
同样需要确保GridMapData拥有公共无参构造函数,若使用带参构造函数,可通过[JsonConstructor]标记指定反序列化用的构造函数。
4. 临时应急方案
如果暂时无法修改配置或更换序列化库,可在代码中显式引用无参构造函数,让AOT编译器检测到它并保留:
// 在程序启动或某个必定执行的代码块中添加 _ = new GridMapData();
此方法不够优雅,仅适合临时测试场景。
内容的提问来源于stack exchange,提问作者brenoca

