当前位置 : 首页 » 文章分类 :  开发  »  Jackson

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 字符串中含有我们并不需要的字段,那么当对应的实体类中不含有该字段时,会抛出一个异常,此设置不抛异常
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);


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(“birth_date”)
private Date birthDate;

@JsonProperty 可以指定字段的命名(还可以指定这个字段需要参与序列化和反序列化)。
@JsonProperty.value:指定的字段名字
@JsonProperty.index:指定顺序,默写数据格式是基于顺序(JSON不是这种数据格式)
@JsonProperty.defaultValue:默认值。注意:这个属性目前为止并没有被core和data-bind使用;制备一些扩展模块使用。

@JsonProperty("result_code")
private String resultCode;

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 所有的字母小写,没有分隔符,例如 lowercase
SnakeCaseStrategy 所有的字母小写,下划线作为名字之间分隔符,例如 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方法时,和Filed效果一样。这个注解只能用在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

下一篇 接下来要学习的

阅读
评论
5.2k
阅读预计24分钟
创建日期 2018-08-16
修改日期 2023-12-06
类别
目录
  1. 变量命名方式
    1. Lower Camel Case 小驼峰式
    2. Upper Camel Case 大驼峰/Pascal
    3. Snake Case 蛇式(下划线分割)
    4. Kebab Case 烤肉式(中线分割)
  2. Jackson 版本和依赖包
    1. Jackson 2.x(fasterxml)主要依赖包
    2. Jackson 1.x(codehaus)主要依赖包
  3. SpringBoot Jackson 配置
    1. spring.jackson.date-format
    2. spring.jackson.time-zone
    3. spring.jackson.serialization
    4. spring.jackson.deserialization
  4. ObjectMapper 配置
    1. 关闭 FAIL_ON_EMPTY_BEANS
      1. 空 Object 序列化错误
    2. 关闭 FAIL_ON_UNKNOWN_PROPERTIES
    3. JsonInclude.Include.NON_NULL
    4. INDENT_OUTPUT缩进美化输出
    5. Jackson 枚举反序列化大小写无关
  5. ObjectMapper 常用方法
    1. writeValueAsString() 序列化
    2. readValue() 反序列化
      1. Json 串转 Java Map 对象
      2. TypeReference 泛型反序列化
      3. TypeReference 集合反序列化
    3. convertValue() 类型转换
      1. Java Object 和 Map 互转
    4. writerWithDefaultPrettyPrinter() 格式化打印
    5. copy() 拷贝实例
  6. Jackson 常用注解
    1. @JsonProperty 字段与成员变量映射
    2. @JsonNaming 序列化命名策略
    3. @JsonSerialize 自定义序列化器
      1. 枚举序列化为小写
      2. Date序列化为时间戳秒
      3. 自动给Long型字段加一个_str结尾的同值字符串字段
    4. @JsonDeserialize 自定义反序列化器
      1. 时间戳秒反序列化为Date
    5. @JsonFormat 指定 String 日期格式
      1. @JsonFormat 和 @DateTimeFormat
      2. SpringBoot 日志和接口返回时间少8小时
    6. @JsonPropertyOrder 指定字段顺序
    7. @JsonCreator 注解反序列化方法
    8. @JsonValue 注解序列化方法
    9. @JsonIgnore 单个字段过滤
    10. @JsonIgnoreProperties 字段过滤
    11. @JsonInclude 指定序列化哪些字段
    12. @JsonManagedReference 和 @JsonBackReference
  7. 其他
    1. 字段名不确定如何解析?Map
    2. boolean isDone json序列化后成两个字段
    3. Direct self-reference leading to cycle 循环自引用
    4. Illegal unquoted character ((CTRL-CHAR, code 10))
    5. Invalid UTF-8 start byte 0xa0
    6. Java 8 LocalDateTime 序列化报错
    7. Hutool 和 Jackson 混用引起的 JSONNull 序列化报错

页面信息

location:
protocol:
host:
hostname:
origin:
pathname:
href:
document:
referrer:
navigator:
platform:
userAgent:

评论