百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分类 > 正文

SpringBoot系列——Jackson序列化

ztj100 2025-02-15 18:24 21 浏览 0 评论

  前言

  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依赖

    
    
      org.springframework.boot
      spring-boot-starter-json
    


  2、一般情况下我们引入MVC,MVC里面帮我们引入了JSON依赖

        
        
            org.springframework.boot
            spring-boot-starter-web
        

  最终引入的依赖是


  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:

    /**
     * 跳转页面,页面引入了jquery,主要用于下面的ajax调用测试
     */
    @GetMapping("/")
    public ModelAndView index(){
        return new ModelAndView("index");
    }


  使用@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 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 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 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日期序列化实现类,在实现类中获取登录账户时区,设置序列化日期格式

@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 {
        @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 {
        @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


版权声明

作者:huanzi-qch

出处:
https://www.cnblogs.com/huanzi-qch

若标题中有“转载”字样,则本文版权归原作者所有。若无转载字样,本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利.

相关推荐

Jquery 详细用法

1、jQuery介绍(1)jQuery是什么?是一个js框架,其主要思想是利用jQuery提供的选择器查找要操作的节点,然后将找到的节点封装成一个jQuery对象。封装成jQuery对象的目的有...

前端开发79条知识点汇总

1.css禁用鼠标事件2.get/post的理解和他们之间的区别http超文本传输协议(HTTP)的设计目的是保证客户机与服务器之间的通信。HTTP的工作方式是客户机与服务器之间的请求-应答协议。...

js基础面试题92-130道题目

92.说说你对作用域链的理解参考答案:作用域链的作用是保证执行环境里有权访问的变量和函数是有序的,作用域链的变量只能向上访问,变量访问到window对象即被终止,作用域链向下访问变量是不被允许的。...

Web前端必备基础知识点,百万网友:牛逼

1、Web中的常见攻击方式1.SQL注入------常见的安全性问题。解决方案:前端页面需要校验用户的输入数据(限制用户输入的类型、范围、格式、长度),不能只靠后端去校验用户数据。一来可以提高后端处理...

事件——《JS高级程序设计》

一、事件流1.事件流描述的是从页面中接收事件的顺序2.事件冒泡(eventbubble):事件从开始时由最具体的元素(就是嵌套最深的那个节点)开始,逐级向上传播到较为不具体的节点(就是Docu...

前端开发中79条不可忽视的知识点汇总

过往一些不足的地方,通过博客,好好总结一下。1.css禁用鼠标事件...

Chrome 开发工具之Network

经常会听到比如"为什么我的js代码没执行啊?","我明明发送了请求,为什么反应?","我这个网站怎么加载的这么慢?"这类的问题,那么问题既然存在,就需要去解决它,需要解决它,首先我们得找对导致问题的原...

轻量级 React.js 虚拟美化滚动条组件RScroll

前几天有给大家分享一个Vue自定义滚动条组件VScroll。今天再分享一个最新开发的ReactPC端模拟滚动条组件RScroll。...

一文解读JavaScript事件对象和表单对象

前言相信做网站对JavaScript再熟悉不过了,它是一门脚本语言,不同于Python的是,它是一门浏览器脚本语言,而Python则是服务器脚本语言,我们不光要会Python,还要会JavaScrip...

Python函数参数黑科技:*args与**kwargs深度解析

90%的Python程序员不知道,可变参数设计竟能决定函数的灵活性和扩展性!掌握这些技巧,让你的函数适应任何场景!一、函数参数设计的三大进阶技巧...

深入理解Python3密码学:详解PyCrypto库加密、解密与数字签名

在现代计算领域,信息安全逐渐成为焦点话题。密码学,作为信息保护的关键技术之一,允许我们加密(保密)和解密(解密)数据。...

阿里Nacos惊爆安全漏洞,火速升级!(附修复建议)

前言好,我是threedr3am,我发现nacos最新版本1.4.1对于User-Agent绕过安全漏洞的serverIdentitykey-value修复机制,依然存在绕过问题,在nacos开启了...

Python模块:zoneinfo时区支持详解

一、知识导图二、知识讲解(一)zoneinfo模块概述...

Golang开发的一些注意事项(一)

1.channel关闭后读的问题当channel关闭之后再去读取它,虽然不会引发panic,但会直接得到零值,而且ok的值为false。packagemainimport"...

Python鼠标与键盘自动化指南:从入门到进阶——键盘篇

`pynput`是一个用于控制和监控鼠标和键盘的Python库...

取消回复欢迎 发表评论: