Jackson的使用(转载)
原文链接
简介
在 Java 开发日常中,我们经常会写接口,尤其微服务流行的今天,各个服务都是通过接口相互调用配合,而接口实现的大部分都是基于 Http 协议,且使用 Json 数据格式,而在 Java 中最常用的就是 Jackson、Fastjson、Gson 三种 Json 解析器。
SpringBoot 默认的 Json 解析器是Jackson 。Jackson不但可以完成简单的序列化和反序列化操作,也能实现复杂的个性化的序列化和反序列化操作。
自定义ObjectMapper
SpringBoot中使用RestController
注解(其实是ResponseBody
的作用)编写的restful接口返回对象使用的是json格式。
注意: User类必须定义对应属性的Getter方法,如果某个属性没有定义Getter方法,也无法序列化
@RestController
public class IndexController {
@GetMapping
public User getUser() {
return new User(1,"tom",18,new Date());
}
}
User类
这里我们使用@Data
给所有属性添加Getter方法
@AllArgsConstructor
@NoArgsConstructor
@Data
public class User implements Serializable {
private static final long serialVersionUID = -1716994393946400799L;
private Integer id;
private String name;
private Integer age;
private Date birth;
}
调用该接口,返回数据为
{
"id": 1,
"name": "tom",
"age": 18,
"birth": "2023-03-19T06:03:34.828+00:00"
}
返回的时间为UTC格式字符串,要改变默认返回格式,我们可以自定义一个ObjectMapper
添加下列配置类
@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对象,并且设置了时间格式。再次访问接口,返回结果如下:
{
"id": 1,
"name": "tom",
"age": 18,
"birth": "2023-03-19 14:07:21"
}
序列化
Jackson可通过使用mapper的writeValueAsString
方法将Java对象序列化为json格式字符串:
@Autowired
private ObjectMapper objectMapper;
@Test
void objectToJson() throws JsonProcessingException {
User user = new User(1, "tom", 18, new Date());
String str = objectMapper.writeValueAsString(user);
System.out.println(str);
}
输出结果
{
"id":1,
"name":"tom",
"age":18,
"birth":"2023-03-19 14:14:04"
}
反序列化
树遍历
当采用树遍历的方式时,json被读入到JsonNode对象中,可以像操作XML DOM那样读取json
readTree
方法可以接受一个字符串或者字节数组、文件、InputStream等, 返回JsonNode作为根节点。
@Autowired
private ObjectMapper objectMapper;
@Test
public void readJsonString() throws JsonProcessingException {
// 解析json对象
User user = new User(1, "tom", 18, new Date());
String str = objectMapper.writeValueAsString(user);
JsonNode node = objectMapper.readTree(str);
int id = node.get("id").asInt();
String name = node.get("name").asText();
int age = node.get("age").asInt();
System.out.println(id);
System.out.println(name + " : " + age);
// 更复杂的例子
String JsonStr =
node = objectMapper.readTree(JsonStr);
String str1 = node.get("store").get("book").get(0).get("category").asText();
System.out.println(str1);
int price = node.get("expensive").asInt();
System.out.println(price);
}
/*
读取resources/static下的test.json文件
*/
String readJsonFile() {
StringBuilder sb = new StringBuilder();
try {
FileReader fr = new FileReader("src/main/resources/static/test.json");
BufferedReader bfd = new BufferedReader(fr);
String s = "";
while((s=bfd.readLine())!=null) {
sb.append(s);
}
} catch (Exception e) {
e.printStackTrace();
}
return sb.toString();
}
test.json
内容如下
{
"store": {
"book": [
{
"category": "reference",
"author": "Nigel Rees",
"title": "Sayings of the Century",
"price": 8.95
},
{
"category": "fiction",
"author": "Evelyn Waugh",
"title": "Sword of Honour",
"price": 12.99
},
{
"category": "fiction",
"author": "Herman Melville",
"title": "Moby Dick",
"isbn": "0-553-21311-3",
"price": 8.99
},
{
"category": "fiction",
"author": "J. R. R. Tolkien",
"title": "The Lord of the Rings",
"isbn": "0-395-19395-8",
"price": 22.99
}
],
"bicycle": {
"color": "red",
"price": 19.95
}
},
"expensive": 10
}
输出
1
tom : 18
reference
10
绑定对象
我们也可以将Java对象和JSON数据进行绑定,如下所示:
/**
* Java对象和JSON数据进行绑定
* 读取json字符串中的内容到java对象中
*/
@Test
void readJsonAsObject() {
try {
String json = "{\"id\":1,\"name\":\"tom\",\"age\":18,\"birth\":\"2023-03-18 23:57:49\"}";
/*
注意要添加User类的无参构造方法,否则会报错
*/
User user = objectMapper.readValue(json, User.class);
String name = user.getName();
int age = user.getAge();
Date birth = user.getBirth();
// 设置Date数据的打印格式
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(sdf.format(birth));
System.out.println(name + " " + age);
} catch (Exception e) {
e.printStackTrace();
}
}
输出
2023-03-18 23:57:49
tom 18
Jackson注解
@JsonProperty
作用在属性上,用来为JSON Key指定一个别名。
@JsonProperty("my-birth")
private Date birth;
调用接口,birth
已经变为my-birth
{
"id": 1,
"name": "tom",
"age": 18,
"my-birth": "2023-03-19 15:04:35"
}
@Jsonlgnore
作用在属性上,用来忽略此属性。
@JsonIgnore
private Integer id;
调用接口,返回数据没有id
属性
{
"name": "tom",
"age": 18,
"birth": "2023-03-19 15:07:37"
}
@JsonIgnoreProperties
作用在类上,可同时忽略多个属性
@JsonIgnoreProperties({"id","age"})
public class User implements Serializable {
...
}
调用接口,返回数据中已忽略id
和age
{
"name": "tom",
"birth": "2023-03-19 15:09:04"
}
@JsonFormat
用于日期格式化,如:
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date birth;
@JsonSerialize
指定一个实现类来自定义序列化。该类必须实现JsonSerializer
接口。
例如自定义一个UserSerializer
来指定User
类的序列化
public class UserSerializer extends JsonSerializer<User> {
@Override
public void serialize(User user, JsonGenerator generator, SerializerProvider provider)
throws IOException {
generator.writeStartObject();
generator.writeStringField("user-name", user.getName());
generator.writeEndObject();
}
}
上面的代码中我们仅仅序列化userName属性,且输出的key是user-name
。
再使用注解@JsonSerialize
来指定User对象的序列化方式
@JsonSerialize(using = UserSerializer.class)
public class User implements Serializable {
...
}
返回的数据就只有一个用户名,且key的名称为user-name
{
"user-name": "tom"
}
@JsonDeserialize
@JsonDeserialize
,用户自定义反序列化,同@JsonSerialize
,类需要实现JsonDeserializer
接口。
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.setName(userName);
return user;
}
}
使用注解@JsonDeserialize
来指定User对象的序列化方式:
@JsonDeserialize (using = UserDeserializer.class)
@JsonSerialize(using = UserSerializer.class)
public class User implements Serializable {
...
}
测试
/**
* 测试User的自定义反序列化
* @throws JsonProcessingException
*/
@Autowired
private ObjectMapper objectMapper;
@Test
void customDeserializer() throws JsonProcessingException {
String json = "{\"user-name\":\"tom\"}";
User user = objectMapper.readValue(json, User.class);
String name = user.getName();
System.out.println(name);
}
输出
tom
集合的反序列化
定义接口时,可以使用@RequestBody
将提交的JSON自动映射到方法参数上,比如:
@PostMapping("size")
public int getSize(@RequestBody List<User> list){
return list.size();
}
上面方法可以接受如下一个JSON请求,并自动映射到User对象上:
[{"name":"tom","age":26},{"name":"barney","age":27}]
Spring Boot 能自动识别出List对象包含的是User类,因为在方法中定义的泛型的类型会被保留在字节码中,所以Spring Boot能识别List包含的泛型类型从而能正确反序列化。
如何对json字符串的集合对象正确反序列化
有些情况下,集合对象并没有包含泛型定义,如下代码所示,反序列化并不能得到期望的结果。
@Autowired
ObjectMapper objectMapper;
@GetMapping("custom")
public String customize() throws IOException {
String jsonStr = "[{\"name\":\"tom\",\"age\":26},{\"name\":\"barney\",\"age\":27}]";
List<User> list = objectMapper.readValue(jsonStr, List.class);
StringBuilder msg = new StringBuilder();
for (User user : list) {
msg.append(user.getName());
}
return msg.toString();
}
访问http://localhost:8080/custom
,控制台抛出异常:
这是因为在运行时刻,泛型己经被擦除了(不同于方法参数定义的泛型,不会被擦除)。为了提供泛型信息,Jackson提供了JavaType
,用来指明集合类型,将上述方法改为:
@Autowired
ObjectMapper objectMapper;
@GetMapping("custom")
public String customize() throws IOException {
String jsonStr = "[{\"name\":\"tom\",\"age\":26},{\"name\":\"barney\",\"age\":27}]";
JavaType type = objectMapper.getTypeFactory().constructParametricType(List.class, User.class);
List<User> list = objectMapper.readValue(jsonStr, type);
StringBuilder msg = new StringBuilder();
for (User user : list) {
msg.append(user.getName()).append(" ");
}
return msg.toString();
}
访问http://localhost:8080/custom
,正确输出