Jackson序列化(1)— [SpringBoot2.x]-Jackson在HttpMessageConverter(消息转换器)中的使用
Jackson序列化(2)— [SpringBoot2.x]-Spring容器中ObjectMapper配置
Jackson序列化(3)— Jackson中ObjectMapper配置详解
Jackson序列化(4)— Jackson“默认的”时间格式化类—StdDateFormat解析
Jackson序列化(5) — Jackson的ObjectMapper.DefaultTyping.NON_FINAL属性
Jackson序列化(6)— Java使用Jackson进行序列化
ObjectMapper
是jackson
的核心。Jackson
的Json操作都是在ObjectMapper
中实现的。ObjectMapper
有一个配置:
ObjectMapper om = new ObjectMapper();
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
这个配置会对JSON的序列化和反序列化带来什么影响呢?
在SpringBoot2.x整合Redis时,若使用Jackson作为序列化工具,为何要设置该配置?
1. 源码的描述
源码:com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping
/**
* Value that means that default typing will be used for
* all non-final types, with exception of small number of
* "natural" types (String, Boolean, Integer, Double), which
* can be correctly inferred from JSON; as well as for
* all arrays of non-final types.
*<p>
* Since 2.4, this does NOT apply to {@link TreeNode} and its subtypes.
*/
NON_FINAL
源码的解释是:对于除了一些自然类型(String、Double、Integer、Double)类型外的非常量(non-final)类型,类型将会用在值的含义上。以便可以在JSON串中正确的推测出值所属的类型。
2. 区别
1. 序列化的区别
我们先不设置ObjectMapper.DefaultTyping.NON_FINAL
属性。对POJO进行序列化。
@Test
public void testDefaultTyping() throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
//POJO无public的属性或方法时,不报错
objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
//null值字段不显示
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
//美化JSON输出
objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
// objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
User user = new User();
user.setAa(new Aa()); //普通POJO对象
user.setAmount1(1.1); //Double对象
user.setUName("Tom"); //String类型
user.setDate(new Date()); //Date类型
String string = objectMapper.writeValueAsString(user); //解析对象
System.out.println(string);
}
输出结果如图1所示,这个是正常的JSON串。
接着开启objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
配置,对POJO对象进行序列化。得到的JSON串的值中带有对象的类型。这也就是源码的含义。如图2所示。
2. 反序列化的区别
我们将POJO对象进行序列化,大多数是为了存储或传输。最终我们还是需要进行反序列化成为POJO的。那么这两种JSON串进行反序列化时会有问题吗?
我们对ObjectMapper
设置了objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
参数,对普通的JSON串进行反序列化。
@Test
public void testDefaultTyping() throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
//POJO无public的属性或方法时,不报错
objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
//null值字段不显示
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
//序列化JSON串时,在值上打印出对象类型
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
String string="{\"date\":1571887801613,\"aa\":{},\"amount1\":1.1,\"uname\":\"Tom\"}";
User user = objectMapper.readValue(string, User.class);
System.out.println(user);
}
运行结果,如下列代码所示,因为我们的JSON串中不含有字段的类型,所以不能进行反序列化。
com.fasterxml.jackson.databind.exc.MismatchedInputException:
Unexpected token (START_OBJECT), expected START_ARRAY:
need JSON Array to contain As.WRAPPER_ARRAY type information for class com.galax.jackson.User
at [Source: (String)"{"date":1571887801613,"aa":{},"amount1":1.1,"uname":"Tom"}"; line: 1, column: 1]
未设置objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
字段,对含有值类型的JSON串进行反序列化。
@Test
public void testDefaultTyping() throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
//POJO无public的属性或方法时,不报错
objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
//null值字段不显示
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
//序列化JSON串时,在值上打印出对象类型
// objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
String string="[\"com.galax.jackson.User\",{\"date\":[\"java.util.Date\",1571888059184],\"aa\":[\"com.galax.jackson.Aa\",{}],\"amount1\":1.1,\"uname\":\"Tom\"}]";
User user = objectMapper.readValue(string, User.class);
System.out.println(user);
}
运行结果依旧是不能解析。
com.fasterxml.jackson.databind.exc.MismatchedInputException:
Cannot deserialize instance of `com.galax.jackson.User` out of START_ARRAY token
3. 注意事项
Spring源码中是使用容器中的ObjectMapper
对象进行序列化和反序列化。当我们将自定义的ObjectMapper对象放入IOC容器中后,会自动覆盖SpringBoot自动装载的ObjectMapper对象。若是我们在自定义的ObjectMapper
中设置了objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
属性。那么可能会影响我们的@RequestBody
反序列化JSON串,导致Spring Boot默认错误返回格式变成数组@RequestBody无法解析Json格式异常。
如何去自定义配置IOC容器中的ObjectMapper
对象,实际上,SpringBoot给了两种方式,详情请参考:SpringBoot配置ObjectMapper源码,以及ObjectMapper序列化/反序列化配置。
4. 项目运用
SpringBoot2.X整合Redis缓存中,使用Jackson作为序列化工具,其中就使用了ObjectMapper.DefaultTyping.NON_FINAL
配置。
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
//序列化时允许非常量字段均输出类型
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
因为数据在反序列化为具体类时,需要传入具体的对象类型。而Redis的序列化配置是公共配置。我们只能传入Object.class
类型进行反序列化。
-
序列化得到的字段为
反序列化会得到LinkedHashMap类型,该类型不能强转为具体的对象类型,即抛出
java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.galax.pojo.Account
- 所以我们需要
ObjectMapper.DefaultTyping.NON_FINAL
配置,在序列化时记录对象类型,以便反序列化时得到对应的具体对象。