在上篇《經過多方調研,最侄訓是決定禁用FastJson!》中,講了FastJson的基本使用以及存在的不確定性問題,所以最終決定在專案中放棄使用,進而選擇市面上比較主流,Spring Boot默認系結的JSON類別庫:Jackson,
本文就來講解一下Jackson的基本使用以及與Spring Boot的結合與實踐,
什么是Jackson
Jackson是比較主流的基于Java的JSON類別庫,可用于Json和XML與JavaBean之間的序列化和反序列化,
沒看錯,Jackson也可以處理JavaBean與XML之間的轉換,基于jackson-dataformat-xml組件,而且比較JDK自帶XML實作更加高效和安全,而我們使用比較多的是處理JSON與JavaBean之間的功能,
Jackson主流到什么程度?單從Maven倉庫中的統計來看,Jackson的使用量排位第一,而Spring Boot支持的三個JSON庫(Gson、Jackson、JSON-B)中,Jackson是首選默認庫,
Jackson也有以下特點:依賴少,簡單易用,決議大Json速度快、記憶體占用比較低、擁有靈活的API、方便擴展與定制,
Jackson類別庫GitHub地址:https://github.com/FasterXML/jackson ,
Jackson的組成部分
Jackson的核心模塊由三部分組成(從Jackson 2.x開始):jackson-core、jackson-annotations、jackson-databind,
- jackson-core:核心包,定義了低級流(Streaming)API,提供基于"流模式"決議,Jackson內部實作正是通過高性能的流模式API的JsonGenerator和JsonParser來生成和決議json,
- jackson-annotations,注解(Annotations)包,提供標準的Jackson注解功能;
- jackson-databind:資料系結(Databind)包,實作了資料系結(和物件序列化)支持,它依賴于Streaming和Annotations包,提供基于“物件系結”決議的API(ObjectMapper)和"樹模型"決議的API(JsonNode);基于"物件系結"決議的API和"樹模型"決議的API依賴基于“流模式”決議的API,
下面看一下不同環境下相關組件的依賴引入情況,
在SpringBoot當中,spring-boot-starter-web間接引入了Jackson組件,也就是如果你使用了SpringBoot框架,那么你的專案中已經有了Jackson依賴,下面的依賴省略了version和scope項,
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
web starter中依賴了json starter:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
</dependency>
json starter最終引入了Jackson:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-parameter-names</artifactId>
</dependency>
上面已經提到過,jackson-databind依賴于Streaming和Annotations包,因此,引入jackson-databind相當于引入了jackson-core和jackson-annotations,
通常情況下,我們單獨使用時,根據需要通過Maven引入jackson-databind、jackson-core和jackson-annotations即可,
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
對于SpringBoot專案,基本上不用再額外添加依賴,
Jackson核心類ObjectMapper
Jackson提供了三種JSON的處理方式,分別是:資料系結、JSON樹模型、流式API,其中前兩項功能都是基于ObjectMapper來實作的,而流式API功能則需要基于更底層的JsonGenerator和JsonParser來實作,
通常情況下我們使用ObjectMapper類就足夠了,它擁有以下功能:
- 從字串、流或檔案中決議JSON,并創建表示已決議的JSON的Java物件(反序列化),
- 將Java物件構建成JSON字串(序列化),
- 將JSON決議為自定義類的物件,也可以決議JSON樹模型的物件;
ObjectMapper基于JsonParser和JsonGenerator來實作JSON實際的讀/寫,這一點看一下ObjectMapper的構造方法即可明白,
具體實體
Jackson的常見使用,就不逐一講解了,通過一些列的實體給大家展示一下,每個實體當中都會通過注釋進行說明,
常見簡單使用
下面的示例是我們經常會用到的用法演示,主要涉及到JavaBean和Json字串之間的轉換,
Jackson在將json轉換為JavaBean屬性時,默認是通過Json欄位的名稱與Java物件中的getter和setter方法進行匹配進行系結,
Jackson取getter和setter方法名稱中去除“get”和“set”部分,并將首字母小寫,例如Json中的name,與JavaBean中的getName()和setName()進行匹配,
但并不是所有的屬性都可以被序列化和反序列化,基本上遵循一下規則:
- public修飾的屬性可序列化和反序列化,
- 屬性提供public的getter/setter方法,該屬性可序列化和反序列化,
- 屬性只有public的setter方法,而無public的getter方法,該屬性只能用于反序列化,
@Slf4j
public class JacksonTest {
/**
* JavaBean轉JSON字串
*/
@Test
public void testJavaBeanToJson() {
WeChat weChat = new WeChat();
weChat.setId("zhuan2quan");
weChat.setName("程式新視界");
weChat.setInterest(new String[]{"Java", "Spring Boot", "JVM"});
ObjectMapper mapper = new ObjectMapper();
try {
String result = mapper.writeValueAsString(weChat);
System.out.println(result);
} catch (JsonProcessingException e) {
log.error("轉換例外", e);
}
}
/**
* JSON字串轉JavaBean
*/
@Test
public void testJsonToJavaBean() {
String json = "{\"id\":\"zhuan2quan\",\"name\":\"程式新視界\",\"interest\":[\"Java\",\"Spring Boot\",\"JVM\"]}";
ObjectMapper mapper = new ObjectMapper();
try {
WeChat weChat = mapper.readValue(json, WeChat.class);
System.out.println(weChat);
} catch (JsonProcessingException e) {
log.error("決議例外", e);
}
}
/**
* JSON字串轉Map集合
*/
@Test
public void testJsonToMap() {
String json = "{\"id\":\"zhuan2quan\",\"name\":\"程式新視界\",\"interest\":[\"Java\",\"Spring Boot\",\"JVM\"]}";
ObjectMapper mapper = new ObjectMapper();
try {
// 對泛型的反序列化,使用TypeReference可以明確的指定反序列化的型別,
Map<String, Object> map = mapper.readValue(json, new TypeReference<Map<String, Object>>() {
});
System.out.println(map);
} catch (JsonProcessingException e) {
log.error("決議例外", e);
}
}
/**
* JavaBean轉檔案
*/
@Test
public void testJavaBeanToFile() {
WeChat weChat = new WeChat();
weChat.setId("zhuan2quan");
weChat.setName("程式新視界");
weChat.setInterest(new String[]{"Java", "Spring Boot", "JVM"});
ObjectMapper mapper = new ObjectMapper();
try {
//寫到檔案
mapper.writeValue(new File("/json.txt"), weChat);
//從檔案中讀取
WeChat weChat1 = mapper.readValue(new File("/json.txt"), WeChat.class);
System.out.println(weChat1);
} catch (IOException e) {
log.error("轉換例外", e);
}
}
/**
* JavaBean轉位元組流
*/
@Test
public void testJavaBeanToBytes() {
WeChat weChat = new WeChat();
weChat.setId("zhuan2quan");
weChat.setName("程式新視界");
weChat.setInterest(new String[]{"Java", "Spring Boot", "JVM"});
ObjectMapper mapper = new ObjectMapper();
try {
// 寫為位元組流
byte[] bytes = mapper.writeValueAsBytes(weChat);
// 從位元組流讀取
WeChat weChat1 = mapper.readValue(bytes, WeChat.class);
System.out.println(weChat1);
} catch (IOException e) {
log.error("轉換例外", e);
}
}
}
上述代碼用到了lombok注解和單元測驗注解,根據需要可進行替換,
JSON樹模型
如果Json字串比較大,則可使用JSON樹模型來靈活的獲取所需的欄位內容,在Jackson中提供了get、path、has等方法來獲取或判斷,
下面直接看兩個示例:
@Slf4j
public class JacksonNodeTest {
/**
* JavaBean轉JSON字串
*/
@Test
public void testJsonNode() {
// 構建JSON樹
ObjectMapper mapper = new ObjectMapper();
ObjectNode root = mapper.createObjectNode();
root.put("id", "zhuan2quan");
root.put("name", "程式新視界");
ArrayNode interest = root.putArray("interest");
interest.add("Java");
interest.add("Spring Boot");
interest.add("JVM");
// JSON樹轉JSON字串
String json = null;
try {
json = mapper.writeValueAsString(root);
} catch (JsonProcessingException e) {
log.error("Json Node轉換例外", e);
}
System.out.println(json);
}
/**
* 決議JSON字串為JSON樹模型
*/
@Test
public void testJsonToJsonNode() {
String json = "{\"id\":\"zhuan2quan\",\"name\":\"程式新視界\",\"interest\":[\"Java\",\"Spring Boot\",\"JVM\"]}";
ObjectMapper mapper = new ObjectMapper();
try {
// 將JSON字串轉為JSON樹
JsonNode jsonNode = mapper.readTree(json);
String name = jsonNode.path("name").asText();
System.out.println(name);
JsonNode interestNode = jsonNode.get("interest");
if (interestNode.isArray()){
for (JsonNode node : interestNode){
System.out.println(node.asText());
}
}
} catch (JsonProcessingException e) {
log.error("Json Node轉換例外", e);
}
}
}
其中get方法和path功能相似,區別在于如果要讀取的key在Json串中不存在時,get方法會null,而path會回傳MissingNode實體物件,在鏈路方法情況下保證不會拋出例外,
流式API
除了上述兩種形式,還可以基于底層的流式API來進行操作,主要通過JsonGenerator和JsonParser兩個API,但操作起來比較復雜,就不再這里演示了,
格式化統一配置
在使用ObjectMapper時,會存在一些欄位在某些情況下不需要進行序列化或反序列化,同時還可能需要指定格式化的一些資訊等,此時,可以通過ObjectMapper進行配置,
//反序列化時忽略json中存在但Java物件不存在的屬性
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
//序列化時日期格式默認為yyyy-MM-dd'T'HH:mm:ss.SSSZ
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
//序列化時自定義時間日期格式
mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
//序列化時忽略值為null的屬性
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
//序列化時忽略值為默認值的屬性
mapper.setDefaultPropertyInclusion(JsonInclude.Include.NON_DEFAULT);
針對于配置項,在2.2版本中新增了一個ObjectMapper的實作類JsonMapper,功能與ObjectMapper一致,不過新增了一個builder方法,可以直接通過JsonMapper.builder().configure()方法來進行配置,最后獲得一個JsonMapper物件,JsonMapper的其他方法基本都集成自ObjectMapper,
注解的使用
上面通過統一配置可對全域格式的序列化和反序列化進行配置,但某些個別的場景下,需要針對具體的欄位進行配置,這就需要用注解,比如當Json字串中的欄位與Java物件中的屬性不一致時,就需要通過注解來建立它們直接的關系,
@JsonProperty,作用JavaBean欄位上,指定一個欄位用于JSON映射,默認情況下映射的JSON欄位與注解的欄位名稱相同,可通過value屬性指定映射的JSON的欄位名稱,
@JsonIgnore可用于欄位、getter/setter、建構式引數上,指定欄位不參與序列化和反序列化,
@JsonIgnoreProperties作用于類上,序列化時@JsonIgnoreProperties({“prop1”, “prop2”})會忽略pro1和pro2兩個屬性,反序列化時@JsonIgnoreProperties(ignoreUnknown=true)會忽略類中不存在的欄位,
@JsonFormat作用于欄位上,通常用來進行格式化操作,
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date date;
如果JavaBean中的時間欄位使用的是JDK8新增的時間日期(LocalDate/LocalTime/LocalDateTime)型別的話,需要添加jackson-datatype-jsr310依賴,在講依賴部分時,SpringBoot默認引入的依賴中就有這個,
當然,還有一些其他的注解,比如@JsonPropertyOrder、@JsonRootName、@JsonAnySetter、@JsonAnyGetter、@JsonNaming等,當使用時參考對應的檔案和示例看一下就可以,這里就不再一一列出了,
自定義決議器
如果上面的注解和統一配置還無法滿足需求,可自定義決議器,示例如下:
public class MyFastjsonDeserialize extends JsonDeserializer<Point> {
@Override
public Point deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
JsonNode node = jsonParser.getCodec().readTree(jsonParser);
Iterator<JsonNode> iterator = node.get("coordinates").elements();
List<Double> list = new ArrayList<>();
while (iterator.hasNext()) {
list.add(iterator.next().asDouble());
}
return new Point(list.get(0), list.get(1));
}
}
定義完成之后,注冊到Mapper中:
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(Point.class, new MyFastjsonDeserialize());
objectMapper.registerModule(module);
Jackson處理XML
Jackson也可以通過jackson-dataformat-xml包提供了處理XML的功能,在處理XML時建議使用woodstox-core包,它是一個XML的實作,比JDK自帶XML實作更加高效,也更加安全,
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
如果使用Java 9及以上版本,可能會出現java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException例外,這是因為Java 9實作了JDK的模塊化,將原本和JDK打包在一起的JAXB實作分隔出來,所以需要手動添加JAXB的實作,
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
</dependency>
下面是代碼示例,基本上和JSON的API非常相似,XmlMapper實際上就是ObjectMapper的子類,
@Test
public void testXml(){
WeChat weChat = new WeChat();
weChat.setId("zhuan2quan");
weChat.setName("程式新視界");
weChat.setInterest(new String[]{"Java", "Spring Boot", "JVM"});
XmlMapper xmlMapper = new XmlMapper();
try {
String xml = xmlMapper.writeValueAsString(weChat);
System.out.println(xml);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
執行之后,輸出結果:
<WeChat>
<id>zhuan2quan</id>
<name>程式新視界</name>
<interest>
<interest>Java</interest>
<interest>Spring Boot</interest>
<interest>JVM</interest>
</interest>
</WeChat>
Spring Boot中的集成
在最開始的時候,我們已經看到Spring Boot默認引入了Jackson的依賴,而且也用我們做什么額外的操作,其實已經在使用Jackson進行Json格式的資料與MVC中引數進行系結操作了,
如果Spring Boot默認的配置并不適合專案需求,也可以通過內置的配置進行配置,以application.yml配置為例,可通過指定以下屬性進行相應選項的配置:
#指定日期格式,比如yyyy-MM-dd HH:mm:ss,或者具體的格式化類的全限定名
spring.jackson.date-format
#是否開啟Jackson的反序列化
spring.jackson.deserialization
#是否開啟json的generators.
spring.jackson.generator
#指定Joda date/time的格式,比如yyyy-MM-ddHH:mm:ss). 如果沒有配置的話,dateformat會作為backup
spring.jackson.joda-date-time-format
#指定json使用的Locale.
spring.jackson.locale
#是否開啟Jackson通用的特性.
spring.jackson.mapper
#是否開啟jackson的parser特性.
spring.jackson.parser
#指定PropertyNamingStrategy(CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES)或者指定PropertyNamingStrategy子類的全限定類名.
spring.jackson.property-naming-strategy
#是否開啟jackson的序列化.
spring.jackson.serialization
#指定序列化時屬性的inclusion方式,具體查看JsonInclude.Include列舉.
spring.jackson.serialization-inclusion
#指定日期格式化時區,比如America/Los_Angeles或者GMT+10.
spring.jackson.time-zone
Spring Boot自動配置非常方便,但某些時候需要我們手動配置Bean來替代自動配置的Bean,可通過如下形式進行配置:
@Configuration
public class JacksonConfig {
@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;
}
}
配置完成,可在使用的地方直接注入即可:
@Resource
private ObjectMapper jsonMapper;
對于上面的注入,可能會有朋友問了,是否有執行緒安全的問題?這個不用擔心ObjectMapper是執行緒安全的,
小結
經過本篇文章的講解,大家對Jackson應該有一個比較全面的了解了,就個人而言,學習Jackson之后,感徑訓是挺有意思的,
原文鏈接:《放棄FastJson!一篇就夠,Jackson的功能原來如此之牛(萬字干貨)》

CSDN認證博客專家
Spring
架構
Java
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/263396.html
標籤:AI
