You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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);
}

方案优势

  1. 类型安全:用List<Shape>替代List<Object>,避免强制类型转换,编译阶段即可发现类型错误
  2. 减少重复代码:通过接口约束公共字段,利用Jackson原生多类型注解替代自定义反序列化器,消除冗余代码
  3. 原生序列化支持:Jackson自动根据type字段完成序列化,保证输出JSON与原始格式完全一致
  4. 符合Records设计理念:保留Records的不可变值语义,同时通过接口实现行为一致性

内容的提问来源于stack exchange,提问作者Frank

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 02:25:25