Jackson的使用(转载)

警告
本文最后更新于 2023-03-19,文中内容可能已过时,请谨慎使用。

​ 在 Java 开发日常中,我们经常会写接口,尤其微服务流行的今天,各个服务都是通过接口相互调用配合,而接口实现的大部分都是基于 Http 协议,且使用 Json 数据格式,而在 Java 中最常用的就是 Jackson、Fastjson、Gson 三种 Json 解析器。

​ SpringBoot 默认的 Json 解析器是Jackson 。Jackson不但可以完成简单的序列化和反序列化操作,也能实现复杂的个性化的序列化和反序列化操作。

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

作用在属性上,用来为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"
}

作用在属性上,用来忽略此属性。

@JsonIgnore
private Integer id;

调用接口,返回数据没有id属性

{
    "name": "tom",
    "age": 18,
    "birth": "2023-03-19 15:07:37"
}

作用在类上,可同时忽略多个属性

@JsonIgnoreProperties({"id","age"})
public class User implements Serializable {
		...
}

调用接口,返回数据中已忽略idage

{
    "name": "tom",
    "birth": "2023-03-19 15:09:04"
}

用于日期格式化,如:

@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date birth;

指定一个实现类来自定义序列化。该类必须实现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,用户自定义反序列化,同@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}]
/images/all/image-20230319153558190.png

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,控制台抛出异常:

错误信息
java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to cc.bnblogs.jsonstudy.pojo.User

这是因为在运行时刻,泛型己经被擦除了(不同于方法参数定义的泛型,不会被擦除)。为了提供泛型信息,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,正确输出

/images/all/image-20230319155532466.png
反序列化成功

相关文章