1 出现异常

这次的异常出现在前端向后端发送请求体里带了两个日期,在后端的实体类中,这两个日期的格式都是JDK8中的时间类LocalDateTime。默认情况下,LocalDateTime只能解析2020-01-01T10:00:00这样标准格式的字符串,这里日期和时间中间有一个T。如果不做任何修改的话,LocalDateTime直接解析2020-05-01 08:00:00这种我们习惯上能接受的日期格式,会抛出异常。

异常信息:

org.springframework.http.converter.HttpMessageNotReadableException: Invalid JSON input: Cannot deserialize value of type `java.time.LocalDateTime` from String "2020-05-04 00:00": Failed to deserialize java.time.LocalDateTime: (java.time.format.DateTimeParseException) Text '2020-05-04 00:00' could not be parsed at index 10; nested exception is com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `java.time.LocalDateTime` from String "2020-05-04 00:00": Failed to deserialize java.time.LocalDateTime: (java.time.format.DateTimeParseException) Text '2020-05-04 00:00' could not be parsed at index 10// 省略部分异常信息Caused by: java.time.format.DateTimeParseException: Text '2020-05-04 00:00' could not be parsed at index 10// 省略部分异常信息

从异常信息中,我们可以看到2020-05-04 00:00解析到索引为10的位置出现问题,因为这里第10位是一个空格,而LocalDateTime的标准格式里第10位是一个T。

2 问题描述

现在的问题是:

  • 后端使用LocalDateTime类。LocalDateTime类相比于之前的Date类,存在哪些优点,网上的资料已经非常详尽。
  • 前端传回的数据,可能是yyyy-MM-dd HH:mm:ss,也可能是yyyy-MM-dd HH:mm,但肯定不会是yyyy-MM-ddTHH:mm:ss。也就是说,前端传回的日期格式是不确定的,可能是年月日时分秒,可能是年月日时分,还可能是其他任何一般人会用到的日期格式。但显然不会是年月日T时分秒,因为这样前端需要额外的转换,且完全不符合人类的使用习惯。

3 尝试过的方法

我的SpringBoot版本是2.2.5。

3.1 @JsonFormat

在实体类的字段上加@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")。

这个方法可以解决问题,缺点是要给每个出现的地方都加上注解,无法做全局配置,而且只能设定一种格式,不能满足我的需求。

3.2 注册Converter<String, LocalDateTime>的实现类成为bean

结果:没有生效。这个方法解决controller层的方法的@RequestParam参数的转化倒是有效。

后来发现这个方案是给控制层方法的参数使用的。也就是下面这种场景:

@GetMapping("/test")
public void test(@RequestParam("time") LocalDateTime time){// 省略代码
}

3.3 注册Formatter<LocalDateTime>的实现类成为bean

结果:没有生效。

后来发现这个方案也是给控制层方法参数使用的。

4 解决问题

参考资料:springboot中json转换LocalDateTime失败的bug解决过程

首先,我们要知道,SpringBoot默认使用的是Jackson进行序列化。从博客中我们可以了解到,将JSON字符串里的日期从字符串格式转换成LocalDateTime类的工作是由com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer类的deserialize()方法完成的。这一点可以通过断点调试确认

解决思路是用自定义的反序列化器替换掉jackson里面的反序列化器,在解析的时候使用自己定义的解析逻辑。

在这里,序列化(serialize)是指将Java对象转成json字符串的操作,而反序列化(deserialize)指将json字符串解析成Java对象的操作。现在要解决的是反序列化问题。

4.1 实体类

public class LeaveApplication {@TableId(type = IdType.AUTO)private Integer id;private Long proposerUsername;// LocalDateTime类private LocalDateTime startTime;// LocalDateTime类private LocalDateTime endTime;private String reason;private String state;private String disapprovedReason;private Long checkerUsername;private LocalDateTime checkTime;// 省略getter、setter
}

4.2 controller层方法

@RestController
public class LeaveApplicationController {private LeaveApplicationService leaveApplicationService;@Autowiredpublic LeaveApplicationController(LeaveApplicationService leaveApplicationService) {this.leaveApplicationService = leaveApplicationService;}/*** 学生发起请假申请* 申请的时候只是向请假申请表里插入一条数据,只有在同意的时候,才会形成job和trigger*/@PostMapping("/leave_application")public void addLeaveApplication(@RequestBody LeaveApplication leaveApplication) {leaveApplicationService.addLeaveApplication(leaveApplication);}}

4.3 自定义LocalDateTimeDeserializer

将com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer类整个地复制过来。这里要注意,我用来原来的类名,所以如果直接将代码复制过来,会有类名冲突,IDEA自动导入``com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer`,将类的前缀全部去掉就行了。

public class LocalDateTimeDeserializer extends JSR310DateTimeDeserializerBase<LocalDateTime> {// 省略不需要修改的代码/*** 关键方法*/@Overridepublic LocalDateTime deserialize(JsonParser parser, DeserializationContext context) throws IOException {if (parser.hasTokenId(6)) {// 修改了这个分支里面的代码String string = parser.getText().trim();if (string.length() == 0) {return !this.isLenient() ? (LocalDateTime) this._failForNotLenient(parser, context, JsonToken.VALUE_STRING) : null;} else {return convert(string);}} else {// 省略了没有修改的代码}}public LocalDateTime convert(String source) {source = source.trim();if ("".equals(source)) {return null;}if (source.matches("^\\d{4}-\\d{1,2}$")) {// yyyy-MMreturn LocalDateTime.parse(source + "-01 00:00:00", dateTimeFormatter);} else if (source.matches("^\\d{4}-\\d{1,2}-\\d{1,2}$")) {// yyyy-MM-ddreturn LocalDateTime.parse(source + " 00:00:00", dateTimeFormatter);} else if (source.matches("^\\d{4}-\\d{1,2}-\\d{1,2} {1}\\d{1,2}:\\d{1,2}$")) {// yyyy-MM-dd HH:mmreturn LocalDateTime.parse(source + ":00", dateTimeFormatter);} else if (source.matches("^\\d{4}-\\d{1,2}-\\d{1,2} {1}\\d{1,2}:\\d{1,2}:\\d{1,2}$")) {// yyyy-MM-dd HH:mm:ssreturn LocalDateTime.parse(source, dateTimeFormatter);} else {throw new IllegalArgumentException("Invalid datetime value '" + source + "'");}}
}

在这个过程中,我对博客中的方法做了改进,在解析字符串的使用,用正则表达式判断这个日期的实际格式,然后再将字符串解析成LocalDateTime。这种方法使转换过程可以兼容多种日期类型,达到了我想要的效果。

4.4 替换反序列化器

但是我按照博客中的方法来替换,却并没有产生效果。反序列化的时候,

@Configuration
public class LocalDateTimeSerializerConfig {@Beanpublic ObjectMapper serializingObjectMapper() {JavaTimeModule module = new JavaTimeModule();// 这里导包的时候选择自己定义的LocalDateTimeDeserializerLocalDateTimeDeserializer dateTimeDeserializer = new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));module.addDeserializer(LocalDateTime.class, dateTimeDeserializer);return Jackson2ObjectMapperBuilder.json().modules(module).featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS).build();}
}

4.5 再次替换反序列化器

我再次踏上查资料的不归路,最后在强大的stack overflow上找到了一个问答,地址:How to custom a global jackson deserializer for java.time.LocalDateTime。

// 这是一个webmvc的配置类
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {// 重写configureMessageConverters@Overridepublic void configureMessageConverters(List<HttpMessageConverter<?>> converters) {JavaTimeModule module = new JavaTimeModule();// 序列化器module.addSerializer(LocalDateTime.class,new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));// 反序列化器// 这里添加的是自定义的反序列化器module.addDeserializer(LocalDateTime.class,new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));ObjectMapper mapper = new ObjectMapper();mapper.registerModule(module);// add converter at the very front// if there are same type mappers in converters, setting in first mapper is used.converters.add(0, new MappingJackson2HttpMessageConverter(mapper));}
}

此时运行程序,发现还是不行,没有走自定义的反序列化器。但是这时候,我看到了原问答里的这句话if there are same type mappers in converters, setting in first mapper is used.,意思是说,如果converter里有一个相同类型的mapper,那么先设置的那个会生效。

然后我想起来,之前在统一返回值格式的时候,如果返回值是String类型,会抛出异常。为了解决这个问题,我重写了webmvc配置里的extendMessageConverters()。

@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {converters.add(0, new MappingJackson2HttpMessageConverter());
}

很可能是这里出了问题,所以我先将这个方法注释掉。果然,再运行程序,日期的解析走到了自定义的反序列化器中。同时,可以看到两个方法里都调了converters.add(),所以之前返回String出现异常的问题也不会再发生。

到此,json字符串里日期解析为LocalDateTime时出现解析异常的问题就完全解决了。

解决json字符串转为对象时LocalDateTime异常问题相关推荐

  1. 将Json字符串转为对象JSONObject

    将Json字符串转为对象JSONObject 有些JSON类型的字符串无法直接转对象,需要先做处理.例如dataWrap.collectData()收集的数据: jsonStr = {"da ...

  2. java php json转字符串_php json字符串转为数组或对象

    从网上查到的方法是 用get_object_vars 把类类型转换成数组 然后在用foreach  遍历即可 $array = get_object_vars($test); $json= '[{&q ...

  3. php json字符串转为数组或对象

    从网上查到的方法是 用get_object_vars 把类类型转换成数组 然后在用foreach  遍历即可 $array = get_object_vars($test); $json= '[{&q ...

  4. c#谷歌 json转对象_利用Google Gson实现JSON字符串和对象之间相互转换

    最近一个项目需要用到JSON,需要在JSON字符串和对象之间相互转换,在网上找了些资料,发现google的Gson还是比较不错的. 废话不说,下面是简单的例子: 先上源码:下载(包含jar包) Per ...

  5. jq js json 转字符串_JQuery如何把JSON字符串转为JSON对象

    本文介绍JQuery如何把JSON字符串转为JSON对象. HTML代码 下面的HTML代码实现JQuery把JSON字符串转为JSON对象. Name: Age: City: Country: va ...

  6. JSON字符串转为指定实体类对象

    创建JsonStringToClass对象即可 package utils;import net.sf.json.JSONArray; import net.sf.json.JSONObject;im ...

  7. 利用viewbag把数据对象传到前端并转换成json对象,及解决json字符串被转义问题

    利用viewbag把数据对象传到前端并转换成json对象,及解决json字符串被转义问题 参考文章: (1)利用viewbag把数据对象传到前端并转换成json对象,及解决json字符串被转义问题 ( ...

  8. jsonobject json对象里面_将json字符串转为json对象,从对象中取需要的数据

    说明:有时候需要取json字符串中的某个键对应的值,这个时候有两个方案: 1.遍历json字符串,取需要的键对应的值.(太慢太繁琐太傻): 2.将json字符串转为json对象,从对象中取(方便): ...

  9. $.parseJSON() 函数用于将符合标准格式的的JSON字符串转为与之对应的JavaScript对象。

    $.parseJSON() 函数用于将符合标准格式的的JSON字符串转为与之对应的JavaScript对象. var obj = jQuery.parseJSON(data); if(data.sta ...

最新文章

  1. 【Qt】Qt再学习(三):Chart Themes Example(常用图表)
  2. reactor与proactor模式
  3. B站上线斯坦福最新「机器学习系统(MLSys)」全集,小伙伴有福了!
  4. Linux系统cpu负载浅析
  5. torch div优化
  6. python slenium 中CSS定位
  7. 大话HashMap的put,get过程
  8. 广州python平均薪资_爬取广州的python和Java薪资,为什么Python 高于Java(有代码)...
  9. c51单片机音乐盒c语言,毕业论文-基于AT89C51单片机的音乐盒设计(C程序).doc
  10. 【C++入门】C++ vector类
  11. 2017年网站建设公司现状分析
  12. Android 保存图片以后通知相册刷新
  13. rocketmq实现延迟队列
  14. 【数据分析】 Titanic乘客获救预测(2)数据处理
  15. PageX、clientX、screenX、offsetX、layerX的区别
  16. access统计班级人数_[access查询]access查询分段统计人数
  17. 微信小程序——定义事件相关
  18. TypeScript 研发规约落地实践
  19. Jmeter自定义函数
  20. sip篇——sip协议是什么?

热门文章

  1. 推荐:12条经过验证的创业赚钱秘诀!(转)
  2. 一个域名下面能搭建多个网站吗?
  3. 量子物理 詹班 计算机,6量子物理作业答案.doc
  4. 街舞中的rolling机器人_这,就是街舞中的那些“Swag”十足的舞蹈类型,你了解吗?...
  5. 如何将网址放到桌面并修改桌面快捷方式的图标
  6. Python爬取王者荣耀所有英雄以及高清大图
  7. 如何让虚拟角色自然融入现实?
  8. 如何用微信公众号快速注册小程序
  9. 用c语言如何以图形方式显示家谱,数据结构_家谱管理系统
  10. 如何合理使用ClickHouse分区表