Jackson使用详解

简介

Jackson 是当前用的比较广泛的,用来序列化和反序列化 json 的 Java 的开源框架。Jackson 社区相对比较活跃,更新速度也比较快, 从 Github 中的统计来看,Jackson 是最流行的 json 解析器之一 。 Spring MVC 的默认 json 解析器便是 Jackson。 Jackson 优点很多。 Jackson 所依赖的 jar 包较少 ,简单易用。与其他 Java 的 json 的框架 Gson 等相比, Jackson 解析大的 json 文件速度比较快;Jackson 运行时占用内存比较低,性能比较好;Jackson 有灵活的 API,可以很容易进行扩展和定制。尽管还有一些其它同样优秀的json解析工具,例如Fast Json、GSON,但是出于最小依赖的考虑,也许Json解析第一选择就应该是Jackson。

基本使用

引入依赖

在pom中添加下列代码

<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-databind 依赖 jackson-core 和 jackson-annotations,所以可以只显示地添加jackson-databind依赖,jackson-core 和 jackson-annotations 也随之添加到 Java 项目工程中。

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

ObjectMapper

Jackson 最常用的 API 就是基于”对象绑定” 的 ObjectMapper,ObjectMapper可以从字符串,流或文件中解析JSON,并创建表示已解析的JSON的Java对象。也可以从Java对象创建JSON。

从json中获取java对象

调用readValue方法即可,例如

People people = objectMapper.readValue(json, People.class);

默认情况下,Jackson通过将JSON字段的名称与Java对象中的getter和setter方法进行匹配,将JSON对象的字段映射到Java对象中的属性。 Jackson删除了getter和setter方法名称的“ get”和“ set”部分,并将其余名称的第一个字符转换为小写。

例如,名为brand的JSON字段与名为getBrand()和setBrand()的Java getter和setter方法匹配。 名为engineNumber的JSON字段将与名为getEngineNumber()和setEngineNumber()的getter和setter匹配。

如果需要以其他方式将JSON对象字段与Java对象字段匹配,则需要使用自定义序列化器和反序列化器,或者使用一些Jackson注解。

上面例子中的json可以是字符串(String)、字符输入流(Reader)、文件(File)、字节输入流(InputStream)、二进制数组byte[])、URL(URL)

如果将JSON字符串转换为List或者Map,还需要使用TypeReference,例如:

String jsonArray = "[{\"name\":\"zhangsan\"}, {\"name\":\"lisi\"}]";
ObjectMapper objectMapper = new ObjectMapper();
List<Car> cars1 = objectMapper.readValue(jsonArray, new TypeReference<List<Car>>(){});



String jsonObject = "{\"name\":\"zhangsan\", \"age\":19}";
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> jsonMap = objectMapper.readValue(jsonObject, new TypeReference<Map<String,Object>>(){});

忽略未知的JSON字段

有时候,与要从JSON读取的Java对象相比,JSON中的字段更多。 默认情况下,Jackson在这种情况下会抛出异常,报不知道XYZ字段异常,因为在Java对象中找不到该字段。但是,有时应该允许JSON中的字段多于相应的Java对象中的字段。 例如,要从REST服务解析JSON,而该REST服务包含的数据远远超出所需的。 在这种情况下,可以使用Jackson配置忽略这些额外的字段。 以下是配置Jackson ObjectMapper忽略未知字段的示例:

objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

不允许基本类型为null

如果JSON字符串包含其值设置为null的字段(对于在相应的Java对象中是基本数据类型(int,long,float,double等)的字段), Java中的基本数据类型不能为null值。 默认情况下,Jackson ObjectMapper会忽略原始字段的空值。Jackson ObjectMapper默认会处理基本数据类型为null的情况,我们可以可以将Jackson ObjectMapper默认配置为失效,这样基本数据为null就会转换失败。

objectMapper.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, true);

自定义反序列化

有时,可能希望以不同于Jackson ObjectMapper缺省方式的方式将JSON字符串读入Java对象。 可以将自定义反序列化器添加到ObjectMapper,可以按需要执行反序列化。

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);
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();
                jsonToken = parser.nextToken();
                if("brand".equals(fieldName)){
                    car.setBrand(parser.getValueAsString());
                } else if ("doors".equals(fieldName)){
                    car.setDoors(parser.getValueAsInt());
                }
            }
        }
        return car;
    }
}

将对象写入json

Jackson ObjectMapper也可以用于从对象生成JSON。 可以使用以下方法之一进行操作:

  • writeValue()
  • writeValueAsString()
  • writeValueAsBytes()
objectMapper.writeValue(json, object);

image-20210117164552149

这里的json可以是多种输出方式;

当然也可以直接转换为字符串

String json = objectMapper.writeValueAsString(object);

Jackson 日期转化

默认的Jackson日期格式,该格式将Date序列化为自1970年1月1日以来的毫秒数(long类型)。

日期的long序列化格式不符合人类的时间查看格式。 因此,Jackson也支持文本日期格式。 可以通过在ObjectMapper上设置SimpleDateFormat来指定要使用的确切Jackson日期格式。 这是在Jackson的ObjectMapper上设置SimpleDateFormat的示例:

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

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

Jackson JSON 树模型

Jackson具有内置的树模型,可用于表示JSON对象。 如果不知道接收到的JSON的格式,或者由于某种原因而不能(或者只是不想)创建一个类来表示它,那么就要用到Jackson的树模型。 如果需要在使用或转化JSON之前对其进行操作,也需要被用到Jackson树模型。 所有这些情况在数据流场景中都很常见。Jackson树模型由JsonNode类表示。 您可以使用Jackson ObjectMapper将JSON解析为JsonNode树模型,就像使用您自己的类一样。

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

只需将JsonNode.class作为第二个参数传递给readValue()方法,而不是本教程前面的示例中使用的Car.class,就可以将JSON字符串解析为JsonNode对象而不是People对象。 。

ObjectMapper类还具有一个特殊的readTree()方法,该方法返回JsonNode。

JsonNode jsonNode = objectMapper.readTree(json);

通过JsonNode类,可以以非常灵活和动态的方式将JSON作为Java对象导航。

将JSON解析为JsonNode(或JsonNode实例树)后,就可以浏览JsonNode树模型。

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

无论访问的是字段,数组还是嵌套对象,都可以使用JsonNode类的get()方法。 通过将字符串作为参数提供给get()方法,可以访问JsonNode的字段。 如果JsonNode表示数组,则需要将索引传递给get()方法。 索引指定要获取的数组元素。

Java对象 转 JsonNode对象

可以使用Jackson ObjectMapper将Java对象转换为JsonNode,而JsonNode是转换后的Java对象的JSON表示形式。 可以通过Jackson ObjectMapper valueToTree()方法将Java对象转换为JsonNode。

JsonNode carJsonNode = objectMapper.valueToTree(people);

JsonNode对象 转 Java对象

可以使用Jackson ObjectMapper treeToValue()方法将JsonNode转换为Java对象。 这类似于使用Jackson Jackson的ObjectMapper将JSON字符串(或其他来源)解析为Java对象。 唯一的区别是,JSON源是JsonNode。

People people = objectMapper.treeToValue(jsonNode);

获取JsonNode字段

JsonNode可以像JSON对象一样具有字段。 假设已将以下JSON解析为JsonNode:

{
    "field1" : "value1",
    "field2" : 999
}
JsonNode field1 = jsonNode.get("field1");
JsonNode field2 = jsonNode.get("field2");

请注意,即使两个字段都是String字段,get()方法也始终返回JsonNode来表示该字段。

在路径中获取JsonNode字段

Jackson JsonNode有一个称为at()的特殊方法。 at()方法可以从JSON图中以给定JsonNode为根的任何位置访问JSON字段。 假设JSON结构如下所示:

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

如果将此JSON解析为JsonNode,则可以使用at()方法访问名称字段

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

注意传递给at()方法的参数:字符串/ identification / name。 这是一个JSON路径表达式。 此路径表达式指定从根JsonNode到您要访问其值的字段的完整路径。

请注意,JSON路径表达式必须以斜杠字符(/字符)开头,at()方法返回一个JsonNode,它表示请求的JSON字段。 要获取该字段的实际值,需要调用下一部分介绍的方法之一。 如果没有节点与给定的路径表达式匹配,则将返回null。

转换JsonNode字段

Jackson 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();

使用默认值转换: 如果JsonNode中的字段可以为null,则在尝试转换它时可以提供默认值。

ObjectMapper objectMapper = new ObjectMapper();
String json = "{ \"f1\":\"Hello\", \"f2\":null }";
JsonNode jsonNode = objectMapper.readTree(json);
String f2Value = jsonNode.get("f2").asText("Default");

asDouble(),asInt()和asLong()方法同样可以采用默认参数值,如果尝试从中获取值的字段为null,则将返回默认参数值。

请注意,如果该字段在JSON中未显式设置为null,但在JSON中丢失,则调用jsonNode.get(“ fieldName”)将返回Java null值,您无法在该Java值上调用asInt() ,asDouble(),asLong()或asText()。 如果尝试这样做,将会导致NullPointerException。

ObjectNode

如前所述,JsonNode类是不可变的。 要创建JsonNode对象图,必须能够更改图中的JsonNode实例,例如 设置属性值和子JsonNode实例等。由于是不可变的,因此无法直接使用JsonNode来实现。

而是创建一个ObjectNode实例,该实例是JsonNode的子类。通过ObjectMapper 的 createObjectNode()方法来创建ObjectNode。

ObjectNode可以通过Set、Put、remove方法来管理ObjectNode,

Jackson注解

Jackson JSON工具包包含一组Java注解,可以使用这些注解来设置将JSON读入对象的方式或从对象生成什么JSON的方式

@JsonProperty

注解在属性

把属性的名称序列化时转换为另外一个名称。

public class Person {

    @JsonProperty("person_id")
    public long  personId = 0;

    public String name = null;
}

@JsonFormat

注解在属性或者方法

把属性的格式序列化时转换成指定的格式。

public class Person {

    public long  personId = 0;

    public String name = null;

    @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm")
    public Date birthDate;
}

@JsonIgnore

注解在属性

用于告诉Jackson忽略Java对象的某个属性(字段)。 在将JSON读取到Java对象中以及将Java对象写入JSON时,都将忽略该属性。

public class Person {

    @JsonIgnore
    public long  personId = 0;

    public String name = null;
}

@JsonIgnoreProperties

注解在

用于指定要忽略的类的属性列表

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

    public long   personId = 0;

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

}

@JsonIgnoreType

注解在属性

用于将整个类型(类)标记为在使用该类型的任何地方都将被忽略。

public class Person {

    @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

注解在

用于告诉Jackson在读写对象时包括需要包括的对应的权限修饰的属性

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

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

}

JsonAutoDetect.Visibility类包含与Java中的可见性级别匹配的常量,表示ANY,DEFAULT,NON_PRIVATE,NONE,PROTECTED_AND_PRIVATE和PUBLIC_ONLY。

@JsonSetter

注解在方法

在Java类内部使用的属性名称与JSON文件中使用的属性名称不同时,指示Jackson为给定的JSON字段使用setter方法

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

@JsonAnySetter

注解在方法

类似于@JsonSetter注解,当Jackson无法直接将此JSON对象的属性映射到类时,可以通过添加@JsonAnySetter注解来告诉Jackson为所有无法识别的字段调用set()方法

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

:只有无法识别的才会调用注解标注的方法,其他的仍就会正常调用对应的方法。

@JsonCreator

注解在构造方法

用于告诉Jackson该Java对象具有一个构造函数(“创建者”),该构造函数可以将JSON对象的字段与Java对象的字段进行匹配,@JsonCreator通常注解在无法使用@JsonSetter注解的情况

单使用@JsonCreator是不够的,我们还必须注解构造函数的参数,以告诉Jackson将JSON对象中的哪些字段传递给哪些构造函数参数

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

@JsonDeserialize

注解在属性

用于为Java对象中给定的属性指定自定义反序列化器类

public class PersonDeserialize {

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

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

其次,这是@JsonDeserialize注解中引用的OptimizedBooleanDeserializer类

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

@JsonInclude

注解在

用于告诉Jackson仅在某些情况下包括属性。例如,仅当属性为非null,非空或具有非默认值时,才应包括该属性。

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

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

}

@JsonInclude注解的一个更通俗的名称应该是@JsonIncludeOnlyWhen,但是写起来会更长。

@JsonGetter

注解在方法

用于告诉Jackson,应该通过调用getter方法而不是通过直接字段访问来获取某个字段值。

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

}

@JsonAnyGetter

注解在方法

可以将Map用作要序列化为JSON的属性的容器。 换句话说,Map中的所有键值对都将作为PersonAnyGetter对象的一部分序列化为JSON。

public class PersonAnyGetter {

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

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

@JsonPropertyOrder

注解在

通常,Jackson会按照在类中找到的顺序序列化PersonPropertyOrder中的属性。@JsonPropertyOrder可用于指定将Java对象的字段序列化为JSON的顺序

@JsonPropertyOrder({"name", "personId"})
public class PersonPropertyOrder {

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

}

@JsonRawValue

注解在属性

告诉Jackson该属性值应直接写入JSON输出

public class PersonRawValue {

    public long   personId = 0;

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

返回的结果

{"personId":0,"address":$#}
 //注意引号

@JsonValue

注解在方法

告诉Jackson不应该尝试序列化对象本身,而应在对象上调用将对象序列化为JSON字符串的方法

public class PersonValue {

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

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

}

输出:

"0,null"

@JsonSerialize

注解在属性

用于为Java对象中的字段指定自定义序列化程序。

public class PersonSerializer {

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

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

OptimizedBooleanSerializer将序列的真值序列化为1,将假值序列化为0。

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

一个好奇的人