快捷搜索:  汽车  科技

springboot默认接口序列化(SpringBoot系列Jackson序列化)

springboot默认接口序列化(SpringBoot系列Jackson序列化)<!-- springboot-json --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-json</artifactId> </dependency>  2、一般情况下我们引入MVC,MVC里面帮我们引入了JSON依赖  1、直接引入JSON依赖  https://docs.spring.io/spring-boot/docs/2.1.6.RELEASE/reference/html/howto-spring-MVC.html#howto-customize-the-jackson-objectmapper  通常,我们将Java

  前言

  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>

springboot默认接口序列化(SpringBoot系列Jackson序列化)(1)

  最终引入的依赖是

springboot默认接口序列化(SpringBoot系列Jackson序列化)(2)

  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配置,每个配置的具体功能见名思意,就不阐述了

springboot默认接口序列化(SpringBoot系列Jackson序列化)(3)

  接收集合对象

  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字符串)

springboot默认接口序列化(SpringBoot系列Jackson序列化)(4)

  后记

  通常,实体类用于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

版权声明

作者:huanzi-qch

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

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

猜您喜欢: