当类具有自定义序列化程序 (Jackson) 时,“SerializerModifier”未处理嵌套对象

Nested objects not being processed by `SerializerModifier` when class has custom Serializer (Jackson)

提问人:Joshua Chambers 提问时间:11/15/2023 最后编辑:Joshua Chambers 更新时间:11/22/2023 访问量:52

问:

好的,所以我构建了一个 功能自定义 ,它限制了序列化的深度,我可以通过创建一个并将其添加到模块中来使用它,如图所示。这效果很好,每次遇到另一个嵌套字段时都会调用,从而创建 完美的实例。但是,当我将自定义序列化程序添加到嵌套类(使用 )时,我的方法永远不会在嵌套字段上运行!BeanSerializerSerializerModifierDepthLimitedSerializer@JsonSerializemodifySerializer

下面是简单的类层次结构,我们将序列化其外部实例:Bar

@Getter @Setter
public class BaseClass {
    private String id;
    private String someBaseProperty;
}
@Getter @Setter
//@JsonSerialize(using = FooSerializer.class)
public class Foo extends BaseClass {
    private String someFooProperty;

}
@Getter @Setter
public class Bar extends BaseClass {
    String someBarProperty;
    Foo fooOfBar;
}

这是简化的自定义序列化程序,您将它传递给它,如果它达到该深度,它只会序列化单个 () 字段,否则,它只是调用:maxDepthidsuper

public class DepthLimitedSerializer extends BeanSerializer {
    public static int DEFAULT_DEPTH = 2;
    private static final ThreadLocal<Integer> maxDepth = ThreadLocal.withInitial(() -> DEFAULT_DEPTH);
    private static final ThreadLocal<Integer> currentDepth = ThreadLocal.withInitial(() -> -1);

    public DepthLimitedSerializer(BeanSerializerBase src, int depth) {
        super(src);
        maxDepth.set(depth);
    }

    @Override
    protected void serializeFields(Object bean, JsonGenerator gen, SerializerProvider provider) throws IOException {
        if (maxDepth.get() < 0 || currentDepth.get() < maxDepth.get()) {
            currentDepth.set(currentDepth.get() + 1);

            super.serializeFields(bean, gen, provider);

            currentDepth.set(currentDepth.get() - 1);
        } else {
            try {
                Arrays.stream(_props).
                    filter(p -> p.getName().equals("id"))
                    .findFirst().orElseThrow()
                    .serializeAsField(bean, gen, provider);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
}

在这个简单的测试中,我们填充 a 和 a 并序列化它们:FooBar

class SerializeTest {
    @Test
    void testSerialization() throws JsonProcessingException {
        Foo foo = new Foo();
        foo.setId("fooID");
        foo.setSomeBaseProperty("someBaseValue");
        foo.setSomeFooProperty("foo property value");

        Bar bar = new Bar();
        bar.setId("barId");
        bar.setSomeBaseProperty("base of Bar");
        bar.setFooOfBar(foo);
        bar.setSomeBarProperty("bar property value");

        String depthOfZero = testSerializationToDepthOf(bar, 0);
        System.out.println("depth of ZERO: " + depthOfZero);
        String depthOfOne = testSerializationToDepthOf(bar, 1);
        System.out.println("depth of ONE: " + depthOfOne);
    }

    String testSerializationToDepthOf(BaseClass model, int depth) throws JsonProcessingException {
        ObjectMapper jackson = new ObjectMapper();
        jackson.enable(SerializationFeature.INDENT_OUTPUT);

        SimpleModule module = new SimpleModule("TestModule");
        module.setSerializerModifier(new BeanSerializerModifier() {
            @Override
            public JsonSerializer<?> modifySerializer(SerializationConfig config, BeanDescription beanDesc,
                                                      JsonSerializer<?> serializer) {
                if (BaseClass.class.isAssignableFrom(beanDesc.getType().getRawClass())) {
                    return new DepthLimitedSerializer((BeanSerializerBase) serializer, depth);
                }
                return super.modifySerializer(config, beanDesc, serializer);
            }
        });

        jackson.registerModule(module);
        return jackson.writeValueAsString(model);
    }
}

这给了我们我们所期望的,请注意,使用 of 时,我们只得到嵌套的字段,这是正确的,但在 a of 时,我们得到完整的输出。这适用于任意深度,当它运行时,该方法以及每个嵌套模型的所有方法都运行!depth0idFoo fooOfBardepth1modifySerializerDepthLimitedSerializer

depth of ZERO: {
  "id" : "barId",
  "someBaseProperty" : "base of Bar",
  "someBarProperty" : "bar property value",
  "fooOfBar" : {
    "id" : "fooID"
  }
}
depth of ONE: {
  "id" : "barId",
  "someBaseProperty" : "base of Bar",
  "someBarProperty" : "bar property value",
  "fooOfBar" : {
    "id" : "fooID",
    "someBaseProperty" : "someBaseValue",
    "someFooProperty" : "foo property value"
  }
}

然而!如果这些子类之一需要自定义序列化,并且我尝试为特定类添加自定义序列化程序,例如:BaseClassFoo

public class FooSerializer extends StdSerializer<Foo> {

    public FooSerializer() {
        super(Foo.class);
    }

    public void serialize(Foo foo, JsonGenerator jgen, SerializerProvider serializerProvider)
            throws IOException {
        jgen.writeStartObject();
        jgen.writeStringField("custom", foo.getId() + "!" + foo.getSomeFooProperty());
        jgen.writeEndObject();
    }
}

我取消了上面一行的注释并将自定义序列化程序分配给该类,然后我的方法和所有其他方法永远不会为我的自定义注释的嵌套类运行,因此被忽略,自定义序列化程序始终运行:@JsonSerialize(using = FooSerializer.class)modifySerializerdepth

depth of ZERO: {
  "id" : "barId",
  "someBaseProperty" : "base of Bar",
  "someBarProperty" : "bar property value",
  "fooOfBar" : {
    "custom" : "fooID!foo property value"
  }
}
depth of ONE: {
  "id" : "barId",
  "someBaseProperty" : "base of Bar",
  "someBarProperty" : "bar property value",
  "fooOfBar" : {
    "custom" : "fooID!foo property value"
  }
}

我所期望的行为是让该方法仍然运行,然后让调用找到 ,但它只为顶级对象(以及其他对象,不是那么自定义注释)运行。modifySerializersuper.modifySerializer()FooSerializer

如何实现此行为?我尝试制作自定义序列化程序 DepthLimitedSerializerSerializer',但到目前为止,我无法协调它们并让它们以正确的顺序协同工作!显然,我不能使用注释来分配序列化程序,但我该怎么做呢?extends , but they are of different types of Jackson

谢谢大家。

java 序列化 jackson jackson-databind jackson-modules

评论


答:

0赞 Joshua Chambers 11/22/2023 #1

这是我最终的工作解决方案。我几乎认为这是一个错误,即通过注释分配的序列化程序没有通过附加到 .文档中没有任何内容说他们会“修改序列化程序,但哦,不会修改那些序列化程序”。我的解决方案有点笨拙,但它有效。SerializerModifiersObjectMapper

首先,如果通过注释找到序列化程序,则添加运行修饰符的自定义。无论如何,这都会发生在重写方法的开头。BeanSerializerFactory

public class FixedBeanSerializerFactory extends BeanSerializerFactory {
    public FixedBeanSerializerFactory(SerializerFactoryConfig config) {
        super(config);
    }

    @Override
    public JsonSerializer<Object> createSerializer(SerializerProvider prov, JavaType origType)
            throws JsonMappingException {

        // Very first thing, let's check if there is explicit serializer annotation:
        final SerializationConfig config = prov.getConfig();
        BeanDescription beanDesc = config.introspect(origType);
        JsonSerializer<?> ser = findSerializerFromAnnotation(prov, beanDesc.getClassInfo());
        if (ser != null) {
            if (_factoryConfig.hasSerializerModifiers()) {
                for (BeanSerializerModifier mod : _factoryConfig.serializerModifiers()) {
                    ser = mod.modifySerializer(config, beanDesc, ser);
                }
            }
            return (JsonSerializer<Object>) ser;
        }

        return super.createSerializer(prov, origType);
    }

    @Override
    public SerializerFactory withConfig(SerializerFactoryConfig config) {
        return new FixedBeanSerializerFactory(config);
    }
}

现在,这比它需要的要长得多,主要是因为我不得不复制很多代码。你问为什么?因为 in 中的方法是 final,所以我无法覆盖它。相反,我被迫覆盖并从中复制一堆代码。DepthLimitedSerializerBeanSerailzierserializeBeanSerializerBeanSerializerBase

public class DepthLimitedSerializer extends BeanSerializerBase {
    public static final int DEFAULT_DEPTH = 2;

    private static final ObjectMapper jackson = new ObjectMapper();
    private static final ThreadLocal<Integer> maxDepth = ThreadLocal.withInitial(() -> DEFAULT_DEPTH);
    private static final ThreadLocal<Integer> currentDepth = ThreadLocal.withInitial(() -> -1);

    public static JsonSerializer<Object> forSerializer(JsonSerializer<Object> serializer,
                                                       BeanDescription beanDesc,
                                                       int depth) {

        if (serializer instanceof BeanSerializerBase) {
            return new DepthLimitedSerializer((BeanSerializerBase) serializer, depth);

        } else {
            BeanSerializerBuilder builder = new BeanSerializerBuilder(beanDesc);
            JavaType type = jackson.constructType(serializer.handledType());
            BeanPropertyWriter[] properties = {};
            BeanPropertyWriter[] filteredProperties = {};
            maxDepth.set(depth);
            return new DepthLimitedSerializer(serializer, type, builder, properties, filteredProperties);
        }
    }

    protected JsonSerializer<Object> src;

    public DepthLimitedSerializer(BeanSerializerBase src, int depth) {
        super(src);
        this.src = src;
        maxDepth.set(depth);
    }

    protected DepthLimitedSerializer(DepthLimitedSerializer depthLimitedSerializer,
                                     BeanPropertyWriter[] properties, BeanPropertyWriter[] filteredProperties) {
        super(depthLimitedSerializer, properties, filteredProperties);
        this.src = depthLimitedSerializer;
    }

    protected DepthLimitedSerializer(JsonSerializer<Object> src, JavaType type, BeanSerializerBuilder builder,
                                     BeanPropertyWriter[] properties, BeanPropertyWriter[] filteredProperties) {
        super(type, builder, properties, filteredProperties);
        this.src = src;
    }

    protected DepthLimitedSerializer(BeanSerializerBase src, ObjectIdWriter objectIdWriter, Object filterId) {
        super(src, objectIdWriter, filterId);
        this.src = src;
    }

    protected DepthLimitedSerializer(BeanSerializerBase src,
                                     BeanPropertyWriter[] properties, BeanPropertyWriter[] filteredProperties) {
        super(src, properties, filteredProperties);
        this.src = src;
    }

    @Override
    public BeanSerializerBase withObjectIdWriter(ObjectIdWriter objectIdWriter) {
        return new DepthLimitedSerializer(this, objectIdWriter, _propertyFilterId);
    }

    @Override
    protected BeanSerializerBase withByNameInclusion(Set<String> toIgnore, Set<String> toInclude) {
        return null;
    }

    @Override // @since 2.11.1
    protected BeanSerializerBase withProperties(BeanPropertyWriter[] properties,
                                                BeanPropertyWriter[] filteredProperties) {
        return new DepthLimitedSerializer(this, properties, filteredProperties);
    }

    @Override
    protected void serializeFields(Object bean, JsonGenerator gen, SerializerProvider provider) throws IOException {
        if (maxDepth.get() < 0 || currentDepth.get() < maxDepth.get()) {
            currentDepth.set(currentDepth.get() + 1);

            super.serializeFields(bean, gen, provider);

            currentDepth.set(currentDepth.get() - 1);
        } else {
            try {
                JsonDepthLimited depthAnnotation = bean.getClass().getAnnotation(JsonDepthLimited.class);
                String defaultFieldName = depthAnnotation.defaultField();
                boolean includeNulls = depthAnnotation.includeNulls();
                if (StringUtils.isNotEmpty(defaultFieldName)) {
                    if (!includeNulls) {
                        Arrays.stream(_props).
                                filter(p -> p.getName().equals(defaultFieldName))
                                .findFirst().orElseThrow()
                                .serializeAsField(bean, gen, provider);
                    } else {
                        Arrays.stream(_props).forEach(p -> {
                            try {
                                if (p.getName().equals(defaultFieldName)) {
                                    p.serializeAsField(bean, gen, provider);
                                } else {
                                    gen.writeNullField(p.getName());
                                }
                            } catch (Exception e) {
                                throw new RuntimeException(e);
                            }
                        });
                    }

                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

    @Override
    protected BeanSerializerBase asArraySerializer() {
        if ((_objectIdWriter == null)
                && (_anyGetterWriter == null)
                && (_propertyFilterId == null)
        ) {
            return new BeanAsArraySerializer(this);
        }
        // already is one, so:
        return this;
    }

    @Override
    public BeanSerializerBase withFilterId(Object filterId) {
        return new DepthLimitedSerializer(this, _objectIdWriter, filterId);
    }

    @Override
    public void serialize(Object bean, JsonGenerator gen, SerializerProvider provider)
            throws IOException {

        if (src == null || src instanceof BeanSerializerBase) {
            if (_objectIdWriter != null) {
                gen.setCurrentValue(bean); // [databind#631]
                _serializeWithObjectId(bean, gen, provider, true);
                return;
            }
            gen.writeStartObject(bean);
            if (_propertyFilterId != null) {
                serializeFieldsFiltered(bean, gen, provider);
            } else {
                serializeFields(bean, gen, provider);
            }
            gen.writeEndObject();

        } else {
            if (maxDepth.get() < 0 || currentDepth.get() < maxDepth.get()) {
                Class<?> t = src.handledType();
                src.serialize(t.cast(bean), gen, provider);
            } else {
                gen.writeNull();
            }
        }

    }

    @Override public String toString() {
        return "DepthAwareSerializer for " + handledType().getName();
    }
}

此注释标记了包含的类,并允许我们传递一些参数。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface JsonDepthLimited {
    String defaultField() default StringUtils.EMPTY;
    boolean includeNulls() default false;
}

以下是我们要演示的 POJO 层次结构:

@JsonDepthLimited(defaultField = "id")
@Getter @Setter
public class BaseClass {
    private String id;
    private String someBaseProperty;
}
@Getter @Setter
@JsonSerialize(using = FooSerializer.class)
public class Foo extends BaseClass {
    private String someFooProperty;
}
@Getter @Setter
public class Bar extends BaseClass {
    String someBarProperty;
    Foo fooOfBar;
}

自定义序列化程序:Foo

public class FooSerializer extends StdSerializer<Foo> {
    protected FooSerializer() {
        super(Foo.class);
    }

    public void serialize(Foo foo, JsonGenerator jgen, SerializerProvider serializerProvider)
            throws IOException {
        jgen.writeStartObject();
        jgen.writeStringField("custom", foo.getId() + "!" + foo.getSomeFooProperty());
        jgen.writeEndObject();
    }
}

最后,演示:


class SerializeTest {
    @Test
    void testSerialization() throws JsonProcessingException {
        Foo foo = new Foo();
        foo.setId("fooID");
        foo.setSomeBaseProperty("someBaseValue");
        foo.setSomeFooProperty("foo property value");

        Bar bar = new Bar();
        bar.setId("barId");
        bar.setSomeBaseProperty("base of Bar");
        bar.setFooOfBar(foo);
        bar.setSomeBarProperty("bar property value");

        String depthOfZero = testSerializationToDepthOf(bar, 0);
        System.out.println("depth of ZERO: " + depthOfZero);
        String depthOfOne = testSerializationToDepthOf(bar, 1);
        System.out.println("depth of ONE: " + depthOfOne);
    }

    String testSerializationToDepthOf(BaseClass model, int depth) throws JsonProcessingException {
        ObjectMapper jackson = new ObjectMapper();
        jackson.enable(SerializationFeature.INDENT_OUTPUT);
        SerializerFactoryConfig factoryConfig = new SerializerFactoryConfig();
        jackson.setSerializerFactory(new FixedBeanSerializerFactory(factoryConfig));

        if (depth >= 0) {
            SimpleModule fragmentModule = new SimpleModule("FragmentModule");
            BeanSerializerModifier modifier = new BeanSerializerModifier() {
                @Override
                public JsonSerializer<?> modifySerializer(SerializationConfig config, BeanDescription beanDesc,
                                                          JsonSerializer<?> serializer) {
                    if (beanDesc.getClassAnnotations().has(JsonDepthLimited.class)) {

                        return DepthLimitedSerializer
                                .forSerializer((JsonSerializer<Object>) serializer, beanDesc, depth);
                    }
                    return serializer;
                }
            };

            fragmentModule.setSerializerModifier(modifier);
            jackson.registerModule(fragmentModule);
        }

        return jackson.writeValueAsString(model);
    }
}

在最终输出中,您可以看到自定义序列化程序在 设置为 时运行,但在深度设置为 时不运行(使用 null),完全按照应有的方式运行。depth10

depth of ZERO: {
  "id" : "barId",
  "someBaseProperty" : "base of Bar",
  "someBarProperty" : "bar property value",
  "fooOfBar" : null
}
depth of ONE: {
  "id" : "barId",
  "someBaseProperty" : "base of Bar",
  "someBarProperty" : "bar property value",
  "fooOfBar" : {
    "custom" : "fooID!foo property value"
  }
}