
本文介绍如何通过 jackson 的 `@jsontypeinfo` 与 `@jsontypename` 注解,结合泛型 wrapper 类,实现按实际 payload 类型自动将外层字段名设为 `"foo"` 或 `"bar"` 的标准化 json 输出。
在构建统一 SOA(面向服务架构)响应格式时,常需将业务数据封装在带有标准头部(如 SoaHeader)的通用容器中,并要求序列化后的 JSON 以 payload 的具体类型名为根键(例如 "foo"、"bar"),而非固定字段名。Jackson 本身不支持直接将泛型实参名(如 PayloadFoo)自动映射为 JSON 键名,但可通过多态序列化机制优雅达成目标。
核心思路是:将 Wrapper
以下为可直接运行的完整示例:
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import lombok.*;
public class JsonSubTypesExample {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper()
.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
public static void main(String[] args) throws JsonProcessingException {
Wrapper fooWrapper = new Wrapper<>(new SoaHeader(), new PayloadFoo("value1"));
Wrapper barWrapper = new Wrapper<>(new SoaHeader(), new PayloadBar("value2"));
System.out.println(OBJECT_MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(fooWrapper));
System.out.println(OBJECT_MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(barWrapper));
}
// 关键:启用多态识别,使用 WRAPPER_OBJECT 方式将 payload 类型名作为外层对象 key
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.WRAPPER_OBJECT, // ← 核心配置:生成 { "foo": { ... } } 结构
property = "type" // 此处仅作占位,WRAPPER_OBJECT 模式下实际忽略该 property 值
)
@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;
}
@JsonTypeName("foo") // ← 序列化时以此名称作为外层 key
@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 等
}
} ✅ 输出效果:
{
"foo" : {
"soaHeader" : { },
"payload" : {
"foo" : "value1"
}
}
}
{
"bar" : {
"soaHeader" : { },
"payload" : {
"bar" : "value2"
}
}
}⚠️ 注意事项:
- @JsonTypeInfo(include = As.WRAPPER_OBJECT) 是实现 { "key": { ... } } 结构的关键,它让 Jackson 将类型名包裹整个对象,而非仅添加一个 "type" 字段;
- @JsonSubTypes 必须显式列出所有可能的子类型及其 name,不可遗漏,否则运行时抛出 InvalidTypeIdException;
- Wrapper
本身不能是 final 或 private(需保证 Jackson 可实例化),且建议保留无参构造器; - 若 payload 类型数量庞大,可考虑自定义 TypeResolverBuilder 或 Serializers 插件实现动态注册,但常规项目中静态声明更清晰可控。
总结:通过 @JsonTypeInfo + @JsonTypeName 组合,不仅能实现题设中“按 inner class 名称动态生成外层 JSON key”的需求,还天然支持反序列化(即 JSON → Java 对象),确保前后端契约一致性,是 Jackson 多态序列化的典型高阶用法。









