可以使用 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)
我正在寻找一种使用 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)