一、前言 平日里在项目中处理JSON一般用的都是阿里巴巴的Fastjson,后来发现使用Spring Boot内置的Jackson来完成JSON的序列化和反序列化操作也挺方便。Jackson不但可以完成简单的序列化和反序列化操作,也能实现复杂的个性化的序列化和反序列化操作。
二、自定义ObjectMapper 我们都知道,在Spring中使用@ResponseBody注解可以将方法返回的对象序列化成JSON,比如:
1 2 3 4 5 6 7 8 @RequestMapping("getuser") @ResponseBody public User getUser() { User user = new User(); user.setUserName("wno704"); user.setBirthday(new Date()); return user; }
User类:
1 2 3 4 5 6 7 8 9 10 @Getter @Setter public class User implements Serializable { private static final long serialVersionUID = 6222176558369919436L; private String userName; private int age; private String password; private Date birthday; }
访问getuser页面输出:
{"userName":"wno704","age":0,"password":null,bth":"2020-08-18T03:27:44.587+00:00"}
可看到时间默认以时间戳的形式输出,如果想要改变这个默认行为,我们可以自定义一个ObjectMapper来替代:
1 2 3 4 5 6 7 8 9 @Configuration public class JacksonConfig { @Bean public ObjectMapper getObjectMapper(){ ObjectMapper mapper = new ObjectMapper(); mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); return mapper; } }
上面配置获取了ObjectMapper对象,并且设置了时间格式。再次访问getuser,页面输出:
{"userName":"wno704","age":0,"password":null,bth":"2020-08-18 11:42:51"}
三、序列化 Jackson通过使用mapper的writeValueAsString方法将Java对象序列化为JSON格式字符串:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Autowired ObjectMapper mapper; @RequestMapping("serialization") @ResponseBody public String serialization() { try { User user = new User(); user.setUserName("mrbird"); user.setBirthday(new Date()); String str = mapper.writeValueAsString(user); return str; } catch (Exception e) { e.printStackTrace(); } return null; }
四、反序列化 使用@ResponseBody注解可以使对象序列化为JSON格式字符串,除此之外,Jackson也提供了反序列化方法。
4.1 树遍历 当采用树遍历的方式时,JSON被读入到JsonNode对象中,可以像操作XML DOM那样读取JSON。readTree方法可以接受一个字符串或者字节数组、文件、InputStream等, 返回JsonNode作为根节点,你可以像操作XML DOM那样操作遍历JsonNode以获取数据。比如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @Autowired ObjectMapper mapper; @RequestMapping("readjsonstring") @ResponseBody public String readJsonString() { try { String json = "{\"name\":\"wno704\",\"age\":28,\"hobby\":{\"first\":\"sleep\",\"second\":\"eat\"}}"; JsonNode node = this.mapper.readTree(json); String name = node.get("name").asText(); int age = node.get("age").asInt(); JsonNode hobby = node.get("hobby"); String first = hobby.get("first").asText(); return name + " " + age + " " + first; } catch (Exception e) { e.printStackTrace(); } return null; }
4.2 绑定对象 我们也可以将Java对象和JSON数据进行绑定,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Autowired ObjectMapper mapper; @RequestMapping("readjsonasobject") @ResponseBody public String readJsonAsObject() { try { String json = "{\"name\":\"mrbird\",\"age\":26}"; User user = mapper.readValue(json, User.class); String name = user.getUserName(); int age = user.getAge(); return name + " " + age; } catch (Exception e) { e.printStackTrace(); } return null; }
五、Jackson注解 Jackson包含了一些实用的注解:
5.1 @JsonProperty @Jsonlgnore,作用在属性上,用来忽略此属性。
1 2 @JsonIgnore private String password;
再次访问getuser页面输出:
{"userName":"mrbird","bth":"2018-04-02 10:45:34"}
password属性已被忽略。
5.2 @Jsonlgnore @Jsonlgnore,作用在属性上,用来忽略此属性。
1 2 @JsonIgnore private String password;
再次访问getuser页面输出:
{"userName":"mrbird","age":0,"bth":"2018-04-02 10:40:45"}
5.3 @JsonIgnoreProperties @JsonIgnoreProperties,忽略一组属性,作用于类上,比如JsonIgnoreProperties({ "password", "age" })。
1 2 3 4 @JsonIgnoreProperties({ "password", "age" }) public class User implements Serializable { ... }
再次访问getuser页面输出:
{"userName":"mrbird","bth":"2018-04-02 10:45:34"}
@JsonFormat,用于日期格式化,如:
1 2 @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date birthday;
5.5 @JsonNaming @JsonNaming,用于指定一个命名策略,作用于类或者属性上。Jackson自带了多种命名策略,你可以实现自己的命名策略,比如输出的key 由Java命名方式转为下面线命名方法 —— userName转化为user-name。
1 2 3 4 @JsonNaming(PropertyNamingStrategy.LowerCaseWithUnderscoresStrategy.class) public class User implements Serializable { ... }
再次访问getuser页面输出:
{"user_name":"mrbird","bth":"2018-04-02 10:52:12"}
5.6 @JsonSerialize @JsonSerialize,指定一个实现类来自定义序列化。类必须实现JsonSerializer接口,代码如下:
1 2 3 4 5 6 7 8 9 10 public class UserSerializer extends JsonSerializer<User> { @Override public void serialize(User user, JsonGenerator generator, SerializerProvider provider) throws IOException, JsonProcessingException { generator.writeStartObject(); generator.writeStringField("user-name", user.getUserName()); generator.writeEndObject(); } }
上面的代码中我们仅仅序列化userName属性,且输出的key是user-name。 使用注解@JsonSerialize来指定User对象的序列化方式:
1 2 3 4 @JsonSerialize(using = UserSerializer.class) public class User implements Serializable { ... }
再次访问getuser页面输出:
{"user-name":"mrbird"}
5.7 @JsonDeserialize @JsonDeserialize,用户自定义反序列化,同@JsonSerialize ,类需要实现JsonDeserializer接口。
1 2 3 4 5 6 7 8 9 10 11 12 public class UserDeserializer extends JsonDeserializer<User> { @Override public User deserialize(JsonParser parser, DeserializationContext context) throws IOException, JsonProcessingException { JsonNode node = parser.getCodec().readTree(parser); String userName = node.get("user-name").asText(); User user = new User(); user.setUserName(userName); return user; } }
使用注解@JsonDeserialize来指定User对象的序列化方式:
1 2 3 4 @JsonDeserialize (using = UserDeserializer.class) public class User implements Serializable { ... }
测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Autowired ObjectMapper mapper; @RequestMapping("readjsonasobject") @ResponseBody public String readJsonAsObject() { try { String json = "{\"user-name\":\"mrbird\"}"; User user = mapper.readValue(json, User.class); String name = user.getUserName(); return name; } catch (Exception e) { e.printStackTrace(); } return null; }
访问readjsonasobject,页面输出:
mrbird
5.8 @JsonView @JsonView,作用在类或者属性上,用来定义一个序列化组。 比如对于User对象,某些情况下只返回userName属性就行,而某些情况下需要返回全部属性。 因此User对象可以定义成如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class User implements Serializable { private static final long serialVersionUID = 6222176558369919436L; public interface UserNameView {}; public interface AllUserFieldView extends UserNameView {}; @JsonView(UserNameView.class) private String userName; @JsonView(AllUserFieldView.class) private int age; @JsonView(AllUserFieldView.class) private String password; @JsonView(AllUserFieldView.class) private Date birthday; ... }
User定义了两个接口类,一个为userNameView,另外一个为AllUserFieldView继承了userNameView接口。这两个接口代表了两个序列化组的名称。属性userName使用了@JsonView(UserNameView.class),而剩下属性使用了@JsonView(AllUserFieldView.class)。
Spring中Controller方法允许使用@JsonView指定一个组名,被序列化的对象只有在这个组的属性才会被序列化,代码如下:
1 2 3 4 5 6 7 8 9 10 11 @JsonView(User.UserNameView.class) @RequestMapping("getuser") @ResponseBody public User getUser() { User user = new User(); user.setUserName("mrbird"); user.setAge(26); user.setPassword("123456"); user.setBirthday(new Date()); return user; }
访问getuser页面输出:
{"userName":"mrbird"}
如果将@JsonView(User.UserNameView.class)替换为@JsonView(User.AllUserFieldView.class),输出:
{"userName":"mrbird","age":26,"password":"123456","birthday":"2018-04-02 11:24:00"}
因为接口AllUserFieldView继承了接口UserNameView所以userName也会被输出。 集合的反序列化
在Controller方法中,可以使用@RequestBody将提交的JSON自动映射到方法参数上,比如:
1 2 3 4 5 @RequestMapping("updateuser") @ResponseBody public int updateUser(@RequestBody List<User> list){ return list.size(); }
上面方法可以接受如下一个JSON请求,并自动映射到User对象上:
[{"userName":"mrbird","age":26},{"userName":"scott","age":27}]
Spring Boot 能自动识别出List对象包含的是User类,因为在方法中定义的泛型的类型会被保留在字节码中,所以Spring Boot能识别List包含的泛型类型从而能正确反序列化。
有些情况下,集合对象并没有包含泛型定义,如下代码所示,反序列化并不能得到期望的结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Autowired ObjectMapper mapper; @RequestMapping("customize") @ResponseBody public String customize() throws JsonParseException, JsonMappingException, IOException { String jsonStr = "[{\"userName\":\"mrbird\",\"age\":26},{\"userName\":\"scott\",\"age\":27}]"; List<User> list = mapper.readValue(jsonStr, List.class); String msg = ""; for (User user : list) { msg += user.getUserName(); } return msg; }
访问customize,控制台抛出异常:
java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.example.pojo.User
这是因为在运行时刻,泛型己经被擦除了(不同于方法参数定义的泛型,不会被擦除)。为了提供泛型信息,Jackson提供了JavaType ,用来指明集合类型,将上述方法改为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Autowired ObjectMapper mapper; @RequestMapping("customize") @ResponseBody public String customize() throws JsonParseException, JsonMappingException, IOException { String jsonStr = "[{\"userName\":\"mrbird\",\"age\":26},{\"userName\":\"scott\",\"age\":27}]"; JavaType type = mapper.getTypeFactory().constructParametricType(List.class, User.class); List<User> list = mapper.readValue(jsonStr, type); String msg = ""; for (User user : list) { msg += user.getUserName(); } return msg; }
访问customize,页面输出:mrbirdscott。