Java运行时动态选报表并完整写入JSON文件的实现方案
解决Gson序列化子类字段丢失的问题
问题根源
Gson默认基于编译时类型进行序列化操作。当你用父类Report的引用指向子类实例时,它只会序列化父类中定义的id、value字段,子类的专属字段(比如agreementId、tierId)会被忽略——因为编译时类型是Report,Gson不会主动识别运行时的实际子类类型。
可行解决方案
方案1:使用RuntimeTypeAdapterFactory(推荐支持反序列化的场景)
Gson的gson-extras包提供了RuntimeTypeAdapterFactory,可以自动识别运行时类型并序列化所有字段,还会在JSON中添加类型标识字段,方便后续反序列化。
步骤1:引入依赖
如果用Maven,在pom.xml中添加:
<dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson-extras</artifactId> <version>2.10.1</version> <!-- 使用最新稳定版本 --> </dependency>
步骤2:代码实现
首先定义父类和子类:
abstract class Report { public String id; public String value; // 构造器、getter/setter按需添加 } class ReportA extends Report { public String agreementId; } class ReportB extends Report { public String tierId; } class ReportC extends Report { public String instanceId; }
然后配置Gson并动态生成报表:
import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.typeadapters.RuntimeTypeAdapterFactory; import java.nio.file.Files; import java.nio.file.Paths; import java.nio.charset.StandardCharsets; public class ReportWriter { public static void main(String[] args) throws Exception { if (args.length == 0) { throw new IllegalArgumentException("请传入报表类型参数:ReportA/ReportB/ReportC"); } String reportType = args[0]; // 注册子类与类型标识的映射 RuntimeTypeAdapterFactory<Report> typeFactory = RuntimeTypeAdapterFactory.of(Report.class, "reportType") .registerSubtype(ReportA.class, "ReportA") .registerSubtype(ReportB.class, "ReportB") .registerSubtype(ReportC.class, "ReportC"); Gson gson = new GsonBuilder() .registerTypeAdapterFactory(typeFactory) .setPrettyPrinting() .create(); // 根据参数动态创建对应报表实例 Report report = switch (reportType) { case "ReportA" -> { ReportA a = new ReportA(); a.id = "REP-A-001"; a.value = "月度协议报表"; a.agreementId = "AG-2024-001"; yield a; } case "ReportB" -> { ReportB b = new ReportB(); b.id = "REP-B-001"; b.value = "层级统计报表"; b.tierId = "TIER-LEVEL-3"; yield b; } case "ReportC" -> { ReportC c = new ReportC(); c.id = "REP-C-001"; c.value = "实例运行报表"; c.instanceId = "INST-PROD-007"; yield c; } default -> throw new IllegalArgumentException("未知报表类型:" + reportType); }; // 序列化并写入文件 String json = gson.toJson(report); Files.write(Paths.get(reportType + ".json"), json.getBytes(StandardCharsets.UTF_8)); } }
序列化后的JSON会包含类型标识字段(比如"reportType": "ReportA"),反序列化时也能自动还原为对应子类实例。
方案2:序列化时指定运行时类型(仅序列化场景首选)
如果只需要序列化(不需要反序列化),可以直接在调用toJson时传入对象的实际类型,无需额外依赖。
修改序列化部分的代码:
// 使用默认Gson实例即可 Gson gson = new GsonBuilder().setPrettyPrinting().create(); // ... 前面创建report的逻辑不变 // 传入report.getClass()指定运行时类型 String json = gson.toJson(report, report.getClass()); Files.write(Paths.get(reportType + ".json"), json.getBytes(StandardCharsets.UTF_8));
这个方案代码最简洁,完全不需要额外配置,Gson会根据传入的实际类型序列化所有字段。
方案3:自定义TypeAdapter(精细控制场景)
如果需要自定义序列化逻辑(比如不想添加类型标识字段),可以编写自定义的TypeAdapter手动处理不同子类的序列化。
import com.google.gson.TypeAdapter; import com.google.gson.stream.JsonWriter; import com.google.gson.stream.JsonReader; import java.io.IOException; import com.google.gson.Gson; class ReportTypeAdapter extends TypeAdapter<Report> { private final Gson gson = new Gson(); @Override public void write(JsonWriter out, Report value) throws IOException { // 根据实际类型选择序列化方式 if (value instanceof ReportA) { gson.toJson(value, ReportA.class, out); } else if (value instanceof ReportB) { gson.toJson(value, ReportB.class, out); } else if (value instanceof ReportC) { gson.toJson(value, ReportC.class, out); } else { throw new IOException("无法识别的报表类型"); } } @Override public Report read(JsonReader in) throws IOException { // 若不需要反序列化,可抛出异常或返回null throw new UnsupportedOperationException("反序列化未实现"); } }
配置Gson时注册该Adapter:
Gson gson = new GsonBuilder() .registerTypeAdapter(Report.class, new ReportTypeAdapter()) .setPrettyPrinting() .create();
这个方案适合需要完全控制序列化过程的场景,比如过滤某些字段或自定义JSON结构。
内容的提问来源于stack exchange,提问作者Willy
相关产品推荐
相关产品推荐

