[翻译]https://github.com/FasterXML/jackson-databind
本文翻译自Jackson的GitHub的快速开始手册
文章目录
依赖
<properties>
<!-- Use the latest version whenever possible. -->
<jackson.version>2.9.8</jackson.version>
</properties>
<dependencies>
<dependency>
<dependency>
<!-- Note: 核心注解(core-annotations)的版本号x.y.0和版本号x.y.1, x.y.2等等兼容 -->
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
</dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
</dependencies>
1分钟教程: POJO类和Json的互相转换
返回目录
最常用的功能就是生成Json字符串, 或从Json字符串构造出一个POJO类对象.
- 假设有一个只有两个属性的简单的POJO类:
// 注意: 也可以使用getter/setter方法用于序列化时的属性读写, 这里直接使用了public访问级别
public class MyValue {
public String name;
public int age;
// 如果使用了getter/setter方法, 属性也可以是`protected`或`private`访问级别
}
- 然后, 需要一个
com.fasterxml.jackson.databind.ObjectMapper
实例, 用于所有的数据绑定(data-binding
):
ObjectMapper mapper = new ObjectMapper(); // 创建一次, 可以重复使用
- 默认的
mapper
实例足够我们使用了, 不过如果需要, 我们也可以对该实例进行更多的配置. 下面是实例的简单运用:
//通过反射来反序列化实例对象
//读取一个本地json文件进行反序列化
MyValue value = mapper.readValue(new File("data.json"), MyValue.class);
// or:读取一个网络文件进行反序列化
value = mapper.readValue(new URL("http://some.com/api/entry.json"), MyValue.class);
// or:直接反序列化一个json字符串
value = mapper.readValue("{\"name\":\"Bob\", \"age\":13}", MyValue.class);
- 如果我们想生成Json数据, 只需倒过来做一遍:
// 把对象写入json文件
mapper.writeValue(new File("result.json"), myResultObject);
// or: 把对象序列化Json的字节数组
byte[] jsonBytes = mapper.writeValueAsBytes(myResultObject);
// or: 把对象序列化为Json字符串
String jsonString = mapper.writeValueAsString(myResultObject);
3分钟教程: 处理通用集合, 使用树模型(Tree Model)
- 对比处理简单的Bean类型的POJO, 更进一步, 我们可以如下处理JDK的
List
或Map集合
:
// 读取Json数据反序列化为集合
Map<String, Integer> scoreByName = mapper.readValue(jsonSource, Map.class);
List<String> names = mapper.readValue(jsonSource, List.class);
// 把集合序列化到Json文件
mapper.writeValue(new File("names.json"), names);
只要Json和集合构造函数匹配, 并且集合数据类型很简单.
- 如果有一个自定义的POJO数据类型的集合, 那就需要显式申明数据类型(注意: 对于POJO属性类型是List类型时, 申明不是必须的)
// 为什么要额外加一个TypeReference ? 因为Java的类型擦除(Java Type Erasure)会阻止类型检查(type detection)
Map<String, ResultValue> results = mapper.readValue(jsonSource,
new TypeReference<Map<String, ResultValue>>() { } );
注意: 不论集合的模板类型是什么, 序列化时都不需要额外的类型包装
- 再进一步: 虽然处理
Map
,List
和其他简单对象(String
, 数字,Boolean
)很简单, 但是对象遍历很笨重. 这时候就需要Tree model
进行处理:
// 如果是Json对象或数组, 可以读取为通用的JsonNode类型;
// 或者已知是Json对象, 就读取为ObjectNode类型, 如果是Json数组, 就是ArrayNode
ObjectNode root = mapper.readTree("stuff.json");
String name = root.get("name").asText();
int age = root.get("age").asInt();
// 也可以修改Json数据: 这里添加了一个"other"的子对象, 为它设置属性"type", 值"student"
root.with("other").put("type", "student");
String json = mapper.writeValueAsString(root);
// 经过以上操作得到的最终Json字符串如下:
// {
// "name" : "Bob", "age" : 13,
// "other" : {
// "type" : "student"
// }
// }
可见, Tree Model比data-binding更便利, 尤其是整体结构是经常变换的, 或者不能很好的映射到Java类时
5分钟教程: 流处理(Streaming parse), 生成器(generator)
返回目录
和data-binding一样便利(序列化/反序列化POJO对象), 和Tree Model一样灵活——增量(incremental)模型
(又名流(streaming)模型
).
该模型是一个底层模型, 数据绑定和树模型都是构建在它上面, 但是它也暴露给了用户, 可以最大限度提升性能和/或控制解析和生成的过程细节.
下面是一个简单的增量模型
示例:
JsonFactory f = mapper.getFactory(); // 也可以通过构造函数直接生成f, 二者选一
// 第一步: 输出Json数据
File jsonFile = new File("test.json");
JsonGenerator g = f.createGenerator(jsonFile);
// 目标Json数据: { "message" : "Hello world!" }
g.writeStartObject();
g.writeStringField("message", "Hello world!");
g.writeEndObject();
g.close();
// 第二步: 把文件读回来
JsonParser p = f.createParser(jsonFile);
JsonToken t = p.nextToken(); // Token类型一定是 JsonToken.START_OBJECT
t = p.nextToken(); // JsonToken.FIELD_NAME
if ((t != JsonToken.FIELD_NAME) || !"message".equals(p.getCurrentName())) {
// 错误处理
}
t = p.nextToken();
if (t != JsonToken.VALUE_STRING) {
// 同上
}
String msg = p.getText();
System.out.printf("My message to you is: %s!\n", msg);
p.close();
10分钟教程: 配置(configuration)
返回目录
有两个不同级别的配置机制: Features和Annotations
Feature的一般用法
- 高层的数据绑定类配置:
// SerializationFeature用于改变Json输出的方式
// 开启输出的标准缩进 (美化打印输出):
mapper.enable(SerializationFeature.INDENT_OUTPUT);
// 允许序列化"空(empty)"的POJO对象(没有属性可以被序列化)
// (如果进行设置, 这种情况就会抛出异常)
mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
// 把java.util.Date, Calendar输出为数字(时间戳)
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
// DeserializationFeature用于定义如何把Json读取成POJO对象
// 遇到未知属性时阻止异常抛出
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
// 允许把Json中的空字符串("")反序列化为null对象值
mapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
同样, 也有底层Json解析, 生成的配置:
// JsonParser.Feature用来进行解析配置:
// (注意: 从Jackson2.5开始, 也可以用`mapper.enable(feature)`/`mapper.disable(feature)`)
// 允许使用C/C++ 格式的注释在JSON数据中 (不符合标准, 默认是disabled)
mapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
// 允许Json数据中属性名称不用引号包围(不符合标准) :
mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
// 允许使用单引号(不符合标准) :
mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
// JsonGenerator.Feature用来配置底层的Json生成过程:
// 强制转义非ASCII码的字符:
mapper.configure(JsonGenerator.Feature.ESCAPE_NON_ASCII, true);
注解: 改变属性名
最简单的使用基于注解的方式是使用@JsonProperty
注解:
public class MyBean {
private String _name;
// 如果没有注解,输出就是`theName`, 但是我们想要的是`name`:
@JsonProperty("name")
public String getTheName() { return _name; }
// 只需要在getter或setter其一添加注解即可, 所以这里省略了注解
public void setTheName(String n) { _name = n; }
}
也可以用Mix-in Annotations
(混合注解)来关联起所有的注解
注解: 忽略属性
有两个重要的注解可以用来忽略属性: @JsonIgnore
作用于单个属性; @JsonIgnoreProperties
作用于类定义
// 下面代码的含义是: 如果在Json数据中有"foo"或"bar"属性, 则反序列化时会被跳过
// 而不管POJO中是否这样的属性
@JsonIgnoreProperties({ "foo", "bar" })
public class MyBean
{
// 该属性不会序列化到Json, 也不会从Json反序列化回来
@JsonIgnore
public String internal;
// 没有注解, 而且public的属性可以照常读写
public String external;
@JsonIgnore
public void setCode(int c) { _code = c; }
// 注意: 也会被忽略! 因为setter方法被注解了
public int getCode() { return _code; }
}
和重命名属性一样, 属性的getter
和setter
方法会"共享"注解, 即如果只有一个方法有@JsonIgnore
注解, 则另一个也会生效. 但是也有可能使用"分隔"注解, 如下例:
public class ReadButDontWriteProps {
private String _name;
@JsonProperty public void setName(String n) { _name = n; }
@JsonIgnore public String getName() { return _name; }
}
在这个例子中, 名为name
的属性不会被导出(因为getter
被忽略); 但是如果名为name
的属性在Json中存在, 那么它可以赋值给POJO对象的属性
注解: 使用自定义构造器
不同于其他的数据绑定式框架, Jackson不要求类必须有"默认构造函数"(即无参构造函数). 如果没有默认构造器, 你可以指定一个有参构造器:
public class CtorBean
{
public final String name;
public final int age;
@JsonCreator // 构造器可以是 public, private等等
private CtorBean(@JsonProperty("name") String name,
@JsonProperty("age") int age)
{
this.name = name;
this.age = age;
}
}
对于不变对象, 这个方法非常有用
或者, 你也可以定义工厂方法:
public class FactoryBean
{
// 属性忽略
@JsonCreator
public static FactoryBean create(@JsonProperty("name") String name) {
// 构造并返回一个对象
}
}
注意: 构造器是一种反序列化的方法, 但是并不和setter
结合注解的方法冲突: 可以将构造函数, 工厂方法中的属性与通过setter或直接使用字段设置的属性混合使用
特殊用法: POJO-to-POJO的转换
返回目录
Jackson有一个非常有用的功能: POJO-to-POJO的转换. 为了便于理解, 你可以想象其中经过了两步: 先把一个POJO对象序列化为一个Json, 然后把Json绑定到另一个POJO对象. 实际实现时其实跳过了生成Json的过程, 而使用了更高效的中间件.
转换可以在任何兼容的类型之间进行, 调用过程如下:
ResultType result = mapper.convertValue(sourceObject, ResultType.class);
只要源类型和目的类型是兼容的, 也就是序列化到Json和反序列化都是可以成功的, 转换就会生效.
下面还有一些更有用的用法:
// 从List<Integer>转换到int[]
List<Integer> sourceList = ...;
int[] ints = mapper.convertValue(sourceList, int[].class);
// 从一个POJO对象转换到Map
Map<String,Object> propertyMap = mapper.convertValue(pojoValue, Map.class);
// ... 再转回来
PojoType pojo = mapper.convertValue(propertyMap, PojoType.class);
// 解码Base64! (这里默认的 byte[] 存储了一个base64解码后的字符串)
// binary: Man is distinguished, not only by his reason, but by this
String base64 = "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlz";
byte[] binary = mapper.convertValue(base64, byte[].class);
基于此, Jackson可以作为基本组件, 进行处理base64编解码的任务, 处理动态bean(Map和POJO的互转)