序言
com.fasterxml.jackson.databind包中有很多属性
- 1)映射特性:MapperFeature;
- 2)序列化特性:SerializationFeature;
- 3)反序列化特性:DeserializationFeature;
最近开发过程中,有同事使用了一个不太恰当的反序列化特性导致在定位问题时找了半天才发现是在某一处转换时出了问题。
【简要描述一下问题】
我们依赖的某个接口模型发生了变化,某个字段枚举值新增了某个属性值,然而在我们这一层没有感知到。在使用方处使用了新的枚举值进行接口调用,然后我们在反序列化时把该新增的枚举值转换为了默认枚举值。
原因是在反序列化时设置了属性DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE,该配置即在反序列化未知枚举值时使用默认值。
【个人建议】
中间层不建议使用该特性,如果某个属性不是关键属性,且服务提供方认为该属性不重要可做降级处理时使用该特性。
映射特性、序列化特性使用较少,一般反序列化特性较多,故下面只简单介绍反序列化的属性使用。
DeserializationFeature
com.fasterxml.jackson.databind.DeserializationFeature中的特性很多,最常用的是FAIL_ON_UNKNOWN_PROPERTIES,即遇到未知属性仍然能够反序列化非未知的属性,保证反序列化不会因为瞎填了一些多余参数而不可用。
下面使用一个demo介绍部分特性,
package com.hust.zhang.serializable;
import static com.fasterxml.jackson.databind.DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT;
import static com.fasterxml.jackson.databind.DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY;
import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES;
import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;
import static com.fasterxml.jackson.databind.DeserializationFeature.READ_ENUMS_USING_TO_STRING;
import static com.fasterxml.jackson.databind.DeserializationFeature.UNWRAP_ROOT_VALUE;
import static com.fasterxml.jackson.databind.DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS;
import static com.fasterxml.jackson.databind.DeserializationFeature.USE_BIG_INTEGER_FOR_INTS;
import static com.fasterxml.jackson.databind.DeserializationFeature.USE_JAVA_ARRAY_FOR_JSON_ARRAY;
import static com.fasterxml.jackson.databind.DeserializationFeature.WRAP_EXCEPTIONS;
import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import lombok.*;
import lombok.extern.slf4j.Slf4j;
import java.text.Normalizer;
import java.util.HashMap;
@Slf4j
public class DeserializationFeatureDemo {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
static {
/**
* 该特性决定对于json浮点数,是否使用BigDecimal来序列化。如果不允许,则使用Double序列化。
* 注意:该特性默认是关闭的,因为性能上来说,BigDecimal低于Double。
*/
OBJECT_MAPPER.configure(USE_BIG_DECIMAL_FOR_FLOATS, true);
/**
* 该特性决定对于json整形(非浮点),是否使用BigInteger来序列化。如果不允许,则根据数值大小来确定 是使用Integer},
* {@link Long} 或者 {@link java.math.BigInteger}
*/
OBJECT_MAPPER.configure(USE_BIG_INTEGER_FOR_INTS, false);
/**
* 该特性决定JSON ARRAY是映射为Object[]还是List<Object>。如果开启,都为Object[],false时,则使用List。
* @since 1.9
*/
OBJECT_MAPPER.configure(USE_JAVA_ARRAY_FOR_JSON_ARRAY, false);
/**
* 该特性决定了使用枚举值的标准序列化机制:如果允许,则枚举假定使用Enum.toString()返回的值作为序列化结构;如果禁止, 则返回Enum.name()的值。
* 注意:默认使用的时Enum.name()的值作为枚举序列化结果。这个的设置和WRITE_ENUMS_USING_TO_STRING需要一致。
* For further details, check out [JACKSON-212]
* @since 1.6
*/
OBJECT_MAPPER.configure(READ_ENUMS_USING_TO_STRING, false);
/**
* 该特性决定了当遇到未知属性(没有映射到属性,没有任何setter或者任何可以处理它的handler),是否应该抛出一个
* JsonMappingException异常。这个特性一般式所有其他处理方法对未知属性处理都无效后才被尝试,属性保留未处理状态。
* 默认情况下,该设置是被打开的。
* @since 1.2
*/
OBJECT_MAPPER.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);
/**
* 该特性决定JSON 整数是否是一个有效的值,当被用来反序列化Java枚举值。如果false,数字可以接受,并且映射为枚举的值ordinal();
* 如果true,则数字不允许并且抛出JsonMappingException异常。后面一种行为原因是因为大部分情况下,枚举被反序列化为 JSON 字符串, 从而造成从整形到枚举的意外映射关系。
* Feature is disabled by default (to be consistent with behavior of Jackson 1.6), i.e. to allow use of JSON
* integers for Java enums.
* @since 1.7
*/
OBJECT_MAPPER.configure(FAIL_ON_NULL_FOR_PRIMITIVES, false);
/**
* 异常封装,不封装Error,catch异常之后,抛出IOException。默认封装异常。
* @since 1.7
*/
OBJECT_MAPPER.configure(WRAP_EXCEPTIONS, true);
/**
* 该特性决定是否接受强制非数组(JSON)值到Java集合类型。如果允许,集合反序列化将尝试处理非数组值。
* @since 1.8
*/
OBJECT_MAPPER.configure(ACCEPT_SINGLE_VALUE_AS_ARRAY, false);
/**
* 该特征允许 unwrap根级别JSON 值,来匹配WRAP_ROOT_VALUE 序列化设置。
* @since 1.9
*/
OBJECT_MAPPER.configure(UNWRAP_ROOT_VALUE, false);
/**
* 该特性可以允许JSON空字符串转换为POJO对象为null。如果禁用,则标准POJO只会从JSON null或者JSON对象转换过来;
* 如果允许,则空JSON字符串可以等价于JSON null。
* @since 1.8
*/
OBJECT_MAPPER.configure(ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, false);
}
/**
* 注意上面的实例对象必须要有无参构造函数,否则在反序列化时创建实例对象
* 会抛出异常com.fasterxml.jackson.databind.exc.InvalidDefinitionException
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
private static class Person {
private String name;
private Long age;
}
public static void main(String[] args) {
String jsonStr = "{\"name\":\"张三\",\"age\":18,\"sex\":\"男\"}";
String normalizedStr = Normalizer.normalize(jsonStr, Normalizer.Form.NFKC);
System.out.println("serialize java object to json : " + normalizedStr);
Person A = parse(jsonStr, Person.class, false);
System.out.println("after deserialize to object :" + JSON.toJSONString(A));
Person B = parse(jsonStr, Person.class, true);
System.out.println("after deserialize to object :" + JSON.toJSONString(B));
String floatJson = "{\"key\":123.10010}";
HashMap<String, Object> map = parse(floatJson, new TypeReference<HashMap<String, Object>>() {
});
System.out.println(JSON.toJSONString(map));
}
private static <T> T parse(String json, Class<T> tClass, boolean failOnUnknownProperties) {
T result = null;
OBJECT_MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, failOnUnknownProperties);
// 忽略 transient 修饰的属性,默认不忽略
OBJECT_MAPPER.configure(MapperFeature.PROPAGATE_TRANSIENT_MARKER, true);
// 默认不修改序列化后日期格式
OBJECT_MAPPER.enable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
try {
result = OBJECT_MAPPER.readValue(json, tClass);
} catch (JsonProcessingException e) {
log.error("Failed to deserialize JSON content, json value : " + json);
}
return result;
}
private static <T> T parse(String json, TypeReference<T> typeReference) {
T result = null;
try {
result = OBJECT_MAPPER.readValue(json, typeReference);
} catch (JsonProcessingException e) {
log.error("Failed to deserialize JSON content, json value : " + json);
}
return result;
}
}