Jakob Jenkov 个人博客 Jackson 部分(译文)

介绍

JSON 是 JavaScript Object Notation 的缩写,JSON 是一种非常流行的数据交换格式,它通常被用来在浏览器和 web 服务器之间传递数据。因为浏览器原生支持解析 JSON 为 JavaScript 对象。在服务器,JSON 需要使用 JSON API 来解析和生成,Jackson 是最流行的 JSON API 之一。

安装

Jackson 由一个 core JAR 文件和两个 other JAR 文件组成,other JAR 文件使用到了 core JAR 文件。这三个 JAR 文件如下:

  • Jackson Core
  • Jackson Annotations
  • Jackson Databind

这三个项目也按上面的顺序互相使用,Jackson Databind 使用到了 Jackson Annotation,Jackson Annotation 使用到了 Jackson Core。

为了安装 Jackson 到你的应用程序,你需要添加这三个 JAR 文件到你应用程序的 classpath 下。

如果你使用 Maven 作为构建工具,那你需要在 pom.xml 文件中添加如下依赖:

<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-core</artifactId>
  <version>2.9.6</version>
</dependency>

<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-annotations</artifactId>
  <version>2.9.6</version>
</dependency>

<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.9.6</version>
</dependency>

jackson-annotations 和 jackson-databind 对 jackson-core 有传递依赖,这意味着如果你需要使用 jackson-databind,那么你只需要添加 jackson-databind 的依赖项,它将会自动引入其他两个包,当然你也可以在 pom.xml 中明确指定你需要使用的依赖。

ObjectMapper

使用 Jackson 的 ObjectMapper 类是使用 Jackson 解析 JSON 最简单的方式。ObjectMapper 可以从字符串、流或者文件解析 JSON,并创建 Java 对象或者对象图来表示解析之后的 JSON。将 JSON 解析成 Java 对象也叫做从 JSON 反序列化为 Java 对象。

ObjectMapper 也可以将 Java 对象转成 JSON,这也叫做序列化 Java 对象成 JSON。

ObjectMapper 能够解析 JSON 并转换成你开发的类的对象,或者转换成内置 JSON 树模型对象。

Jackson 使用 ObjectMapper 这个名字是因为它可以将 JSON 映射成 Java 对象(反序列化),或者将 Java 对象映射成 JSON(序列化)。

ObjectMapper 位于 Jackson Databind 这个项目中,所以你需要在你的项目中引入该项目。

示例

下面是 ObjectMapper 的一个简单示例:

ObjectMapper objectMapper = new ObjectMapper();

String carJson =
    "{ \"brand\" : \"Mercedes\", \"doors\" : 5 }";

try {
    Car car = objectMapper.readValue(carJson, Car.class);

    System.out.println("car brand = " + car.getBrand());
    System.out.println("car doors = " + car.getDoors());
} catch (IOException e) {
    e.printStackTrace();
}

Car 类是自己创建的,如你所见,Car.class 作为 readValue() 方法的第二个参数被解析,第一个参数是 JSON 的来源(string,stream 或者 file),Car 类定义如下:

public class Car {
    private String brand = null;
    private int doors = 0;

    public String getBrand() { return this.brand; }
    public void   setBrand(String brand){ this.brand = brand;}

    public int  getDoors() { return this.doors; }
    public void setDoors (int doors) { this.doors = doors; }
}

ObjectMapper 如何将 JSON 字段和 Java 字段匹配

要使用 Jackson 正确的从 JSON 中读取 Java 对象,知道 Jackson 是如何映射 JSON 对象字段和 Java 对象字段是非常重要的,所以接下来将会解释 Jackson 是怎么做的。

默认情况下,Jackson 通过匹配 JSON 字段的 name 到 Java 对象的 getter 和 setter 方法来映射 JSON 对象字段和 Java 对象字段。Jackson 会移除 getter 和 setter 方法名的 “get” 和 “set” 字符,并将剩下字符的第一个字符装成小写格式。

比如,JSON 字段 brand 会匹配 Java 对象中名为 getBrand() 和 setBrand() 的 getter 和 setter 方法。JSON 字段 engineNumber 会匹配名为 getEngineNumber() 和 setEngineNumber() 的 getter 和 setter 方法。

如果你想要用不同的方式来匹配 JSON 对象字段和 Java 对象字段,那么你需要使用自定义的 serializer 和 deserializer,或者使用一些 Jackson 提供的注解。

Jackson 包含了一系列 Java 注解,使用它们你可以修改 Jackson 在 JSON 和 Java 对象之间转换的行为。

JSON 字符串转 Java 对象

将一个 JSON 字符串转成 Java 对象非常的简单,你已经看过这种例子的,就是使用的 ObjectMapper 的 readValue() 方法,下面是另一个简单例子:

ObjectMapper objectMapper = new ObjectMapper();

String carJson =
    "{ \"brand\" : \"Mercedes\", \"doors\" : 5 }";

Car car = objectMapper.readValue(carJson, Car.class);

JSON Reader 转 Java 对象

你也可以从 JSON Reader 实例中读取 Java 对象,示例如下:

ObjectMapper objectMapper = new ObjectMapper();

String carJson =
        "{ \"brand\" : \"Mercedes\", \"doors\" : 4 }";
Reader reader = new StringReader(carJson);

Car car = objectMapper.readValue(reader, Car.class);

JSON 文件转 Java 对象

从文件中读取 JSON 可以考虑通过 FileReader 类来完成,也可以使用 File 类。示例如下:

ObjectMapper objectMapper = new ObjectMapper();

File file = new File("data/car.json");

Car car = objectMapper.readValue(file, Car.class);

JSON URL 转 Java 对象

你可能会需要从一个 URL 读取 JSON,像这样:

ObjectMapper objectMapper = new ObjectMapper();

URL url = new URL("file:data/car.json");

Car car = objectMapper.readValue(url, Car.class);

这个例子使用了 file URL,当然你也可以使用 HTTP URL,如 http://jenkov.com/some-data.json

JSON InputStream 转 Java 对象

使用 ObjectMapper 从 InputStream 读取 JSON 也是可能的,下面的例子中展示了如何从一个 InputStream 中读取 JSON:

ObjectMapper objectMapper = new ObjectMapper();

InputStream input = new FileInputStream("data/car.json");

Car car = objectMapper.readValue(input, Car.class);

JSON Byte Array 转 Java 对象

Jackson 也支持从字节数组中读取 JSON,示例如下:

ObjectMapper objectMapper = new ObjectMapper();

String carJson =
        "{ \"brand\" : \"Mercedes\", \"doors\" : 5 }";

byte[] bytes = carJson.getBytes("UTF-8");

Car car = objectMapper.readValue(bytes, Car.class);

JSON 数组字符串转对象数组

Jackson 的 ObjectMapper 也支持从一个 JSON 数组字符串中读取对象数组,示例如下:

String jsonArray = "[{\"brand\":\"ford\"}, {\"brand\":\"Fiat\"}]";

ObjectMapper objectMapper = new ObjectMapper();

Car[] cars2 = objectMapper.readValue(jsonArray, Car[].class);

注意,Car 数组类被传递给 readValue() 方法的第二个参数是为了告诉 ObjectMapper 你想要读取一个 Car 实例的数组。读取数组对象也可以通过其他的 JSON 源,而不只是 JSON 字符串,比如,文件、URL、InputStream、Reader 等。

JSON 数组字符串转 List 对象

Jackson ObjectMapper 也可以从 JSON 数组字符串中读取到 Java List 对象,如下所示:

String jsonArray = "[{\"brand\":\"ford\"}, {\"brand\":\"Fiat\"}]";

ObjectMapper objectMapper = new ObjectMapper();

List<Car> cars1 = objectMapper.readValue(jsonArray, new TypeReference<List<Car>>(){});

注意 TypeReference 参数被传递给了 readValue() 方法,这个参数告诉 Jackson 要读取一个存储 Car 对象的 List。

JSON 字符串转 Map

Jackson ObjectMapper 也可以从 JSON 字符串中读取 Map,这在你事先不知道要解析的 JSON 结构的时候非常有用。在 JSON 对象中的每个字段对应于 Map 中的一个键值对。示例如下:

String jsonObject = "{\"brand\":\"ford\", \"doors\":5}";

ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> jsonMap = objectMapper.readValue(jsonObject,
    new TypeReference<Map<String,Object>>(){});

忽略不知道的 JSON 字段

某些情况下,JSON 的字段数量多于你要转成的 Java 对象中的字段数量,当发生这种情况下时,Jackson 默认会抛出异常。但是有时允许 JSON 中的字段要多于关联的 Java 对象中的字段,在这种情况下,Jackson 允许你通过 Jackson 配置来忽略额外的字段,配置如下:

objectMapper.configure(
    DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

JSON 中 Null 值对应 Java 基本数据类型

如果 JSON 字符串有一个值被设置为 null,而这个字段在关联的 Java 对象中的基本数据类型,而基本数据类型是不能设置成 null 的,因此在转换的时候 Jackson 默认会忽略掉该基本数据类型。比如有下面的 Java 类和 JSON 字符串:

public class Car {

    private String brand = null;
    private int doors = 0;

    public String getBrand() { return this.brand; }
    public void   setBrand(String brand){ this.brand = brand;}

    public int  getDoors(){ return this.doors; }
    public void setDoors (int doors) { this.doors = doors; }

    @Override
    public String toString() {
        return "Car{" +
                "brand='" + brand + '\'' +
                ", doors=" + doors +
                '}';
    }
}
{ "brand":"Toyota", "doors":null }

注意看在 JSON 字符串中 doors 字段的值是 null,但是对应的 Java 对象中的 doors 是 int 类型的,使用 ObjectMapper 解析:

@Test
public void test01() throws JsonProcessingException {
    String jsonStr = "{ \"brand\":\"Toyota\", \"doors\":null }";
    ObjectMapper objectMapper = new ObjectMapper();
    Car car = objectMapper.readValue(jsonStr, Car.class);
    System.out.println(car.toString());
}

打印后出来的 car 的 doors 的值是 int 类型的初始值,也就是 0。

Car{brand='Toyota', doors=0}

当然,你也可以配置 Jackson 在遇到这种场景的时候抛出异常,配置如下:

objectMapper.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, true);

自定义 Deserializer

某些情况下,你可能想要通过不同于 Jackson ObjectMapper 默认行为的方式来解析 JSON 字符串为 Java 对象,这时你可以向 ObjectMapper 添加一个 custom Deserializer 来执行你想要的反序列化行为。

下面例子给 Jackson ObjectMapper 注册了一个自定义的 Deserializer:

String json = "{ \"brand\" : \"Ford\", \"doors\" : 6 }";

SimpleModule module =
        new SimpleModule("CarDeserializer", new Version(3, 1, 8, null, null, null));
module.addDeserializer(Car.class, new CarDeserializer(Car.class));

ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(module);

Car car = mapper.readValue(json, Car.class);

CarDeserializer.java

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;

import java.io.IOException;

public class CarDeserializer extends StdDeserializer<Car> {

    public CarDeserializer(Class<?> vc) {
        super(vc);
    }

    @Override
    public Car deserialize(JsonParser parser, DeserializationContext deserializer) throws IOException {
        Car car = new Car();
        while(!parser.isClosed()){
            JsonToken jsonToken = parser.nextToken();

            if(JsonToken.FIELD_NAME.equals(jsonToken)){
                String fieldName = parser.getCurrentName();
                System.out.println(fieldName);

                jsonToken = parser.nextToken();

                if("brand".equals(fieldName)){
                    car.setBrand(parser.getValueAsString());
                } else if ("doors".equals(fieldName)){
                    car.setDoors(parser.getValueAsInt());
                }
            }
        }
        return car;
    }
}

Java 对象转 JSON

Jackson ObjectMapper 也可以用来从一个对象中生成 JSON,你可以以下方法之一实现这一功能:

  • writeValue()
  • writeValueAsString()
  • writeValueAsBytes()

下面的例子从 Car 对象中生成 JSON:

ObjectMapper objectMapper = new ObjectMapper();

Car car = new Car();
car.brand = "BMW";
car.doors = 4;

objectMapper.writeValue(
    new FileOutputStream("data/output-2.json"), car);

该例子首先创建了 ObjectMapper 和 Car 实例,最后调用 ObjectMapper 的 writeValue() 方法来讲 Car 实例转换成 JSON,并写入到给定的 FileOutputStream 中。

ObjectMapper 的 writeValueAsString() 方法和 writeValueAsBytes() 方法都可以从对象中生成 JSON,并以字符串和字节数组的格式返回,writeValueAsString() 方法的使用示例如下:

ObjectMapper objectMapper = new ObjectMapper();

Car car = new Car();
car.brand = "BMW";
car.doors = 4;

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

输出的 JSON:

{"brand":"BMW","doors":4}

自定义 Serializer

某些时候你可能需要不同于 Jackson 的默认行为来序列化一个 Java 对象到 JSON,比如,你可能想要在 JSON 中使用不同于 Java 对象的字段名,或者你想要完全忽略某些字段。

你可以给 ObjectMapper 提供自定义的 Serializer 来实现该功能。Serializer 作为一个确定类被注册,它将会在 ObjectMapper 序列化 Car 对象的时候被调用,下面例子展示了如何给 Car 类注册一个自定义 Serializer:

CarSerializer carSerializer = new CarSerializer(Car.class);
ObjectMapper objectMapper = new ObjectMapper();

SimpleModule module =
        new SimpleModule("CarSerializer", new Version(2, 1, 3, null, null, null));
module.addSerializer(Car.class, carSerializer);

objectMapper.registerModule(module);

Car car = new Car();
car.setBrand("Mercedes");
car.setDoors(5);

String carJson = objectMapper.writeValueAsString(car);

被 Jackson 自定义 serializer 产生的字符串看起来像下面这样:

{"producer":"Mercedes","doorCount":5}

CarSerializer.java

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;

import java.io.IOException;

public class CarSerializer extends StdSerializer<Car> {

    protected CarSerializer(Class<Car> t) {
        super(t);
    }

    public void serialize(Car car, JsonGenerator jsonGenerator,
                          SerializerProvider serializerProvider)
            throws IOException {

        jsonGenerator.writeStartObject();
        jsonGenerator.writeStringField("producer", car.getBrand());
        jsonGenerator.writeNumberField("doorCount", car.getDoors());
        jsonGenerator.writeEndObject();
    }
}

注意,传递给 serialize() 方法的第二个参数是 Jackson JsonGenerator 实例,你可以使用它来序列化 Java 对象,在上面的例子中是 Car 对象。

日期格式

Jackson 默认将 java.util.Date 对象序列化为它的 long 类型值,它代表从 1970.1.1 至今的毫秒数。然而,Jackson 也支持序列化日期为字符串,在这一节我们将仔细研究 Jackson 日期格式。

Date to long

首先我将会向你展示 Jackson 默认的日期格式,即将 Date 序列化为 long 类型值,示例如下:

public class Transaction {
    private String type = null;
    private Date date = null;

    public Transaction() {
    }

    public Transaction(String type, Date date) {
        this.type = type;
        this.date = date;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date = date;
    }
}

要使用 Jackson ObjectMapper 序列化一个 Transaction 对象就像你序列化其他 Java 对象一样,它的代码看起来长这样:

Transaction transaction = new Transaction("transfer", new Date());

ObjectMapper objectMapper = new ObjectMapper();
String output = objectMapper.writeValueAsString(transaction);

System.out.println(output);

打印输出如下:

{"type":"transfer","date":1516442298301}

注意看 date 字段的格式,它是一个 long 类型值,就像之前解释的那样。

Date to String

long 类型值对人类来说可读性并不高,因此 Jackson 也支持文本类型的日期格式。你可以通过给 ObjectMapper 设置一个 SimpleDateFormat 来给 Jackson 指定额外的日期格式,示例如下:

SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
objectMapper2.setDateFormat(dateFormat);

String output2 = objectMapper2.writeValueAsString(transaction);
System.out.println(output2);

打印输出如下:

{"type":"transfer","date":"2018-01-20"}

注意看此时 date 字段被格式化为了字符串。

JSON Tree Model(树模型)

Jackson 有一个内建的 Tree Model 用来表示一个 JSON 对象。当你不知道一个 JSON 的层级构建的时候,Jackson 的 Tree Model 非常有用。如果你需要在使用和传递 JSON 对象之前修改它,那么 Jackson Tree Model 也是非常有用的,这些场景在 Data Streaming 中非常常见。

Jackson Tree Model 用 JsonNode 类来表示,你可以使用 ObjectMapper 来解析 JSON 为一个 JsonNode 树模型,就像对你自己的类所做的那样。

下面的章节将会展示 Jackson ObjectMapper 如何读取和写入 JsonNode 实例。

下面是一个 Jackson Tree Model 的简单示例:

String carJson =
        "{ \"brand\" : \"Mercedes\", \"doors\" : 5 }";

ObjectMapper objectMapper = new ObjectMapper();

try {

    JsonNode jsonNode = objectMapper.readValue(carJson, JsonNode.class);

} catch (IOException e) {
    e.printStackTrace();
}

正如你所看到的,JSON 字符串被解析成了一个 JsonNode 对象而不是 Car 对象,只需要传递 JsonNode.class 给 readValue() 方法的第二个参数即可。

ObjectMapper 也有一个特殊方法 readTree(),该方法总是返回一个 JsonNode,如下所示:

String carJson =
        "{ \"brand\" : \"Mercedes\", \"doors\" : 5 }";

ObjectMapper objectMapper = new ObjectMapper();

try {

    JsonNode jsonNode = objectMapper.readTree(carJson);

} catch (IOException e) {
    e.printStackTrace();
}

JsonNode 使你能够以一种非常灵活和动态的方法将 JSON 作为 Java 对象来导航。下面会有专门讲解 JsonNode 的章节,这里会展示它的基础用法。

一旦你将 JSON 解析成 JsonNode 之后,你将能够导航该 JsonNode Tree Model,下面是一个简单的例子展示了如何访问 JSON 字段、数组和内嵌对象。

String carJson =
        "{ \"brand\" : \"Mercedes\", \"doors\" : 5," +
        "  \"owners\" : [\"John\", \"Jack\", \"Jill\"]," +
        "  \"nestedObject\" : { \"field\" : \"value\" } }";

ObjectMapper objectMapper = new ObjectMapper();


try {

    JsonNode jsonNode = objectMapper.readValue(carJson, JsonNode.class);

    JsonNode brandNode = jsonNode.get("brand");
    String brand = brandNode.asText();
    System.out.println("brand = " + brand);

    JsonNode doorsNode = jsonNode.get("doors");
    int doors = doorsNode.asInt();
    System.out.println("doors = " + doors);

    JsonNode array = jsonNode.get("owners");
    JsonNode jsonNode = array.get(0);
    String john = jsonNode.asText();
    System.out.println("john  = " + john);

    JsonNode child = jsonNode.get("nestedObject");
    JsonNode childField = child.get("field");
    String field = childField.asText();
    System.out.println("field = " + field);

} catch (IOException e) {
    e.printStackTrace();
}

注意现在的 JSON 字符串包含一个名为 owners 的数组字段和一个内嵌的对象字段叫 nestedObject。

不管你是在访问字段、数组还是内嵌对象,你都可以使用 JsonNode 的 get() 方法,给 get() 方法提供字符串作为参数你可以访问 JsonNode 的字段,如果这个 JsonNode 表示一个数组,你需要传递 index 到 get() 方法中,index 指定了元素在数组中的位置。

Java 对象转 JsonNode

使用 Jackson ObjectMapper 将 Java 对象转换成 JsonNode 是可能的,此时 JsonNode 是被转换的 Java 对象的 JSON 表现形式。你可以通过 ObjectMapper 的 valueToTree() 方法来将 Java 对象转换成 JsonNode,示例如下:

ObjectMapper objectMapper = new ObjectMapper();

Car car = new Car();
car.brand = "Cadillac";
car.doors = 4;

JsonNode carJsonNode = objectMapper.valueToTree(car);
JsonNode 转 Java 对象

你可以通过使用 ObjectMapper 的 treeToValue() 方法来将 JsonNode 转换成 Java 对象,这类似于使用 ObjectMapper 将 JSON 字符串转成 Java 对象。唯一不同的是,JSON 源是 JsonNode。示例如下:

ObjectMapper objectMapper = new ObjectMapper();

String carJson = "{ \"brand\" : \"Mercedes\", \"doors\" : 5 }";

JsonNode carJsonNode = objectMapper.readTree(carJson);

Car car = objectMapper.treeToValue(carJsonNode);

上面的例子有点 “人造的”,因为我先将 JSON 字符串转成 JsonNode 然后再将 JsonNode 转成 Car 对象。显然,你也可以直接将 JSON 字符串转成 Car 对象,而不需要先转成 JsonNode。

JsonNode

Jackson 中的 JsonNode 类,指的是 com.fasterxml.jackson.databind.JsonNode,用来表示 JSON 树模型。Jackson 可以将 JSON 字符串解析成 JsonNode 实例,也可以从 JsonNode 实例中输出 JSON。该章将会解释如何反序列化 JSON 字符串为 JsonNode 和序列化 JsonNode 为 JSON 字符串,也会解释如何从头构建 JsonNode 对象图,以便你后续将其序列化为 JSON 字符串。

JsonNode vs ObjectNode

Jackson 中的 JsonNode 是不可变对象,这意味着,你不可以直接构建一个 JsonNode 的对象图,相反,你可以构建 JsonNode 子类 ObjectNode 的对象图。作为 JsonNode 的子类,你可以在使用 JsonNode 的任何地方使用 ObjectNode。

读取 JSON 字符串到 JsonNode

读取 JSON 字符串到 JsonNode 中,你需要先创建 ObjectMapper 实例,然后调用 readTree() 方法并传入 JSON 源作为参数,下面是一个反序列化 JSON 字符串到 JsonNode 的简单示例:

String json = "{ \"f1\" : \"v1\" } ";

ObjectMapper objectMapper = new ObjectMapper();

JsonNode jsonNode = objectMapper.readTree(json);

System.out.println(jsonNode.get("f1").asText());

写入 JsonNode 到 JSON 字符串

你需要使用 ObjectMapper 的 writeValueAsString() 方法,示例如下:

ObjectMapper objectMapper = new ObjectMapper();

JsonNode jsonNode = readJsonIntoJsonNode();

String json = objectMapper.writeValueAsString(jsonNode);

readJsonIntoJsonNode() 是我创建的一个方法,在方法内部将一个 JSON 字符串解析成 JsonNode 并返回,readJsonIntoJsonNode() 方法对这个例子并不重要,正如你知道的那样,它产生了一个 JsonNode 对象。重要的是下一行代码调用了 ObjectMapper 的 writeValueAsString() 方法,它将 JsonNode 写入到 JSON 字符串。

获取 JsonNode 字段

一个 JsonNode 可以拥有字段就像 JSON 对象一样,想象一下你已经解析下面的 JSON 字符串为 JsonNode:

{
    "field1" : "value1",
    "field2" : 999
}

这个 JSON 对象有两个字段 field1 和 field2,如果你有一个上面 JSON 对象的 JsonNode 表示,你可以获取这两个字段像下面这样:

JsonNode jsonNode = ... //parse above JSON into a JsonNode

JsonNode field1 = jsonNode.get("field1");
JsonNode field2 = jsonNode.get("field2");

注意,虽然这两个字段是字符串类型,但是 get() 方法总是返回一个 JsonNode 来表示这个字段。

通过 Path 获取 JsonNode 字段

JsonNode 有一个特殊的方法叫做 at(),at() 方法可以从 JSON 图中的任何位置访问 JSON 字段,想象一下有一个 JSON 结果如下:

{
  "identification" :  {
        "name" : "James",
        "ssn: "ABC123552"
    }
}

如果这个 JSON 字符串被解析成 JsonNode 你可以使用 at() 方法访问 name 字段像下面这样:

JsonNode nameNode = jsonNode.at("/identification/name");

注意传递给 at() 方法的参数:一个字符串 “/identification/name”,这是一个 JSON path 表达式,path 表达式指定了一个从 root JsonNode 开始到你要访问的字段的完整路径。这类似于从文件系统的 root 到 Unix 文件系统中具体文件的文件路径。

注意 JSON path 表达式必须以 / 字符开始。

at() 方法返回一个 JsonNode 来表示你请求的 JSON 字段,要获取字段真正的值你需要调用下一节介绍的方法之一,如果没有 node 匹配你给的 path 表达式,则会返回 null。

转换 JsonNode 字段

JsonNode 包含了一系列的方法来转换一个字段的值到另外一种数据类型,比如说,将字符串字段转换成 long 类型,或者反过来。下面是一个转换 JsonNode 字段为一些常用数据类型的示例:

String f2Str = jsonNode.get("f2").asText();
double f2Dbl = jsonNode.get("f2").asDouble();
int    f2Int = jsonNode.get("f2").asInt();
long   f2Lng = jsonNode.get("f2").asLong();

想象一下 f2 字段包含值 123456,但是它可以被转换成 String、double、int 和 long。

在 JsonNode 中的字段是一个 null 值的情况下,当你在转换的时候你可以提供一个默认值,示例如下:

ObjectMapper objectMapper = new ObjectMapper();

String json = "{ \"f1\":\"Hello\", \"f2\":null }";

JsonNode jsonNode = objectMapper.readTree(json);

String f2Value = jsonNode.get("f2").asText("Default");

正如你在示例中看到的那样,JSON 字符串中声明了 f2 字段,但是被设置成 null,在这种情况下,调用 jsonNode.get("f2").asText("Default") 将会返回默认值,在上面的例子中是字符串 “Default”。

请注意,如果在 JSON 中的字段没有被明确的声明为 null,而是没有定义,那么调用 jsonNode.get("fieldName") 将会返回一个 Java null 值,如果你接着调用 asInt(),asDouble(),asLong() 或者 asText() 方法,那么你会得到一个 NullPointerException,示例如下:

ObjectMapper objectMapper = new ObjectMapper();

String json = "{ \"f1\":\"Hello\" }";

JsonNode jsonNode = objectMapper.readTree(json);

JsonNode f2FieldNode = jsonNode.get("f2");

运行下面的代码,f2FieldNode 变量的值为 null。

处理 Null 字段值

让我们花费几分钟时间来探索如何处理 JsonNode 中的 null 值,在两种情况下 JsonNode 中的字段会是 null 值,第一种是从 JsonNode 创建是就不存在字段,如下所示:

ObjectMapper objectMapper = new ObjectMapper();

String json = "{ \"f1\":\"Hello\" }";

JsonNode jsonNode = objectMapper.readTree(json);

JsonNode f2FieldNode = jsonNode.get("f2");

在上面的例子中,JSON 字符串并不存在 f2 字段,jsonNode.get("f2") 方法会返回 null,再来看另外一个例子:

ObjectMapper objectMapper = new ObjectMapper();

String json = "{ \"f1\":\"Hello\" \"f2\":null }";

JsonNode jsonNode = objectMapper.readTree(json);

JsonNode f2FieldNode = jsonNode.get("f2");

在这个例子中 JSON 字符串中 f2 字段的值为 null,此时调用 jsonNode.get("f2") 方法会返回一个合法的 JsonNode 表示 null 值,你可以通过调用 JsonNode 的 isNull() 方法来检查是否表示 null 值,如下所示:

boolean isFieldValueNull = f2FieldNode.isNull();

为了给 JsonNode 字段做一个正确的 null 检查,你必须像下面这样做:

JsonNode fieldNode = parentNode.get("fieldName");

if(fieldNode == null || fieldNode.isNull()) {
    // the field is either not present in parentNode, or explicitly set to null .
}

遍历 JsonNode 图

表示 JSON 对象或 JSON 数组的 JsonNode 可以像其他对象图一样被迭代,你可以迭代它的内嵌字段来做到这一点,示例如下:

public static void traverse(JsonNode root){
    
    if(root.isObject()){
        Iterator<String> fieldNames = root.fieldNames();

        while(fieldNames.hasNext()) {
            String fieldName = fieldNames.next();
            JsonNode fieldValue = root.get(fieldName);
            traverse(fieldValue);
        }
    } else if(root.isArray()){
        ArrayNode arrayNode = (ArrayNode) root;
        for(int i = 0; i < arrayNode.size(); i++) {
            JsonNode arrayElement = arrayNode.get(i);
            traverse(arrayElement);
        }
    } else {
        // JsonNode root represents a single value field - do something with it.
        
    }
}

注意你可能需要修改这个例子来填充你自己的用例,准确的实现依赖于你需要通过迭代节点做些什么,然而,你可以使用上面的例子作为模板,然后修改它直到符合你的需要。

ObjectNode

在之前我们提到过 JsonNode 是不可变的,但是要创建 JsonNode 对象图,你需要改变 JsonNode 对象图,比如设置属性值和孩子节点等,由于 JsonNode 的不可变性,我们不能直接创建一个 JsonNode。

相反,你可以创建一个 JsonNode 的子类 ObjectNode,通过 ObjectMapper 的 createObjectNode() 方法创建一个 ObjectNode,示例如下:

ObjectMapper objectMapper = new ObjectMapper();

ObjectNode objectNode = objectMapper.createObjectNode();
设置字段

为 ObjectNode 设置字段你可以调用 set() 方法,传递字符串类型的字段名和 JsonNode 作为参数,示例如下:

ObjectMapper objectMapper = new ObjectMapper();
ObjectNode parentNode = objectMapper.createObjectNode();

JsonNode childNode = readJsonIntoJsonNode();

parentNode.set("child1", childNode);

ObjectNode 类也有一系列方法让你为字段设置基本数据类型,示例如下:

objectNode.put("field1", "value1");
objectNode.put("field2", 123);
objectNode.put("field3", 999.999);
删除字段

ObjectNode 类有一个 remove() 方法,它用来从 ObjectNode 中删除字段,示例如下:

objectNode.remove("fieldName");
迭代字段

你可以通过 JsonNode 的 fields() 方法获取所有的字段,然后迭代所有的字段,示例如下:

Iterator<Map.Entry<String, JsonNode>> fields = jsonNode.fields();

while(fields.hasNext()) {
    Map.Entry<String, JsonNode> field = fields.next();
    String   fieldName  = field.getKey();
    JsonNode fieldValue = field.getValue();

    System.out.println(fieldName + " = " + fieldValue.asText());
}

JsonNode 有一个名为 fieldNames() 方法,该方法返回一个迭代器来迭代 JsonNode 中的所有字段名,你可以使用字段名来获取字段值,示例如下:

Iterator<String> fieldNames = jsonNode.fieldNames();

while(fieldNames.hasNext()) {
    String fieldName = fieldNames.next();

    JsonNode field = jsonNode.get(fieldName);
}

JsonParser

Jackson JsonParser 类是一个低级 JSON 解析器,它类似于处理 XML 的 Java StAX 解析器,只不过 JsonParser 解析 JSON 而不是 XML。

Jackson JsonParser 比 Jackson ObjectMapper 更低级别,这使得 JsonParser 比 ObjectMapper 更快,但使用起来也更麻烦。

创建 JsonParser

要创建 JsonParser,你首先要创建一个 JsonFactory,JsonFactory 被用来创建 JsonParser 实例,JsonFactory 类包含几个 createParser() 方法,每一个传入不同的 JSON 源作为参数。在下面的例子中使用创建 JsonParser 来解析 JSON 字符串:

String carJson =
        "{ \"brand\" : \"Mercedes\", \"doors\" : 5 }";

JsonFactory factory = new JsonFactory();
JsonParser  parser  = factory.createParser(carJson);

你可以传递 Reader、InputStream、URL、byte array 或者 char array 给 createParser() 方法。

解析 JSON

一旦你创建了一个 JsonParser 你就可以使用它来解析 JSON 了,JsonParser 的工作方式是将 JSON 拆分为一个 token 序列,这样你就可以逐个迭代它们了。

下面的例子简单的迭代了所有的 token 并用 System.out 打印它们,如下所示:

String carJson =
        "{ \"brand\" : \"Mercedes\", \"doors\" : 5 }";

JsonFactory factory = new JsonFactory();
JsonParser  parser  = factory.createParser(carJson);

while(!parser.isClosed()){
    JsonToken jsonToken = parser.nextToken();

    System.out.println("jsonToken = " + jsonToken);
}

如果 isClosed() 返回 false,表明仍然有很多 token 在 JSON 源中。

你使用 JsonParser 的 nextToken() 方法获取 JsonToken,你也可以使用 JsonToken 实例来检查 token,在 JsonToken 类中 token 的类型用一组常量来表示,这些常量如下:

START_OBJECT
END_OBJECT
START_ARRAY
END_ARRAY
FIELD_NAME
VALUE_EMBEDDED_OBJECT
VALUE_FALSE
VALUE_TRUE
VALUE_NULL
VALUE_STRING
VALUE_NUMBER_INT
VALUE_NUMBER_FLOAT

你可以使用这些常量来查询当前的 JsonToken 是什么类型的,你可以通过这些常量的 equals() 方法来做到这一点,如下所示:

String carJson =
        "{ \"brand\" : \"Mercedes\", \"doors\" : 5 }";

JsonFactory factory = new JsonFactory();
JsonParser  parser  = factory.createParser(carJson);

Car car = new Car();
while(!parser.isClosed()){
    JsonToken jsonToken = parser.nextToken();

    if(JsonToken.FIELD_NAME.equals(jsonToken)){
        String fieldName = parser.getCurrentName();
        System.out.println(fieldName);

        jsonToken = parser.nextToken();

        if("brand".equals(fieldName)){
            car.brand = parser.getValueAsString();
        } else if ("doors".equals(fieldName)){
            car.doors = parser.getValueAsInt();
        }
    }
}

System.out.println("car.brand = " + car.brand);
System.out.println("car.doors = " + car.doors);

如果 token 指向一个字段名的话,JsonParser 的 getCurrentName() 方法返回当前字段名。

如果 token 指向一个字符串类型字段值的话,getValueAsString() 以字符串类型返回当前 token 的值。

如果 token 指向一个 int 类型字段值的话,getValueAsInt() 以 int 类型返回当前 token 的值。

JsonParser 有很多类似的方法来获取当前 token 不同类型的值。

JsonGenerator

Jackson JsonGenerator 被用来从 Java 生成 JSON。

创建 JsonGenerator

为了创建一个 JsonGenerator 你首先需要创建一个 JsonFactory 实例,如下所示:

JsonFactory factory = new JsonFactory();

之后你可以使用 JsonFactory 的 createGenerator() 方法来创建 JsonFactory,示例如下:

JsonFactory factory = new JsonFactory();

JsonGenerator generator = factory.createGenerator(
    new File("data/output.json"), JsonEncoding.UTF8);

createGenerator() 方法的第一个参数是生成 JSON 字符串的目的地,在上面的例子中是一个 File 对象,这意味着生成的 JSON 字符串会被写道给定的 file 中,createGenerator() 方法被重写了,所以有其他版本的 createGenerator() 方法如传递 OutputStream 参数的版本等,你可以通过给定不同的选项来决定生成的 JSON 字符串被写到哪里。

createGenerator() 方法的第二个参数是生成 JSON 字符串时的字符串编码,例子中时 UTF-8。

生成 JSON 字符串

你创建了 JsonGenerator 之后你就可以开始生成 JSON 字符串了,JsonGenerator 包含一系列的 write…() 方法用来写入 JSON 对象的不同部分,示例如下:

JsonFactory factory = new JsonFactory();

JsonGenerator generator = factory.createGenerator(
    new File("data/output.json"), JsonEncoding.UTF8);

generator.writeStartObject();
generator.writeStringField("brand", "Mercedes");
generator.writeNumberField("doors", 5);
generator.writeEndObject();

generator.close();

上面的例子首先调用了 writeStartObject() 方法,它会写入 { 字符到输出中,然后调用 writeStringField() 来写入 brand 字段名和它的值到输出中,之后调用 writeNumberField() 方法来写入 doors 字段和它的值到输出,最后,调用 writeEndObject() 来写入 } 字符到输出中。

JsonGenerator 有许多 write 方法可以使用,上面的例子只展示了一部分。

关闭 JsonGenerator

但你完成生成 JSON 之后你需要关闭 JsonGenerator,通过调用 close() 方法可以做到这一点,如下所示:

generator.close();

关闭 JsonGenerator 会关闭 file 或者 OutputStream 等 JsonGenerator 写入的目的地。

相关配置

//在反序列化时忽略在 json 中存在但 Java 对象不存在的属性
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
//在序列化时自定义时间日期格式
objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
//在序列化时忽略值为 null 的属性
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
//在序列化时忽略值为默认值的属性
objectMapper.setDefaultPropertyInclusion(JsonInclude.Include.NON_DEFAULT);

参考:
https://blog.csdn.net/u014379746/article/details/108169210

https://jenkov.com/tutorials/java-json/jackson-objectmapper.html

https://github.com/FasterXML/jackson-docs

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值