Jackson
Jackson 使用笔记
FasterXML / jackson
https://github.com/FasterXML/jackson
变量命名方式
Lower Camel Case 小驼峰式
如果第一个单词首字母小写,称之为 Lower Camel Case 小驼峰式,例如 “getUserName”。
Upper Camel Case 大驼峰/Pascal
如果第一个单词首字母大写,称之为 Upper Camel Case 大驼峰式,或者 Pascal 命名法(Pascal Case),例如 “GetUserName”。
Snake Case 蛇式(下划线分割)
如果所有单词都小写,称之为 lower snake case(小蛇式),例如”get_user_name”。
如果所有单词都大写,称之为 upper snake case(大蛇式),例如”GET_USER_NAME”。
Kebab Case 烤肉式(中线分割)
名称中间的标点被替换成连字符(-),所有单词都小写,例如”get-user-name”。
Jackson 版本和依赖包
Jackson 1.x 的包名带有 codehaus 关键字,maven 依赖的 groupid 是 org.codehaus.jackson
Jackson 2.x 的包名带有 fasterxml 关键字,maven 依赖的 groupid 是 com.fasterxml.jackson.core
Jackson 2.x(fasterxml)主要依赖包
Jackson 2.x(fasterxml) 主要包含三个依赖
- jackson-core 核心包
- jackson-annotations 注解包
- jackson-databind 数据绑定包
依赖关系为,jackson-databind 依赖 jackson-core 和 jackson-annotations
所以只需要在项目中引入 databind,其他两个就会自动引入
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.1.0</version>
</dependency>
Jackson 1.x(codehaus)主要依赖包
Jackson 1.x(codehaus) 主要包含两个依赖
- jackson-core-asl
- jackson-mapper-asl
其中 jackson-mapper-asl 依赖 jackson-core-asl
所以只需要引入 jackson-mapper-asl 的依赖就可以了
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>1.9.11</version>
</dependency>
SpringBoot Jackson 配置
spring.jackson.date-format
全局日期格式
spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone
全局日期时区
spring:
jackson:
time-zone: GMT+8
spring.jackson.serialization
全局配置序列化参数:
spring:
jackson:
serialization:
FAIL_ON_EMPTY_BEANS: false # 序列化时遇到 null 不报错
spring.jackson.deserialization
全局配置反序列化参数:
spring:
deserialization:
fail_on_unknown_properties: false # 反序列化时遇到不认识的字段忽略不报错
ObjectMapper 配置
关闭 FAIL_ON_EMPTY_BEANS
jackson 序列化 bean 时,遇到 null 默认会报错,关闭此属性即可。mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
或者
spring:
jackson:
serialization:
FAIL_ON_EMPTY_BEANS: false
空 Object 序列化错误
报错:
org.springframework.core.codec.CodecException: Type definition error: [simple type, class java.lang.Object]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class java.lang.Object and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)
…
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class java.lang.Object and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)
解决:
new Object() 改为 new HashMap();
关闭 FAIL_ON_UNKNOWN_PROPERTIES
JSON 反序列化时,如果json串中含有我们并不需要的字段,那么当对应的实体类中不含有该字段时,会抛出一个异常,此设置不抛异常mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
等于mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
JsonInclude.Include.NON_NULL
设置 Jackson 序列化时只包含不为空的字段mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
INDENT_OUTPUT 缩进美化输出
objectMapper.configure(SerializationConfig.Feature.INDENT_OUTPUT, true);
ORDER_MAP_ENTRIES_BY_KEYS 序列化时保持key有序
objectMapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true);
Jackson 枚举反序列化大小写无关
默认只有和枚举名完全相同(包括大小写)的才能自动反序列化。
例如枚举定义的是 TYPE 传入小写的 type 是无法自动反序列化的,会报错:
Caused by: com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type com.masikkk.enums.TypeEnum
from String “alert”: not one of the values accepted for Enum class: [ALERT, COMMENT]
at [Source: (PushbackInputStream); line: 5, column: 14] (through reference chain: com.masikkk.vo.MessageVO[“type”])
有下面几种方法:
方法一、@JsonProperty
旁边搭配 @JsonAlias({"location", "LOCATION", "Location"})
注解,忽略大小写。
方法二、使用自定义的 JsonDeserializer 反序列化器
方法三、使用 @JsonFormat(with = JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES)
设置单字段忽略大小写。
方法四、配置全局 ObjectMapper 属性的 ACCEPT_CASE_INSENSITIVE_PROPERTIES
为 true
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
方法五、如果使用的是 SpringBoot 2.1.x 及以上,可直接在 application.yml 中配置 Spring Jackson 属性
spring.jackson.mapper.ACCEPT_CASE_INSENSITIVE_ENUMS = true
或
spring.jackson.mapper.accept-case-insensitive-enums = true
或 yml
spring:
jackson:
mapper:
accept-case-insensitive-enums: true
ObjectMapper 常用方法
writeValueAsString() 序列化
public String writeValueAsString(Object value) throws JsonProcessingException
将给定的 Java 类序列化为 json 串。
readValue() 反序列化
public <T> T readValue(String content, Class<T> valueType) throws JsonProcessingException, JsonMappingException
public <T> T readValue(String content, TypeReference<T> valueTypeRef) throws JsonProcessingException, JsonMappingException
public <T> T readValue(String content, JavaType valueType) throws JsonProcessingException, JsonMappingException
将给定的 json 串反序列化为指定类型
Json 串转 Java Map 对象
import org.codehaus.jackson.map.ObjectMapper;
ObjectMapper mapper = new ObjectMapper();
String jsonStr = "{\"key2\":\"value1\",\"key2\":\"value2\"}";
Map<String, Object> tmpMap = mapper.readValue(jsonStr, Map.class);
TypeReference 泛型反序列化
fastjson 和 Jackson 都提供了用于处理泛型反序列化的类 TypeReference
, 用于将 json 串直接反序列化为具体类型,如果不使用 TypeReference
会反序列化为一堆 map
例如
// 定义一个通用的 包裹单个bean的 http响应结构,具体的bean类型是泛型
public class CommonBeanResponse<T> {
private T data;
@JsonProperty("request_id")
private String requestId;
@JsonProperty("server_time")
private long serverTime;
@JsonProperty("result_code")
private String resultCode;
private String message;
public T getData() { return data; }
public void setData(T data) { this.data = data; }
// 其他 getter setter 省略
...
public static void main(String[] args) {
CommonBeanResponse<UserBean> responseSrc = new CommonBeanResponse<>();
responseSrc.setData(new UserBean());
String jsonStr = objectMapper.writeValueAsString(responseSrc);
// 正确,可反序列化为具体的 UserBean
CommonBeanResponse<UserBean> responseDst = objectMapper.readValue(jsonStr, new TypeReference<CommonBeanResponse<UserBean>>() {});
// 错误,反序列化后 data域是一堆 k-v map
CommonBeanResponse<UserBean> responseDst2 = objectMapper.readValue(jsonStr, CommonBeanResponse.class);
}
}
TypeReference 集合反序列化
再比如直接读取到 List
List<UserBean> userBeans = getUserBeanList();
String jsonStr = objectMapper.writeValueAsString(userBeans);
List beanList = objectMapper.readValue(jsonStr, new TypeReference<List<UserBean>>() {});
为什么使用TypeReference (解释原理)
https://yq.aliyun.com/articles/609441
TypeReference – 让Jackson Json在List/Map中识别自己的Object
https://blog.csdn.net/ssjiang/article/details/7769525
alibaba/fastjson - TypeReference
https://github.com/alibaba/fastjson/wiki/TypeReference
convertValue() 类型转换
public <T> T convertValue(Object fromValue, Class<T> toValueType) throws IllegalArgumentException
public <T> T convertValue(Object fromValue, TypeReference<T> toValueTypeRef) throws IllegalArgumentException
public <T> T convertValue(Object fromValue, JavaType toValueType) throws IllegalArgumentException
类型转换便捷方法,此方法相当于先将 fromValue 序列化为 json 串,然后再反序列化为 toValueType 类型,只不过内部用临时缓冲区实现转换。
Java Object 和 Map 互转
1、object 转 Map
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> map = objectMapper.convertValue(new Object(), Map.class);
2、 Map 转 Object
ObjectMapper objectMapper = new ObjectMapper();
User user = objectMapper.convertValue(new HashMap<>(), User.class);
writerWithDefaultPrettyPrinter() 格式化打印
public ObjectWriter writerWithDefaultPrettyPrinter()
构造一个使用默认漂亮打印格式的 ObjectWriter, 常用于格式化日志打印。
log.info(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(object));
copy() 拷贝实例
public ObjectMapper copy() {
_checkInvalidCopy(ObjectMapper.class);
return new ObjectMapper(this);
}
拷贝当前 ObjectMapper 实例,常用于创建多个不同配置属性的 ObjectMapper 实例。
Jackson 常用注解
@JsonProperty 字段与成员变量映射
用于属性,把属性的名称序列化时转换为另外一个名称。
@JsonProperty 可以指定字段的徐丽华命名(还可以指定这个字段需要参与序列化和反序列化)。
@JsonProperty.value 指定字段的序列化名字
@JsonProperty.index 指定顺序,默写数据格式是基于顺序(JSON不是这种数据格式)
@JsonProperty.defaultValue 默认值。注意:这个属性目前为止并没有被core和data-bind使用;制备一些扩展模块使用。
@JsonProperty.access 指定这个字段是否需要参与序列化和反序列化
@JsonProperty("result_code")
private String resultCode;
// 序列化时忽略此字段,反序列化时保留,可用于比如含 base64 图片参数的log打印
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private String doNotPrint
Jackson 框架的高阶应用
https://www.ibm.com/developerworks/cn/java/jackson-advanced-application/index.html
@JsonNaming 序列化命名策略
@JsonNaming
注解用来指定属性序列化使用的命名策略,覆盖默认实现。可以通过 value
属性指定策略,包括自定义策略。
除了默认的 LOWER_CAMEL_CASE
机制外,Jackson 还提供了四种内置命名策略:KebabCaseStrategy
“Lisp” 风格,采用小写字母、连字符作为分隔符,例如 “lower-case” 或 “first-name”LowerCaseStrategy
所有的字母小写,没有分隔符,例如 lowercaseSnakeCaseStrategy
所有的字母小写,下划线作为名字之间分隔符,例如 snake_case.UpperCamelCaseStrategy
所有名字(包括第一个字符)都以大写字母开头,后跟小写字母,没有分隔符,例如 UpperCamelCase
例如常用的 驼峰 转 下划线分隔
单个类的注解命名
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
public class MyClass {
}
全局命名objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE)
注意:
1、如果同时设置了全局规则和某个类的命名规则,类的命名规则会覆盖全局设置。
1、如果同时设置类的命名规则 和使用 @JsonProperty
在字段上指定序列化名称,则 @JsonProperty
覆盖类上的命名规则。
Jackson 属性自定义命名策略
https://mlog.club/article/5953
@JsonSerialize 自定义序列化器
枚举序列化为小写
使用示例如下:
@JsonInclude(Include.NON_NULL)
public class Bean {
@JsonSerialize(using = EnumLowerCaseSerializer.class)
@JsonProperty("inviter_identity")
private InviterIdentity inviterIdentity;
}
其中
InviterIdentity是自定义枚举类
EnumLowerCaseSerializer是自定义的一个将枚举转化为name小写的转化器,继承自Jackson的JsonSerializer抽象类,重写了其中的serialize()方法
public class EnumLowerCaseSerializer extends JsonSerializer<Enum> {
@Override
public void serialize(Enum value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeString(value == null ? null : value.name().toLowerCase());
}
}
Date序列化为时间戳秒
@JsonInclude(Include.NON_NULL)
public class Bean {
// 创建时间
@JsonProperty("create_time")
@JsonSerialize(using = SecondSerializer.class)
@JsonDeserialize(using = SecondDeserializer.class)
private Date createTime;
}
其中的Date转换为时间戳秒的序列化类:
package com.masikkk.common.json;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
import java.util.Date;
public class SecondSerializer extends JsonSerializer<Date> {
@Override
public void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
jsonGenerator.writeString(date.getTime() / 1000 + "");
}
}
SpringMVC日期转换之JsonSerialize
https://blog.csdn.net/rendiyiforarchitect/article/details/8056514
自动给Long型字段加一个_str结尾的同值字符串字段
@JsonInclude(Include.NON_NULL)
public class Bean {
// 用户UUID
@JsonSerialize(using = LongUUIDSerializer.class)
private Long uuid;
}
本来uuid是Long型的,可能出现在前后端传输中丢失精度,加上这个序列化类注解后,可自动生成一个名为 uuid_str 的字段,值是 uuid 对应的String类型。
其中的 LongUUIDSerializer 是自定义的序列化器,给注解的Long字段自动加一个 _str 结尾的字符串字段
package com.masikkk.json;
import java.io.IOException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.base.GeneratorBase;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
public class LongUUIDSerializer extends JsonSerializer<Long> {
// 新增json名称固定后缀
public final static String jsonNameSuffix = "_str";
@Override
public void serialize(Long value, JsonGenerator jsonGenerator, SerializerProvider serializers)
throws IOException, JsonProcessingException {
jsonGenerator.writeNumber(value);
if (jsonGenerator instanceof GeneratorBase) {
String jsonPropertyName = ((GeneratorBase) jsonGenerator).getOutputContext().getCurrentName();
jsonGenerator.writeStringField(jsonPropertyName.concat(jsonNameSuffix), String.valueOf(value));
}
}
}
@JsonDeserialize 自定义反序列化器
时间戳秒反序列化为Date
@JsonInclude(Include.NON_NULL)
public class Bean {
// 创建时间
@JsonProperty("create_time")
@JsonSerialize(using = SecondSerializer.class)
@JsonDeserialize(using = SecondDeserializer.class)
private Date createTime;
}
其中的 SecondDeserializer 是时间戳秒转换为Date的反序列类:
package com.masikkk.common.json;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import java.io.IOException;
import java.util.Date;
public class SecondDeserializer extends JsonDeserializer<Date> {
@Override
public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
return new Date(Long.parseLong(jsonParser.getText()) * 1000);
}
}
@JsonFormat 指定 String 日期格式
import com.fasterxml.jackson.annotation.JsonFormat;
@JsonProperty("create_time")
@JsonFormat(shape = Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@JsonProperty("update_time")
@JsonFormat(shape = Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
@JsonProperty("update_time")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyyMMddHHmmss", timezone = "GMT+8")
private Date updateTime;
@JsonFormat 和 @DateTimeFormat
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
用于 string 日期反序列化为 Date
,入参使用@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
用于 Date
序列化为 string,出参使用
SpringBoot 日志和接口返回时间少8小时
一般来说是因为没指定 Jackson 时区
解决:
1、可以在具体字段上指定,如下
@JsonFormat(pattern = "yyyy-MM-dd HH-mm-ss", timezone = "GMT+8")
private Timestamp updateTime;
2、objectMapper上设置
@Bean
public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization() {
return jacksonObjectMapperBuilder ->
jacksonObjectMapperBuilder.timeZone(TimeZone.getTimeZone("GMT+8"));
}
3、在spring中配置spring.jackson.time-zone=GMT+8
4、改用 @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
注解
@JsonPropertyOrder 指定字段顺序
@JsonPropertyOrder({ “id”, “label”, “target”, “source”, “attributes” })
public class BeanClass{
}
@JsonCreator 注解反序列化方法
json反序列化为java对象时,该注解用于定义构造函数。当从json创建java时,@JsonCreator注解的构造函数被会调用,如果没有@JsonCreator注解,则默认调用java类的无参构造函数,此时,如果java类中只有有参构造函数,而无默认的无参构造函数,在反序列化时会抛出这样的异常:com.fasterxml.jackson.databind.JsonMappingException,所以,当我们不使用@JsonCreator指定反序列化的构造函数,而又在java类中重载了构造函数时,一定要记得编写类的无参构造函数。
@JsonCreator :反序列化时的构造方法,入参为对应该枚举的json值
public enum VehicleUserRole {
UNKNOWN((byte) 0, "未知"),
VehicleUserRole1((byte) 1, "1"),
VehicleUserRole2((byte) 2, "2"),
VehicleUserRole3((byte) 3, "3");
private byte code;
private String name;
VehicleUserRole(byte code, String name) {
this.code = code;
this.name = name;
}
public byte getCode() {
return code;
}
public String getName() {
return name;
}
private static Map<Byte, VehicleUserRole> map = Maps.newHashMap();
static {
for (VehicleUserRole value : VehicleUserRole.values()) {
map.put(value.getCode(), value);
}
map = Collections.unmodifiableMap(map);
}
@JsonCreator
public static VehicleUserRole getVehicleUserRole(String str) {
if (StringUtils.isBlank(str)) {
return UNKNOWN;
}
return VehicleUserRole.valueOf(str.trim().toUpperCase());
}
public static VehicleUserRole get(byte code) {
return map.get(code);
}
}
反序列化构造方法也可以这样写,更稳妥:
@JsonCreator
public static OperatorRole forValue(String nameString) {
for (OperatorRole operatorRole : OperatorRole.values()) {
if (operatorRole.name().equalsIgnoreCase(nameString)) {
return operatorRole;
}
}
return OperatorRole.UNKNOWN;
}
使用处:
@JsonSerialize(using = EnumLowerCaseSerializer.class)
@JsonProperty("vehicle_user_role")
private VehicleUserRole vehicleUserRole;
@JsonValue 注解序列化方法
序列化时,用来生成json值的方法
public enum InviterIdentity {
UNKNOW((byte) 0, "未知身份"),
InviterIdentity1((byte) 1, "1"),
InviterIdentity2((byte) 2, "2"),
InviterIdentity3((byte) 3, "3");
private byte code;
private String name;
private static Map<Byte, InviterIdentity> codeMap = Maps.newHashMap();
static {
for (InviterIdentity inviterIdentity : InviterIdentity.values()) {
codeMap.put(inviterIdentity.code, inviterIdentity);
}
codeMap = Collections.unmodifiableMap(codeMap);
}
InviterIdentity(byte code, String name) {
this.code = code;
this.name = name;
}
public Byte getCode() {
return code;
}
public String getName() {
return name;
}
@JsonValue
public String getInviterIdentity() {
return codeMap.get(code).toString().toLowerCase();
}
public static InviterIdentity getIdentityByCode(Byte code) {
return codeMap.get(code);
}
}
// 使用处
@JsonSerialize(using = EnumLowerCaseSerializer.class)
@JsonProperty("inviter_identity")
private InviterIdentity inviterIdentity;
Jackson 枚举序列化/反序列化
https://blog.csdn.net/z69183787/article/details/54292789
@JsonIgnore 单个字段过滤
@JsonIgnore 注解用来忽略某些字段,可以用在 Field 或者 Getter 方法上,用在 Setter 方法时,和 Field 效果一样。这个注解只能用在 POJO 存在的字段要忽略的情况。
@JsonIgnoreProperties 字段过滤
比如要接收的json字段不固定,或者其中某些字段用不到,可以使用 @JsonIgnoreProperties 做字段过滤
在json转换成的实体类加注解 @JsonIgnoreProperties(ignoreUnknown = true)
, 注意这是类级别的注解。将这个注解写在类上之后,就会忽略类中不存在的字段,达到按需接受的目的。
这个注解还可以指定要忽略的字段。使用方法如下:
@JsonIgnoreProperties({ “internalId”, “secretKey” })
指定的字段不会被序列化和反序列化。
@JsonInclude 指定序列化哪些字段
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
只序列化非null元素
@JsonInclude(Include.NON_NULL)
只序列化非null和非””元素
@JsonInclude(Include.NON_EMPTY)
@JsonManagedReference 和 @JsonBackReference
@JsonManagedReference 和 @JsonBackReference 总是成对出现的
@JsonManagedReference 注解,通常说明这个字段是一个双向引用的字段,这个字段在这个双向引用中的角色为 “父”,与这个字段对应的引用需要注解为 @JsonBackReference
@JsonBackReference 注解,通常说明这个字段是关联的一个双向引用字段,这个字段在这个双向引用的角色是 “孩子”。这个字段的值只能是对象(Bean),不能是 集合(Collection),图(Map),数组(Array)和枚举类型(enumeration)。
其他
字段名不确定如何解析?Map
Gson解析JSON中动态未知字段key的方法
https://blog.csdn.net/Chaosminds/article/details/49049455
java 解析不确定key的json
https://blog.csdn.net/qq_15058425/article/details/56834565
Android json解析动态获取key以及解析技巧
https://blog.csdn.net/u013072976/article/details/43561779
boolean isDone json序列化后成两个字段
// 是否从没购买过
@JsonProperty("is_never_bought")
private Boolean neverBought;
public Boolean getNeverBought() {return isNeverBought;}
public void setNeverBought(Boolean neverBought) {isNeverBought = neverBought;}
序列化后有两个字段:
“never_bought”: true,
“is_never_bought”: true
解决方法,getNeverBought() 改为 getIsNeverBought()
Direct self-reference leading to cycle 循环自引用
Jackson序列化时报错:
failed to process json obj
com.fasterxml.jackson.databind.JsonMappingException: Direct self-reference leading to cycle (through reference chain:
原因是要序列化的bean中引用了自己,例如:
UserKafkaMessage message = new UserKafkaMessage();
message.setName("xxx");
... ...
message.setBefore(message); // 自己引用了自己
其中 before 字段是 UserKafkaMessage 类型的,本意是要存放user信息变更前的各个字段的值,没想到写代码时写错了直接把 message 自己的引用放进去了,导致出现序列化时 Direct self-reference leading to cycle 错误。
Illegal unquoted character ((CTRL-CHAR, code 10))
背景
spring 对外暴露了一个接口,参数是一个字节数组 image, 正常情况下 image 参数可以接收 base64 编码的图片,自动转换为字节数组
public class FeatureExtractRequest {
@NotEmpty(message = "image cannot be empty!")
private byte[] image;
}
问题
调用方调用时,好多图片传入时报错
com.fasterxml.jackson.databind.JsonMappingException: Illegal unquoted character ((CTRL-CHAR, code 10)): has to be escaped using backslash to be included in string value
看样子是 Jackson 在将 base64 解码为字节数组时报错了
自己本地也试了下,如果入参 image 不是正确的 base64 编码格式,就会报这个错,比如参数是 {“image”: “dss”} 我们知道 base64 是用4个字符编码3个字节,不足时补=
,传入3个字符肯定是错的,就会报这个错。如果换成 {“image”: “dsss”} 四个字符,就没问题了。
原因
经排查,调用方使用了URL格式的base64编码(用下划线_
替换/
),我本地试了下,正常的4个字符没问题,但如果把其中一个字符换成_
,比如 {“image”: “dss_”}, 也会报参数解析错误,但错误提示比较明显,spring默认使用jackson将 base64 反序列化为 byte[], 但使用的是basic的base64编码,不是url的,所以无法识别下划线。
org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type `byte[]` from String "": Failed to decode VALUE_STRING as base64 (MIME-NO-LINEFEEDS): Illegal character '_' (code 0x5f) in base64 content; nested exception is com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `byte[]` from String "": Failed to decode VALUE_STRING as base64 (MIME-NO-LINEFEEDS): Illegal character '_' (code 0x5f) in base64 content
解决
调用方把base64编码格式改为basic的之后好了。
Invalid UTF-8 start byte 0xa0
Caused by: com.fasterxml.jackson.core.JsonParseException: Invalid UTF-8 start byte 0xa0
Caused by: com.fasterxml.jackson.core.JsonParseException: Invalid UTF-8 middle byte 0x48
Java 8 LocalDateTime 序列化报错
问题:
对象中有 Java 8 LocalDateTime 类型时间字段,序列化时报错:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Java 8 date/time type `java.time.LocalDateTime` not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling (through reference chain: com.masikkk.blog.api.vo.response.GetCommentsResponse["comments"]->java.util.ArrayList[0]->com.masikkk.blog.api.vo.CommentVO["childComments"]->java.util.ArrayList[0]->com.masikkk.blog.api.vo.CommentVO["createTime"])
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:77) ~[jackson-databind-2.13.2.jar:2.13.2]
原因:
Jackson 默认不支持 Java 8 LocalDateTime 序列化,想支持需要单独添加 com.fasterxml.jackson.datatype:jackson-datatype-jsr310 依赖
解决:
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.14.0</version>
</dependency>
高版本的 spring-boot-starter-web 会自带这个依赖,不需要单独引入
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule()); // 注册时间模块
String s = objectMapper.writeValueAsString(VOList);
9 Serialize Java 8 Date With Jackson
https://www.baeldung.com/jackson-serialize-dates
Hutool 和 Jackson 混用引起的 JSONNull 序列化报错
Feign Client 接口调用报错:
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class cn.hutool.json.JSONNull and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: java.util.LinkedHashMap["name"])
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:77)
代码如下:
public interface HttpBinService {
@RequestMapping(method = RequestMethod.POST, value = "/anything")
Object postObject(@RequestBody Object body);
}
@FeignClient(name = "httpBin", url = "https://httpbin.org")
public interface HttpBinFeignClient extends HttpBinService {
}
@Test
public void testHutoolDeserialize() {
String json = "{\"score\":0.5975835,\"age\":0,\"code\":\"\",\"name\":null}";
Map<String, Object> map = JSONUtil.toBean(json, Map.class);
Object resp = httpBinFeignClient.postObject(map);
log.info("resp {}", resp);
}
原因:
Hutool 会将 “name”: null 的 value 反序列化为 cn.hutool.json.JSONNull
类型。
之后使用 Feign Client 调用接口时,默认 Encoder 是 SpringEncoder + AbstractJackson2HttpMessageConverter,Jackson 无法序列化 JSONNull 类型导致报错。
解决:
改用 Jackson 反序列化
@Test
public void testJacksonDeserialize() {
String json = "{\"score\":0.5975835,\"age\":0,\"code\":\"\",\"name\":null}";
Map<String, Object> map = JsonMappers.NonEmpty.fromJson(json, new TypeReference<Map<String, Object>>() {
});
Object resp = httpBinFeignClient.postObject(map);
log.info("resp {}", resp);
}
上一篇 Apache-Cassandra
下一篇 接下来要学习的
页面信息
location:
protocol
: host
: hostname
: origin
: pathname
: href
: document:
referrer
: navigator:
platform
: userAgent
: