Jackson 序列化与反序列化

18 阅读4分钟

前言

在某些场景下,需要从数据源(例如:JSON对象、流程变量、...)中分区别取出模型的各个字段并将其正确的反序列化为字段对应的类型且设值。

例如:有多个JSON对象(例如: JSON1、JSON2),然后需要根据这多个JSON对象中取出对应的字段,完成对模型A的实例填充。

案例&详解

现有模型VpnUpdateInputTest如下:

public class VpnUpdateInputTest {  
    private String sourceName;  
    @ComplexType  
    private List<AclPolicy> aclPolicies;  
    private Object devices;  
  
    @ComplexType  
    private Map<String, Object> test1;  
    @ComplexType  
    private Map<String, AclPolicy> test2;  
    @ComplexType  
    private HashMap<String, List<String>> test3;  
    @ComplexType  
    private LinkedHashMap<String, List<AclPolicy>> test4;  
    @ComplexType  
    private LinkedHashMap<AclPolicy, List<AclPolicy>> test5;  
    @ComplexType  
    private LinkedHashMap<AclPolicy, Map<String, List<AclPolicy>>> test6;  
}

希望对于含有@ComplexType注解字段,能够动态反序列化出其对应的类型;

首先,需要能够动态的拿到该模型每个字段的具体类型;

Field[] declaredFields = klass.getDeclaredFields();  
  
for (Field declaredField : declaredFields) {  
    declaredField.setAccessible(Boolean.TRUE);  
    for (NodeParam input : inputs) {  
        if (declaredField.getName().equals(input.getLeftName())) {  
  
            var complexType = declaredField.getAnnotation(ComplexType.class);  
            var isComplexType = Objects.nonNull(complexType);  
  
            Object filedValue;  
            if (isComplexType) {  
                ObjectMapper objectMapper = CommonUtils.getObjectMapper();  
  
                Type genericType = declaredField.getGenericType();  
                JavaType javaType = getJavaType(genericType, objectMapper);  
  
                filedValue = objectMapper.readValue(objectMapper.writeValueAsString(variablesAsMap.get(input.getRightName())), javaType);  
            } else {  
                filedValue = variablesAsMap.get(input.getRightName());  
            }  
  
            declaredField.set(newInstance, filedValue);  
            break;  
        }  
    }  
}

其中,getJavaType方法为具体获取字段类型的核心方法:

private static JavaType getJavaType(Type actualTypeArgument, ObjectMapper objectMapper) {  
    JavaType javaType;  
    Class<?> klass;  
    if (actualTypeArgument instanceof ParameterizedType actualPt) {  
        klass = (Class<?>) actualPt.getRawType();  
  
        if (Collection.class.isAssignableFrom(klass)) {  
            // 分支:Collection接口的实现类  
  
            Type elementActualTypeArgument = actualPt.getActualTypeArguments()[0];  
            JavaType elementType = getJavaType(elementActualTypeArgument, objectMapper);  
            javaType = constructParametricTypeByJavaType(objectMapper, klass, elementType);  
  
        } else if (Map.class.isAssignableFrom(klass)) {  
            // 分支:Map接口的实现类  
  
            Type keyActualTypeArgument = actualPt.getActualTypeArguments()[0];  
            JavaType keyType = getJavaType(keyActualTypeArgument, objectMapper);  
  
            Type valueActualTypeArgument = actualPt.getActualTypeArguments()[1];  
            JavaType valueType = getJavaType(valueActualTypeArgument, objectMapper);  
  
            javaType = constructParametricTypeByJavaType(objectMapper, klass, keyType, valueType);  
  
        } else {  
            javaType = constructParametricTypeByClass(objectMapper, klass);  
        }  
    } else {  
        klass = (Class<?>) actualTypeArgument;  
        javaType = constructParametricTypeByClass(objectMapper, klass);  
    }  
    return javaType;  
}

getJavaType通过递归的方式获取到对应字段的最终对应的JavaType,可以满足即使字段类型再复杂都可以正确的获取到其对应的JavaType。

构建JavaType的基础函数,如下:

// 根据Class构建JavaType
public static JavaType constructParametricTypeByClass(ObjectMapper mapper, Class<?> parametrized, Class<?>... parameterClasses) {  
    return mapper.getTypeFactory().constructParametricType(parametrized, parameterClasses);  
}  

// 根据JavaType构建JavaType
public static JavaType constructParametricTypeByJavaType(ObjectMapper mapper, Class<?> parametrized, JavaType... parameterJavaTypes) {  
    return mapper.getTypeFactory().constructParametricType(parametrized, parameterJavaTypes);  
}  

// 根据Class构建CollectionType(集合类型)
public static CollectionType constructCollectionType(ObjectMapper mapper, Class<? extends Collection> parametrized, Class<?> parameterClasses) {  
    return mapper.getTypeFactory().constructCollectionType(parametrized, parameterClasses);  
}  

// 根据Class构建MapType(Map类型)
public static MapType constructMapType(ObjectMapper mapper, Class<? extends Map> mapClass, Class<?> keyClass, Class<?> valueClass) {  
    return mapper.getTypeFactory().constructMapType(mapClass, keyClass, valueClass);  
}

通过以上的操作,我们就完成了通过Jackson反序列化时,动态指定类型的操作;它通过反射计算出对应模型字段的类型,然后再反序列化为对应的类型并且支持任何复杂的类型

注意:在利用Jackson反序列化时,如果类型为Map且其对应的Key为复杂类型时,我们需要手动将该类型的序列化与反序列化的操作实现注册到ObjectMapper中,否则会在反序列化的时候报错。

  • Key为复杂类型时,如果没有指定对应类型的序列化实现会调用其ToString方法
  • Key为复杂类型时,如果没有指定对应类型的反序列化实现时会报错Can not find a (Map) Key deserializer for type

注册自定义的序列化与反序列化

首先,需要实现序列化与反序列化的接口,如下:

反序列化

import com.fasterxml.jackson.databind.DeserializationContext;  
import com.fasterxml.jackson.databind.KeyDeserializer;  
  
/**  
 * 使用Map时, 如果key是自定义对象或者非Jackson内置支持的类型, 需要自定义KeyDeserializer  
 * 本例中, key是AclPolicy, 为了能够正确反序列化, 需要自定义KeyDeserializer, 否者会报错:Can not find a (Map) Key deserializer for type ....  
 */
 public class AclPolicyTestDeserializer extends KeyDeserializer {  
    @Override  
    public Object deserializeKey(String key, DeserializationContext ctxt) {  
        ObjectMapper mapper = (ObjectMapper) ctxt.getParser().getCodec();
        return mapper.readValue(key, AclPolicy.class);
    }  
}

序列化

public class AclPolicyTestSerializer extends JsonSerializer {
    @Override
    public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        ObjectMapper mapper = (ObjectMapper) gen.getCodec();
        gen.writeFieldName(mapper.writeValueAsString(value));
    }
}

需要额外注意的是,序列化实现的接口是 JsonSerializer,而反序列化的接口是 KeyDeserializer,有点不一样;原因是,当复杂类型作为Map的Key时,需要实现其对应的KeyDeserializer接口(否者反序列化时会报错),但是作为Key时,如果没有实现其对应的序列化类只会调用ToString方法,就导致了反序列化时不好处理,因此我们需要实现其对应的序列化接口;

然而,如果我又有将该类型不是作为Map的Key情况且序列化时,有一些特殊操作时,我们也要实现该类型对应的JsonDeserializer接口;如下:

class AclPolicyTestDeserializer extends JsonDeserializer<AclPolicy> {  
  
    @Override  
    public AclPolicy deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JacksonException {  
        String text = jsonParser.getText();  
        ObjectMapper mapper = (ObjectMapper) deserializationContext.getParser().getCodec();  
        return mapper.readValue(text, AclPolicy.class);  
    }  
}

然后将其注册进ObjectMapper

ObjectMapper objectMapper = CommonUtils.getObjectMapper();  
SimpleModule simpleModule = new SimpleModule();  
simpleModule.addKeyDeserializer(AclPolicy.class, new AclPolicyTestDeserializer());  
objectMapper.registerModule(simpleModule);

采用 SimpleModule 的方式,虽然可以满足需求,但有一个问题,就是每新增一个类,都需要在这里配置一下,一个功能,两处编写,很多新同学容易忘掉,造成潜在问题。最好的方式应该是类似下面这样的;利用注解在定义字段时就将对应字段的序列化与反序列化配置好。

public class VpnUpdateInputTest {  
    @ComplexType  
    @JsonSerialize(keyUsing = AclPolicyTestSerializer.class)
    @JsonDeserialize(keyUsing = AclPolicyTestDeserializer.class)
    private LinkedHashMap<AclPolicy, Map<String, List<AclPolicy>>> test6;  
}