平日里在项目中处理JSON一般用的都是阿里巴巴的Fastjson,后来发现使用Spring Boot内置的Jackson来完成JSON的序列化和反序列化操作也挺方便。Jackson不但可以完成简单的序列化和反序列化操作,也能实现复杂的个性化的序列化和反序列化操作。
自定义ObjectMapper
我们都知道,在Spring中使用@ResponseBody
注解可以将方法返回的对象序列化成JSON,比如:
1 | @RequestMapping("getuser") |
User类:
1 | public class User implements Serializable { |
访问getuser
页面输出:
1 | {"userName":"mrbird","age":0,"password":null,"birthday":1522634892365} |
可看到时间默认以时间戳的形式输出,如果想要改变这个默认行为,我们可以自定义一个ObjectMapper来替代:
1 | import java.text.SimpleDateFormat; |
上面配置获取了ObjectMapper对象,并且设置了时间格式。再次访问getuser
,页面输出:
1 | {"userName":"mrbird","age":0,"password":null,"birthday":"2018-04-02 10:14:24"} |
序列化
Jackson通过使用mapper的writeValueAsString
方法将Java对象序列化为JSON格式字符串:
1 | @Autowired |
反序列化
使用@ResponseBody
注解可以使对象序列化为JSON格式字符串,除此之外,Jackson也提供了反序列化方法。
树遍历
当采用树遍历的方式时,JSON被读入到JsonNode对象中,可以像操作XML DOM那样读取JSON。比如:
1 | @Autowired |
readTree
方法可以接受一个字符串或者字节数组、文件、InputStream等, 返回JsonNode作为根节点,你可以像操作XML DOM那样操作遍历JsonNode以获取数据。
解析多级JSON例子:
1 | String json = "{\"name\":\"mrbird\",\"hobby\":{\"first\":\"sleep\",\"second\":\"eat\"}}";; |
绑定对象
我们也可以将Java对象和JSON数据进行绑定,如下所示:
1 | @Autowired |
Jackson注解
Jackson包含了一些实用的注解:
@JsonProperty
@JsonProperty
,作用在属性上,用来为JSON Key指定一个别名。
1 | @JsonProperty("bth") |
再次访问getuser
页面输出:
1 | {"userName":"mrbird","age":0,"password":null,"bth":"2018-04-02 10:38:37"} |
key birthday已经被替换为了bth。
@Jsonlgnore
@Jsonlgnore
,作用在属性上,用来忽略此属性。
1 | @JsonIgnore |
再次访问getuser
页面输出:
1 | {"userName":"mrbird","age":0,"bth":"2018-04-02 10:40:45"} |
password属性已被忽略。
@JsonIgnoreProperties
@JsonIgnoreProperties
,忽略一组属性,作用于类上,比如JsonIgnoreProperties({ "password", "age" })
。
1 | @JsonIgnoreProperties({ "password", "age" }) |
再次访问getuser
页面输出:
1 | {"userName":"mrbird","bth":"2018-04-02 10:45:34"} |
@JsonFormat
@JsonFormat
,用于日期格式化,如:
1 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
@JsonNaming
@JsonNaming
,用于指定一个命名策略,作用于类或者属性上。Jackson自带了多种命名策略,你可以实现自己的命名策略,比如输出的key 由Java命名方式转为下面线命名方法 —— userName转化为user-name。
1 | @JsonNaming(PropertyNamingStrategy.LowerCaseWithUnderscoresStrategy.class) |
再次访问getuser
页面输出:
1 | {"user_name":"mrbird","bth":"2018-04-02 10:52:12"} |
@JsonSerialize
@JsonSerialize
,指定一个实现类来自定义序列化。类必须实现JsonSerializer
接口,代码如下:
1 | import java.io.IOException; |
上面的代码中我们仅仅序列化userName属性,且输出的key是user-name
。 使用注解@JsonSerialize
来指定User对象的序列化方式:
1 | @JsonSerialize(using = UserSerializer.class) |
再次访问getuser
页面输出:
1 | {"user-name":"mrbird"} |
@JsonDeserialize
@JsonDeserialize
,用户自定义反序列化,同@JsonSerialize
,类需要实现JsonDeserializer
接口。
1 | import java.io.IOException; |
使用注解@JsonDeserialize
来指定User对象的序列化方式:
1 | @JsonDeserialize (using = UserDeserializer.class) |
测试:
1 | @Autowired |
访问readjsonasobject
,页面输出:
1 | mrbird |
@JsonView
@JsonView
,作用在类或者属性上,用来定义一个序列化组。 比如对于User对象,某些情况下只返回userName属性就行,而某些情况下需要返回全部属性。 因此User对象可以定义成如下:
1 | public class User implements Serializable { |
User定义了两个接口类,一个为userNameView
,另外一个为AllUserFieldView
继承了userNameView
接口。这两个接口代表了两个序列化组的名称。属性userName使用了@JsonView(UserNameView.class)
,而剩下属性使用了@JsonView(AllUserFieldView.class)
。
Spring中Controller方法允许使用@JsonView
指定一个组名,被序列化的对象只有在这个组的属性才会被序列化,代码如下:
1 | @JsonView(User.UserNameView.class) |
访问getuser
页面输出:
1 | {"userName":"mrbird"} |
如果将@JsonView(User.UserNameView.class)
替换为@JsonView(User.AllUserFieldView.class)
,输出:
1 | {"userName":"mrbird","age":26,"password":"123456","birthday":"2018-04-02 11:24:00"} |
因为接口AllUserFieldView
继承了接口UserNameView
所以userName也会被输出。
集合的反序列化
在Controller方法中,可以使用@RequestBody
将提交的JSON自动映射到方法参数上,比如:
1 | @RequestMapping("updateuser") |
上面方法可以接受如下一个JSON请求,并自动映射到User对象上:
1 | [{"userName":"mrbird","age":26},{"userName":"scott","age":27}] |
Spring Boot 能自动识别出List对象包含的是User类,因为在方法中定义的泛型的类型会被保留在字节码中,所以Spring Boot能识别List包含的泛型类型从而能正确反序列化。
有些情况下,集合对象并没有包含泛型定义,如下代码所示,反序列化并不能得到期望的结果。
1 | @Autowired |
访问customize
,控制台抛出异常:
1 | java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.example.pojo.User |
这是因为在运行时刻,泛型己经被擦除了(不同于方法参数定义的泛型,不会被擦除)。为了提供泛型信息,Jackson提供了JavaType ,用来指明集合类型,将上述方法改为:
1 | @Autowired |
访问customize
,页面输出:mrbirdscott
。