Java中Jackson快速入門
Java生態圈中有很多處理JSON和XML格式化的類庫,Jackson是其中比較著名的一個。雖然JDK自帶了XML處理類庫,但是相對來說比較低階,使用本文介紹的Jackson等高階類庫處理起來會方便很多。
引入類庫
由於Jackson相關類庫按照功能分為幾個相對獨立的,所以需要同時引入多個類庫,為了方便我將版本號單獨提取出來設定,相關Gradle配置如下。
ext { jacksonVersion = '2.9.5' } dependencies { compile group: 'com.fasterxml.jackson.core',name: 'jackson-core',version: jacksonVersion compile group: 'com.fasterxml.jackson.core',name: 'jackson-databind',name: 'jackson-annotations',version: jacksonVersion // 引入XML功能 compile group: 'com.fasterxml.jackson.dataformat',name: 'jackson-dataformat-xml',version: jacksonVersion // 比JDK自帶XML實現更高效的類庫 compile group: 'com.fasterxml.woodstox',name: 'woodstox-core',version: '5.1.0' // Java 8 新功能 compile group: 'com.fasterxml.jackson.datatype',name: 'jackson-datatype-jsr310',version: jacksonVersion compile group: 'com.fasterxml.jackson.module',name: 'jackson-module-parameter-names',version: jacksonVersion compile group: 'com.fasterxml.jackson.datatype',name: 'jackson-datatype-jdk8',version: jacksonVersion compileOnly group: 'org.projectlombok',name: 'lombok',version: '1.16.22' }
Maven配置請去mvnrepository搜尋。
Jackson註解
Jackson類庫包含了很多註解,可以讓我們快速建立Java類與JSON之間的關係。詳細文件可以參考Jackson-Annotations。下面介紹一下常用的。
屬性命名
@JsonProperty註解指定一個屬性用於JSON對映,預設情況下對映的JSON屬性與註解的屬性名稱相同,不過可以使用該註解的value值修改JSON屬性名,該註解還有一個index屬性指定生成JSON屬性的順序,如果有必要的話。
屬性包含
還有一些註解可以管理在對映JSON的時候包含或排除某些屬性,下面介紹一下常用的幾個。
@JsonIgnore註解用於排除某個屬性,這樣該屬性就不會被Jackson序列化和反序列化。
@JsonIgnoreProperties註解是類註解。在序列化為JSON的時候,@JsonIgnoreProperties({"prop1","prop2"})會忽略pro1和pro2兩個屬性。在從JSON反序列化為Java類的時候,@JsonIgnoreProperties(ignoreUnknown=true)會忽略所有沒有Getter和Setter的屬性。該註解在Java類和JSON不完全匹配的時候很有用。
@JsonIgnoreType也是類註解,會排除所有指定型別的屬性。
序列化相關
@JsonPropertyOrder和@JsonProperty的index屬性類似,指定屬性序列化時的順序。
@JsonRootName註解用於指定JSON根屬性的名稱。
處理JSON
簡單對映
我們用Lombok設定一個簡單的Java類。
@Data @AllArgsConstructor @NoArgsConstructor public class Friend { private String nickname; private int age; }
然後就可以處理JSON資料了。首先需要一個ObjectMapper物件,序列化和反序列化都需要它。
ObjectMapper mapper = new ObjectMapper(); Friend friend = new Friend("yitian",25); // 寫為字串 String text = mapper.writeValueAsString(friend); // 寫為檔案 mapper.writeValue(new File("friend.json"),friend); // 寫為位元組流 byte[] bytes = mapper.writeValueAsBytes(friend); System.out.println(text); // 從字串中讀取 Friend newFriend = mapper.readValue(text,Friend.class); // 從位元組流中讀取 newFriend = mapper.readValue(bytes,Friend.class); // 從檔案中讀取 newFriend = mapper.readValue(new File("friend.json"),Friend.class); System.out.println(newFriend);
程式結果如下。可以看到生成的JSON屬性和Java類中定義的一致。
{"nickname":"yitian","age":25} Friend(nickname=yitian,age=25)
集合的對映
除了使用Java類進行對映之外,我們還可以直接使用Map和List等Java集合組織JSON資料,在需要的時候可以使用readTree方法直接讀取JSON中的某個屬性值。需要注意的是從JSON轉換為Map物件的時候,由於Java的型別擦除,所以型別需要我們手動用new TypeReference<T>給出。
ObjectMapper mapper = new ObjectMapper(); Map<String,Object> map = new HashMap<>(); map.put("age",25); map.put("name","yitian"); map.put("interests",new String[]{"pc games","music"}); String text = mapper.writeValueAsString(map); System.out.println(text); Map<String,Object> map2 = mapper.readValue(text,new TypeReference<Map<String,Object>>() { }); System.out.println(map2); JsonNode root = mapper.readTree(text); String name = root.get("name").asText(); int age = root.get("age").asInt(); System.out.println("name:" + name + " age:" + age);
程式結果如下。
{"name":"yitian","interests":["pc games","music"],"age":25}
{name=yitian,interests=[pc games,music],age=25}
name:yitian age:25
Jackson配置
Jackson預定義了一些配置,我們通過啟用和禁用某些屬性可以修改Jackson執行的某些行為。詳細文件參考JacksonFeatures。下面我簡單翻譯一下Jackson README上列出的一些屬性。
// 美化輸出 mapper.enable(SerializationFeature.INDENT_OUTPUT); // 允許序列化空的POJO類 // (否則會丟擲異常) mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); // 把java.util.Date,Calendar輸出為數字(時間戳) mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); // 在遇到未知屬性的時候不丟擲異常 mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); // 強制JSON 空字串("")轉換為null物件值: mapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT); // 在JSON中允許C/C++ 樣式的註釋(非標準,預設禁用) mapper.configure(JsonParser.Feature.ALLOW_COMMENTS,true); // 允許沒有引號的欄位名(非標準) mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES,true); // 允許單引號(非標準) mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES,true); // 強制轉義非ASCII字元 mapper.configure(JsonGenerator.Feature.ESCAPE_NON_ASCII,true); // 將內容包裹為一個JSON屬性,屬性名由@JsonRootName註解指定 mapper.configure(SerializationFeature.WRAP_ROOT_VALUE,true);
這裡有三個方法,configure方法接受配置名和要設定的值,Jackson 2.5版本新加的enable和disable方法則直接啟用和禁用相應屬性,我推薦使用後面兩個方法。
用註解管理對映
前面介紹了一些Jackson註解,下面來應用一下這些註解。首先來看看使用了註解的Java類。
@Data @NoArgsConstructor @AllArgsConstructor @JsonRootName("FriendDetail") @JsonIgnoreProperties({"uselessProp1","uselessProp3"}) public class FriendDetail { @JsonProperty("NickName") private String name; @JsonProperty("Age") private int age; private String uselessProp1; @JsonIgnore private int uselessProp2; private String uselessProp3; }
然後看看程式碼。需要注意的是,由於設定了排除的屬性,所以生成的JSON和Java類並不是完全對應關係,所以禁用DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES是必要的。
ObjectMapper mapper = new ObjectMapper(); //mapper.enable(SerializationFeature.WRAP_ROOT_VALUE); mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); FriendDetail fd = new FriendDetail("yitian",25,"",""); String text = mapper.writeValueAsString(fd); System.out.println(text); FriendDetail fd2 = mapper.readValue(text,FriendDetail.class); System.out.println(fd2);
執行結果如下。可以看到生成JSON的時候忽略了我們制定的值,而且在轉換為Java類的時候對應的屬性為空。
{"NickName":"yitian","Age":25}
FriendDetail(name=yitian,age=25,uselessProp1=null,uselessProp2=0,uselessProp3=null)
然後取消註釋程式碼中的那行,也就是啟用WRAP_ROOT_VALUE功能,再執行一下程式,執行結果如下。可以看到生成的JSON結果發生了變化,而且由於JSON結果變化,所以Java類轉換失敗(所有欄位值全為空)。WRAP_ROOT_VALUE這個功能在有些時候比較有用,因為有些JSON檔案需要這種結構。
{"FriendDetail":{"NickName":"yitian","Age":25}}
FriendDetail(name=null,age=0,uselessProp3=null)
Java8日期時間類支援
Java8增加了一套全新的日期時間類,Jackson對此也有支援。這些支援是以Jackson模組形式提供的,所以首先就是註冊這些模組。
ObjectMapper mapper = new ObjectMapper() .registerModule(new JavaTimeModule()) .registerModule(new ParameterNamesModule()) .registerModule(new Jdk8Module());
匯入類庫之後,Jackson也可以自動搜尋所有模組,不需要我們手動註冊。
mapper.findAndRegisterModules();
我們新建一個帶有LocalDate欄位的Java類。
@Data @NoArgsConstructor @AllArgsConstructor @JsonRootName("Person") public class Person { @JsonProperty("Name") private String name; @JsonProperty("NickName") private String nickname; @JsonProperty("Age") private int age; @JsonProperty("IdentityCode") private String identityCode; @JsonProperty @JsonFormat(pattern = "yyyy-MM-DD") private LocalDate birthday; }
然後來看看程式碼。
static void java8DateTime() throws IOException { Person p1 = new Person("yitian","易天","10000",LocalDate.of(1994,1,1)); ObjectMapper mapper = new ObjectMapper() .registerModule(new JavaTimeModule()); //mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); String text = mapper.writeValueAsString(p1); System.out.println(text); Person p2 = mapper.readValue(text,Person.class); System.out.println(p2); }
執行結果如下。可以看到,生成的JSON日期變成了[1994,1]這樣的時間戳形式,一般情況下不符合我們的要求。
{"birthday":[1994,1],"Name":"yitian","NickName":"易天","Age":25,"IdentityCode":"10000"}
Person(name=yitian,nickname=易天,identityCode=10000,birthday=1994-01-01)
取消註釋那行程式碼,程式執行結果如下。這樣一來就變成了我們一般使用的形式了。如果有格式需要的話,可以使用@JsonFormat(pattern = "yyyy-MM-DD")註解格式化日期顯示。
{"birthday":"1994-01-01",birthday=1994-01-01)
處理XML
Jackson是一個處理JSON的類庫,不過它也通過jackson-dataformat-xml包提供了處理XML的功能。Jackson建議我們在處理XML的時候使用woodstox-core包,它是一個XML的實現,比JDK自帶XML實現更加高效,也更加安全。
這裡有個注意事項,如果你正在使用Java 9以上的JDK,可能會出現java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException異常,這是因為Java 9實現了JDK的模組化,將原本和JDK打包在一起的JAXB實現分隔出來。所以這時候需要我們手動新增JAXB的實現。在Gradle中新增下面的程式碼即可。
compile group: 'javax.xml.bind',name: 'jaxb-api',version: '2.3.0'
註解
Jackson XML除了使用Jackson JSON和JDK JAXB的一些註解之外,自己也定義了一些註解。下面簡單介紹一下幾個常用註解。
@JacksonXmlProperty註解有三個屬性,namespace和localname屬性用於指定XML名稱空間的名稱,isAttribute指定該屬性作為XML的屬性()還是作為子標籤().
@JacksonXmlRootElement註解有兩個屬性,namespace和localname屬性用於指定XML根元素名稱空間的名稱。
@JacksonXmlText註解將屬性直接作為未被標籤包裹的普通文字表現。
@JacksonXmlCData將屬性包裹在CDATA標籤中。
XML對映
新建如下一個Java類。
@Data @NoArgsConstructor @AllArgsConstructor @JsonRootName("Person") public class Person { @JsonProperty("Name") private String name; @JsonProperty("NickName") //@JacksonXmlText private String nickname; @JsonProperty("Age") private int age; @JsonProperty("IdentityCode") @JacksonXmlCData private String identityCode; @JsonProperty("Birthday") //@JacksonXmlProperty(isAttribute = true) @JsonFormat(pattern = "yyyy/MM/DD") private LocalDate birthday; }
下面是程式碼示例,基本上和JSON的API非常相似,XmlMapper實際上就是ObjectMapper的子類。
Person p1 = new Person("yitian",1)); XmlMapper mapper = new XmlMapper(); mapper.findAndRegisterModules(); mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); mapper.enable(SerializationFeature.INDENT_OUTPUT); String text = mapper.writeValueAsString(p1); System.out.println(text); Person p2 = mapper.readValue(text,Person.class); System.out.println(p2);
執行結果如下。
<Person> <Name>yitian</Name> <NickName>易天</NickName> <Age>25</Age> <IdentityCode><![CDATA[10000]]></IdentityCode> <Birthday>1994/01/01</Birthday> </Person> Person(name=yitian,birthday=1994-01-01)
如果取消那兩行註釋,那麼執行結果如下。可以看到Jackson XML註解對生成的XML的控制效果。
<Person birthday="1994/01/01"> <Name>yitian</Name>易天 <Age>25</Age> <IdentityCode><![CDATA[10000]]></IdentityCode> </Person> Person(name=yitian,nickname=null,birthday=1994-01-01)
Spring Boot整合
自動配置
Spring Boot對Jackson的支援非常完善,只要我們引入相應類庫,Spring Boot就可以自動配置開箱即用的Bean。Spring自動配置的ObjectMapper(或者XmlMapper)作了如下配置,基本上可以適應大部分情況。
- 禁用了MapperFeature.DEFAULT_VIEW_INCLUSION
- 禁用了DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
- 禁用了SerializationFeature.WRITE_DATES_AS_TIMESTAMPS
如果需要修改自動配置的ObjectMapper屬性也非常簡單,Spring Boot提供了一組環境變數,直接在application.properties檔案中修改即可。
|Jackson列舉|Spring環境變數| |—–|—–| com.fasterxml.jackson.databind.DeserializationFeature|spring.jackson.deserialization.=true|false com.fasterxml.jackson.core.JsonGenerator.Feature|spring.jackson.generator.=true|false com.fasterxml.jackson.databind.MapperFeature|spring.jackson.mapper.=true|false com.fasterxml.jackson.core.JsonParser.Feature|spring.jackson.parser.=true|false com.fasterxml.jackson.databind.SerializationFeature|spring.jackson.serialization.=true|false com.fasterxml.jackson.annotation.JsonInclude.Include|spring.jackson.default-property-inclusion=always|non_null|non_absent|non_default|non_empty
由於Spring會同時配置相應的HttpMessageConverters,所以我們其實要做的很簡單,用Jackson註解標註好要對映的Java類,然後直接讓控制器返回物件即可!下面是一個Java類。
@JsonRootName("person") public class Person { @JsonProperty private String name; @JsonProperty private int id; @JsonFormat(pattern = "yyyy-MM-DD") private LocalDate birthday; public Person(String name,int id,LocalDate birthday) { this.name = name; this.id = id; this.birthday = birthday; } }
然後是控制器程式碼。在整個過程中我們只需要引入Jackson類庫,然後編寫業務程式碼就好了。關於如何配置Jackson類庫,我們完全不需要管,這就是Spring Boot的方便之處。
@Controller public class MainController { private Person person = new Person("yitian",10000,1)); @RequestMapping("/") public String index() { return "index"; } @RequestMapping(value = "/json",produces = "application/json") @ResponseBody public Person json() { return person; } }
進入localhost:8080/xml就可以看到對應結果了。
手動配置
Spring Boot自動配置非常方便,但不是萬能的。在必要的時候,我們需要手動配置Bean來替代自動配置的Bean。
@Configuration public class JacksonConfig { @Bean @Primary @Qualifier("xml") public XmlMapper xmlMapper(Jackson2ObjectMapperBuilder builder) { XmlMapper mapper = builder.createXmlMapper(true) .build(); mapper.enable(SerializationFeature.INDENT_OUTPUT); mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); return mapper; } @Bean @Qualifier("json") public ObjectMapper jsonMapper(Jackson2ObjectMapperBuilder builder) { ObjectMapper mapper = builder.createXmlMapper(false) .build(); mapper.enable(SerializationFeature.INDENT_OUTPUT); mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); return mapper; } }
然後在需要的地方進行依賴注入。需要注意為了區分ObjectMapper和XmlMapper,需要使用@Qualifier註解進行標記。
@Controller public class MainController { private ObjectMapper jsonMapper; private XmlMapper xmlMapper; private Person person = new Person("yitian",1)); public MainController(@Autowired @Qualifier("json") ObjectMapper jsonMapper,@Autowired @Qualifier("xml") XmlMapper xmlMapper) { this.jsonMapper = jsonMapper; this.xmlMapper = xmlMapper; }
以上就是Jackson類庫的一些介紹,希望對大家有所幫助。專案程式碼在我的Github,感興趣的同學可以看看。
到此這篇關於Java中Jackson快速入門的文章就介紹到這了,更多相關Jackson快速入門內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!