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);
// 或
Map<String, Object> tmpMap = mapper.readValue(jsonStr, new TypeReference<>() {
                });

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 所有的字母小写,没有分隔符,例如 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 = JsonFormat.Shape.STRING, pattern = "yyyyMMddHHmmss", timezone = "GMT+8")
private Date updateTime;

@JsonFormat 和 @DateTimeFormat

@JsonFormat Jackson 提供的注解,用于 Date, LocalTime 等时间字段的 序列化 和 反序列化,仅在 application/json 格式数据有效 @DateTimeFormat Spring 提供的注解,用于 string 日期反序列化为 Date, LocalTime 等时间类型,不仅在json中使用,还可用在 x-www-form-urlencoded 表单数据反序列化中。

@DateTimeFormat(pattern = "HH:mm:ss")  // 处理表单或 URL 参数
@JsonFormat(pattern = "HH:mm:ss", timezone = "GMT+8")  // 处理 JSON 请求/响应
private LocalTime eventTime;

@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date date;

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);
}