原文連結:https://mp.weixin.qq.com/s/m34q4AC-T0fc9oIWxDqEVg
本文就來講解一下Jackson的基本使用以及與Spring Boot的結合與實踐。
什麼是JacksonJackson是比較主流的基於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核心類ObjectMapperJackson提供了三種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方法,該屬性只能用於反序列化。@Slf4jpublic 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等方法來獲取或判斷。
下面直接看兩個示例:
@Slf4jpublic 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.SSSZmapper.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的子類。
@Testpublic 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會作為backupspring.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。可透過如下形式進行配置:
@Configurationpublic 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; }}
配置完成,可在使用的地方直接注入即可:
@Resourceprivate ObjectMapper jsonMapper;
對於上面的注入,可能會有朋友問了,是否有執行緒安全的問題?這個不用擔心ObjectMapper是執行緒安全的。
小結經過本篇文章的講解,大家對Jackson應該有一個比較全面的瞭解了。就個人而言,學習Jackson之後,感覺還是挺有意思的。