SpringBoot系列——Jackson序列化

  前言

  Spring Boot提供了與三個JSON映射庫的集成:html

  • Gson
  • Jackson
  • JSON-B

  Jackson是首選的默認庫。前端

  官網介紹:java

  https://docs.spring.io/spring-boot/docs/2.1.6.RELEASE/reference/html/boot-features-json.html#boot-features-json-jacksonjquery

  https://docs.spring.io/spring-boot/docs/2.1.6.RELEASE/reference/html/howto-spring-mvc.html#howto-customize-the-jackson-objectmappergit

 

  一般,咱們將Java對象轉成Json時稱之爲序列化,反之將Json轉成Java對象時稱之爲反序列化,本文簡單介紹一下Jackson,以及在SpringBoot項目開發中經常使用的Jackson方法github

 

  如何引入

  SpringBoot提供了JSON依賴,咱們能夠按下面方式引入web

 

  一、直接引入JSON依賴ajax

    <!-- springboot-json -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-json</artifactId>
    </dependency>

 

  二、通常狀況下咱們引入MVC,MVC裏面幫咱們引入了JSON依賴spring

        <!-- springboot web(MVC)-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

  最終引入的依賴是數據庫

 

  Jackson註解

  Jackson的註解詳細介紹

  英文官方介紹:https://github.com/FasterXML/jackson-annotations

 

  經常使用註解

  @JsonProperty 序列化、反序列化時,屬性的名稱

  @JsonIgnoreProperties 序列化、反序列化忽略屬性,多個時用「,」隔開

  @JsonIgnore  序列化、反序列化忽略屬性

  @JsonAlias  爲反序列化期間要接受的屬性定義一個或多個替代名稱,能夠與@JsonProperty一塊兒使用

  @JsonInclude 當屬性的值爲空(null或者"")時,不進行序列化,能夠減小數據傳輸

  @JsonFormat 序列化、反序列化時,格式化時間

 

  測試

  完整測試案例:

@Data
//序列化、反序列化忽略的屬性,多個時用「,」隔開
@JsonIgnoreProperties({"captcha"})
//當屬性的值爲空(null或者"")時,不進行序列化,能夠減小數據傳輸
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class UserVo {

    // 序列化、反序列化時,屬性的名稱
    @JsonProperty("userName")
    private String username;

    // 爲反序列化期間要接受的屬性定義一個或多個替代名稱,能夠與@JsonProperty一塊兒使用
    @JsonAlias({"pass_word", "passWord"})
    @JsonProperty("pwd")
    private String password;

    //序列化、反序列化時,格式化時間
    @JsonFormat(locale = "zh", timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
    private Date createDate;

    //序列化、反序列化忽略屬性
    @JsonIgnore
    private String captcha;

}

 

  寫一個controller測試一下

  先寫一個頁面跳轉

    /**
     * 跳轉頁面,頁面引入了jquery,主要用於下面的ajax調用測試
     */
    @GetMapping("/")
    public ModelAndView index(){
        return new ModelAndView("index");
    }

 

  使用@RestController標註類,相對於全部的方法都用@ResponseBody標註,MVC會幫咱們調用序列化,將Java對象轉成Json再響應給調用方,同時形參要加@RequestBody標註,MVC會幫咱們調用反序列化將Json轉成Java對象,這就要求咱們調用的時候須要傳一個Json字符串過來

@RestController
@RequestMapping("/")
public class TestContrller {/*
        $.ajax({
           type:"POST",
           url:"http://localhost:1099/testByJson",
           data:JSON.stringify({
                userName:"sa",
                pass_word:"123fff",
                captcha:"abcd",
                createDate:"2019-08-05 11:34:31"
            }),
           dataType:"JSON",
           contentType:"application/json;charset=UTF-8",
           success:function(data){
               console.log(data);
           },
           error:function(data){
                console.log("報錯啦");
           }
        })
     */
    @PostMapping("testByJson")
    public UserVo testByJson(@RequestBody UserVo userVo){
        System.out.println(userVo);
        return userVo;
    }
}

  

  調用測試

  一、先註釋全部註解,僅打開這個兩個類上面的註解@JsonIgnoreProperties、@JsonInclude

@Data
//序列化、反序列化忽略的屬性,多個時用「,」隔開
@JsonIgnoreProperties({"captcha"})
//當屬性的值爲空(null或者"")時,不進行序列化,能夠減小數據傳輸
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class UserVo {

    // 序列化、反序列化時,屬性的名稱
//    @JsonProperty("userName")
    private String username;

    // 爲反序列化期間要接受的屬性定義一個或多個替代名稱,能夠與@JsonProperty一塊兒使用
//    @JsonAlias({"pass_word", "passWord"})
//    @JsonProperty("pwd")
    private String password;

    //序列化、反序列化時,格式化時間
//    @JsonFormat(locale = "zh", timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
    private Date createDate;

    //序列化、反序列化忽略屬性
//    @JsonIgnore
    private String captcha;

}
UserVo

  前端調用時所有按屬性名稱

   data:JSON.stringify({
        username:"sa",
        password:"123fff",
        captcha:"abcd"
    })

  反序列化(後端控制檯打印)

UserVo(username=sa, password=123fff, createDate=null, captcha=null)

  序列化(ajax的回調)

{username: "sa", password: "123fff"}

  captcha屬性前端已經傳值,但設置了@JsonIgnoreProperties註解反序列化時該屬性被忽略,所以爲空,而序列化的時候@JsonInclude配置的是JsonInclude.Include.NON_EMPTY,當屬性的值爲空(null或者"")時,不進行序列化,因此序列化的最終結果如上所示

 

  二、先註釋全部註解,放開@JsonProperty、@JsonAlias、@JsonIgnore

@Data
//序列化、反序列化忽略的屬性,多個時用「,」隔開
//@JsonIgnoreProperties({"captcha"})
//當屬性的值爲空(null或者"")時,不進行序列化,能夠減小數據傳輸
//@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class UserVo {

    // 序列化、反序列化時,屬性的名稱
    @JsonProperty("userName")
    private String username;

    // 爲反序列化期間要接受的屬性定義一個或多個替代名稱,能夠與@JsonProperty一塊兒使用
    @JsonAlias({"pass_word", "passWord"})
    @JsonProperty("pwd")
    private String password;

    //序列化、反序列化時,格式化時間
//    @JsonFormat(locale = "zh", timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
    private Date createDate;

    //序列化、反序列化忽略屬性
    @JsonIgnore
    private String captcha;

}
UserVo

  前端調用仍是按屬性名稱

   data:JSON.stringify({
        username:"sa",
        password:"123fff",
        captcha:"abcd"
    })

  反序列化(後端控制檯打印)

UserVo(username=null, password=null, createDate=null, captcha=null)

  序列化(ajax的回調)

{createDate: null, userName: null, pwd: null}

  captcha被@JsonIgnore標註,序列化、反序列忽略它,username、password被@JsonProperty標註,傳參的時候只能用別名,password同時被@JsonAlias標註,能夠用代替名稱

  所以咱們能夠這樣調用

   data:JSON.stringify({
        userName:"sa",
        pass_word:"123fff",
        //如下兩種也同樣
        //passWord:"123fff",
        //pwd:"123fff",
        captcha:"abcd"
    })

  反序列化(後端控制檯打印)

UserVo(username=sa, password=123fff, createDate=null, captcha=null)

  序列化(ajax的回調)

{userName: "sa", pwd: "123fff"}

  

  三、先註釋全部註解,放開@JsonFormat

@Data
//序列化、反序列化忽略的屬性,多個時用「,」隔開
//@JsonIgnoreProperties({"captcha"})
//當屬性的值爲空(null或者"")時,不進行序列化,能夠減小數據傳輸
//@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class UserVo {

    // 序列化、反序列化時,屬性的名稱
//    @JsonProperty("userName")
    private String username;

    // 爲反序列化期間要接受的屬性定義一個或多個替代名稱,能夠與@JsonProperty一塊兒使用
//    @JsonAlias({"pass_word", "passWord"})
//    @JsonProperty("pwd")
    private String password;

    //序列化、反序列化時,格式化時間
    @JsonFormat(locale = "zh", timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
    private Date createDate;

    //序列化、反序列化忽略屬性
//    @JsonIgnore
    private String captcha;

}
UserVo

  前端調用

   data:JSON.stringify({
        createDate:"2019-08-05 11:34:31"
    })

  反序列化(後端控制檯打印)

UserVo(username=null, password=null, createDate=Mon Aug 05 11:34:31 GMT+08:00 2019, captcha=null)

  序列化(ajax的回調)

{username: null, password: null, createDate: "2019-08-05 11:34:31", captcha: null}

  PS:沒有配置以前這樣調用會報錯400

Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type `java.util.Date` from String "2019-08-05 11:34:31": not a valid representation (error: Failed to parse Date value '2019-08-05 11:34:31': Cannot parse date "2019-08-05 11:34:31": while it seems to fit format 'yyyy-MM-dd'T'HH:mm:ss.SSSZ', parsing fails (leniency? null)); nested exception is com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `java.util.Date` from String "2019-08-05 11:34:31": not a valid representation (error: Failed to parse Date value '2019-08-05 11:34:31': Cannot parse date "2019-08-05 11:34:31": while it seems to fit format 'yyyy-MM-dd'T'HH:mm:ss.SSSZ', parsing fails (leniency? null))

 

 

  若是不是以反序列化的方式注入,而是MVC的方式注入又是怎麼樣呢?去掉@RequestBody就變成MVC注入

    /*
        $.ajax({
           type:"GET",
           url:"http://localhost:1099/testByMvc",
           data:{
                username:"sa",
                password:"123fff",
                captcha:"abcd"
            },
           dataType:"JSON",
           contentType:"application/json;charset=UTF-8",
           success:function(data){
               console.log(data);
           },
           error:function(data){
                console.log("報錯啦");
           }
        })
     */
    @GetMapping("testByMvc")
    public UserVo testByMvc(UserVo userVo){
        System.out.println(userVo);return userVo;
    }

  放開全部註釋

@Data
//序列化、反序列化忽略的屬性,多個時用「,」隔開
@JsonIgnoreProperties({"captcha"})
//當屬性的值爲空(null或者"")時,不進行序列化,能夠減小數據傳輸
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class UserVo {

    // 序列化、反序列化時,屬性的名稱
    @JsonProperty("userName")
    private String username;

    // 爲反序列化期間要接受的屬性定義一個或多個替代名稱,能夠與@JsonProperty一塊兒使用
    @JsonAlias({"pass_word", "passWord"})
    @JsonProperty("pwd")
    private String password;

    //序列化、反序列化時,格式化時間
    @JsonFormat(locale = "zh", timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
    private Date createDate;

    //序列化、反序列化忽略屬性
    @JsonIgnore
    private String captcha;

}
UserVo

  MVC注入的時候,接參過程Jackson的註解就再也不生效了,這時候咱們傳參就得按照MVC的規則來,Date類型首先就不能傳字符串

  前端調用

           data:{
                username:"sa",
                password:"123fff",
                captcha:"abcd"
            }

  後臺打印

UserVo(username=sa, password=123fff, createDate=null, captcha=abcd)

  ajax回調,因爲仍是使用了@RestController,全部MVC會幫咱們調用序列化再響應回去

{userName: "sa", pwd: "123fff"}

  那MVC方式注入,Date日期類型該怎麼支持傳字符串呢?在配置文件新增MVC日期格式化就能夠愉快的傳輸固定格式的日期字符串了

#MVC接參時,日期處理
spring.mvc.date-format=yyyy-MM-dd HH:mm:ss

  (偷個懶,效果與預期同樣,就貼圖了。。。)

 

  同時,不論是採用哪一種注入方法,咱們能夠配置全局的日期處理,這樣一來就能夠愉快開發了

#全局日期格式化處理

#MVC接參時,日期處理
spring.mvc.date-format=yyyy-MM-dd HH:mm:ss

#Jackson序列化、反序列化時,日期處理
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss

 

  咱們順便來看一下在配置文件都有哪些Jackson配置,每一個配置的具體功能見名思意,就不闡述了

 

  ObjectMapper

  以上都是配置註解,具體操做都是MVC幫咱們作了,那咱們如何使用Jackson進行Json操做呢?咱們在官方文檔能夠看到Jackson爲咱們提供了com.fasterxml.jackson.databind.ObjectMapper類操做Json

  官方文檔相關介紹:https://docs.spring.io/spring-boot/docs/2.1.6.RELEASE/reference/html/howto-spring-mvc.html#howto-customize-the-jackson-objectmapper

 

  經常使用方法

    public static void main(String[] args) {
        try {
            ObjectMapper mapper = new ObjectMapper();

            //當屬性的值爲空(null或者"")時,不進行序列化,能夠減小數據傳輸
            mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);

            //設置日期格式
            mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

            //一、Java對象轉Json字符串
            UserVo userVo = new UserVo();
            userVo.setUsername("張三");
            userVo.setPassword("666");
            String jsonString = mapper.writeValueAsString(userVo);
            System.out.println(jsonString);

            //二、Json字符串轉Java對象
            jsonString = "{\"userName\":\"張三\"}";
            UserVo userVo1 = mapper.readValue(jsonString, UserVo.class);
            System.out.println(userVo1);

            //三、Java對象類型轉換
            HashMap<Object, Object> map = new HashMap<>();
            map.put("userName", "張三");
            UserVo userVo2 = mapper.convertValue(map, UserVo.class);
            System.out.println(userVo2);

            //四、將json字符串轉換成List
            String listJsonString = "[{\"userName\":\"張三\"},{\"userName\":\"李四\"}]";
            List<UserVo> userVoList = mapper.readValue(listJsonString, mapper.getTypeFactory().constructParametricType(List.class, UserVo.class));
            System.out.println(userVoList);
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

 

   還有一些不怎麼經常使用的方法,好比下面這幾個(除了轉成Json字符串)

 

  後記

  一般,實體類用於ORM映射框架與數據打交道,好比:User,要求對象的屬性要與數據庫字段一一對應,少了不行,多了也不行,沒有對應映射的得用註解標註(好比JPA),因此咱們通常用Vo對象進行傳輸、接參等,會多不少亂七八糟的屬性(分頁信息,僅用於接參的臨時屬性等),好比:UserVo,User、UserVo兩個對象使用工具類相互轉換,有時候Vo對象有些亂七八糟的屬性不想進行序列化傳輸,就須要設置序列化過濾

  在SpringBoot中使用Jackson操做Json序列化、反序列化的簡單操做就暫時記錄到這,之後再繼續補充

 

  補充

  2019-10-22補充:不一樣時區,時間序列化處理

  需求:要求系統根據當前登陸帳號存儲的時區字段,web端顯示對應時區的時間

  一般狀況下,系統會分爲svc端服務、web端服務,svc服務負責與數據庫打交道,web服務負責與瀏覽器打交道;所以,咱們能夠在svc服務數據存庫的時候統一存儲GMT+0000,web服務序列化響應的時候根據當前登陸帳戶時區進行顯示,簡單來講就是:web端服務根據當前登陸人的時區來顯示日期時間,但svc端服務日期入庫統一採用GMT+0000時區。

  實現:

  svc端服務,在系統啓動時設置全局默認GMT+0000時區

@SpringBootApplication
public class XXXApplication {
    public static void main(String[] args) {
        //設置全局默認時區
        TimeZone.setDefault(TimeZone.getTimeZone("GMT+0000"));
        SpringApplication.run(XXXApplication .class, args);
    }
}

 

  web端服務,設置自定義JsonSerializer<Date>日期序列化實現類,在實現類中獲取登陸帳戶時區,設置序列化日期格式

@JsonComponent
public class WebDateFormat {

    //SimpleDateFormat對象
    private static final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z");

    @Autowired
    private LoginService loginService;

    //格式化日期
    public static class DateFormatSerializer extends JsonSerializer<Date> {
        @Override
        public void serialize(Date value, JsonGenerator gen, SerializerProvider serializers) {
            try {
                //獲取登陸帳號時區字段,並設置序列化日期格式
                String timeZone = loginService.getLoginUser().getTimeZone();
                format.setTimeZone(TimeZone.getTimeZone(timeZone));
                gen.writeString(format.format(value));

            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    //解析日期字符串
    public static class DateParseDeserializer extends JsonDeserializer<Date> {
        @Override
        public Date deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
            try {
                //獲取登陸帳號時區字段,並設置序列化日期格式
                String timeZone = loginService.getLoginUser().getTimeZone();
                format.setTimeZone(TimeZone.getTimeZone(timeZone));
                return format.parse(p.getValueAsString());

            } catch (ParseException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

 

 

  代碼開源

  代碼已經開源、託管到個人GitHub、碼雲:

  GitHub:https://github.com/huanzi-qch/springBoot

  碼雲:https://gitee.com/huanzi-qch/springBoot

相關文章
相關標籤/搜索