点赞再看,能源有限。 微信搜「 程序猿阿朗 」。

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

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


<!-- more -->

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 进行操作。

  • Streaming 在 jackson-core 模块。 定义了一些流解决相干的 API 以及特定的 JSON 实现。
  • Annotations 在 jackson-annotations 模块,蕴含了 Jackson 中的注解。
  • Databind 在 jackson-databind 模块, 在 Streaming 包的根底上实现了数据绑定,依赖于 StreamingAnnotations 包。

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

Jackson Maven 依赖

在应用 Jackson 时,大多数状况下咱们只须要增加 jackson-databind 依赖项,就能够应用 Jackson 性能了,它依赖了上面两个包。

  • com.fasterxml.jackson.core:jackson-annotations
  • com.fasterxml.jackson.core:jackson-core
<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 类。

这个类中有一些罕用的办法:

  • readValue() 办法能够进行 JSON 的反序列化操作,比方能够将字符串、文件流、字节流、字节数组等将常见的内容转换成 Java 对象。
  • writeValue() 办法能够进行 JSON 的序列化操作,能够将 Java 对象转换成 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 */@Datapublic 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();@Testvoid 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();@Testvoid 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();@Testvoid 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.LinkedHashMapname:aLangage:27skillList:[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();@Testvoid 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@NoArgsConstructorpublic 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@NoArgsConstructorpublic 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 */@Datapublic 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 */@Datapublic 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 */@Datapublic 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@NoArgsConstructorpublic 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@NoArgsConstructorpublic 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 总结

  • Jackson 是 Java 中比拟流量的 JSON 解决库之一,它是 Spring 的默认 JSON 工具。
  • Jackson 次要有三个模块组成,Streaming API 、Annotations 和 Data Binding 。
  • Jackson 中的 ObjectMapper 类非常弱小,能够进行 JSON 相干解决,同时能够联合正文以及配置进行自定义转换逻辑。
  • Jackson 扩展性很好,如 CSV、XML、YAML 格局解决都对 Jackson 有相应的适配等。

判若两人,文章代码都寄存在 Github.com/niumoo/javaNotes.

<完>

文章继续更新,能够微信搜一搜「 程序猿阿朗 」或拜访「程序猿阿朗博客 」第一工夫浏览。本文 Github.com/niumoo/JavaNotes 曾经收录,有很多知识点和系列文章,欢送Star。