可以使用 Jackson 从 class 创建类型树吗?

Can Jackson be used to create a type-tree from a class?

我正在寻找一种使用 Jackson 来创建类型树而不是值树的方法。 我原以为这是可能的,但我 运行 遇到一个问题,即 Jackson 在遇到值为 null 的字段时创建 NullNode 对象。

我感兴趣的是类型,而不是值。目前我正在执行以下解决方法,因为我无法为 Jackson 提供 class 来构建树:

package org.example.jackson.typetree;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class FunctioningTest {
    static class SomeClass{
        public Integer integer;
        public String string;
    }

    @Test
    void extractTypeTree() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        final Constructor<?> constructor = SomeClass.class.getDeclaredConstructor();
        final Object o =  constructor.newInstance((Object[]) null);
        final var jsonNode = new ObjectMapper().valueToTree(o);
        final var fields = jsonNode.fields();
        while(fields.hasNext()){
            final var child = fields.next();
            if(child.getValue().isIntegralNumber() || child.getValue().isTextual()){
                System.out.println("Nice!");
            }else if(child.getValue().isNull()){
                System.out.println("Booooh...!");
            }
        }
    }
}

正如我所提到的,这会产生一个 ObjectNode 实例,该实例具有 2 个 NullNode 实例作为子实例。然而,我想要的是获得一个带有 IntNode/NumericNode 的 ObjectNode 和一个 TextNode,而不管 SomeClass 实例中字段的实际值如何。

杰克逊可以用来做这个吗?

我又花了几个小时试图找出一种遍历 Class 的方法,我找到了一种对我来说足够好的方法。

只是强调一下,Jackson 并没有创建类型树。相反,它有几种可用于访问序列化程序的访问者类型。 Jackson 中的序列化程序是将 class 的实例序列化为某些输出(JSON、YAML 等)的方法。当访问这些序列化程序时,我们可以依次检查对象的属性并依次访问这些对象。

以下是此机制的基本实现,可以对其进行调整以手动构建类型树。我使用了 jackson-databind:2.12.1 但我相信该机制是在 jackson-databind: 2.2.0.

中引入的
package org.example.jackson.type.tree;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.jsonFormatVisitors.*;
import com.fasterxml.jackson.databind.ser.SerializerFactory;
import org.junit.jupiter.api.Test;

import java.util.Set;

public class FunctioningTest {

    @Test
    void extractTypeTree() throws JsonMappingException {
        final var objectMapper = new ObjectMapper();
        final var serializerProvider = objectMapper.getSerializerProviderInstance();
        final var javaType = objectMapper.constructType(SomeClass.class);
        final var serializerFactory = objectMapper.getSerializerFactory();
        final var typeSerializer = serializerFactory.createSerializer(serializerProvider, javaType);
        final var FieldVisitor = new FieldVisitor(serializerProvider, serializerFactory);
        typeSerializer.acceptJsonFormatVisitor(FieldVisitor, javaType);

    }

    static class SomeClass {
        public Integer integer;
        public String string;
        public FieldClass fieldClass;
    }

    static class FieldClass {
        public boolean aBoolean;
        public double floatingPoint;
    }

    static class FieldVisitor extends JsonFormatVisitorWrapper.Base implements JsonFormatVisitorWrapper {

        private final SerializerFactory serializerFactory;

        public FieldVisitor(final SerializerProvider provider, final SerializerFactory serializerFactory) {
            super(provider);
            this.serializerFactory = serializerFactory;
        }

        @Override
        public JsonObjectFormatVisitor expectObjectFormat(JavaType javaType) throws JsonMappingException {
            System.out.println("FieldVisitor (Object): " + javaType);
            final var objectVisitor = new ObjectVisitor(getProvider());
            final var objectSerializer = serializerFactory.createSerializer(getProvider(), javaType);
            final var properties = objectSerializer.properties();
            while (properties.hasNext()) {
                final var property = properties.next();
                final var propertyType = property.getType();
                final var propertySerializer = serializerFactory.createSerializer(getProvider(), propertyType);
                propertySerializer.acceptJsonFormatVisitor(this, propertyType);
            }
            return objectVisitor;
        }

        @Override
        public JsonArrayFormatVisitor expectArrayFormat(JavaType type) throws JsonMappingException {
            return null;
        }

        @Override
        public JsonStringFormatVisitor expectStringFormat(JavaType type) throws JsonMappingException {
            System.out.println("FieldVisitor (String): " + type);
            return new StringVisitor();
        }

        @Override
        public JsonNumberFormatVisitor expectNumberFormat(JavaType type) throws JsonMappingException {
            System.out.println("FieldVisitor (Number): " + type);
            return new NumberVisitor();
        }

        @Override
        public JsonIntegerFormatVisitor expectIntegerFormat(JavaType type) throws JsonMappingException {
            System.out.println("FieldVisitor (Integer): " + type);
            return new IntegerVisitor();
        }

        @Override
        public JsonBooleanFormatVisitor expectBooleanFormat(JavaType type) throws JsonMappingException {
            System.out.println("FieldVisitor (Boolean): " + type);
            return new BooleanVisitor();
        }

        @Override
        public JsonNullFormatVisitor expectNullFormat(JavaType type) throws JsonMappingException {
            System.out.println("Null: " + type);
            return null;
        }

        @Override
        public JsonAnyFormatVisitor expectAnyFormat(JavaType type) throws JsonMappingException {
            return null;
        }

        @Override
        public JsonMapFormatVisitor expectMapFormat(JavaType type) throws JsonMappingException {
            return null;
        }

    }

    static class ObjectVisitor extends JsonObjectFormatVisitor.Base implements JsonObjectFormatVisitor {

        public ObjectVisitor(SerializerProvider serializerProvider) {
            super(serializerProvider);
        }

        @Override
        public void property(BeanProperty writer) throws JsonMappingException {
            System.out.println("ObjectVisitor: " + writer);
        }

        @Override
        public void property(String name, JsonFormatVisitable handler, JavaType propertyTypeHint) throws JsonMappingException {
            System.out.println("ObjectVisitor: " + String.join(", ", name, handler.toString(), propertyTypeHint.toString()));
        }

        @Override
        public void optionalProperty(BeanProperty writer) throws JsonMappingException {
            System.out.println("ObjectVisitor (optional): " + writer);
        }

        @Override
        public void optionalProperty(String name, JsonFormatVisitable handler, JavaType propertyTypeHint) throws JsonMappingException {
            System.out.println("ObjectVisitor (optional): " + String.join(", ", name, handler.toString(), propertyTypeHint.toString()));
        }
    }

    static class StringVisitor implements JsonStringFormatVisitor {
        @Override
        public void format(JsonValueFormat format) {
            System.out.println("StringVisitor (format): " + format);
        }

        @Override
        public void enumTypes(Set<String> enums) {
            System.out.println("StringVisitor (enums): " + enums);
        }
    }

    static class IntegerVisitor implements JsonIntegerFormatVisitor {

        @Override
        public void numberType(JsonParser.NumberType type) {
            System.out.println("IntegerVisitor (numberType): " + type);
        }

        @Override
        public void format(JsonValueFormat format) {
            System.out.println("IntegerVisitor (format): " + format);
        }

        @Override
        public void enumTypes(Set<String> enums) {
            System.out.println("IntegerVisitor (enums): " + enums);
        }
    }

    static public class BooleanVisitor implements JsonBooleanFormatVisitor {
        @Override
        public void format(JsonValueFormat format) {
            System.out.println("BooleanVisitor (format): " + format);
        }

        @Override
        public void enumTypes(Set<String> enums) {
            System.out.println("BooleanVisitor (enums): " + enums);
        }
    }

    static class NumberVisitor implements JsonNumberFormatVisitor {
        @Override
        public void numberType(JsonParser.NumberType type) {
            System.out.println("NumberVisitor (numberType): " + type);
        }

        @Override
        public void format(JsonValueFormat format) {
            System.out.println("NumberVisitor (format): " + format);
        }

        @Override
        public void enumTypes(Set<String> enums) {
            System.out.println("NumberVisitor (enums): " + enums);
        }
    }
}

输出:

FieldVisitor (Object): [simple type, class io.serpentes.examples.schema.sources.jackson.FunctioningTest$SomeClass]
FieldVisitor (Integer): [simple type, class java.lang.Integer]
IntegerVisitor (numberType): INT
FieldVisitor (String): [simple type, class java.lang.String]
FieldVisitor (Object): [simple type, class io.serpentes.examples.schema.sources.jackson.FunctioningTest$FieldClass]
FieldVisitor (Boolean): [simple type, class boolean]
FieldVisitor (Number): [simple type, class double]
NumberVisitor (numberType): DOUBLE
ObjectVisitor (optional): property 'aBoolean' (field "io.serpentes.examples.schema.sources.jackson.FunctioningTest$FieldClass#aBoolean, no static serializer)
ObjectVisitor (optional): property 'floatingPoint' (field "io.serpentes.examples.schema.sources.jackson.FunctioningTest$FieldClass#floatingPoint, no static serializer)
ObjectVisitor (optional): property 'integer' (field "io.serpentes.examples.schema.sources.jackson.FunctioningTest$SomeClass#integer, no static serializer)
ObjectVisitor (optional): property 'string' (field "io.serpentes.examples.schema.sources.jackson.FunctioningTest$SomeClass#string, no static serializer)
ObjectVisitor (optional): property 'fieldClass' (field "io.serpentes.examples.schema.sources.jackson.FunctioningTest$SomeClass#fieldClass, no static serializer)