其他分享
首页 > 其他分享> > Jackson 解析 JSON 详细教程

Jackson 解析 JSON 详细教程

作者:互联网

点赞再看,动力无限。 微信搜「 程序猿阿朗 」。

本文 Github.com/niumoo/Java…未读代码博客 已经收录,有很多知识点和系列文章。

JSON 对于开发者并不陌生,如今的 WEB 服务、移动应用、甚至物联网大多都是以 JSON 作为数据交换的格式。学习 JSON 格式的操作工具对开发者来说是必不可少的。这篇文章将介绍如何使用 Jackson 开源工具库对 JSON 进行常见操作。

JSON 介绍

什么是 JSON ?JSON 是 ”JavaScript Object Notation“ 的缩写,JSON 是一种基于文本的格式,可以把它理解为是一个结构化的数据,这个结构化数据中可以包含键值映射、嵌套对象以及数组等信息。

{
  "array": [
    1,
    2,
    3
  ],
  "boolean": true,
  "color": "gold",
  "null": null,
  "number": 123,
  "object": {
    "a": "b",
    "c": "d"
  },
  "string": "www.wdbyte.com"
}
复制代码

Jackson 介绍

Jackson 和 FastJson 一样,是一个 Java 语言编写的,可以进行 JSON 处理的开源工具库,Jackson 的使用非常广泛,Spring 框架默认使用 Jackson 进行 JSON 处理。

Jackson 有三个核包,分别是 Streaming、Databid、Annotations,通过这些包可以方便的对 JSON 进行操作。

得益于 Jackson 高扩展性的设计,有很多常见的文本格式以及工具都有对 Jackson 的相应适配,如 CSV、XML、YAML 等。

Jackson Maven 依赖

在使用 Jackson 时,大多数情况下我们只需要添加 jackson-databind 依赖项,就可以使用 Jackson 功能了,它依赖了下面两个包。

jackson-databind 依赖

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.13.3</version>
</dependency>
复制代码

为了方便这篇文章后续的代码演示,我们同时引入 Junit 进行单元测试和 Lombok 以减少 Get/Set 的代码编写。

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.8.2</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.22</version>
</dependency>
复制代码

ObjectMapper 对象映射器

ObjectMapper 是 Jackson 库中最常用的一个类,使用它可以进行 Java 对象和 JSON 字符串之间快速转换。如果你用过 FastJson,那么 Jackson 中的 ObjectMapper 就如同 FastJson 中的 JSON 类。

这个类中有一些常用的方法:

大多数情况下,ObjectMapper 的工作原理是通过 Java Bean 对象的 Get/Set 方法进行转换时映射的,所以正确编写 Java 对象的 Get/Set 方法尤为重要,不过 ObjectMapper 也提供了诸多配置,比如可以通过配置或者注解的形式对 Java 对象和 JSON 字符串之间的转换过程进行自定义。这些在下面部分都会介绍到。

Jackson JSON 基本操作

Jackson 作为一个 Java 中的 JSON 工具库,处理 JSON 字符串和 Java 对象是它最基本最常用的功能,下面通过一些例子来演示其中的用法。

Jackson JSON 序列化

编写一个 Person 类,定义三个属性,名称、年龄以及技能。

/**
 * @author https://www.wdbyte.com
 */
@Data
public class Person {
    private String name;
    private Integer age;
    private List<String> skillList;
}
复制代码

将 Java 对象转换成 JSON 字符串。

import java.util.Arrays;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

/**
 * @author https://www.wdbyte.com
 */
class PersonTest {

    ObjectMapper objectMapper = new ObjectMapper();

    @Test
    void pojoToJsonString() throws JsonProcessingException {
        Person person = new Person();
        person.setName("aLng");
        person.setAge(27);
        person.setSkillList(Arrays.asList("java", "c++"));

        String json = objectMapper.writeValueAsString(person);
        System.out.println(json);
        String expectedJson = "{\"name\":\"aLng\",\"age\":27,\"skillList\":[\"java\",\"c++\"]}";
        Assertions.assertEquals(json, expectedJson);
    }
}
复制代码

输出的 JSON 字符串:

{"name":"aLng","age":27,"skillList":["java","c++"]}
复制代码

Jackson 甚至可以直接把序列化后的 JSON 字符串写入文件或者读取成字节数组。

mapper.writeValue(new File("result.json"), myResultObject);
// 或者
byte[] jsonBytes = mapper.writeValueAsBytes(myResultObject);
// 或者
String jsonString = mapper.writeValueAsString(myResultObject);
复制代码

Jackson JSON 反序列化

直接贴出代码:

package com.wdbyte.jackson;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

/**
 * @author https://www.wdbyte.com
 */
class PersonTest {

    ObjectMapper objectMapper = new ObjectMapper();

    @Test
    void jsonStringToPojo() throws JsonProcessingException {
        String expectedJson = "{\"name\":\"aLang\",\"age\":27,\"skillList\":[\"java\",\"c++\"]}";
        Person person = objectMapper.readValue(expectedJson, Person.class);
        System.out.println(person);
        Assertions.assertEquals(person.getName(), "aLang");
        Assertions.assertEquals(person.getSkillList().toString(), "[java, c++]");
    }
}
复制代码

输出结果:

Person(name=aLang, age=27, skillList=[java, c++])
复制代码

上面的例子演示了如何使用 Jackson 把一个 JSON 字符串反序列化成 Java 对象,其实 Jackson 对文件中的 JSON 字符串、字节形式的 JSON 字符串反序列化同样简单。

比如先准备了一个 JSON 内容文件 Person.json。

{
  "name": "aLang",
  "age": 27,
  "skillList": [
    "java",
    "c++"
  ]
}
复制代码

下面进行读取转换。

ObjectMapper objectMapper = new ObjectMapper();

@Test
void testJsonFilePojo() throws IOException {
    File file = new File("src/Person.json");
    Person person = objectMapper.readValue(file, Person.class);
  	// 或者
    // person = mapper.readValue(new URL("http://some.com/api/entry.json"), MyValue.class);
    System.out.println(person);
    Assertions.assertEquals(person.getName(), "aLang");
    Assertions.assertEquals(person.getSkillList().toString(), "[java, c++]");
}
复制代码

同样输出了 Person 内容。

Person(name=aLang, age=27, skillList=[java, c++])
复制代码

JSON 转 List

上面演示 JSON 字符串都是单个对象的,如果 JSON 是一个对象列表那么使用 Jackson 该怎么处理呢?

已经存在一个文件 PersonList.json.

[
  {
    "name": "aLang",
    "age": 27,
    "skillList": [
      "java",
      "c++"
    ]
  },
  {
    "name": "darcy",
    "age": 26,
    "skillList": [
      "go",
      "rust"
    ]
  }
]
复制代码

读取它然后转换成 List<Person>

ObjectMapper objectMapper = new ObjectMapper();

@Test
void fileToPojoList() throws IOException {
    File file = new File("src/EmployeeList.json");
    List<Person> personList = objectMapper.readValue(file, new TypeReference<List<Person>>() {});
    for (Person person : personList) {
        System.out.println(person);
    }
    Assertions.assertEquals(personList.size(), 2);
    Assertions.assertEquals(personList.get(0).getName(), "aLang");
    Assertions.assertEquals(personList.get(1).getName(), "darcy");
}
复制代码

可以输出对象内容:

Person(name=aLang, age=27, skillList=[java, c++])
Person(name=darcy, age=26, skillList=[go, rust])
复制代码

JSON 转 Map

JSON 转 Map 在我们没有一个对象的 Java 对象时十分实用,下面演示如何使用 Jackson 把 JSON 文本转成 Map 对象。

ObjectMapper objectMapper = new ObjectMapper();

@Test
void jsonStringToMap() throws IOException {
    String expectedJson = "{\"name\":\"aLang\",\"age\":27,\"skillList\":[\"java\",\"c++\"]}";
    Map<String, Object> employeeMap = objectMapper.readValue(expectedJson, new TypeReference<Map>() {});
    System.out.println(employeeMap.getClass());
    for (Entry<String, Object> entry : employeeMap.entrySet()) {
        System.out.println(entry.getKey() + ":" + entry.getValue());
    }
    Assertions.assertEquals(employeeMap.get("name"), "aLang");
}
复制代码

可以看到 Map 的输出结果:

class java.util.LinkedHashMap
name:aLang
age:27
skillList:[java, c++]
复制代码

Jackson 忽略字段

如果在进行 JSON 转 Java 对象时,JSON 中出现了 Java 类中不存在的属性,那么在转换时会遇到 com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException 异常。

使用 objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) 可以忽略不存在的属性。

ObjectMapper objectMapper = new ObjectMapper();

@Test
void jsonStringToPojoIgnoreProperties() throws IOException {
    // UnrecognizedPropertyException
    String json = "{\"yyy\":\"xxx\",\"name\":\"aLang\",\"age\":27,\"skillList\":[\"java\",\"c++\"]}";
    objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    Person person = objectMapper.readValue(json, Person.class);
  	System.out.printf(person.toString());
    Assertions.assertEquals(person.getName(), "aLang");
    Assertions.assertEquals(person.getSkillList().toString(), "[java, c++]");
}
复制代码

正常输出:

Person(name=aLang, age=27, skillList=[java, c++])
复制代码

Jackson 日期格式化

在 Java 8 之前我们通常使用 java.util.Date 类来处理时间,但是在 Java 8 发布时引入了新的时间类 java.time.LocalDateTime. 这两者在 Jackson 中的处理略有不同。

先创建一个有两种时间类型属性的 Order 类。

package com.wdbyte.jackson;

import java.time.LocalDateTime;
import java.util.Date;

import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author https://www.wdbyte.com
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {
  
    private Integer id;

    private Date createTime;

    private LocalDateTime updateTime;
}

复制代码

Date 类型

下面我们新建一个测试用例来测试两种时间类型的 JSON 转换。

package com.wdbyte.jackson;

import java.time.LocalDateTime;
import java.util.Date;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

/**
 * @author https://www.wdbyte.com
 */
class OrderTest {

    ObjectMapper objectMapper = new ObjectMapper();

    @Test
    void testPojoToJson0() throws JsonProcessingException {
        Order order = new Order(1, new Date(), null);
        String json = objectMapper.writeValueAsString(order);
        System.out.println(json);

        order = objectMapper.readValue(json, Order.class);
        System.out.println(order.toString());

        Assertions.assertEquals(order.getId(), 1);
    }

}
复制代码

在这个测试代码中,我们只初始化了 Date 类型的时间,下面是输出的结果:

{"id":1,"createTime":1658320852395,"updateTime":null}
Order(id=1, createTime=Wed Jul 20 20:40:52 CST 2022, updateTime=null)
复制代码

可以看到正常的进行了 JSON 的序列化与反序列化,但是 JSON 中的时间是一个时间戳格式,可能不是我们想要的。

LocalDateTime 类型

为什么没有设置 LocalDateTime 类型的时间呢?因为默认情况下进行 LocalDateTime 类的 JSON 转换会遇到报错。

/**
 * @author https://www.wdbyte.com
 */
class OrderTest {

    ObjectMapper objectMapper = new ObjectMapper();

    @Test
    void testPojoToJson() throws JsonProcessingException {
        Order order = new Order(1, new Date(), LocalDateTime.now());
        String json = objectMapper.writeValueAsString(order);
        System.out.println(json);

        order = objectMapper.readValue(json, Order.class);
        System.out.println(order.toString());

        Assertions.assertEquals(order.getId(), 1);
    }
}
复制代码

运行后会遇到报错:

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: 
			Java 8 date/time type `java.time.LocalDateTime` not supported by default: 
				add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" 
          to enable handling (through reference chain: com.wdbyte.jackson.Order["updateTime"])

复制代码

这里我们需要添加相应的数据绑定支持包。

添加依赖:

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    <version>2.13.3</version>
</dependency>
复制代码

然后在定义 ObjectMapper 时通过 findAndRegisterModules() 方法来注册依赖。

import java.time.LocalDateTime;
import java.util.Date;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

/**
 * @author https://www.wdbyte.com
 */
class OrderTest {

    ObjectMapper objectMapper = new ObjectMapper().findAndRegisterModules();

    @Test
    void testPojoToJson() throws JsonProcessingException {
        Order order = new Order(1, new Date(), LocalDateTime.now());
        String json = objectMapper.writeValueAsString(order);
        System.out.println(json);

        order = objectMapper.readValue(json, Order.class);
        System.out.println(order.toString());

        Assertions.assertEquals(order.getId(), 1);
    }
}
复制代码

运行可以得到正常序列化与反序列化日志,不过序列化后的时间格式依旧奇怪。

{"id":1,"createTime":1658321191562,"updateTime":[2022,7,20,20,46,31,567000000]}
Order(id=1, createTime=Wed Jul 20 20:46:31 CST 2022, updateTime=2022-07-20T20:46:31.567)
复制代码

时间格式化

通过在字段上使用注解 @JsonFormat 来自定义时间格式。

import java.time.LocalDateTime;
import java.util.Date;

import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {

    private Integer id;

    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
    private Date createTime;

    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
    private LocalDateTime updateTime;
}
复制代码

再次运行上面的列子可以得到时间格式化后的 JSON 字符串。

{"id":1,"createTime":"2022-07-20 20:49:46","updateTime":"2022-07-20 20:49:46"}
Order(id=1, createTime=Wed Jul 20 20:49:46 CST 2022, updateTime=2022-07-20T20:49:46)
复制代码

Jackson 常用注解

@JsonIgnore

使用 @JsonIgnore 可以忽略某个 Java 对象中的属性,它将不参与 JSON 的序列化与反序列化。

示例:

package com.wdbyte.jackson;

import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;

/**
 * @author https://www.wdbyte.com
 */
@Data
public class Cat {

    private String name;

    @JsonIgnore
    private Integer age;
}
复制代码

编写单元测试类。

package com.wdbyte.jackson;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

/**
 * @author https://www.wdbyte.com
 */
class CatTest {

    ObjectMapper objectMapper = new ObjectMapper();

    @Test
    void testPojoToJson() throws JsonProcessingException {
        Cat cat = new Cat();
        cat.setName("Tom");
        cat.setAge(2);
        String json = objectMapper.writeValueAsString(cat);
        System.out.println(json);

        Assertions.assertEquals(json, "{\"name\":\"Tom\"}");

        cat = objectMapper.readValue(json, Cat.class);
        Assertions.assertEquals(cat.getName(), "Tom");
        Assertions.assertEquals(cat.getAge(), null);
    }

}
复制代码

输出结果中 age 属性为 null

{"name":"Tom"}
复制代码

@JsonGetter

使用 @JsonGetter 可以在对 Java 对象进行 JSON 序列化时自定义属性名称。

import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;

/**
 * @author https://www.wdbyte.com
 */
@Data
public class Cat {

    private String name;

    private Integer age;

    @JsonGetter(value = "catName")
    public String getName() {
        return name;
    }
}
复制代码

编写单元测试类进行测试。

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

/**
 * @author https://www.wdbyte.com
 */
class CatTest {

    ObjectMapper objectMapper = new ObjectMapper();

    @Test
    void testPojoToJson2() throws JsonProcessingException {
        Cat cat = new Cat();
        cat.setName("Tom");
        cat.setAge(2);
        String json = objectMapper.writeValueAsString(cat);
        System.out.println(json);
        Assertions.assertEquals(json, "{\"age\":2,\"catName\":\"Tom\"}");
    }
}
复制代码

输出结果,name 已经设置成了 catName

{"age":2,"catName":"Tom"}
复制代码

@JsonSetter

使用 @JsonSetter 可以在对 JSON 进行反序列化时设置 JSON 中的 key 与 Java 属性的映射关系。

package com.wdbyte.jackson;

import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonSetter;
import lombok.Data;

/**
 * @author https://www.wdbyte.com
 * @date 2022/07/17
 */
@Data
public class Cat {
    @JsonSetter(value = "catName")
    private String name;

    private Integer age;

    @JsonGetter(value = "catName")
    public String getName() {
        return name;
    }
}
复制代码

编写单元测试类进行测试。

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

/**
 * @author https://www.wdbyte.com
 */
class CatTest {

    ObjectMapper objectMapper = new ObjectMapper();

    @Test
    void testPojoToJson2() throws JsonProcessingException {
        String json = "{\"age\":2,\"catName\":\"Tom\"}";
        Cat cat = objectMapper.readValue(json, Cat.class);
        System.out.println(cat.toString());
        Assertions.assertEquals(cat.getName(), "Tom");
    }
}
复制代码

输出结果:

Cat(name=Tom, age=2)
复制代码

@JsonAnySetter

使用 @JsonAnySetter 可以在对 JSON 进行反序列化时,对所有在 Java 对象中不存在的属性进行逻辑处理,下面的代码演示把不存在的属性存放到一个 Map 集合中。

import java.util.Map;

import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.google.common.collect.Maps;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author https://www.wdbyte.com
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
    private String name;
    private Integer age;
    private Map<String, Object> diyMap = new HashMap<>();

    @JsonAnySetter
    public void otherField(String key, String value) {
        this.diyMap.put(key, value);
    }
}

复制代码

编写单元测试用例。

import java.util.HashMap;
import java.util.Map;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

/**
 * @author https://www.wdbyte.com
 */
class StudentTest {

    private ObjectMapper objectMapper = new ObjectMapper();

    @Test
    void testJsonToPojo() throws JsonProcessingException {
        Map<String, Object> map = new HashMap<>();
        map.put("name", "aLang");
        map.put("age", 18);
        map.put("skill", "java");

        String json = objectMapper.writeValueAsString(map);
        System.out.println(json);

        Student student = objectMapper.readValue(json, Student.class);
        System.out.println(student);

        Assertions.assertEquals(student.getDiyMap().get("skill"), "java");
    }

}
复制代码

输出结果中可以看到 JSON 中的 skill 属性因为不在 Java 类 Student 中,所以被放到了 diyMap 集合。

{"skill":"java","name":"aLang","age":18}
Student(name=aLang, age=18, diyMap={skill=java})
复制代码

@JsonAnyGetter

使用 @JsonAnyGetter 可以在对 Java 对象进行序列化时,使其中的 Map 集合作为 JSON 中属性的来源。下面做个示例。

import java.util.HashMap;
import java.util.Map;

import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.google.common.collect.Maps;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;

/**
 * @author https://www.wdbyte.com
 */
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class Student {
    @Getter
    @Setter
    private String name;

    @Getter
    @Setter
    private Integer age;
  
    @JsonAnyGetter
    private Map<String, Object> initMap = new HashMap() {{
        put("a", 111);
        put("b", 222);
        put("c", 333);
    }};
}
复制代码

编写单元测试用例。

class StudentTest {

    private ObjectMapper objectMapper = new ObjectMapper();

    @Test
    void testPojoToJsonTest() throws JsonProcessingException {
        Student student = new Student();
        student.setName("aLang");
        student.setAge(20);
        String json = objectMapper.writeValueAsString(student);
        System.out.println(json);
      
       Assertions.assertEquals(json,"{\"name\":\"aLang\",\"age\":20,\"a\":111,\"b\":222,\"c\":333}");
    }

}
复制代码

输出结果:

{"name":"aLang","age":20,"a":111,"b":222,"c":333}
复制代码

Jackson 总结

一如既往,文章代码都存放在 Github.com/niumoo/java….

<完>

文章持续更新,可以微信搜一搜「 程序猿阿朗 」或访问「程序猿阿朗博客 」第一时间阅读。本文 Github.com/niumoo/Java… 已经收录,有很多知识点和系列文章,欢迎Star。

我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿

来源:https://juejin.cn/post/7122993335010066463

标签:教程,Jackson,java,JSON,jackson,import,com,ObjectMapper
来源: https://www.cnblogs.com/konglxblog/p/16519374.html