使用@JsonSerialize转换器时@JsonUnwrapped为何失效?
这个问题其实是Jackson在处理注解优先级和执行顺序上的一个典型坑点,我之前也碰到过类似的情况。先给你拆解一下背后的原因,再给你可行的解决方案。
核心原因
Jackson在处理@JsonUnwrapped时,逻辑是先解析被包裹对象的内部结构,再把结构展开到父对象中;但如果被包裹的类上标注了@JsonSerialize(converter = ...),Jackson会优先执行转换器,把原对象直接转换成另一个类型的实例,然后把这个转换后的实例当作一个普通属性来序列化——这就跳过了原本的解包逻辑,自然@JsonUnwrapped就被忽略了。
先把你未写完的示例代码补全,方便理解:
public class RootClass { @JsonUnwrapped public NestedClass nested = new NestedClass(); public static void main(String[] args) throws JsonProcessingException { RootClass obj = new RootClass(); System.out.println(new ObjectMapper().writeValueAsString(obj)); } } @JsonSerialize(converter = NestedClassConverter.class) class NestedClass { public String foo = "fooVal"; public String bar = "barVal"; } class NestedClassConverter extends StdConverter<NestedClass, NestedClassWrapper> { @Override public NestedClassWrapper convert(NestedClass value) { NestedClassWrapper wrapper = new NestedClassWrapper(); wrapper.setNestedFoo(value.foo); wrapper.setNestedBar(value.bar); return wrapper; } } class NestedClassWrapper { private String nestedFoo; private String nestedBar; // getter/setter public String getNestedFoo() { return nestedFoo; } public void setNestedFoo(String nestedFoo) { this.nestedFoo = nestedFoo; } public String getNestedBar() { return nestedBar; } public void setNestedBar(String nestedBar) { this.nestedBar = nestedBar; } }
这段代码的输出会是:
{"nested":{"nestedFoo":"fooVal","nestedBar":"barVal"}}
而不是我们期望的解包后的结果:
{"nestedFoo":"fooVal","nestedBar":"barVal"}
解决方案
方案一:将@JsonSerialize移到字段上(推荐)
把@JsonSerialize注解从NestedClass类上移除,转而标注在RootClass的nested字段上,和@JsonUnwrapped一起使用:
public class RootClass { @JsonUnwrapped @JsonSerialize(converter = NestedClassConverter.class) public NestedClass nested = new NestedClass(); // main方法不变 } // 移除NestedClass上的@JsonSerialize注解 class NestedClass { public String foo = "fooVal"; public String bar = "barVal"; } // Converter和Wrapper类不变
修改后再执行,输出就会变成我们想要的解包格式:
{"nestedFoo":"fooVal","nestedBar":"barVal"}
为什么这样有效?
当两个注解都标注在字段上时,Jackson的执行顺序变成了:先通过转换器把NestedClass转换成NestedClassWrapper,再对转换后的NestedClassWrapper实例应用@JsonUnwrapped的解包逻辑,这样就兼顾了转换和解包的需求。
方案二:自定义序列化器(复杂场景可选)
如果因为某些原因必须在NestedClass类上保留@JsonSerialize,可以自定义一个序列化器,在序列化过程中手动处理解包逻辑。不过这种方式代码量更大,一般只在特殊场景下使用。
举个简单的例子,自定义序列化器来模拟解包:
class CustomNestedSerializer extends JsonSerializer<NestedClass> { @Override public void serialize(NestedClass value, JsonGenerator gen, SerializerProvider serializers) throws IOException { // 手动写入解包后的字段 gen.writeStringField("nestedFoo", value.foo); gen.writeStringField("nestedBar", value.bar); } }
然后把类上的注解改成:
@JsonSerialize(using = CustomNestedSerializer.class) class NestedClass { // ... 属性不变 }
这种方式直接跳过了转换器,手动控制序列化内容,也能实现解包效果,但灵活性较低,后续字段变更时需要同步修改序列化器。
内容的提问来源于stack exchange,提问作者Kirill

