SpringBoot系列——Jackson序列化


  前言

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

  • Gson
  • Jackson
  • JSON-B

  Jackson是首選的默認庫。

  官網介紹:

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

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

 

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

 

  如何引入

  SpringBoot提供了JSON依賴,我們可以按下面方式引入

 

  1、直接引入JSON依賴

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

 

  2、一般情況下我們引入MVC,MVC里面幫我們引入了JSON依賴

        <!-- 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 序列化、反序列化時,格式化時間

 

  測試

 

  寫一個controller測試一下

  先寫一個頁面跳轉

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

 

  反序列化方式

  完整測試Vo:

@Data
//序列化、反序列化忽略的屬性,多個時用“,”隔開
@JsonIgnoreProperties({"captcha"})
//當屬性的值為空(null或者"")時,不進行序列化,可以減少數據傳輸
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class UserVoByJson {

    // 序列化、反序列化時,屬性的名稱
    @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;

}

 

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

    /*
        $.ajax({
           type:"POST",
           url:"http://localhost:10099/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("報錯啦");
           }
        })
     */
    /**
     * 反序列化方式注入,只能post請求
     */
    @PostMapping("testByJson")
    public UserVoByJson testByJson(@RequestBody UserVoByJson userVo) {
        System.out.println(userVo);
        return userVo;
    }

 

  調用測試

  1、先注釋所有注解,僅打開這個兩個類上面的注解@JsonIgnoreProperties、@JsonInclude

@Data
//序列化、反序列化忽略的屬性,多個時用“,”隔開
@JsonIgnoreProperties({"captcha"})
//當屬性的值為空(null或者"")時,不進行序列化,可以減少數據傳輸
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class UserVoByJson {

    // 序列化、反序列化時,屬性的名稱
//    @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;

}

  前端調用時全部按屬性名稱

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

  反序列化(后端控制台打印)

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

  序列化(ajax的回調)

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

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

 

  2、先注釋所有注解,放開@JsonProperty、@JsonAlias、@JsonIgnore

@Data
//序列化、反序列化忽略的屬性,多個時用“,”隔開
//@JsonIgnoreProperties({"captcha"})
//當屬性的值為空(null或者"")時,不進行序列化,可以減少數據傳輸
//@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class UserVoByJson {

    // 序列化、反序列化時,屬性的名稱
    @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;

}

  前端調用還是按屬性名稱

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

  反序列化(后端控制台打印)

UserVoByJson(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"
    })

  反序列化(后端控制台打印)

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

  序列化(ajax的回調)

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

  

  3、先注釋所有注解,放開@JsonFormat

@Data
//序列化、反序列化忽略的屬性,多個時用“,”隔開
//@JsonIgnoreProperties({"captcha"})
//當屬性的值為空(null或者"")時,不進行序列化,可以減少數據傳輸
//@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class UserVoByJson {

    // 序列化、反序列化時,屬性的名稱
//    @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;

}

  前端調用

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

  反序列化(后端控制台打印)

UserVoByJson(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方式注入

  Vo類

@Data
public class UserVoByMvc {
    private String username;
    private String password;
    private Date createDate;
    private String captcha;
}

 

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

    /*
        $.ajax({
           type:"POST",
           url:"http://localhost:10099/testByMvc",
           data:{
                username:"sa",
                password:"123fff",
                captcha:"abcd"
            },
           dataType:"JSON",
           //contentType:"application/json;charset=UTF-8",//使用這個,get請求能接到參數,post接不到
           contentType:"application/x-www-form-urlencoded",//使用這個,get、post都能接收到參數
           success:function(data){
               console.log(data);
           },
           error:function(data){
                console.log("報錯啦");
           }
        })
     */
    /**
     * MVC方式注入
     */
    @RequestMapping("testByMvc")
    public UserVoByMvc testByMvc(UserVoByMvc userVo) {
        System.out.println(userVo);
        return userVo;
    }

 

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

  前端調用

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

  后台打印

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

  ajax回調

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

  那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配置,每個配置的具體功能見名思意,就不闡述了

 

  接收集合對象

 

  1、反序列化方式

    /*
    let datas = [];//對象集合
     for(let i = 0; i < 5; i++){
         let data = {"userName":i + ""};//對象
         datas.push(data);
     }
     $.ajax({
         type:"POST",
         url:"http://localhost:10099/testListByJson",
         data:JSON.stringify(datas),
         dataType:"JSON",
         contentType:"application/json;charset=UTF-8",
         success:function(data){
            console.log(data);
         },
         error:function(data){
             console.log("報錯啦");
         }
     })
     */
    /**
     * 反序列化方式,接收集合對象,只能post請求
     */
    @PostMapping("testListByJson")
    public String testListByJson(@RequestBody List<UserVoByJson> userVos){
        userVos.forEach(System.out::println);
        return "{\"code\":200}";
    }

  后台打印

UserVoByJson(username=0, password=null, createDate=null, captcha=null)
UserVoByJson(username=1, password=null, createDate=null, captcha=null)
UserVoByJson(username=2, password=null, createDate=null, captcha=null)
UserVoByJson(username=3, password=null, createDate=null, captcha=null)
UserVoByJson(username=4, password=null, createDate=null, captcha=null)

 

  

  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

 

  常用方法

    /**
     * 測試 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"));

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

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

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

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

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

  打印

{"userName":"張三","pwd":"666"}
UserVoByJson(username=張三, password=null, createDate=null, captcha=null)
UserVoByJson(username=張三, password=null, createDate=null, captcha=null)
[UserVoByJson(username=張三, password=null, createDate=null, captcha=null), UserVoByJson(username=李四, password=null, createDate=null, captcha=null)]

 

 

   還有一些不怎么常用的方法,比如下面這幾個(除了轉成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);
            }
        }
    }
}

 

  2020-07-31更新

  lombok跟mvc注入的bug記錄

    /**
     mvc參數自動注入 反射生成set方法,正常來說是set后面緊跟的第一個字母大寫,比如這樣:objId,setObjId,
     但當它碰到第二個字母大寫的時候,第一個是小寫,比如:uPosition,setuPosition,
     實體類中我用的是lombok,它幫我們生成的是setUPosition,導致找不到set方法值注入不進去

     解決方法:手動寫uPosition uUnits的set,get方法,覆蓋lombok幫我們生成的set,get方法
     */
    public String getuPosition() {
        return uPosition;
    }
    public void setuPosition(String uPosition) {
        this.uPosition = uPosition;
    }
    public String getuUnits() {
        return uUnits;
    }
    public void setuUnits(String uUnits) {
        this.uUnits = uUnits;
    }

  所以當某個字段接不到參、或者序列化丟失數據、ORM框架無法映射數據等問題時,可以往這個方向去排查問題

 

 

  代碼開源

  代碼已經開源、托管到我的GitHub、碼雲:

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

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM