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

如何配置Jackson实现:若上级对象已存在相同值的unit字段则省略该字段序列化

解决Jackson序列化时省略重复嵌套unit字段的方案

这个问题确实很头疼——嵌套对象里重复的unit字段会让JSON变得臃肿,既浪费传输带宽又不便于阅读。好在Jackson的扩展性很强,我们可以通过自定义序列化逻辑+线程上下文栈来实现“上级已存在相同unit则省略当前unit”的需求,同时保证单个对象序列化时保留unit字段。

核心思路

  1. 用线程安全的栈结构跟踪序列化过程中当前上下文的unit值(因为嵌套层级可能有多级,栈能准确维护进出顺序);
  2. 给unit字段写一个自定义序列化器,序列化时检查当前对象的unit是否和栈顶的上下文unit一致,一致则跳过输出;
  3. 通过BeanSerializerModifier拦截带unit字段的对象序列化,在序列化开始时将当前unit压入栈,结束时弹出,保证上下文正确回溯。

具体实现步骤

1. 实现线程安全的Unit上下文栈

这个栈用来记录序列化过程中每一层的unit值,确保嵌套对象的上下文能正确回溯:

public class UnitSerializationContext {
    private static final ThreadLocal<Stack<String>> unitStack = ThreadLocal.withInitial(Stack::new);

    // 将当前unit压入栈顶
    public static void pushUnit(String unit) {
        if (unit != null) {
            unitStack.get().push(unit);
        }
    }

    // 获取当前栈顶的unit值(即最近的上级unit)
    public static String getCurrentUnit() {
        Stack<String> stack = unitStack.get();
        return stack.isEmpty() ? null : stack.peek();
    }

    // 弹出栈顶的unit值(当前对象序列化完成后回溯上下文)
    public static void popUnit() {
        Stack<String> stack = unitStack.get();
        if (!stack.isEmpty()) {
            stack.pop();
        }
    }

    // 清理上下文,避免线程复用导致的残留
    public static void clear() {
        unitStack.get().clear();
    }
}

2. 编写ConditionalUnitSerializer自定义序列化器

这个序列化器负责判断是否输出当前对象的unit字段:

public class ConditionalUnitSerializer extends JsonSerializer<String> {
    @Override
    public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        String currentContextUnit = UnitSerializationContext.getCurrentUnit();
        // 只有当当前unit与上下文unit不同时,才输出该字段
        if (value != null && !value.equals(currentContextUnit)) {
            gen.writeString(value);
        }
    }

    // 处理多态类型的序列化(如果你的对象有多态需求,保留这个方法)
    @Override
    public void serializeWithType(String value, JsonGenerator gen, SerializerProvider serializers, TypeSerializer typeSer) throws IOException {
        serialize(value, gen, serializers);
    }
}

3. 实现BeanSerializerModifier拦截对象序列化

用来在对象序列化前后维护上下文栈的状态:

public class UnitAwareSerializerModifier extends BeanSerializerModifier {
    @Override
    public JsonSerializer<?> modifySerializer(SerializationConfig config, BeanDescription beanDesc, JsonSerializer<?> serializer) {
        // 只拦截带有unit字段的类
        try {
            beanDesc.getBeanClass().getDeclaredField("unit");
            return new UnitAwareBeanSerializer((BeanSerializer) serializer);
        } catch (NoSuchFieldException e) {
            return serializer;
        }
    }

    private static class UnitAwareBeanSerializer extends BeanSerializer {
        public UnitAwareBeanSerializer(BeanSerializer src) {
            super(src);
        }

        @Override
        public void serialize(Object bean, JsonGenerator gen, SerializerProvider provider) throws IOException {
            int originalStackSize = UnitSerializationContext.unitStack.get().size();
            try {
                // 获取当前对象的unit值(假设所有带unit的类都有getUnit()方法)
                Method getUnitMethod = bean.getClass().getMethod("getUnit");
                String currentUnit = (String) getUnitMethod.invoke(bean);
                
                // 如果当前unit不为空,且和上下文栈顶不同,压入栈
                if (currentUnit != null && !currentUnit.equals(UnitSerializationContext.getCurrentUnit())) {
                    UnitSerializationContext.pushUnit(currentUnit);
                }
                
                // 执行默认序列化逻辑
                super.serialize(bean, gen, provider);
            } catch (NoSuchMethodException e) {
                // 没有getUnit()方法,直接序列化
                super.serialize(bean, gen, provider);
            } catch (Exception e) {
                throw new IOException("Failed to access unit field during serialization", e);
            } finally {
                // 序列化完成后,将栈回溯到原来的大小,避免上下文污染
                while (UnitSerializationContext.unitStack.get().size() > originalStackSize) {
                    UnitSerializationContext.popUnit();
                }
            }
        }
    }
}

4. 注册组件到ObjectMapper

把自定义的序列化器和Modifier注册到Jackson的核心对象中:

// 初始化ObjectMapper
ObjectMapper objectMapper = new ObjectMapper();

// 创建自定义模块
SimpleModule unitModule = new SimpleModule("ConditionalUnitModule");

// 给String类型的unit字段注册自定义序列化器
// 如果你只想给特定类的unit字段用这个序列化器,可以在字段上加@JsonSerialize(using = ConditionalUnitSerializer.class)
unitModule.addSerializer(String.class, new ConditionalUnitSerializer());

// 注册序列化器Modifier
unitModule.setSerializerModifier(new UnitAwareSerializerModifier());

// 注册模块到ObjectMapper
objectMapper.registerModule(unitModule);

5. 使用注意事项

  • 单独序列化对象:当单独序列化某个带unit的对象时,上下文栈是空的,所以unit字段会正常输出,符合你的需求;
  • 上下文清理:在序列化根对象之前,建议调用UnitSerializationContext.clear(),避免线程池复用导致的上下文残留;
  • 非String类型的unit:如果你的unit是枚举或其他类型,只需修改ConditionalUnitSerializer的泛型和序列化逻辑即可;
  • null值处理:如果存在unit为null的场景,可以在序列化器里添加对应的判断逻辑(比如是否输出null的unit)。

测试效果

按照你的需求,序列化根对象时会自动省略与上级相同的unit字段,而单独序列化对象时保留unit:

// 根对象序列化输出(符合预期)
{ "projects": [ { "name": "project1", "unit": "mm", "size": { "x": { "value": 100 }, "y": { "value": 120 }, "z": { "value": 80 } }, "materials": [ { "type": "wood", "size": { "x": { "value": 100 }, "y": { "value": 120 }, "z": { "value": 80 } } } ] } ] }

// 单独序列化material对象输出(符合预期)
{ "type": "wood", "unit": "mm", "size": { "x": { "value": 100 }, "y": { "value": 120 }, "z": { "value": 80 } } }

// 单独序列化size的x字段输出(符合预期)
{ "value": 100, "unit": "mm" }

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 15:19:10