如何配置Jackson实现:若上级对象已存在相同值的unit字段则省略该字段序列化
解决Jackson序列化时省略重复嵌套unit字段的方案
这个问题确实很头疼——嵌套对象里重复的unit字段会让JSON变得臃肿,既浪费传输带宽又不便于阅读。好在Jackson的扩展性很强,我们可以通过自定义序列化逻辑+线程上下文栈来实现“上级已存在相同unit则省略当前unit”的需求,同时保证单个对象序列化时保留unit字段。
核心思路
- 用线程安全的栈结构跟踪序列化过程中当前上下文的unit值(因为嵌套层级可能有多级,栈能准确维护进出顺序);
- 给
unit字段写一个自定义序列化器,序列化时检查当前对象的unit是否和栈顶的上下文unit一致,一致则跳过输出; - 通过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
相关产品推荐
相关产品推荐

