Jackson中类含自定义Serializer时,SerializerModifier不处理嵌套对象
问题原因
Jackson序列化器的优先级规则是:类上@JsonSerialize(using=...)指定的序列化器 > 通过BeanSerializerModifier修改的序列化器。当你给Foo类添加@JsonSerialize(using = FooSerializer.class)后,Jackson会直接使用FooSerializer,跳过你的DepthLimitedSerializer替换逻辑——你的Modifier只处理了BeanSerializerBase类型的序列化器,而FooSerializer不属于这个类型,所以modifySerializer方法不会对它生效,深度限制逻辑自然被忽略。
可行解决方案
方案1:在自定义序列化器中集成深度控制逻辑
如果FooSerializer是你自己维护的,最简单的方式是把深度限制的逻辑抽成工具类,然后在FooSerializer中复用这个逻辑,和DepthLimitedSerializer保持一致的判断逻辑。
步骤1:抽离深度控制工具类
public class DepthControl { public static final ThreadLocal<Integer> maxDepth = ThreadLocal.withInitial(() -> 2); public static final ThreadLocal<Integer> currentDepth = ThreadLocal.withInitial(() -> -1); // 判断是否需要完整序列化 public static boolean shouldSerializeFull() { return maxDepth.get() < 0 || currentDepth.get() < maxDepth.get(); } // 进入嵌套层级 public static void enterDepth() { currentDepth.set(currentDepth.get() + 1); } // 退出嵌套层级 public static void exitDepth() { currentDepth.set(currentDepth.get() - 1); } }
步骤2:修改DepthLimitedSerializer使用工具类
public class DepthLimitedSerializer extends BeanSerializer { public DepthLimitedSerializer(BeanSerializerBase src, int depth) { super(src); DepthControl.maxDepth.set(depth); } @Override protected void serializeFields(Object bean, JsonGenerator gen, SerializerProvider provider) throws IOException { if (DepthControl.shouldSerializeFull()) { DepthControl.enterDepth(); super.serializeFields(bean, gen, provider); DepthControl.exitDepth(); } else { try { Arrays.stream(_props) .filter(p -> p.getName().equals("id")) .findFirst().orElseThrow() .serializeAsField(bean, gen, provider); } catch (Exception e) { throw new RuntimeException(e); } } } }
步骤3:修改FooSerializer加入深度控制
public class FooSerializer extends StdSerializer<Foo> { public FooSerializer() { super(Foo.class); } @Override public void serialize(Foo foo, JsonGenerator gen, SerializerProvider provider) throws IOException { if (DepthControl.shouldSerializeFull()) { DepthControl.enterDepth(); gen.writeStartObject(); // 序列化父类字段 gen.writeStringField("id", foo.getId()); gen.writeStringField("someBaseProperty", foo.getSomeBaseProperty()); // 序列化当前类字段 gen.writeStringField("someFooProperty", foo.getSomeFooProperty()); gen.writeEndObject(); DepthControl.exitDepth(); } else { // 仅序列化id gen.writeStartObject(); gen.writeStringField("id", foo.getId()); gen.writeEndObject(); } } }
方案2:用包装器包装自定义序列化器(无需修改原有FooSerializer)
如果不想修改已有的FooSerializer,可以写一个通用的深度限制包装器,在BeanSerializerModifier中把FooSerializer替换为包装后的实例。
步骤1:实现通用深度包装序列化器
import java.lang.reflect.Method; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; public class DepthWrappingSerializer<T> extends JsonSerializer<T> { private final JsonSerializer<T> delegate; public DepthWrappingSerializer(JsonSerializer<T> delegate) { this.delegate = delegate; } @Override public void serialize(T value, JsonGenerator gen, SerializerProvider provider) throws IOException { if (DepthControl.shouldSerializeFull()) { DepthControl.enterDepth(); delegate.serialize(value, gen, provider); DepthControl.exitDepth(); } else { // 仅序列化id字段(通用实现,可根据实际调整) gen.writeStartObject(); try { Method getIdMethod = value.getClass().getMethod("getId"); String id = (String) getIdMethod.invoke(value); gen.writeStringField("id", id); } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { throw new RuntimeException("Failed to serialize id field", e); } gen.writeEndObject(); } } }
步骤2:修改BeanSerializerModifier处理FooSerializer
import com.fasterxml.jackson.databind.BeanDescription; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializationConfig; import com.fasterxml.jackson.databind.ser.BeanSerializerBase; import com.fasterxml.jackson.databind.ser.BeanSerializerModifier; public class DepthLimitModifier extends BeanSerializerModifier { private final int maxDepth; public DepthLimitModifier(int maxDepth) { this.maxDepth = maxDepth; } @Override public JsonSerializer<?> modifySerializer(SerializationConfig config, BeanDescription beanDesc, JsonSerializer<?> serializer) { // 处理默认BeanSerializer if (serializer instanceof BeanSerializerBase) { return new DepthLimitedSerializer((BeanSerializerBase) serializer, maxDepth); } // 处理自定义的FooSerializer if (serializer instanceof FooSerializer) { return new DepthWrappingSerializer<>(serializer); } return serializer; } }
方案3:让自定义序列化器实现ContextualSerializer感知深度
通过实现ContextualSerializer接口,让FooSerializer在序列化时感知当前的深度上下文,动态调整序列化行为。
实现上下文感知的FooSerializer
import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.BeanProperty; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.ser.ContextualSerializer; import com.fasterxml.jackson.databind.ser.std.StdSerializer; public class FooSerializer extends StdSerializer<Foo> implements ContextualSerializer { private Integer currentDepth; private Integer maxDepth; // 默认构造器 public FooSerializer() { super(Foo.class); } // 带深度参数的构造器(内部使用) private FooSerializer(Integer currentDepth, Integer maxDepth) { super(Foo.class); this.currentDepth = currentDepth; this.maxDepth = maxDepth; } @Override public void serialize(Foo foo, JsonGenerator gen, SerializerProvider provider) throws IOException { if (maxDepth < 0 || currentDepth < maxDepth) { // 完整序列化 gen.writeStartObject(); gen.writeStringField("id", foo.getId()); gen.writeStringField("someBaseProperty", foo.getSomeBaseProperty()); gen.writeStringField("someFooProperty", foo.getSomeFooProperty()); gen.writeEndObject(); } else { // 仅序列化id gen.writeStartObject(); gen.writeStringField("id", foo.getId()); gen.writeEndObject(); } } @Override public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException { // 从ThreadLocal获取当前深度和最大深度 Integer current = DepthControl.currentDepth.get(); Integer max = DepthControl.maxDepth.get(); return new FooSerializer(current, max); } }
注意事项
- 使用
ThreadLocal时,务必在序列化完成后清理,避免内存泄漏:try { DepthControl.maxDepth.set(1); String json = objectMapper.writeValueAsString(bar); // 处理json } finally { DepthControl.maxDepth.remove(); DepthControl.currentDepth.remove(); } - 如果你的项目中有多个自定义序列化器,方案2的通用包装器可以扩展支持更多类型,无需逐个修改序列化器代码。
内容的提问来源于stack exchange,提问作者Joshua Chambers
相关产品推荐
相关产品推荐

