Java生態(tài)圈中有很多處理JSON和XML格式化的類庫哲银,Jackson是其中比較著名的一個扇售。雖然JDK自帶了XML處理類庫,但是相對來說比較低級,使用本文介紹的Jackson等高級類庫處理起來會方便很多寺滚。
引入類庫
由于Jackson相關(guān)類庫按照功能分為幾個相對獨立的秀又,所以需要同時引入多個類庫恋昼,為了方便我將版本號單獨提取出來設(shè)置谒兄,相關(guān)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', version: jacksonVersion
compile group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: jacksonVersion
// 引入XML功能
compile group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-xml', version: jacksonVersion
// 比JDK自帶XML實現(xiàn)更高效的類庫
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之間的關(guān)系。詳細文檔可以參考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
也是類注解,會排除所有指定類型的屬性客年。
序列化相關(guān)
@JsonPropertyOrder
和@JsonProperty
的index
屬性類似霞幅,指定屬性序列化時的順序。
@JsonRootName
注解用于指定JSON根屬性的名稱量瓜。
處理JSON
簡單映射
我們用Lombok設(shè)置一個簡單的Java類司恳。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Friend {
private String nickname;
private int age;
}
然后就可以處理JSON數(shù)據(jù)了。首先需要一個ObjectMapper對象绍傲,序列化和反序列化都需要它扔傅。
ObjectMapper mapper = new ObjectMapper();
Friend friend = new Friend("yitian", 25);
// 寫為字符串
String text = mapper.writeValueAsString(friend);
// 寫為文件
mapper.writeValue(new File("friend.json"), friend);
// 寫為字節(jié)流
byte[] bytes = mapper.writeValueAsBytes(friend);
System.out.println(text);
// 從字符串中讀取
Friend newFriend = mapper.readValue(text, Friend.class);
// 從字節(jié)流中讀取
newFriend = mapper.readValue(bytes, Friend.class);
// 從文件中讀取
newFriend = mapper.readValue(new File("friend.json"), Friend.class);
System.out.println(newFriend);
程序結(jié)果如下。可以看到生成的JSON屬性和Java類中定義的一致猎塞。
{"nickname":"yitian","age":25}
Friend(nickname=yitian, age=25)
集合的映射
除了使用Java類進行映射之外试读,我們還可以直接使用Map和List等Java集合組織JSON數(shù)據(jù),在需要的時候可以使用readTree方法直接讀取JSON中的某個屬性值邢享。需要注意的是從JSON轉(zhuǎn)換為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);
程序結(jié)果如下。
{"name":"yitian","interests":["pc games","music"],"age":25}
{name=yitian, interests=[pc games, music], age=25}
name:yitian age:25
Jackson配置
Jackson預(yù)定義了一些配置韩容,我們通過啟用和禁用某些屬性可以修改Jackson運行的某些行為款违。詳細文檔參考JacksonFeatures。下面我簡單翻譯一下Jackson README上列出的一些屬性群凶。
// 美化輸出
mapper.enable(SerializationFeature.INDENT_OUTPUT);
// 允許序列化空的POJO類
// (否則會拋出異常)
mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
// 把java.util.Date, Calendar輸出為數(shù)字(時間戳)
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
// 在遇到未知屬性的時候不拋出異常
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
// 強制JSON 空字符串("")轉(zhuǎn)換為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);
// 強制轉(zhuǎn)義非ASCII字符
mapper.configure(JsonGenerator.Feature.ESCAPE_NON_ASCII, true);
// 將內(nèi)容包裹為一個JSON屬性,屬性名由@JsonRootName注解指定
mapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
這里有三個方法请梢,configure方法接受配置名和要設(shè)置的值赠尾,Jackson 2.5版本新加的enable和disable方法則直接啟用和禁用相應(yīng)屬性,我推薦使用后面兩個方法毅弧。
用注解管理映射
前面介紹了一些Jackson注解气嫁,下面來應(yīng)用一下這些注解。首先來看看使用了注解的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;
}
然后看看代碼寸宵。需要注意的是,由于設(shè)置了排除的屬性元咙,所以生成的JSON和Java類并不是完全對應(yīng)關(guān)系梯影,所以禁用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, "", 0, "");
String text = mapper.writeValueAsString(fd);
System.out.println(text);
FriendDetail fd2 = mapper.readValue(text, FriendDetail.class);
System.out.println(fd2);
運行結(jié)果如下庶香〖坠鳎可以看到生成JSON的時候忽略了我們制定的值,而且在轉(zhuǎn)換為Java類的時候?qū)?yīng)的屬性為空赶掖。
{"NickName":"yitian","Age":25}
FriendDetail(name=yitian, age=25, uselessProp1=null, uselessProp2=0, uselessProp3=null)
然后取消注釋代碼中的那行感猛,也就是啟用WRAP_ROOT_VALUE
功能,再運行一下程序倘零,運行結(jié)果如下唱遭。可以看到生成的JSON結(jié)果發(fā)生了變化呈驶,而且由于JSON結(jié)果變化拷泽,所以Java類轉(zhuǎn)換失敗(所有字段值全為空)。WRAP_ROOT_VALUE
這個功能在有些時候比較有用司致,因為有些JSON文件需要這種結(jié)構(gòu)拆吆。
{"FriendDetail":{"NickName":"yitian","Age":25}}
FriendDetail(name=null, age=0, uselessProp1=null, uselessProp2=0, uselessProp3=null)
Java8日期時間類支持
Java8增加了一套全新的日期時間類,Jackson對此也有支持脂矫。這些支持是以Jackson模塊形式提供的枣耀,所以首先就是注冊這些模塊。
ObjectMapper mapper = new ObjectMapper()
.registerModule(new JavaTimeModule())
.registerModule(new ParameterNamesModule())
.registerModule(new Jdk8Module());
導(dǎo)入類庫之后庭再,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", "易天", 25, "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);
}
運行結(jié)果如下『薮辏可以看到院促,生成的JSON日期變成了[1994,1,1]這樣的時間戳形式,一般情況下不符合我們的要求斧抱。
{"birthday":[1994,1,1],"Name":"yitian","NickName":"易天","Age":25,"IdentityCode":"10000"}
Person(name=yitian, nickname=易天, age=25, identityCode=10000, birthday=1994-01-01)
取消注釋那行代碼常拓,程序運行結(jié)果如下。這樣一來就變成了我們一般使用的形式了辉浦。如果有格式需要的話弄抬,可以使用@JsonFormat(pattern = "yyyy-MM-DD")
注解格式化日期顯示。
{"birthday":"1994-01-01","Name":"yitian","NickName":"易天","Age":25,"IdentityCode":"10000"}
Person(name=yitian, nickname=易天, age=25, identityCode=10000, birthday=1994-01-01)
處理XML
Jackson是一個處理JSON的類庫盏浙,不過它也通過jackson-dataformat-xml
包提供了處理XML的功能眉睹。Jackson建議我們在處理XML的時候使用woodstox-core
包,它是一個XML的實現(xiàn)废膘,比JDK自帶XML實現(xiàn)更加高效竹海,也更加安全。
這里有個注意事項丐黄,如果你正在使用Java 9以上的JDK斋配,可能會出現(xiàn)java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException
異常,這是因為Java 9實現(xiàn)了JDK的模塊化灌闺,將原本和JDK打包在一起的JAXB實現(xiàn)分隔出來艰争。所以這時候需要我們手動添加JAXB的實現(xiàn)。在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的屬性(<a b="xxx"></a>)還是作為子標簽(<a><b></b></a>).
@JacksonXmlRootElement
注解有兩個屬性,namespace和localname屬性用于指定XML根元素命名空間的名稱机错。
@JacksonXmlText
注解將屬性直接作為未被標簽包裹的普通文本表現(xiàn)爬范。
@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", "易天", 25, "10000", LocalDate.of(1994, 1, 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);
運行結(jié)果如下斥难。
<Person>
<Name>yitian</Name>
<NickName>易天</NickName>
<Age>25</Age>
<IdentityCode><![CDATA[10000]]></IdentityCode>
<Birthday>1994/01/01</Birthday>
</Person>
Person(name=yitian, nickname=易天, age=25, identityCode=10000, birthday=1994-01-01)
如果取消那兩行注釋,那么運行結(jié)果如下财搁≌赫ǎ可以看到Jackson XML注解對生成的XML的控制效果。
<Person birthday="1994/01/01">
<Name>yitian</Name>易天
<Age>25</Age>
<IdentityCode><![CDATA[10000]]></IdentityCode>
</Person>
Person(name=yitian, nickname=null, age=25, identityCode=10000, birthday=1994-01-01)
Spring Boot集成
自動配置
Spring Boot對Jackson的支持非常完善尖奔,只要我們引入相應(yīng)類庫,Spring Boot就可以自動配置開箱即用的Bean穷当。Spring自動配置的ObjectMapper(或者XmlMapper)作了如下配置提茁,基本上可以適應(yīng)大部分情況。
- 禁用了MapperFeature.DEFAULT_VIEW_INCLUSION
- 禁用了DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
- 禁用了SerializationFeature.WRITE_DATES_AS_TIMESTAMPS
如果需要修改自動配置的ObjectMapper屬性也非常簡單馁菜,Spring Boot提供了一組環(huán)境變量茴扁,直接在application.properties文件中修改即可。
Jackson枚舉 | Spring環(huán)境變量 |
---|---|
com.fasterxml.jackson.databind.DeserializationFeature | spring.jackson.deserialization.<feature_name>=true|false |
com.fasterxml.jackson.core.JsonGenerator.Feature | spring.jackson.generator.<feature_name>=true|false |
com.fasterxml.jackson.databind.MapperFeature | spring.jackson.mapper.<feature_name>=true|false |
com.fasterxml.jackson.core.JsonParser.Feature | spring.jackson.parser.<feature_name>=true|false |
com.fasterxml.jackson.databind.SerializationFeature | spring.jackson.serialization.<feature_name>=true|false |
com.fasterxml.jackson.annotation.JsonInclude.Include | spring.jackson.default-property-inclusion=always|non_null|non_absent|non_default|non_empty |
由于Spring會同時配置相應(yīng)的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類庫盏道,然后編寫業(yè)務(wù)代碼就好了稍浆。關(guān)于如何配置Jackson類庫,我們完全不需要管猜嘱,這就是Spring Boot的方便之處衅枫。
@Controller
public class MainController {
private Person person = new Person("yitian", 10000, LocalDate.of(1994, 1, 1));
@RequestMapping("/")
public String index() {
return "index";
}
@RequestMapping(value = "/json", produces = "application/json")
@ResponseBody
public Person json() {
return person;
}
}
進入localhost:8080/xml就可以看到對應(yīng)結(jié)果了。
手動配置
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;
}
}
然后在需要的地方進行依賴注入益楼。需要注意為了區(qū)分ObjectMapper和XmlMapper猾漫,需要使用@Qualifier
注解進行標記。
@Controller
public class MainController {
private ObjectMapper jsonMapper;
private XmlMapper xmlMapper;
private Person person = new Person("yitian", 10000, LocalDate.of(1994, 1, 1));
public MainController(@Autowired @Qualifier("json") ObjectMapper jsonMapper, @Autowired @Qualifier("xml") XmlMapper xmlMapper) {
this.jsonMapper = jsonMapper;
this.xmlMapper = xmlMapper;
}
以上就是Jackson類庫的一些介紹偏形,希望對大家有所幫助静袖。項目代碼在我的Github,感興趣的同學可以看看俊扭。