
本文介绍如何利用 jackson 的 `@jsontypeinfo` 与 `@jsontypename` 实现泛型 wrapper 类的序列化,使不同子类型(如 payloadfoo、payloadbar)自动映射为对应字段名(如 `"foo"`、`"bar"`),生成符合 soa 规范的嵌套 json 结构。
在构建统一 API 响应格式(如 SOA 风格)时,常需将业务数据封装进标准化容器,例如包含 soaHeader 和 payload 的通用 Wrapper 类。但默认情况下,Jackson 序列化 Wrapper
关键在于:@JsonRootName 仅作用于类本身,不支持动态绑定泛型实际类型;@JsonTypeName 单独使用也无法改变外层包装结构。正确解法是结合 多态序列化机制 —— 将 Wrapper
以下为完整实现方案:
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
// 推荐:关闭空 Bean 报错,避免无属性类序列化失败
private static final ObjectMapper mapper = new ObjectMapper()
.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);首先,为 Wrapper 类添加多态配置:
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.WRAPPER_OBJECT, // ✅ 关键:以类型名为外层字段名包裹整个对象
property = "" // 空字符串表示不额外添加类型字段,直接用类型名作 key
)
@JsonSubTypes({
@JsonSubTypes.Type(value = PayloadFoo.class, name = "foo"),
@JsonSubTypes.Type(value = PayloadBar.class, name = "bar")
})
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class Wrapper {
private SoaHeader soaHeader;
private T payload;
} ⚠️ 注意:include = JsonTypeInfo.As.WRAPPER_OBJECT 是实现 "foo": { ... } 结构的核心——它让 Jackson 将整个对象序列化为 { "foo": { "soaHeader": ..., "payload": ... } },而非 { "type": "foo", "soaHeader": ..., "payload": ... }。
接着,为每个 payload 子类标注 @JsonTypeName:
@JsonTypeName("foo")
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class PayloadFoo {
private String foo;
}
@JsonTypeName("bar")
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class PayloadBar {
private String bar;
}
@Data
public static class SoaHeader {
// 可扩展 header 字段,如 timestamp、traceId 等
}最后,序列化示例:
public static void main(String[] args) throws JsonProcessingException {
Wrapper fooWrapper = new Wrapper<>(
new SoaHeader(),
new PayloadFoo("hello")
);
Wrapper barWrapper = new Wrapper<>(
new SoaHeader(),
new PayloadBar("world")
);
System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(fooWrapper));
// 输出:
// {
// "foo" : {
// "soaHeader" : { },
// "payload" : {
// "foo" : "hello"
// }
// }
// }
System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(barWrapper));
// 输出:
// {
// "bar" : {
// "soaHeader" : { },
// "payload" : {
// "bar" : "world"
// }
// }
// }
} ✅ 优势总结:
- 类型安全:编译期绑定 PayloadFoo → "foo",避免字符串硬编码错误;
- 可扩展:新增 PayloadBaz 仅需添加 @JsonTypeName("baz") 及 @JsonSubTypes.Type 条目;
- 符合规范:天然生成 SOA 所需的“类型名即资源路径”的 JSON 层级结构。
⚠️ 注意事项:
- @JsonTypeInfo 必须加在 Wrapper 类上(而非字段),且 include = WRAPPER_OBJECT 不可省略;
- 若 Wrapper 需反序列化,还需确保 payload 字段能被 Jackson 正确推断泛型类型(建议配合 TypeReference 或 @JsonUnwrapped 进阶处理);
- Lombok 注解(如 @Data)需与 Jackson 兼容,推荐使用 lombok.config 启用 lombok.anyConstructor.addConstructorProperties=true 避免构造器问题。









