Jackson整理

Jackson是最流行的JSON API之一,它提供多种不同的方式来处理JSON。

Jackson包含2个不同的JSON解析器:

  • ObjectMapper
  • JsonParser

包含2个不同的JSON生成器:

  • ObjectMapper
  • JsonGenerator

Jackson包含一个核心JAR文件和2个另外的JAR文件:

  • Jackson Core
  • Jackson Annotations
  • Jackson Databind

Jackson Annotation使用Jackson Core,Jackson Databind使用Jackson Annotaion。

如果使用maven来管理依赖的话,只需要加入jackson-databind,它会自动依赖另外的两个包。

Jackson ObjectMapper

在Jackson中,ObjectMapper是最简单也是最常用的类。ObjectMapper可以用于从字符串、流、文件等处将JSON解析成Java对象或者对象图(object graph)。这个过程称为反序列化。

ObjectMapper也可以从Java对象中创建JSON。这个过程称为序列化。

Jackson的ObjectMapper可以将JSON反序列化到自定义的类对象中,或者反序列化到内建的JSON数模型中。

Jackson ObjectMapper的工作原理

为了正确地将JSON反序列到Java对象中,了解Jackson是如何将JSON对象的字段映射到Java对象的字段是非常重要的。

默认情况下,Jackson通过匹配JSON对象中的字段和Java对象中的getter和setter方法来对JSON对象的字段和Java对象的字段进行映射。Jackson将getter和setter方法名的get和set部分,然后把第一个字母转成小写。

比如,JSON字段brand匹配getBrand()setBrand()方法。engineNumber匹配getEngineNumbersetEngineNumber方法。

从JSON字符串中读取对象

1
2
3
4
5
6
ObjectMapper objectMapper = new ObjectMapper();

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

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

从Reader中读取对象

1
2
3
4
5
6
7
ObjectMapper objectMapper = new ObjectMapper();

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

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

从File中读取对象

可以从FileReader、StringReader或者File中读取对象

1
2
3
4
5
ObjectMapper objectMapper = new ObjectMapper();

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

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

从URL中读取对象

1
2
3
4
5
ObjectMapper objectMapper = new ObjectMapper();

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

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

从InputStream中读取对象

1
2
3
4
5
ObjectMapper objectMapper = new ObjectMapper();

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

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

从字节数组中读取对象

1
2
3
4
5
6
7
8
ObjectMapper objectMapper = new ObjectMapper();

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

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

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

从JSON数组字符串中读取对象数组

1
2
3
4
5
String jsonArray = "[{\"brand\":\"ford\"}, {\"brand\":\"Fiat\"}]";

ObjectMapper objectMapper = new ObjectMapper();

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

从JSON数组字符串中读取对象列表

1
2
3
4
5
String jsonArray = "[{\"brand\":\"ford\"}, {\"brand\":\"Fiat\"}]";

ObjectMapper objectMapper = new ObjectMapper();

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

忽略未知的JSON字段

有时候JSON中的字段会比反序列化的目标Java对象字段多。默认情况下Jackson会抛出错误,告诉你JSON中的某个字段在Java对象中找不到。

但是,有时候需要允许JSON字段比Java对象多。Jackson允许你忽略这些多余的字段:

1
2
objectMapper.configure(
DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

自定义反序列化

有时候可能想要以自己的方式来反序列化JSON字符串,而不是ObjectMapper默认的方式。你为ObjectMapper可以自定义一个反序列化器,这个反序列化器可以以你需要的方式工作。

下面的代码展示了如何注册并使用自定义的反序列化器:

1
2
3
4
5
6
7
8
9
10
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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;
}
}

序列化对象

ObjectMapper也可以用于从对象生成JSON。使用以下方法中的一个:

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

以下是将Car对象序列化为JSON的例子:

1
2
3
4
5
6
7
8
ObjectMapper objectMapper = new ObjectMapper();

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

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

上面的例子首先新建一个ObjectMappper,然后新建一个Car实例,最后调用ObjectMapperwriteValue()方法将Car对象序列化成JSON,写到给定的FileOutputStream中。

ObjectMapperwriteValueAsString()writeValueAsByte()方法都可以生成JSON,返回字符串或者字节数组。下面的例子展示了如何调用writeValueAsString()

1
2
3
4
5
6
7
8
ObjectMapper objectMapper = new ObjectMapper();

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

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

上面的例子输出如下JSON:

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

自定义序列化器

有时候你想要生成一个不同于默认规则的JSON。比如,使用不用于Java对象的字段名称,或者忽略某些字段。

Jackson可以让你自定义ObjectMapper的序列化器。序列化器注册到类上,每次ObjectMapper序列化这个类时都会被调用。下面的例子展示了如何给Car注册一个自定义的序列化器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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);

这个自定义的序列化器输出如下JSON:

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

CarSerializer如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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实例。你可以使用这个实例来序列化对象。

日期格式

默认情况下,Jackson会将java.util.Date对象转化为long值,即距离1970年1月1日的毫秒值。然而Jackson也支持将日期格式化为字符串。

日期转化为long

首先展示一下默认情况下Jackson将Date转化为距离1970年1月1日的毫秒值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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;
}
}

序列化Transaction对象和之前序列化其他对象是一样的。

1
2
3
4
5
6
Transaction transaction = new Transaction("transfer", new Date());

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

System.out.println(output);

输出和以下字符串类似的JSON:

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

注意date字段的格式,它是一个long整型。

日期转换为字符串

Date转化为long整型非常不适合人类阅读。因此Jackson支持更加适合阅读的格式。你可以给ObjectMapper设置SimpleDateFormat来给Date类型指定格式。下面是这样的一个例子:

1
2
3
4
5
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
objectMapper2.setDateFormat(dateFormat);

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

输出类似如下的字符串:

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

JSON树模型

Jackson内建有一个可用于表示JSON对象的树模型。如果你不知道需要接收的JSON数据是怎样的,或者你无法创建一个类来表示,Jackson的树模型则非常有用。

Jackson的树模型用JsonNode类来表示。你可以以之前的方法,使用ObjectMapper来将JSON转化为JsonNode

Jackson树模型示例

1
2
3
4
5
6
7
8
9
10
11
12
String carJson =
"{ \"brand\" : \"Mercedes\", \"doors\" : 5 }";

ObjectMapper objectMapper = new ObjectMapper();

try {

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

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

如上例所示,只要简单地将传递到readValue()方法的第二个参数改为JsonNode.class,JSON就可以被转化成JsonNode对象。

JsonNode类

下面的例子展示了如何通过JsonNode来访问JSON字段、数组、嵌套对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
String carJson =
"{ \"brand\" : \"Mercedes\", \"doors\" : 5," +
" \"owners\" : [\"John\", \"Jack\", \"Jill\"]," +
" \"nestedObject\" : { \"field\" : \"value\" } }";

ObjectMapper objectMapper = new ObjectMapper();


try {

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

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

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

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

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

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

注意,JSON字符串包含一个数组字段owners和一个嵌套对象字段nestedObject

不管是访问一个字段、数组、嵌套对象,都可以使用get()方法。通过在get()方法中传递一个字符串作为参数,可以访问JsonNode的一个字段。如果JsonNode代表数组,你需要在get()方法中传递一个索引值,索引值表示你想要获取的元素的位置。

JsonParser

JsonParser是一个底层的JSON解析器。它比ObjectMapper要更加底层,因此比ObjectMapper更快,但是也比ObjectMapper更笨重。

创建JsonParser

首先需要创建JsonFactoryJsonFactory用于创建JsonParser实例。JsonFactory包含多个createParser()方法,使用不同的JSON源作为参数。

以下是创建JsonParser的示例:

1
2
3
4
5
String carJson =
"{ \"brand\" : \"Mercedes\", \"doors\" : 5 }";

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

你也可以将ReaderInputStreamURL、字节数组、字符数组传入createParser()方法。

使用JsonParser解析JSON

创建完JsonParser之后,你可以使用它来解析JSON。JsonParser的工作原理就是将JSON分解成一系列的token,你可以一个个迭代它们。

以下示例简单展示了迭代tokens,将它们打印到标准输出。这不是一个非常有用的例子,但是它展示了JSON中的tokens被分解,以及如何迭代tokens。

1
2
3
4
5
6
7
8
9
10
11
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);
}

只要JsonParserisClosed()方法返回false,则表示JSON中还有没被访问的token。

你可以使用JsonParsernextToken()方法来获得JsonToken。你可以使用JsonToken实例来检查给定的token。Token类型由JsonToken中一系列的常量来表示。这些常量如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
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()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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指向的是字段名称,JsonParsergetCurrentName()方法返回当前字段的名称。

如果token指向的是字符串字段值,getValueAsString()方法以字符串的形式返回当前token的值。如果token指向的是整型字段值,getValueAsInt()方法以int的形式返回当前token的值。JsonParser还有更多相似的方法以不同类型获取当前token的值。

JsonGenerator

JsonGenerator用于从Java对象中生成JSON。

创建JsonGenerator

首先要创建JsonFactory实例。以下代码展示了如何创建JsonFactory

1
JsonFactory factory = new JsonFactory();

创建JsonFactory之后,你可以使用JsonFactorycreateGenerator()方法来创建JsonGenerator。以下代码展示了如何创建JsonGenerator

1
2
3
4
JsonFactory factory = new JsonFactory();

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

createGenerator()方法的第一个参数是生成JSON的目的地。上面例子的参数是一个File对象。这意味着生成的JSON对象会被写到给定的文件中。createGenerator()方法是重载的,可以插入不同的参数来指定生成的JSON写到何处。

createGenerator()方法的第二个参数是生成JSON时使用的字符编码。上面的例子中使用了UTF-8.

使用JsonGenerator来生成JSON

创建JsonGenerator之后可以开始生成JSON。JsonGenerator包含一系列write...()方法,你可以用来输出JSON对象的一部分。以下是一个使用JsonGenerator来生成JSON的例子:

1
2
3
4
5
6
7
8
9
10
11
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有很多其他用于输出的方法。这个例子只展示了一部分。

关闭JsonGenerator

JSON输出完成后,你应该调用close()方法关闭JsonGenerator

1
generator.close();

关闭JsonGenerator也会关闭文件或者OutputStream等。

注释

Jackson包含一系列的注释,用于影响JSON的序列化或者反序列化过程。

读+写 注释

Jackson包含一系列会同时影响序列化和反序列化过程的注释。我们把这类注释称为”读+写 注释”。

@JsonIgnore

注释@JsonIgnore用于告诉Jackson忽略Java对象中的某个属性(字段)。该属性在序列化和反序列化的过程中都会被忽略。实例如下:

1
2
3
4
5
6
7
8
9
import com.fasterxml.jackson.annotation.JsonIgnore;

public class PersonIgnore {

@JsonIgnore
public long personId = 0;

public String name = null;
}

上面的类中,属性personId在序列化和反序列化时都会被忽略。

@JsonIgnoreProperties

注释@JsonIgnoreProperties用于指定一个列表,处于这个列表中的类属性都会被忽略。注释@JsonIgnoreProperties放在类声明之上而不是忽略字段之上。示例如下:

1
2
3
4
5
6
7
8
9
10
11
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@JsonIgnoreProperties({"firstName", "lastName"})
public class PersonIgnoreProperties {

public long personId = 0;

public String firstName = null;
public String lastName = null;

}

在这个例子中,属性firstNamelastName都会被忽略,因为它们都处于@JsonIgnoreProperties注释之中。

@JsonIgnoreType

注释@JsonIgnoreType用于标记整个类,无论这个类在哪被使用,它都会被忽略。示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import com.fasterxml.jackson.annotation.JsonIgnoreType;

public class PersonIgnoreType {

@JsonIgnoreType
public static class Address {
public String streetName = null;
public String houseNumber = null;
public String zipCode = null;
public String city = null;
public String country = null;
}

public long personId = 0;

public String name = null;

public Address address = null;
}

以上例子中,所有的Address实例都会被忽略。

@JsonAutoDetect

注释@JsonAutoDetect用于告诉Jackson在序列化和反序列化过程中包含非public的属性。示例如下:

1
2
3
4
5
6
7
8
9
import com.fasterxml.jackson.annotation.JsonAutoDetect;

@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY )
public class PersonAutoDetect {

private long personId = 123;
public String name = null;

}

JsonAutoDetect.Visibility类包含了匹配可见等级的常量:ANYDEFAULTNON_PRIVATENONEPROTECTED_AND_PRIVATEPUBLIC_ONLY

读 注释

Jackson包含一系列只影响反序列化过程的的注释。我们称之为”读 注释”。

@JsonSetter

注释@JsonSetter用于告诉Jackson这个setter方法匹配的属性名称。当JSON中的字段名称和Java对象中的字段名称不同时,这个注释就很有用。

如下所示,Person类使用personId作为它的id属性:

1
2
3
4
5
6
7
8
9
10
11
public class Person {

private long personId = 0;
private String name = null;

public long getPersonId() { return this.personId; }
public void setPersonId(long personId) { this.personId = personId; }

public String getName() { return name; }
public void setName(String name) { this.name = name; }
}

但是在JSON中使用id字段而不是personId

1
2
3
4
{
"id" : 1234,
"name" : "John"
}

默认情况下,Jackson无法将JSON中的id属性映射到Java类中的personId属性。

@JsonSetter注释告诉Jackson针对给定的JSON字段使用它所注释的setter方法。我们的例子中,在setPersonId()方法上加上@JsonSetter注释。

1
2
3
4
5
6
7
8
9
10
11
12
public class Person {

private long personId = 0;
private String name = null;

public long getPersonId() { return this.personId; }
@JsonSetter("id")
public void setPersonId(long personId) { this.personId = personId; }

public String getName() { return name; }
public void setName(String name) { this.name = name; }
}

@JsonSetter注释中指定的值是匹配JSON字段名的值。在这个例子中,这个值是id,因为我们要将id字段映射到setPersonId()方法。

@JsonAnySetter

@JsonAnySetter注释告诉Jackson如果在JSON中遇到不能识别的字段,调用同一个setter方法。不能识别的字段指的是那些不能映射到Java对象的setter方法的字段。

1
2
3
4
5
6
7
8
9
10
11
12
public class Bag {

private Map<String, Object> properties = new HashMap<>();

public void set(String fieldName, Object value){
this.properties.put(fieldName, value);
}

public Object get(String fieldName){
return this.properties.get(fieldName);
}
}

JSON对象如下:

1
2
3
4
{
"id" : 1234,
"name" : "John"
}

Jackson无法直接将JSON对象中的idname映射到Bag类中,因为Bag类不包含相应的公共字段或者setter方法。

你可以使用@JsonAnySetter注释,对所有无法识别的字段都调用set()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Bag {

private Map<String, Object> properties = new HashMap<>();

@JsonAnySetter
public void set(String fieldName, Object value){
this.properties.put(fieldName, value);
}

public Object get(String fieldName){
return this.properties.get(fieldName);
}
}

现在如果遇到不能识别的字段,都会调用set()方法。

注意,这个注释只会在无法识别的字段上生效。比如你在Bag类中增加了一个公共的name属性或者是一个setName(String)方法,JSON中的name字段就会映射到这个属性或者setter方法。

@JsonCreator

@JsonCreator注释用于告诉Jackson Java对象拥有一个构造器,JSON中的字段可以映射到这个构造器中的字段。

当无法使用@JsonSetter注释时,@JsonCreator就能发挥作用了。比如,某些不可变的对象没有任何setter方法,需要将初始值都注入到构造器中。示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class PersonImmutable {

private long id = 0;
private String name = null;

public PersonImmutable(long id, String name) {
this.id = id;
this.name = name;
}

public long getId() {
return id;
}

public String getName() {
return name;
}

}

为了告诉Jackson调用PersonImmutable的构造函数,我们必须在构造函数上加上@JsonCreator注释。不过这样还不够,我们必须在构造函数的参数中加上注释,告诉Jackson JSON对象的字段映射到构造方法的哪个参数中。示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class PersonImmutable {

private long id = 0;
private String name = null;

@JsonCreator
public PersonImmutable(
@JsonProperty("id") long id,
@JsonProperty("name") String name ) {

this.id = id;
this.name = name;
}

public long getId() {
return id;
}

public String getName() {
return name;
}

}

注意构造函数上的注释和构造函数参数上的注释。现在可以从以下JSON对象中反序列化出PersonImmutable对象:

1
2
3
4
{
"id" : 1234,
"name" : "John"
}

@JacksonInject

@JacksonInject注释用于在经过解析的对象中注入值,而不是从JSON中读取这些值。举例来说,你从多个不同的源中下载person JSON对象,想要知道person对象的来源。因为来源信息并没有包含在JSON中,你可以将来源信息注入到生成的Java对象中。

为了将Java类的某个字段标记为用于注入,在该字段上加上@JacksonInject注释。示例如下:

1
2
3
4
5
6
7
public class PersonInject {
public long id = 0;
public String name = null;

@JacksonInject
public String source = null;
}

为了在source字段中注入值,创建ObjectMapper时就需要做一些额外的工作。示例如下:

1
2
3
4
InjectableValues inject = new InjectableValues.Std().addValue(String.class, "jenkov.com");
PersonInject personInject = new ObjectMapper().reader(inject)
.forType(PersonInject.class)
.readValue(new File("data/person.json"));

我们注意到,为了在source字段注入值,需要在InjectableValues addValue()方法中设置。这个值只能绑定在String类型上,而不是特定的字段名。由@JacksonInject注释来指定注入到哪个字段中。

如果你是从多个源下载person JSON对象,需要注入多个不同的源,你需要为每个源重复上面的代码。

@JsonDeserialize

@JsonDeserialize注释用于为Java对象的某个字段指定自定义的反序列化类。比如你想要将布尔值falsetrue优化为01

首先你需要在自定义反序列化的字段上加上@JsonDeserialize注释。示例如下:

1
2
3
4
5
6
7
8
public class PersonDeserialize {

public long id = 0;
public String name = null;

@JsonDeserialize(using = OptimizedBooleanDeserializer.class)
public boolean enabled = false;
}

自定义的反序列化类OptimizedBooleanDeserializer如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class OptimizedBooleanDeserializer
extends JsonDeserializer<Boolean> {

@Override
public Boolean deserialize(JsonParser jsonParser,
DeserializationContext deserializationContext) throws
IOException, JsonProcessingException {

String text = jsonParser.getText();
if("0".equals(text)) return false;
return true;
}
}

注意OptimizedBooleanDeserializer类使用泛型Boolean继承JsonDeserializer。这样一来deserialize()方法返回Boolean对象。如果你想要反序列化其他类型(比如java.util.Date),必须在泛型括号中指定类型。

调用jsonParser参数的getText()方法获取反序列化字段的值。你可以根据目标字段,将这个值转换成任意的值和类型。

最后来看看如何使用自定义的反序列化器以及@JsonDeserializer注释:

1
2
3
PersonDeserialize person = objectMapper
.reader(PersonDeserialize.class)
.readValue(new File("data/person-optimized-boolean.json"));

注意,我们首先需要使用ObjectMapperreader()方法来为PersonDeserialize类创建一个reader,然后调用readValue()

写 注释

Jackson也包含一系列只影响序列化过程的的注释。我们称之为”写 注释”。

@JsonInclude

@JsonInclude注释告诉Jackson只包含符合某些情况的属性。比如,只包含那些”非null”、”非空”、”非默认值”的属性。如下所示:

1
2
3
4
5
6
7
8
9
import com.fasterxml.jackson.annotation.JsonInclude;

@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class PersonInclude {

public long personId = 0;
public String name = null;

}

在上例中,只包含name属性,因为@JsonInclude中设置的值是”not-empty”,意味着”非null”、”非空字符串”。

@JsonInclude注释换一种说法是@JsonIncludeOnlyWhen,但是它的名称更长。

@JsonGetter

@JsonGetter注释告诉Jackson通过getter方法来获取某个字段的值,而不是直接通过字段访问。如果Java类使用jQuery风格的getter和setter名称,@JsonGetter注释是有用的。比如你用了personId()personId(long id)方法而不是getPersonId()setPersonId()方法。

示例如下:

1
2
3
4
5
6
7
8
9
10
11
public class PersonGetter {

private long personId = 0;

@JsonGetter("id")
public long personId() { return this.personId; }

@JsonSetter("id")
public void personId(long personId) { this.personId = personId; }

}

如你所见,personId()方法由@JsonGetter注释。值设置的方法由@JsonGetter注释。personId在JSON对象中使用了id作为名称。输出的JSON对象如下所示:

1
{"id":0}

注意,@JsonSetter注释在反序列化过程中使用,而不是序列化过程中。

@JsonAnyGetter

@JsonAnyGetter注释可以让你使用Map作为属性容器,这些属性会被序列化到JSON中。示例如下:

1
2
3
4
5
6
7
8
9
public class PersonAnyGetter {

private Map<String, Object> properties = new HashMap<>();

@JsonAnyGetter
public Map<String, Object> properties() {
return properties;
}
}

当看到@JsonAnyGetter注释时,Jackson获取@JsonAnyGetter注释的方法返回的Map,然后将Map中每个键值对当做属性。换句话说,Map中的所有键值对都会被作为PersonAnyGetter对象的一部分被序列化。

@JsonPropertyOrder

@JsonPropertyOrder用于指定Java对象中的字段以何种顺序序列化到JSON中。示例如下:

1
2
3
4
5
6
7
@JsonPropertyOrder({"name", "personId"})
public class PersonPropertyOrder {

public long personId = 0;
public String name = null;

}

默认情况下,PersonPropertyOrder中的字段以它们定义的顺序来序列化。如果需要改变这种顺序,就需要使用@JsonPropertyOrder注释来指定不同的顺序。

@JsonRawValue

@JsonRawValue注释告诉Jackson这个属性在序列化时应该直接输出。如果字段是String类型,序列化输出时这个值会被默认加上引号,但是如果加上了@JsonRawValue注释,Jackson就不会加上引号。

如下所示,当PersonRawValue类不加@JsonRawValue注释:

1
2
3
4
5
6
public class PersonRawValue {

public long personId = 0;

public String address = "$#";
}

序列化输出如下:

1
{"personId":0,"address":"$#"}

现在我们在address属性中加上@JsonRawValue注释:

1
2
3
4
5
6
7
public class PersonRawValue {

public long personId = 0;

@JsonRawValue
public String address = "$#";
}

序列化address属性时Jackson不会加上引号,输出如下:

1
{"personId":0,"address":$#}

当然这是一个无效的JSON,什么时候才真正需要这么做呢?

address属性包含了一个JSON字符串,这个JSON字符串就会作为JSON对象结构的一部分被序列化到最后的JSON对象中,而不是以字符串的形式被序列化。示例如下:

1
2
3
4
5
6
7
8
9
public class PersonRawValue {

public long personId = 0;

@JsonRawValue
public String address =
"{ \"street\" : \"Wall Street\", \"no\":1}";

}

序列化输出如下:

1
2
3
4
{
"personId":0,
"address":{ "street" : "Wall Street", "no":1}
}

我们主要到,address中的JSON字符串现在是JSON结构中的一部分。

如果没有@JsonRawValue注释,序列化输出的JSON就会像这样:

1
{"personId":0,"address":"{ \"street\" : \"Wall Street\", \"no\":1}"}

address属性被包裹在引号中,其中的所有引号都被转义了。

@JsonValue

@JsonValue告诉Jackson不要尝试序列化这个对象本身,而是调用这个对象的某个方法,将这个方法返回的对象序列化到JSON。注意如果这个方法返回的字符串中包含引号,Jackson会将这些引号全部转义,所以你不能返回一个JSON对象。如果你有这种需求,应该使用@JsonRawValue来替代。

示例如下:

1
2
3
4
5
6
7
8
9
10
11
public class PersonValue {

public long personId = 0;
public String name = null;

@JsonValue
public String toJson(){
return this.personId + "," + this.name;
}

}

序列化输出结果如下:

1
"0,null"

结果上会被加上引号,如果里面的字符串包含引号,则会被转义。

@JsonSerialize

@JsonSerialize注释用于给Java对象的字段指定一个自定义的序列化器。示例如下:

1
2
3
4
5
6
7
8
public class PersonSerializer {

public long personId = 0;
public String name = "John";

@JsonSerialize(using = OptimizedBooleanSerializer.class)
public boolean enabled = false;
}

注意@JsonSerialize注释处于enabled字段之上。

OptimizedBooleanSerializer会将true转化为1,将false转化为0,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class OptimizedBooleanSerializer extends JsonSerializer<Boolean> {

@Override
public void serialize(Boolean aBoolean, JsonGenerator jsonGenerator,
SerializerProvider serializerProvider)
throws IOException, JsonProcessingException {

if(aBoolean){
jsonGenerator.writeNumber(1);
} else {
jsonGenerator.writeNumber(0);
}
}
}

https://coding.net/u/wangqi/p/jackson-test/git?public=true
http://tutorials.jenkov.com/java-json/index.html
https://my.oschina.net/liuyuantao/blog/865822