Jackson快速入門

Java生態(tài)圈中有很多處理JSON和XML格式化的類庫(kù),Jackson是其中比較著名的一個(gè)。雖然JDK自帶了XML處理類庫(kù),但是相對(duì)來(lái)說(shuō)比較低級(jí),使用本文介紹的Jackson等高級(jí)類庫(kù)處理起來(lái)會(huì)方便很多。

引入類庫(kù)

由于Jackson相關(guān)類庫(kù)按照功能分為幾個(gè)相對(duì)獨(dú)立的,所以需要同時(shí)引入多個(gè)類庫(kù),為了方便我將版本號(hào)單獨(dú)提取出來(lái)設(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實(shí)現(xiàn)更高效的類庫(kù)
    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配置請(qǐng)去mvnrepository搜索。

Jackson注解

Jackson類庫(kù)包含了很多注解,可以讓我們快速建立Java類與JSON之間的關(guān)系。詳細(xì)文檔可以參考Jackson-Annotations。下面介紹一下常用的。

屬性命名

@JsonProperty注解指定一個(gè)屬性用于JSON映射,默認(rèn)情況下映射的JSON屬性與注解的屬性名稱相同,不過(guò)可以使用該注解的value值修改JSON屬性名,該注解還有一個(gè)index屬性指定生成JSON屬性的順序,如果有必要的話。

屬性包含

還有一些注解可以管理在映射JSON的時(shí)候包含或排除某些屬性,下面介紹一下常用的幾個(gè)。

@JsonIgnore注解用于排除某個(gè)屬性,這樣該屬性就不會(huì)被Jackson序列化和反序列化。

@JsonIgnoreProperties注解是類注解。在序列化為JSON的時(shí)候,@JsonIgnoreProperties({"prop1", "prop2"})會(huì)忽略pro1和pro2兩個(gè)屬性。在從JSON反序列化為Java類的時(shí)候,@JsonIgnoreProperties(ignoreUnknown=true)會(huì)忽略所有沒(méi)有Getter和Setter的屬性。該注解在Java類和JSON不完全匹配的時(shí)候很有用。

@JsonIgnoreType也是類注解,會(huì)排除所有指定類型的屬性。

序列化相關(guān)

@JsonPropertyOrder@JsonPropertyindex屬性類似,指定屬性序列化時(shí)的順序。

@JsonRootName注解用于指定JSON根屬性的名稱。

處理JSON

簡(jiǎn)單映射

我們用Lombok設(shè)置一個(gè)簡(jiǎn)單的Java類。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Friend {
    private String nickname;
    private int age;
}

然后就可以處理JSON數(shù)據(jù)了。首先需要一個(gè)ObjectMapper對(duì)象,序列化和反序列化都需要它。

        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é)果如下??梢钥吹缴傻腏SON屬性和Java類中定義的一致。

{"nickname":"yitian","age":25}
Friend(nickname=yitian, age=25)

集合的映射

除了使用Java類進(jìn)行映射之外,我們還可以直接使用Map和List等Java集合組織JSON數(shù)據(jù),在需要的時(shí)候可以使用readTree方法直接讀取JSON中的某個(gè)屬性值。需要注意的是從JSON轉(zhuǎn)換為Map對(duì)象的時(shí)候,由于Java的類型擦除,所以類型需要我們手動(dòng)用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ù)定義了一些配置,我們通過(guò)啟用和禁用某些屬性可以修改Jackson運(yùn)行的某些行為。詳細(xì)文檔參考JacksonFeatures。下面我簡(jiǎn)單翻譯一下Jackson README上列出的一些屬性。

// 美化輸出
mapper.enable(SerializationFeature.INDENT_OUTPUT);
// 允許序列化空的POJO類
// (否則會(huì)拋出異常)
mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
// 把java.util.Date, Calendar輸出為數(shù)字(時(shí)間戳)
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

// 在遇到未知屬性的時(shí)候不拋出異常
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
// 強(qiáng)制JSON 空字符串("")轉(zhuǎn)換為null對(duì)象值:
mapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);

// 在JSON中允許C/C++ 樣式的注釋(非標(biāo)準(zhǔn),默認(rèn)禁用)
mapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
// 允許沒(méi)有引號(hào)的字段名(非標(biāo)準(zhǔn))
mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
// 允許單引號(hào)(非標(biāo)準(zhǔn))
mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
// 強(qiáng)制轉(zhuǎn)義非ASCII字符
mapper.configure(JsonGenerator.Feature.ESCAPE_NON_ASCII, true);
// 將內(nèi)容包裹為一個(gè)JSON屬性,屬性名由@JsonRootName注解指定
mapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);

這里有三個(gè)方法,configure方法接受配置名和要設(shè)置的值,Jackson 2.5版本新加的enable和disable方法則直接啟用和禁用相應(yīng)屬性,我推薦使用后面兩個(gè)方法。

用注解管理映射

前面介紹了一些Jackson注解,下面來(lái)應(yīng)用一下這些注解。首先來(lái)看看使用了注解的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類并不是完全對(duì)應(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);

運(yùn)行結(jié)果如下。可以看到生成JSON的時(shí)候忽略了我們制定的值,而且在轉(zhuǎn)換為Java類的時(shí)候?qū)?yīng)的屬性為空。

{"NickName":"yitian","Age":25}
FriendDetail(name=yitian, age=25, uselessProp1=null, uselessProp2=0, uselessProp3=null)

然后取消注釋代碼中的那行,也就是啟用WRAP_ROOT_VALUE功能,再運(yùn)行一下程序,運(yùn)行結(jié)果如下??梢钥吹缴傻腏SON結(jié)果發(fā)生了變化,而且由于JSON結(jié)果變化,所以Java類轉(zhuǎn)換失敗(所有字段值全為空)。WRAP_ROOT_VALUE這個(gè)功能在有些時(shí)候比較有用,因?yàn)橛行㎎SON文件需要這種結(jié)構(gòu)。

{"FriendDetail":{"NickName":"yitian","Age":25}}
FriendDetail(name=null, age=0, uselessProp1=null, uselessProp2=0, uselessProp3=null)

Java8日期時(shí)間類支持

Java8增加了一套全新的日期時(shí)間類,Jackson對(duì)此也有支持。這些支持是以Jackson模塊形式提供的,所以首先就是注冊(cè)這些模塊。

        ObjectMapper mapper = new ObjectMapper()
                .registerModule(new JavaTimeModule())
                .registerModule(new ParameterNamesModule())
                .registerModule(new Jdk8Module());

導(dǎo)入類庫(kù)之后,Jackson也可以自動(dòng)搜索所有模塊,不需要我們手動(dòng)注冊(cè)。

        mapper.findAndRegisterModules();

我們新建一個(gè)帶有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;

}

然后來(lái)看看代碼。

    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);
    }

運(yùn)行結(jié)果如下??梢钥吹剑傻腏SON日期變成了[1994,1,1]這樣的時(shí)間戳形式,一般情況下不符合我們的要求。

{"birthday":[1994,1,1],"Name":"yitian","NickName":"易天","Age":25,"IdentityCode":"10000"}
Person(name=yitian, nickname=易天, age=25, identityCode=10000, birthday=1994-01-01)

取消注釋那行代碼,程序運(yùn)行結(jié)果如下。這樣一來(lái)就變成了我們一般使用的形式了。如果有格式需要的話,可以使用@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是一個(gè)處理JSON的類庫(kù),不過(guò)它也通過(guò)jackson-dataformat-xml包提供了處理XML的功能。Jackson建議我們?cè)谔幚鞽ML的時(shí)候使用woodstox-core包,它是一個(gè)XML的實(shí)現(xiàn),比JDK自帶XML實(shí)現(xiàn)更加高效,也更加安全。

這里有個(gè)注意事項(xiàng),如果你正在使用Java 9以上的JDK,可能會(huì)出現(xiàn)java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException異常,這是因?yàn)镴ava 9實(shí)現(xiàn)了JDK的模塊化,將原本和JDK打包在一起的JAXB實(shí)現(xiàn)分隔出來(lái)。所以這時(shí)候需要我們手動(dòng)添加JAXB的實(shí)現(xiàn)。在Gradle中添加下面的代碼即可。

compile group: 'javax.xml.bind', name: 'jaxb-api', version: '2.3.0'

注解

Jackson XML除了使用Jackson JSON和JDK JAXB的一些注解之外,自己也定義了一些注解。下面簡(jiǎn)單介紹一下幾個(gè)常用注解。

@JacksonXmlProperty注解有三個(gè)屬性,namespace和localname屬性用于指定XML命名空間的名稱,isAttribute指定該屬性作為XML的屬性(<a b="xxx"></a>)還是作為子標(biāo)簽(<a><b></b></a>).

@JacksonXmlRootElement注解有兩個(gè)屬性,namespace和localname屬性用于指定XML根元素命名空間的名稱。

@JacksonXmlText注解將屬性直接作為未被標(biāo)簽包裹的普通文本表現(xiàn)。

@JacksonXmlCData將屬性包裹在CDATA標(biāo)簽中。

XML映射

新建如下一個(gè)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實(shí)際上就是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);

運(yùn)行結(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)

如果取消那兩行注釋,那么運(yùn)行結(jié)果如下??梢钥吹絁ackson XML注解對(duì)生成的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集成

自動(dòng)配置

Spring Boot對(duì)Jackson的支持非常完善,只要我們引入相應(yīng)類庫(kù),Spring Boot就可以自動(dòng)配置開(kāi)箱即用的Bean。Spring自動(dòng)配置的ObjectMapper(或者XmlMapper)作了如下配置,基本上可以適應(yīng)大部分情況。

  • 禁用了MapperFeature.DEFAULT_VIEW_INCLUSION
  • 禁用了DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
  • 禁用了SerializationFeature.WRITE_DATES_AS_TIMESTAMPS

如果需要修改自動(dòng)配置的ObjectMapper屬性也非常簡(jiǎn)單,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會(huì)同時(shí)配置相應(yīng)的HttpMessageConverters,所以我們其實(shí)要做的很簡(jiǎn)單,用Jackson注解標(biāo)注好要映射的Java類,然后直接讓控制器返回對(duì)象即可!下面是一個(gè)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;
    }
}

然后是控制器代碼。在整個(gè)過(guò)程中我們只需要引入Jackson類庫(kù),然后編寫業(yè)務(wù)代碼就好了。關(guān)于如何配置Jackson類庫(kù),我們完全不需要管,這就是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;
    }
}

進(jìn)入localhost:8080/xml就可以看到對(duì)應(yīng)結(jié)果了。

結(jié)果

手動(dòng)配置

Spring Boot自動(dòng)配置非常方便,但不是萬(wàn)能的。在必要的時(shí)候,我們需要手動(dòng)配置Bean來(lái)替代自動(dòng)配置的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;
    }
}

然后在需要的地方進(jìn)行依賴注入。需要注意為了區(qū)分ObjectMapper和XmlMapper,需要使用@Qualifier注解進(jìn)行標(biāo)記。

@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類庫(kù)的一些介紹,希望對(duì)大家有所幫助。項(xiàng)目代碼在我的Github,感興趣的同學(xué)可以看看。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Web MVC Spring Web MVC 是包含在 Spring 框架中的 Web 框架,建立于...
    Hsinwong閱讀 22,970評(píng)論 1 92
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 47,290評(píng)論 6 342
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,697評(píng)論 19 139
  • 其實(shí)我日更并沒(méi)有多久,大約就50天而已。 但是我最后,在今天做出了艱難的決定,就是暫停日更,恢復(fù)成周更或不定時(shí)更。...
    魚(yú)頭shiny閱讀 222評(píng)論 4 3
  • 米利都的泰勒斯 (約前624-前546) 思維方式向?qū)嵺`領(lǐng)域的轉(zhuǎn)變令泰勒斯開(kāi)始相信世間萬(wàn)物絕不是由神話傳說(shuō)塑造而成...
    仇志飛閱讀 548評(píng)論 0 0

友情鏈接更多精彩內(nèi)容