Jackson
是Java
语言中的一个JSON
类库。性能良好,同时拥有高度可扩展的API
等都是Jackson
的亮点。参考下图(时间参考本文发布时间),可以看到在Jackson
在主流JSON
类库中的使用相当广泛(其中也有部分得益于SpringMVC
将其作为默认的Json
消息转换器)。接下来赶紧进入主题吧,看看本文主要介绍的Jackson
用法你都会吗?

时间相关配置
Jackson
的核心类是com.fasterxml.jackson.databind.ObjectMapper
,基本所有的序列化(对象转JSON
)/反序列化(JSON
转对象)都是通过该类实现的。全文基于jackson 2.11.2
版本进行介绍。
关于时间这一块,通常我们需要将时间格式化(未特殊声明,全文用格式化指代序列化和反序列化)成符合我们要求的格式。Jackson
默认会将java.util.Date
格式化成13
位毫秒级时间戳,并且对JSR310
的格式化也是当作普通类处理(遍历getXxx
方法,去掉get
前缀,Xxx
首字母小写作为属性名进行格式化)。
public class JacksonTest {
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
static class DateVo implements Serializable {
private static final long serialVersionUID = 1L;
private Date date;
private LocalDateTime localDateTime;
private LocalDate localDate;
private LocalTime localTime;
}
@Test
@DisplayName("默认的Jackson时间序列化")
void defaultTime() throws JsonProcessingException {
String json = prettyOm.writeValueAsString(dateVo);
System.out.println(json);
}
}
上述代码的运行结果如下,格式化结果非常不符合我们的一般要求:
{
"date" : 1599910607807,
"localDateTime" : {
"month" : "SEPTEMBER",
"year" : 2020,
"dayOfMonth" : 12,
"hour" : 19,
"minute" : 36,
"monthValue" : 9,
"nano" : 813000000,
"second" : 47,
"dayOfWeek" : "SATURDAY",
"dayOfYear" : 256,
"chronology" : {
"id" : "ISO",
"calendarType" : "iso8601"
}
},
"localDate" : {
"year" : 2020,
"month" : "SEPTEMBER",
"dayOfMonth" : 12,
"monthValue" : 9,
"era" : "CE",
"dayOfWeek" : "SATURDAY",
"dayOfYear" : 256,
"leapYear" : true,
"chronology" : {
"id" : "ISO",
"calendarType" : "iso8601"
}
},
"localTime" : {
"hour" : 19,
"minute" : 36,
"second" : 47,
"nano" : 814000000
}
}
针对java.util.Date
类,可以通过@com.fasterxml.jackson.annotation.JsonFormat
注解实现格式化,但是针对JSR310
提供的时间类暂时没有注解可以让我们直接进行格式化。这时候需要我们注册相应的格式化模块。如下所示:
public class JacksonTest {
private final SimpleModule jsr310Module;
{
// 自定义 JSR310 格式化模块,设置常用的 JSR310 类及其处理器
DateTimeFormatter ldtFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
DateTimeFormatter ldFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
DateTimeFormatter ltFormatter = DateTimeFormatter.ofPattern("HH:mm:ss");
jsr310Module = new SimpleModule()
.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(ldtFormatter))
.addSerializer(LocalDate.class, new LocalDateSerializer(ldFormatter))
.addSerializer(LocalTime.class, new LocalTimeSerializer(ltFormatter))
.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(ldtFormatter))
.addDeserializer(LocalDate.class, new LocalDateDeserializer(ldFormatter))
.addDeserializer(LocalTime.class, new LocalTimeDeserializer(ltFormatter));
}
@Test
@DisplayName("自定义时间格式化")
void customDateFormat() throws Exception {
// 直接使用 jackson-datatype-jsr310 提供的 JSR310 格式化实现,虽然非常全面,但是格式还是需要进一步调整。
// JavaTimeModule javaTimeModule = new JavaTimeModule();
ObjectMapper om = new ObjectMapper()
// 设置地区为中国
.setLocale(Locale.CHINA)
// 设置时区为东8区
.setTimeZone(TimeZone.getTimeZone(ZoneOffset.ofHours(8)))
// 设置 Date 时间格式化格式为 yyyy-MM-dd HH:mm:ss
.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINA))
// 关闭时间转时间戳
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
// 激活缩进换行配置(优雅输出)
.enable(SerializationFeature.INDENT_OUTPUT)
// 注册 JSR310 时间API的相关格式转换模块
.registerModule(jsr310Module);
System.out.println(om.writeValueAsString(dateVo));
}
}
注册JSR310
的时间格式化模块一般有两种选择,一种是直接使用jackson-datatype-jsr310
依赖提供的com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
模块器,该模块器覆盖了JSR310
的大部分时间类,但是格式一般还需要我们进一步调整,相当繁琐;所以一般我们都是自己注册所需要的时间模块,如上代码,我们针对LocalDateTime
、LocalDate
、LocalTime
三个最常用的时间类进行格式化处理,最终注册到ObjectMapper
中。
同时,对于Date
数据类型,我们一般也都是通过配置定义格式化,而不是通过注解方式定义格式化,因为使用注解需要标注在所有使用Date
的类上,容易遗漏并且过于繁琐。配置时主要设置地区、时区、格式化定义并关闭默认的时间戳格式化。
优雅输出
默认Jackson
序列化结果是单行的,如果我们需要输出包含缩进换行的优雅JSON
字符串,有以下两种方法可以实现我们的要求。
临时性
临时性的输出优雅JSON
字符串,可以直接通过com.fasterxml.jackson.databind.ObjectMapper.writerWithDefaultPrettyPrinter
方法获取对应的ObjectWriter
,然后通过ObjectWriter
序列化,该方法只针对本次输出。(当然我们将ObjectWriter
长期持有,该类的实例是线程安全的。后续通过ObjectWriter
进行序列化也相当于永久性。)
永久性
另一种是直接激活ObjectMapper
的优雅输出配置,通过ObjectMapper.enable(SerializationFeature.INDENT_OUTPUT)
或者ObjectMapper.configure(SerializationFeature.INDENT_OUTPUT, true)
永久性激活优雅输出配置。
其他常见配置
除了上述说的时间配置和优雅输出配置,还有以下几个配置我们通常会用到:
FAIL_ON_EMPTY_BEANS
如果你的类中所有字段定义都没有提供getter
方法,如下代码所示:
public class JacksonTest {
static class EmptyBean implements Serializable {
private static final long serialVersionUID = 1L;
private int minutes;
public int minutes() {
return minutes;
}
public EmptyBean minutes(int minutes) {
this.minutes = minutes;
return this;
}
}
@Test
@DisplayName("允许空Bean")
void failOnEmptyBeans() throws Exception {
// Jackson 序列化规则默认从 getXxx 中删除 get 前缀,然后将首字母小写
EmptyBean eb = new EmptyBean().minutes(1);
System.out.println(prettyOm.writeValueAsString(eb));
// System.out.println(prettyOm.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS).writeValueAsString(eb));
}
}
那么这时候在序列化时将会抛出com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class com.xxx.Cls and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)
。异常信息里也提醒了我们可以如何处理该问题。
这时候通过ObjectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
或者ObjectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
可以关闭该功能。这样就能正常序列化成{}
(一个空JSON
对象)。
JsonInclude.Include.NON_NULL
如果某个对象字段值为null
,默认Jackson
也会将null
字段也序列化进JSON
中,而我们通常为了节约带宽考虑,都是需要不序列化null
字段的,此时可以通过ObjectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL)
关闭null
值的序列化。
FAIL_ON_NULL_FOR_PRIMITIVES
如果JSON
的某个字段值为null
,并且该字段将映射到实体的基本数据类型字段,那么如果开启了DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES
(默认是关闭状态),将会抛出com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot map null into type int (set DeserializationConfig.DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES to 'false' to allow)
异常。
FAIL_ON_UNKNOWN_PROPERTIES
当JSON
的字段比类的字段多时,Jackson
在反序列化时将会直接抛出无法识别的属性异常com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "nickname" (class com.swj.mj.jackson.JacksonTest$UnknownPropertiesBean), not marked as ignorable (one known property: "name"])
。
public class JacksonTest {
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
static class UnknownPropertiesBean implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
}
@Test
void failOnUnknownProperties() throws Exception {
String json = "{"name": "Reka","nickname": null}";
// om.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
UnknownPropertiesBean bean = om.readValue(json, UnknownPropertiesBean.class);
assert "Reka".equals(bean.getName());
}
}
这时候需要通过ObjectMapper.disable(SerializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
或者ObjectMapper.configure(SerializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
关闭该功能。该功能一般需要关闭,因为对接外部接口时,我们只需要用到其中一部分字段或者对方接口字段增加,如果此时没关闭该功能将会导致功能出异常。
ALLOW_SINGLE_QUOTES
有时候会遇到一些非标准JSON
字符串,比如字符串使用了单引号而不是双引号,这时候默认Jackson
会抛出异常com.fasterxml.jackson.core.JsonParseException: Unexpected character (''' (code 39)): was expecting double-quote to start field name at [Source: (String)"{'name': 'Reka','nickname': null}"; line: 1, column: 3]
。这时候可以通过Object.enable(JsonParser.Feature.ALLOW_SINGLE_QUOTES)
或者ObjectMapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true)
允许单引号。
一些常见的注解
除了@JsonFormat
注解外,Jackson
还提供了以下注解供我们使用。

但我们一般比较常用的注解有:@JsonProperty
、@JsonAlias
、@JsonFormat
、@JsonIgnore
、@JsonPropertyOrder
。下面分别简单介绍一个各个注解的功能:
@JsonProperty
该注解主要是重新定义字段在格式化时的名称,通常标注在字段或者getter
方法上。基本用法如下:
public class JsonPropertyBean implements Serializable {
private static final long serialVersionUID = 1L;
@JsonProperty("nickName")
private String nickname;
private String name;
@JsonProperty("username")
public String getName() {
return this.name;
}
public String getNickname() {
return nickname;
}
}
@JsonAlias
该注解用于定义字段的反序列化别名,即将一个或者多个不同的别名映射到字段上,从Jackson 2.9
开始提供。基本用法如下:
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class JsonAliasBean implements Serializable {
private static final long serialVersionUID = 1L;
@JsonAlias("nickName")
private String nickname;
@JsonAlias({"username", "Name"})
private String name;
}
这样在反序列化的时候就可以将nickName
或者nickname
映射到nickname
字段上,将username
、Name
或者name
映射到name
字段上。
@JsonFormat
该注解常用于定义java.util.Date
的格式化,主要通过locale
定义地区,timezone
定义时区,pattern
定义格式。基本用法如下:
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class JsonFormatBean implements Serializable {
private static final long serialVersionUID = 1L;
@JsonFormat(locale = "zh_CN", timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
private Date date;
}
@JsonIgnore
该注解标注的字段将不参与格式化(序列化和反序列化),与transient
起相同效果。
@JsonPropertyOrder
该注解常标注在类上,用于控制序列化时的字段顺序;有两种排序定义:一种是直接指定字段名的顺序(通过value
字符串数组定义顺序),另一种是根据字段的字母序排序(通过alphabetic = true
激活字母序)。基本用法如下:
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
@JsonPropertyOrder(alphabetic = true)
public class JsonPropertyOrderBean implements Serializable {
private static final long serialVersionUID = 1L;
private String username;
private String nickname;
private Date createTime;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
@JsonPropertyOrder({"createTime", "nickname", "username"})
public class JsonPropertyOrderBean implements Serializable {
private static final long serialVersionUID = 1L;
private String username;
private String nickname;
private Date createTime;
}
自定义序列化器和反序列化器
我们先前介绍了关于JSR310
时间类的格式化需要我们通过注册模块的方式进行配置,这其中就涉及到了自定义序列化器和反序列化器的功能。自定义可以让我们高度自由地处理序列化和反序列化规则,一般自定义序列化器通过继承com.fasterxml.jackson.databind.ser.std.StdSerializer
实现,自定义反序列化器通过继承com.fasterxml.jackson.databind.deser.std.StdDeserializer
实现:
public class ZonedDateTimeSerializer extends StdSerializer<ZonedDateTime> {
private static final long serialVersionUID = 1L;
private final DateTimeFormatter formatter;
public ZonedDateTimeSerializer(DateTimeFormatter formatter) {
super(ZonedDateTime.class);
this.formatter = formatter;
}
@Override
public void serialize(ZonedDateTime value, JsonGenerator gen, SerializerProvider provider) throws IOException {
if (Objects.isNull(value)) {
gen.writeNull();
} else {
gen.writeString(formatter.format(value));
}
}
}
public class ZonedDateTimeDeserializer extends StdDeserializer<ZonedDateTime> {
private static final long serialVersionUID = 1L;
private final DateTimeFormatter formatter;
public ZonedDateTimeDeserializer(DateTimeFormatter formatter) {
super(ZonedDateTime.class);
this.formatter = formatter;
}
@Override
public ZonedDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
String value = p.getValueAsString();
if (Objects.isNull(value) || value.isEmpty()) {
return null;
}
return this.formatter.parse(p.getValueAsString(), ZonedDateTime::from);
}
}
public class JacksonTest {
@Test
void customModule() throws Exception {
ObjectMapper om = new ObjectMapper();
DateTimeFormatter zdtFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss ZZZ");
om.enable(SerializationFeature.INDENT_OUTPUT)
.registerModule(
new SimpleModule()
.addSerializer(ZonedDateTime.class, new ZonedDateTimeSerializer(zdtFormatter))
.addDeserializer(ZonedDateTime.class, new ZonedDateTimeDeserializer(zdtFormatter))
);
ZonedDateTimeBean bean = new ZonedDateTimeBean();
System.out.println(om.writeValueAsString(bean));
String json = "{}";
assert Objects.isNull(om.readValue(json, ZonedDateTimeBean.class).getDateTime());
bean.setDateTime(ZonedDateTime.now());
System.out.println(om.writeValueAsString(bean));
json = "{"dateTime" : "2020-09-12 21:25:01 +0800"}";
assert om.readValue(json, ZonedDateTimeBean.class).getDateTime().equals(ZonedDateTime.of(
LocalDateTime.of(2020, 9, 12, 21, 25, 1), ZoneOffset.ofHours(8)));
}
}
如上,我们定义了ZonedDateTime
的格式化器,然后添加到SimpleModule
模块中,并注册到ObjectMaper
中即可使用。
结束语
本篇介绍到的Jackson
用法不知你是否都熟练掌握了呢?限于篇幅原因,这里我们介绍的都是比较基础的配置与用法,下篇我们将会介绍Jackson
的泛型反序列化、自定义Jackson
格式化注解、与Spring
集成、与redis
集成等的用法及其注意点。敬请期待!
原文始发于微信公众号(三维家技术实践):这些Jackson用法你都会吗(上篇)
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/30568.html