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

使用@JsonSerialize转换器时@JsonUnwrapped为何失效?

问题解答:@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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 11:09:12