如何用Jackson与Java Records处理含公共字段的多态JSON?
最优方案:Jackson + Java Records 实现多类型JSON序列化/反序列化
背景说明
Java Records 设计上不支持继承,但我们需要处理包含公共字段的多类型JSON结构(比如所有形状都有type、width、height,不同形状还有专属字段)。当前方案存在重复代码、使用List<Object>类型不安全的问题,以下是基于Jackson框架的优化方案。
步骤1:定义公共Shape接口
利用Records可以实现接口的特性,定义一个包含公共字段方法的接口,既约束公共行为,又让Drawing类的shapes字段具备类型安全性。
@JsonTypeInfo( use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type" // 指定JSON中用来区分类型的字段 ) @JsonSubTypes({ @JsonSubTypes.Type(value = ShapeA.class, name = "shapeA"), @JsonSubTypes.Type(value = ShapeB.class, name = "shapeB"), @JsonSubTypes.Type(value = ShapeC.class, name = "shapeC") }) public interface Shape { String type(); Integer width(); Integer height(); }
@JsonTypeInfo:告诉Jackson通过type字段识别具体类型@JsonSubTypes:建立type字段值与具体Record类的映射关系
步骤2:实现各Shape Record类
每个形状Record实现Shape接口,保留自身专属字段即可,Jackson会自动匹配字段名(无需额外@JsonProperty,除非字段名与JSON不一致):
@JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_NULL) public record ShapeA(String type, Integer width, Integer height, String label) implements Shape {} @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.NON_NULL) public record ShapeB(String type, Integer width, Integer height, Integer length) implements Shape {} @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.NON_NULL) public record ShapeC(String type, Integer width, Integer height, String url, String color) implements Shape {}
步骤3:简化Drawing Record类
去掉自定义反序列化器,直接使用List<Shape>作为字段类型,实现类型安全:
@JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.NON_NULL) public record Drawing(String name, List<Shape> shapes) {}
步骤4:验证序列化/反序列化
测试代码无需自定义反序列化逻辑,Jackson会自动完成类型映射,同时保证序列化后的JSON与原始格式一致:
@Test void fromJsonToJson() throws IOException { File f = new File(this.getClass().getResource("/test.json").getFile()); String jsonFromFile = Files.readString(f.toPath()); ObjectMapper mapper = new ObjectMapper(); Drawing drawing = mapper.readValue(jsonFromFile, Drawing.class); String jsonFromObject = mapper.writeValueAsString(drawing); assertAll( () -> assertEquals("testDrawing", drawing.name()), () -> assertTrue(drawing.shapes().get(0) instanceof ShapeA), () -> assertTrue(drawing.shapes().get(1) instanceof ShapeB), () -> assertTrue(drawing.shapes().get(2) instanceof ShapeC) ); // 验证JSON结构完全一致(忽略空格换行差异) JsonNode originalNode = mapper.readTree(jsonFromFile); JsonNode generatedNode = mapper.readTree(jsonFromObject); assertEquals(originalNode, generatedNode); }
方案优势
- 类型安全:用
List<Shape>替代List<Object>,避免强制类型转换,编译阶段即可发现类型错误 - 减少重复代码:通过接口约束公共字段,利用Jackson原生多类型注解替代自定义反序列化器,消除冗余代码
- 原生序列化支持:Jackson自动根据
type字段完成序列化,保证输出JSON与原始格式完全一致 - 符合Records设计理念:保留Records的不可变值语义,同时通过接口实现行为一致性
内容的提问来源于stack exchange,提问作者Frank
相关产品推荐
相关产品推荐

