FasterXML Jackson

本篇文章参考于简书:https://www.jianshu.com/p/4bd355715419,在此基础上验证并稍作修改。

 

老版本的Jackson使用的包名为org.codehaus.jackson,而新版本使用的是com.fasterxml.jackson

Jackson主要包含了3个模块:

  • jackson-core
  • jackson-annotations
  • jackson-databind
    其中,jackson-annotations依赖于jackson-core,jackson-databind又依赖于jackson-annotations。

Jackson有三种方式处理Json:

通常来说,我们在日常开发中使用的是第3种方式,有时为了简便也会使用第2种方式,比如你要从一个很大的Json对象中只读取那么一两个字段的时候,采用databind方式显得有些重,JsonNode反而更简单。

使用ObjectMapper

本文的例子以jackson-databind-2.8.1为例。
创建User类如下:

public class User {
    private String name;
    private int age;
    private int sex;
	
    public String getName() {
	return name;
    }
    public void setName(String name) {
	this.name = name;
    }
    public int getAge() {
	return age;
    }
    public void setAge(int age) {
	this.age = age;
    }
    public int getSex() {
	return sex;
    }
    public void setSex(int sex) {
	this.sex = sex;
    }
}

序列化一个User对象:

public static void main(String[] args) throws IOException {
    ObjectMapper objectMapper = new ObjectMapper();
    User user = new User();
    user.setName("123");
    user.setAge(12);
    user.setSex(1);
    System.out.println(objectMapper.writeValueAsString(user));
}

返回结果:

{"name":"123","age":12,"sex":1}

在默认情况下,ObjectMapper在序列化时,将所有的字段一一序列化,无论这些字段是否有值,或者为null。

另外,序列化依赖于getter方法,如果某个字段没有getter方法,那么该字段是不会被序列化的。由此可见,在序列化时,OjbectMapper是通过反射机制找到了对应的getter,然后将getter方法对应的字段序列化到Json中。请注意,此时ObjectMapper并不真正地检查getter对应的属性是否存在于User对象上,而是通过getter的命名规约进行调用,比如对于getAbc()方法:

 public String getAbc(){
     return "this is abc";
 }

即便User上没有abc属性,abc也会被序列化:

{"name":"123","age":12,"sex":1,"abc":"this is abc"}

反序列化一个Json字符串,其中少了一个name字段:

public static void main(String[] args) throws IOException {
    ObjectMapper objectMapper = new ObjectMapper();
    User user = objectMapper.readValue("{\"age\":12,\"sex\":1}", User.class);
    System.out.println("name: " + user.getName());
    System.out.println("age: " + user.getAge());
    System.out.println("sex: " + user.getSex());
}

输出:

name: null
age: 12
sex: 1

可以看出,少了name字段,程序依然正常工作,只是name的值为null。

另外,如果我们向Json中增加一个User中没有的字段:

public static void main(String[] args) throws IOException {
    ObjectMapper objectMapper = new ObjectMapper();
    User user = objectMapper.readValue("{\"name\":\"123\",\"age\":12,\"sex\":1,\"abc\":\"this is abc\"}", User.class);
    System.out.println("name: " + user.getName());
    System.out.println("age: " + user.getAge());
    System.out.println("sex: " + user.getSex());
}

此时运行程序将报错:

Exception in thread "main" com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "abc" (class com.study.common.User), not marked as ignorable (3 known properties: "name", "sex", "age"])
 at [Source: (String)"{"name":"123","age":12,"sex":1,"abc":"this is abc"}"; line: 1, column: 39] (through reference chain: com.study.common.User["abc"])

表示在User对象上找不到对应的abc属性,但是如果我们在User上增加一个空的setter:

public void setAbc(String abc) {
		
}

那么此时运行成功,由此可见OjbectMapper是通过反射的机制,通过调用Json中字段所对应的setter方法进行反序列化的。并且此时,依赖于User上有默认构造函数。

综上,在默认情况下(即不对ObjectMapper做任何额外配置,也不对Java对象加任何Annotation),ObjectMapper依赖于Java对象的默认的无参构造函数进行反序列化,并且严格地通过getter和setter的命名规约进行序列化和反序列化。

去除getter和setter

纯粹地为了技术方面的原因而添加getter和setter是不好的,可以通过以下方式去除掉对getter和setter的依赖:

objectMapper.setVisibility(PropertyAccessor.ALL, Visibility.NONE)
    .setVisibility(PropertyAccessor.FIELD, Visibility.ANY);

ObjectMapper将通过反射机制直接操作Java对象上的字段。

此时创建User如下:

public class User {
    private String name;
    private int age;
    private int sex;

    public User(String name, int age, int sex) {
	super();
	this.name = name;
	this.age = age;
	this.sex = sex;
    }
}

序列化User:

public static void main(String[] args) throws IOException {
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.setVisibility(PropertyAccessor.ALL, Visibility.NONE)
        .setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
    User user = new User("name", 12, 1);
    System.out.println(objectMapper.writeValueAsString(user));
}

然而,此时反序列化的时候报错:

Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of com.study.common.User: no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?)
 at [Source: (String)"{"name":"name","age":12,"sex":1}"; line: 1, column: 2]

这是因为ObjectMapper在为字段设值之前,无法初始化User对象,此时有两种解决方式:

  1. 为User增加默认构造函数:
    public User() {
        
    }
  2. 在已有构造函数上加上@JsonCreator注解,通常与@JsonProperty一起使用:
    @JsonCreator
    public User(@JsonProperty("name")String name, @JsonProperty("age")int age, @JsonProperty("sex")int sex) {
        super();
        this.name = name;
        this.age = age;
        this.sex = sex;
    }

忽略字段

@JsonIgnore用于字段上,表示该字段在序列化和反序列化的时候都将被忽略。

@JsonIgnoreProperties主要用于类上:


@JsonIgnoreProperties(value = {"age","sex"},ignoreUnknown = true)

表示对于age和sex字段,反序列化和序列化均忽略,而对于Json中存在的未知字段,在反序列化时忽略,ignoreUnknown不对序列化起效。

序列化时排除null或者空字符串

@JsonInclude(JsonInclude.Include.NON_NULL)
public class User {

表示在序列化User时,将值为null的字段排除掉。

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

表示在序列化User时,将值为null的字段或者空的字符串字段排除掉。

用某个方法的返回值序列化整个对象

有时我们并不想讲对象的所有字段都序列化,而是希望用一个方法的返回值来序列化,比如toString()方法,此时可以用@JsonValue

@JsonValue
public String toString(){
    return "someValue";
}

此时整个对象在序列化后的值将变为“someValue”。

反序列化时陈列多余字段

在默认情况下,如果Json数据中有多余的字段,那么在反序列化时Jackson发现无法找到对应的对象字段,便会抛出UnrecognizedPropertyException: Unrecognized field xxx异常,此时可以做如下配置:

objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

ObjectMapper配置:

ObjectMapper objectMapper = new ObjectMapper();
//去掉默认的时间戳格式     
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);

//设置为东八区
objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+8"));

// 设置输入:禁止把POJO中值为null的字段映射到json字符串中
objectMapper.configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false);

//空值不序列化
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);

//反序列化时,属性不存在的兼容处理
objectMapper.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);

//序列化时,日期的统一格式
objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

//序列化日期时以timestamps输出,默认true
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);

//序列化枚举是以toString()来输出,默认false,即默认以name()来输出
objectMapper.configure(SerializationFeature.WRITE_ENUMS_USING_TO_STRING,true);

//序列化枚举是以ordinal()来输出,默认false
objectMapper.configure(SerializationFeature.WRITE_ENUMS_USING_INDEX,false);

//类为空时,不要抛异常
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);

//反序列化时,遇到未知属性时是否引起结果失败
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

//单引号处理
objectMapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);

//解析器支持解析结束符
objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true);

 平时用的配置如下:

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.findAndRegisterModules()
    .setVisibility(PropertyAccessor.ALL, Visibility.NONE)
    .setVisibility(PropertyAccessor.FIELD, Visibility.ANY)
    .setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"))
    .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

另外,添加对Java 8的支持,在pom.xml文件中加入依赖:


    com.fasterxml.jackson.core
    jackson-core
    2.9.0


    com.fasterxml.jackson.core
    jackson-databind
    2.8.1

以上配置有以下几点:

  • 对于所有的使用ObjectMapper的地方,推荐采用直接处理字段的方式,即:
objectMapper.setVisibility(PropertyAccessor.ALL, Visibility.NONE)
    .setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
  • 反序列化时自动忽略多余字段(这样,如果在Java中对象中删除某些字段,原来持久化为Json的数据依然可以正确的反序列化;另外,在访问第三方API时,也可以忽略掉不需要的字段):
objectMapper.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);
  • 对于Json持久化数据来说(比如使用MySQL的JSON格式存储Java对象),同时使用Spring Boot,此时Jackson承担了两个作用,一是负责与API消费端数据通信的序列化和反序列化,二是对Java对象和数据库之间的序列化和反序列化。以上推荐配置可以做到:
    • 无需setter和getter,直接对字段操作,当然依然可以使用@JsonCreator
    • 如果Java对象需要添加字段,并且新增字段没有默认值(即默认值为null),则既有数据无需做迁移,也无需对应在数据库中增加字段。如果新增字段有默认值,那么需要自己做数据迁移。
    • 如果Java对象需要删除字段,既有数据也无需做迁移,对应字段也无需删除,只是被删除的字段依然保存在数据库中,有人可能认为这是脏数据,但是另一个方面有可以认为我们保留了历史数据。
    • 如果需要对Java对象的某些字段改名,那么此时便需要自己做数据迁移了。

你可能感兴趣的:(java)