农场主的黑科技.

万能的Jackson

字数统计: 1.4k阅读时长: 8 min
2018/11/30 Share

众所周知从Springboot的rest中直接返回Object对象,Spring就会调用Jackson自动转换成Json格式后返回。但有些Jackson的使用技巧你可能还不知道,或许每次都会去谷歌。这篇文章会对Jackson的一些用得少但又超级赞的功能作总结。

首先会介绍Stack Overflow 中找到的一些技巧,随后我会总结一下这次在项目中遇到的坑。

Jackson使用技巧

会分别介绍在SpringBoot的rest中的用法 ,使用注解配置的方法,还有通过ObjectMapper手动构造JSON串时的用法。

用snake_case的JSON匹配camelCase的POJO

来自 java - Jackson overcoming underscores in favor of camel-case - Stack Overflow

SpringBoot的rest中使用

两种配置方式,通过springboot的 application.properties:

1
spring.jackson.property-naming-strategy=SNAKE_CASE

或在类上使用注解:

1
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)

配置后Controller可以取到snake_case的值并赋值给POJO。Controller返回POJO时,Jackson也会先把POJO的属性构造成snake_case的JSON再返回。

注解配置的方式使用

不推荐!

1
2
3
4
5
class User{
@JsonProperty("first_name")
protected String firstName;
protected String getFirstName(){return firstName;}
}

通过ObjectMapper手动构造JSON串时使用

对ObjectMapper调用setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE)

用法示例

1
2
3
4
5
6
7
8
9
10
11
12
13
public static class Hoge {
// 这个属性对应JSON的hoge_fuga项
public String hogeFuga;
}

public static void snakeCaseJsonToCamelCasePojo() throws IOException {
ObjectMapper mapper = new ObjectMapper()
.setPropertyNamingStrategy(
PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);

Hoge hogeObj = mapper.readValue("{\"hoge_fuga\": \"piyopiyo\"}", Hoge.class);
System.out.println(hogeObj.hogeFuga);
}

不对null的POJO属性进行序列化

原文 java - How to tell Jackson to ignore a field during serialization if its value is null? - Stack Overflow

对属性或POJO类配置注解的方式使用

1
2
3
4
5
@JsonInclude(JsonInclude.Include.NON_NULL)
public static class Fuga {
public List<String> strings = null; // 这个属性不会被序列化
public int number = 3;
}

在application.properties中统一配置的方式使用

如果是SpringBoot,这应该是最方便的方法了

1
spring.jackson.default-property-inclusion=non_null	#如果值为null,构造json时不加入

对ObjectMapper进行设置后使用

1
2
3
4
5
6
public static void ignoreNullFieldByObjectMapper() throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper()
.setSerializationInclusion(JsonInclude.Include.NON_NULL);
String json = mapper.writeValueAsString(new Fuga());
System.out.println(json);
}

Date对象序列化后的格式

来自 Date format Mapping to JSON Jackson

注解配置的方式使用

1
2
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm a z")
private Date date;

我试了下,好像必须要配时区

1
2
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm", locale = "CHINA",timezone = "Asia/Shanghai")
private Date createdDate;

在application.properties中统一配置的方式使用

1
2
3
4
5
spring:
jackson:
date-format: yyyy-MM-dd HH:mm
locale: zh_CN
time-zone: Asia/Shanghai

对ObjectMapper进行设置后使用

1
2
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm a z");
myObjectMapper.setDateFormat(df);

对ObjectMapper#writeValue() 等输出的JSON进行格式化(pretty print)

对格式没需求

java - Pretty printing JSON from Jackson 2.2’s ObjectMapper - Stack Overflow

想把格式化的缩进从2格改到4格

java - Custom pretty printer using Jackson library - Stack Overflow

想把格式弄成JSON.stringify()那种

https://gist.github.com/komiya-atsushi/832a97c84ccae7cdfc2a

在项目中遇到的坑

之前不知道可以配置无视null,又觉得直接把POJO传出去太危险,毕竟会暴露表构造,所以就对很多接口都专门生成VO类。然后用BeanUtils#copyProperties在每次返回前把POJO拷贝到VO上再返回的。今天早上发现有这个功能之后马上对整个Controller层重构了一番,还是太naive啊。

还有就是对Date对象的格式化了,之前也是每次在返回前手动用DataFormat把POJO中的Date对象转成String后再拷贝个VO。像下面这样,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private static <T, R> void checkDate(T po, R vo) {
BeanWrapper poWrapper = new BeanWrapperImpl(po);
BeanWrapper voWrapper = new BeanWrapperImpl(vo);
for (PropertyDescriptor pd : voWrapper.getPropertyDescriptors()) {
Object date;
String fieldName = pd.getDisplayName();
if (pd.getPropertyType() == Date.class
&& (date = poWrapper.getPropertyValue(fieldName)) != null) {

SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm");
String formatted = formatter.format(date);

voWrapper.setPropertyValue(fieldName, formatted);

}
}
}

当时为了写这段也是费了好大功夫。

还有要注意的就是DTO是服务间的数据传输对象,所以感觉还是直接用Date类传递比较好。但我又是在application.properties中同一配置的date-formmat格式,所以是拿不到Date类的。比如我用SpringCloud的FeignClient试图获取带Date的DTO,果然抛了异常

1
com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `java.util.Date` from String

因为它远程调用的另一端并没有进行相同的格式化。

这时我想到,Date默认的Json格式是2018-11-30T15:08:09.955+0800这种。这种格式可以通过StdDateFormat.DATE_FORMAT_STR_ISO8601获取,还有就是注解配置比application.properties配置的优先级要高,

根据这两点,我在DTO的Date类的属性上加入以下注解

1
2
@JsonFormat(pattern = StdDateFormat.DATE_FORMAT_STR_ISO8601)
private Date registerTime;

完美解决。

也就是说,当FeignClient对这个registerTime进行反序列化的时候不会尝试使用application.properties中配置的yyyy-MM-dd HH:mm格式去解析,而是使用StdDateFormat.DATE_FORMAT_STR_ISO8601,也就是”yyyy-MM-dd’T’HH:mm:ss.SSSZ”,去解析它,而这个格式正是远程调用方进行JSON序列化时默认使用的格式,所以能反序列化成Date类。

参考文章 Jackson の痒いところ Tips

CATALOG
  1. 1. Jackson使用技巧
  2. 2. 在项目中遇到的坑