首頁>技術>

原文連結:https://mp.weixin.qq.com/s/m34q4AC-T0fc9oIWxDqEVg

本文就來講解一下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方法,該屬性只能用於反序列化。
@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之後,感覺還是挺有意思的。

4
最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • WPF介面應用開發技巧——繫結到列集合