JavaWeb插件合集 Jackson之快速开始


[翻译]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的ListMap集合:
// 读取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)

返回目录
有两个不同级别的配置机制: FeaturesAnnotations

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; }
}

和重命名属性一样, 属性的gettersetter方法会"共享"注解, 即如果只有一个方法有@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的互转)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值