終極CRUD-3-用Jackson解析json
目錄
- 1 jackson json基本介紹和使用
- 2 jackson 常用的註解
- 2.1@JsonProperty
- 2.2 @JsonIgnore
- 2.3 @JsonIgnoreProperties
- 2.4 @JsonTypeName和@JsonTypeInfo
- 2.5 @JsonFormat
- 2.6 @JsonAnyGetter
- 2.7 @JsonAnySetter
- 3 jackson 處理泛型轉換
- 3.1 思考下面程式
- 3.1 JavaType
- 3.2 TypeReference
- 4 jackson 自定義序列化和反序列化規則
- 4.1 enable disable configure
- 4.2 SerializationFeature.INDENT_OUTPUT
- 4.3 SerializationFeature.FAIL_ON_EMPTY_BEANS
- 4.4 SerializationFeature.WRITE_DATES_AS_TIMESTAMPS
- 4.5 DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
- 4.6 DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT
- 4.7 SerializationFeature.WRAP_ROOT_VALUE
- 5 踩坑心得
- 5.1 TypeReference
- 5.2 DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT
- 6 感悟
- 6.1 以Json的角度理解Map和List
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
TypeReference
比javaType
模式更加方便,程式碼也更加簡潔
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可以通過SerializationFeature
和DeserializationFeature
來自定義,序列化和反序列化規則,這也是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://github.com/FasterXML/jackson/
https://blog.csdn.net/u011054333/article/details/80504154#commentBox
https://www.ibm.com/developerworks/cn/java/jackson-advanced-application/index.html
http://www.manongjc.com/article/114528.html
https://www.baeldung.com/jackson-object-mapper-tutorial
請尊重作者勞動成果,轉載請註明出處。以上內容若有侵權,請聯絡作者,立即刪