終極CRUD-3-用Jackson解析json


1 jackson json基本介紹和使用

網上有很多關於jackson和json的介紹和使用,我就不重復造輪子了,本篇博客主要介紹jackson的高級應用和博主我自己踩坑心得。

如果對json和jackson不熟悉的朋友,可以看下面兩篇博客。

https://www.runoob.com/json/json-tutorial.html JSON教程

https://blog.csdn.net/u011054333/article/details/80504154#commentBox jackson快速入門

2 jackson 常用的注解

2.1@JsonProperty

這個注解非常有用,看下面代碼:

public class Person {
    @JsonProperty("username")
    private String name;
    private Integer age;
    //省略getter setter
}
    @Test
    public void test1() throws Exception{
        ObjectMapper mapper = new ObjectMapper();
        Person person = new Person("adai",21);
        System.out.println(mapper.writeValueAsString(person));
    }

輸出為 {"age":21,"username":"adai"}

可以看到,在序列化的json串中,username替代了name

2.2 @JsonIgnore

public class Person {
    @JsonIgnore
    private String name;
    private Integer age;
    //省略getter setter
}
    @Test
    public void test1() throws Exception{
        ObjectMapper mapper = new ObjectMapper();
        Person person = new Person("adai",21);
        System.out.println(mapper.writeValueAsString(person));
    }

輸出為 {"age":21}

2.3 @JsonIgnoreProperties

①這個注解和@JsonIgnore有些類似,不過主要是作用在類上面

@JsonIgnoreProperties(value = {"name","age"})
public class Person {
    private String name;
    private Integer age;
    private Double height;
    //省略getter setter
}
    @Test
    public void test1() throws Exception {
        ObjectMapper mapper = new ObjectMapper();
        Person person = new Person("adai", 21, 172D);
        String json = mapper.writeValueAsString(person);
        System.out.println(json);
    }

輸出為 {"height":172.0}

可以看出@JsonIgnoreProperties(value = {"name","age"}) 忽略了name和age屬性,在序列化的時候,會忽略這兩個屬性

②@JsonIgnoreProperties注解還有一個ignoreUnknown屬性,主要用在反序列化上

在正常情況下,如果我們json串中有一些key值和我們的POJO對象不匹配,那么將會拋出異常。

    @Test
    public void test2() throws Exception {
        ObjectMapper mapper = new ObjectMapper();
        System.out.println(mapper.readValue("	    {\"name\":\"adai\",\"age\":21,\"height222\":172.0}", Person.class));   
       					 // !!注意height222與我們的pojo對象不匹配
    }									

程序將會拋出異常

com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "height222" (class com.antiy.common.adai.demo.Person), not marked as ignorable (3 known properties: "name", "age", "height"])
 at [Source: (String)"{"name":"adai","age":21,"height222":172.0}"; line: 1, column: 42] (through reference chain: com.antiy.common.adai.demo.Person["height222"])

此時如果我們在Person類上加上@JsonIgnoreProperties(ignoreUnknown = true)

@JsonIgnoreProperties(ignoreUnknown = true)
public class Person {
    private String name;
    private Integer age;
    private Double height;
    //省略getter setter
}

輸出為 Person(name=adai, age=21, height=null)

使用 mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); 也可以達到同樣的目的

建議:ignoreUnknown和FAIL_ON_UNKNOWN_PROPERTIES盡量不要設置為true,如果反序列化的時候,json串中的相關key和POJO屬性不匹配,就讓程序拋出異常,即使發現錯誤,不過具體情況還需要參考具體業務,jackson默認該值為false

2.4 @JsonTypeName和@JsonTypeInfo

主要作用:在json串中又包裝了一層

①正常情況下,序列化的字符串是 {"name":"adai","age":21,"height":172.0}

當我們在Person類上加上@@JsonTypeName和@JsonTypeInfo時

@JsonTypeName(value = "user222")
@JsonTypeInfo(include = JsonTypeInfo.As.WRAPPER_OBJECT, use = JsonTypeInfo.Id.NAME)
public class Person {
    private String name;
    private Integer age;
    private Double height;
    //省略getter setter
}

輸出為 {"user222":{"name":"adai","age":21,"height":172.0}}

②我們也可以使用@JsonRootName("user222")和mapper.enable(SerializationFeature.WRAP_ROOT_VALUE)來達到同樣的效果

@JsonRootName("user222")
public class Person {
    private String name;
    private Integer age;
    private Double height;
    //省略getter setter
}
    @Test
    public void test1() throws Exception {
        ObjectMapper mapper = new ObjectMapper();
        mapper.enable(SerializationFeature.WRAP_ROOT_VALUE);
        Person person = new Person("adai", 21, 172D);
        System.out.println(mapper.writeValueAsString(person));
    }

輸出為 {"user222":{"name":"adai","age":21,"height":172.0}}

2.5 @JsonFormat

主要用在Date屬性上

public class Person {
    private String name;
    private Integer age;
    private Double height;
    private Date date;
    //省略getter setter
}
    @Test
    public void test1() throws Exception {
        ObjectMapper mapper = new ObjectMapper();
        Person person = new Person("adai", 21, 172D,new Date());
        System.out.println(mapper.writeValueAsString(person));
    }

輸出為 {"name":"adai","age":21,"height":172.0,"date":1558842751645}

注意:jackson默認會將Date類型序列化成時間戳,這是因為SerializationFeature中的WRITE_DATES_AS_TIMESTAMPS(true),該值默認為true,當我們手動將改值設為false時。

mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

輸出為 {"name":"adai","age":21,"height":172.0,"date":"2019-05-26T03:56:38.660+0000"}

這時候date就不再是時間戳了,但是和我們中國的時間格式有一些差別,這個時候就可以使用@JsonFormat

public class Person {
    private String name;
    private Integer age;
    private Double height;
    @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss:SSS",timezone="GMT+8")
    private Date date;
    //省略getter setter
}

輸出為 {"name":"adai","age":21,"height":172.0,"date":"2019-05-26 11:58:07:296"}

2.6 @JsonAnyGetter

該注解主要用在序列化:

1.方法是非靜態,沒有參數的,方法名隨意
2.方法返回值必須是Map類型
3.在一個實體類中僅僅用在一個方法上
4.序列化的時候json字段的key就是返回Map的key,value就是Map的value

public class Person {
    private String name;
    private Integer age;
    private Map<String, Object> map = new HashMap<>();

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @JsonAnyGetter // 注意這個注解
    public Map<String, Object> getOther(){
        return map;
    }
}
    @Test
    public void test1() throws Exception {
        ObjectMapper mapper = new ObjectMapper();
        Person person = new Person();
        person.setName("adai");
        person.setAge(21);
        Map<String, Object> other = person.getOther();
        other.put("city", "chengdu");
        System.out.println(mapper.writeValueAsString(person));
    }

輸出為 {"name":"adai","age":21,"city":"chengdu"}

當我們在public Map<String, Object> getOther()上去掉@JsonAnyGetter這個注解的時候

輸出為 {"name":"adai","age":21,"other":{"city":"chengdu"}}

可以看出加上這個注解以后序列化的時候就會將Map里面的值也相當於實體類里面的字段給顯示出來了。

2.7 @JsonAnySetter

主要作用於反序列化上

1.用在非靜態方法上,注解的方法必須有兩個參數,第一個是json字段中的key,第二個是value,方法名隨意

2.反序列化的時候將對應不上的字段全部放到Map里面

public class Person {
    private String name;
    private Integer age;
    private Map<String, String> map = new HashMap<>();

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @JsonAnySetter //注意這個注解
    public void setOther(String key, String value){
        this.map.put(key, value);
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", map=" + map +
                '}';
    }
}
    @Test
    public void test1() throws Exception {
        ObjectMapper mapper = new ObjectMapper();
        String json = "{\"name\":\"adai\",\"age\":21,\"color\":\"red\",\"city\":12}";
        Person person = mapper.readValue(json, Person.class);
        System.out.println(person);
    }

輸出為 Person{name='adai', age=21, map={color=red, city=12}}

可以看出,使用@JsonAnySetter注解,在json串中多余的屬性會被自動放在map屬性中,而不會拋出UnrecognizedPropertyException異常

注意:如果是Map<String,String> 那么即使是 {"name":"adai","age":21,"city":12,"weather":true}中的city對應數值 12 和weather對應布爾 true也會被封裝進Map<String, String>中,但是Map<String, Integer> 無法封裝String或其他類型,只能封裝Integer

3 jackson 處理泛型轉換

Java中 List和Map主要和泛型打交道,我們重點以這兩個為例子,來學習jackson中如何在反序列中保留泛型信息的。

3.1 思考下面程序

public class Student {
    private String name;
    private Integer age;
    //省略getter setter
}
    @Test
    public void test3() throws Exception {
        ObjectMapper mapper = new ObjectMapper();
        List<Student> list = new ArrayList<>();
        list.add(new Student("adai",21));
        list.add(new Student("apei",22));
        String json = mapper.writeValueAsString(list);
        List<Student> student = mapper.readValue(json, List.class);
        System.out.println(student.get(0).getName());
    }

該程序在編譯期不會報錯,可以執行。那么在運行期的時候可以通過嗎?

答案是:否定的。 即程序運行失敗

java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.antiy.common.adai.demo.Student

原因①:因為在反序列化的時候,mapper.readValue(json, List.class)並沒有告訴jackson,這個json數據可以封裝成Student對象,所以jackson默認將[{"name":"adai","age":21},{"name":"apei","age":22}]封裝成兩個LinkedHashMap對象,然后放入到List集合中。

原因②:既然我們知道了List中保存的對象在運行期是LinkedHashMap,那么為什么在代碼中還可以student.get(0).getName(),這就跟Java編譯期的泛型擦除有關系了,我們可以看下反編譯后的代碼

        List<Student> student = (List)mapper.readValue(json, List.class);
        System.out.println(((Student)student.get(0)).getName());

student.get(0)實際上的對象是LinkedHashMap,然后強轉成Student,自然就報錯了!

3.1 JavaType

我們可以使用JavaType來保存泛型信息

List:

    @Test
    public void test4() throws Exception {
        ObjectMapper mapper = new ObjectMapper();
        JavaType javaType = mapper.getTypeFactory().constructParametricType(List.class, Student.class);
        List<Student> list = new ArrayList<>();
        list.add(new Student("adai",21));
        list.add(new Student("apei",22));
        String json = mapper.writeValueAsString(list);
        List<Student> student2 = mapper.readValue(json, javaType);
        System.out.println(student2.get(0).getName());
    }

輸出為 adai

Map:

    @Test
    public void test5() throws Exception {
        ObjectMapper mapper = new ObjectMapper();
        JavaType javaType = mapper.getTypeFactory().constructParametricType(Map.class, 		String.class, Student.class); // 第二個參數是Map的key,第三個參數是Map的value
        Map<String, Student> map = new HashMap<>();
        map.put("first",new Student("adai",21));
        map.put("second",new Student("apei",22));
        String json = mapper.writeValueAsString(map);
        Map<String, Student> result = mapper.readValue(json, javaType);
        System.out.println(result.get("first").getName());
    }

輸出為 adai

3.2 TypeReference

TypeReferencejavaType模式更加方便,代碼也更加簡潔

List:

    @Test
    public void test6() throws Exception {
        ObjectMapper mapper = new ObjectMapper();
        List<Student> list = new ArrayList<>();
        list.add(new Student("adai",21));
        list.add(new Student("apei",22));
        String json = mapper.writeValueAsString(list);
        List<Student> student2 = mapper.readValue(json, new 		 	   TypeReference<List<Student>>(){}); 
        System.out.println(student2.get(0).getName());
    }

輸出為 adai

Map:

    @Test
    public void test7() throws Exception {
        ObjectMapper mapper = new ObjectMapper();
        Map<String, Student> map = new HashMap<>();
        map.put("first",new Student("adai",21));
        map.put("second",new Student("apei",22));
        String json = mapper.writeValueAsString(map);
        Map<String, Student> result = mapper.readValue(json, new TypeReference<Map<String,Student>>(){});
        System.out.println(result.get("first").getName());
    }

輸出為 adai

可以看到,使用TypeReference,只需要在mapper.readValue后面增加一個 new TypeReference匿名內部類,寫上自己想要封裝的泛型對象,比javaType少了一行mapper.getTypeFactory().constructParametricType聲明

4 jackson 自定義序列化和反序列化規則

jackson可以通過SerializationFeatureDeserializationFeature來自定義,序列化和反序列化規則,這也是jackson非常強大的地方。

4.1 enable disable configure

請看下面一個例子:

mapper.configure(SerializationFeature.INDENT_OUTPUT,true);
mapper.enable(SerializationFeature.INDENT_OUTPUT);
mapper.disable(SerializationFeature.INDENT_OUTPUT);

這里有三個方法,configure方法接受配置名和要設置的值,Jackson 2.5版本新加的enable和disable方法則直接啟用和禁用相應屬性,我推薦使用后面兩個方法。

4.2 SerializationFeature.INDENT_OUTPUT

默認為false,該屬性主要是美化json輸出

普通序列化的json串:

{"name":"adai","age":21}

開啟該屬性后的json串:

{
  "name" : "adai",
  "age" : 21
}

4.3 SerializationFeature.FAIL_ON_EMPTY_BEANS

默認為true,該屬性的意思是,如果一個對象中沒有任何的屬性,那么在序列化的時候就會報錯

public class Teacher {}
   @Test
    public void test1() throws Exception {
        ObjectMapper mapper = new ObjectMapper();
        Teacher teacher = new Teacher();
        System.out.println(mapper.writeValueAsString(teacher));
    }

程序運行將會報錯:

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class com.antiy.common.adai.entity.Teacher and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)

當我們進行設置: mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)

輸出為 {}

4.4 SerializationFeature.WRITE_DATES_AS_TIMESTAMPS

默認為true,該屬性的意思是,jackson默認會將Date類型的數據序列化成時間戳

詳情可以參考 2.5 @JsonFormat

4.5 DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES

默認為true,該屬性的意思是,在反序列的時候,如果json串中存在一些key,但是在POJO中沒有,那么程序將會拋出異常

    @Test
    public void test2() throws Exception {
        ObjectMapper mapper = new ObjectMapper();
        Student student = new Student("adai",21);
        String json = "{\"name\":\"adai\",\"age222\":21}"; //Student中沒有age222
        mapper.readValue(json,Student.class);
    }

程序將會報錯:UnrecognizedPropertyException: Unrecognized field "age222"

此時我們將FAIL_ON_UNKNOWN_PROPERTIES設置為false

    public void test2() throws Exception {
        ObjectMapper mapper = new ObjectMapper();
        mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
        Student student = new Student("adai",21);
        String json = "{\"name\":\"adai\",\"age222\":21}";
        System.out.println(mapper.readValue(json, Student.class));
    }

輸出為 Student(name=adai, age=null)

4.6 DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT

該值默認為false,該屬性的意思是,允許JSON空字符串值(“”)作為null綁定到POJO的屬性上,看代碼可能比較好理解一點。

public class Teacher {
    private Student student;
    // 省略 getter setter constructor
}
    @Test
    public void test2() throws Exception {
        ObjectMapper mapper = new ObjectMapper();
        String json = "{\"student\":\"\"}";
        System.out.println(mapper.readValue(json, Teacher.class));
    }

程序將會報錯,MismatchedInputException,因為json串中key值student對應的value為 ""

此時我們可以設置DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT為true

輸出為 Teacher(student=null)

"" 空串 被轉換成null值 封裝到Teacher對象的student屬性中

4.7 SerializationFeature.WRAP_ROOT_VALUE

默認為false,該屬性的意思是,將內容包裹為一個JSON屬性,屬性名由@JsonRootName注解指定。

詳情請見 2.4 @JsonTypeName和@JsonTypeInfo

5 踩坑心得

5.1 TypeReference

一定要導入正確的TypeReference

5.2 DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT

注意,該屬性只接受POJO的 “” 空字符串轉換成 null,在json中,String非常特殊。

請先看4.6章節的內容。

此時我將Teacher中的student類型,換成String

public class Teacher {
    private String student;
}
  @Test
    public void test2() throws Exception {
        ObjectMapper mapper = new ObjectMapper();
        String json = "{\"student\":\"\"}";
        System.out.println(mapper.readValue(json, Teacher.class));
    }

輸出為 Teacher(student=)

原來以為,如果是String屬性,那么""也會轉換成null,結果恰恰相反,只有POJO對象,“”才會轉換成null

參考 stackoverflow:https://stackoverflow.com/questions/22688713/jackson-objectmapper-deserializationconfig-feature-accept-empty-string-as-null-o

6 感悟

6.1 以Json的角度理解Map和List

在對象序列化和反序列化的過程中,自己對Map和List又有了新的理解。

Map可以當做是一個任意對象,保存字段屬性。

在 3.1中,如果jackson不知道反序列化的對象,那么jackson將會以LinkedHashMap來進行處理,這正是因為Map的 Key-Value 特性。

    @Test
    public void test2() throws Exception {
        ObjectMapper mapper = new ObjectMapper();
        Map<String, Object> map = new HashMap<>(2);
        map.put("name","adai");
        map.put("age",21);
        System.out.println("map序列化: " + mapper.writeValueAsString(map));
        Student student = new Student("adai",21);
        System.out.println("student序列化: " + mapper.writeValueAsString(student));
    }

輸出為 map序列化: {"name":"adai","age":21}
student序列化: {"name":"adai","age":21}

可以看到Map和Student序列化的結果都是一樣的,那么在反序列化的時候,可以用Student對象接受的數據,自然而然也可以用Map接收,這就是為什么在關於泛型反序列化的時候,如果jackson不知道具體的對象,全部都會用LinkHashMap接收

List就當做是一個數組


參考資料: https://blog.csdn.net/u011054333/article/details/80504154#commentBox https://www.ibm.com/developerworks/cn/java/jackson-advanced-application/index.html
作者: 一杯熱咖啡AAA
出處: https://www.cnblogs.com/AdaiCoffee/
本文以學習、研究和分享為主,歡迎轉載。如果文中有不妥或者錯誤的地方還望指出,以免誤人子弟。如果你有更好的想法和意見,可以留言討論,謝謝!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM