解决json字符串转为对象时LocalDateTime异常问题
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;
@Autowired
public 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> {
// 省略不需要修改的代码
/**
* 关键方法
*/
@Override
public 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-MM
return LocalDateTime.parse(source + "-01 00:00:00", dateTimeFormatter);
} else if (source.matches("^\\d{4}-\\d{1,2}-\\d{1,2}$")) {
// yyyy-MM-dd
return 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:mm
return 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:ss
return LocalDateTime.parse(source, dateTimeFormatter);
} else {
throw new IllegalArgumentException("Invalid datetime value '" + source + "'");
}
}
}
在这个过程中,我对博客中的方法做了改进,在解析字符串的使用,用正则表达式判断这个日期的实际格式,然后再将字符串解析成LocalDateTime。这种方法使转换过程可以兼容多种日期类型,达到了我想要的效果。
4.4 替换反序列化器
但是我按照博客中的方法来替换,却并没有产生效果。反序列化的时候,
@Configuration
public class LocalDateTimeSerializerConfig {
@Bean
public ObjectMapper serializingObjectMapper() {
JavaTimeModule module = new JavaTimeModule();
// 这里导包的时候选择自己定义的LocalDateTimeDeserializer
LocalDateTimeDeserializer 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
@Override
public 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时出现解析异常的问题就完全解决了。
本文由博客群发一文多发等运营工具平台 OpenWrite 发布
解决json字符串转为对象时LocalDateTime异常问题的更多相关文章
- 小程序json字符串转为对象
小程序里json字符串转为对象使用JSON.parse()方法转变无效, 看报错提示有单引号“ ' ” 因为单引号而无效, 将单引号全改双引号即可. 报错如下: VM11050:1 thirdScri ...
- C# -- 把json字符串转为对象并读取各属性的值
前面2种方法是不需要声明一个Json字符串的类型即可把Json字符串转换为Dictionary对象 而第3种方法则是声明一个Json字符串的强类型对象,然后反序列化为该对象的数据. List<, ...
- C# json字符串转为对象
方法1: using System.Web.Script.Serialization; string ss = "{\"NewsCount\":\"3482\& ...
- php json字符串转为数组或对象
从网上查到的方法是 用get_object_vars 把类类型转换成数组 然后在用foreach 遍历即可 $array = get_object_vars($test); $json= '[{&q ...
- Google Gson实现JSON字符串和对象之间相互转换
User实体类 package com.test.json; /** * User 实体类 */ public class User { private String name; private St ...
- json字符串转为json对象-jQuery.parseJSON()
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...
- DataTable转json字符串,jQuery.parseJSON()把json字符串转为标准的json对象格式
1.string res = DataTableToJson.DataTable2Json(dt);讲DataTable转换为json字符串 http://www.365mini.com/page/j ...
- 客户端、服务器端中JSON字符串与对象的转换
客户端: 字符串转为对象:$.parseJSON(json); 对象转为字符串:JSON.stringify(_pasteDataItem) 服务器端(c#): 对象: [DataContract(N ...
- 速战速决 (6) - PHP: 获取 http 请求数据, 获取 get 数据 和 post 数据, json 字符串与对象之间的相互转换
[源码下载] 速战速决 (6) - PHP: 获取 http 请求数据, 获取 get 数据 和 post 数据, json 字符串与对象之间的相互转换 作者:webabcd 介绍速战速决 之 PHP ...
随机推荐
- php-fpm 进程数的设定
近日,服务器出现异常,网站不能正常访问.经排查是php的问题. 在重启php-fpm时,恢复正常.1分钟之后又出现故障.查看php日志文件 /usr/local/php/var/log 后提示 WAR ...
- Java 泛型、通配符? 解惑
Java 泛型通配符?解惑 分类: JAVA 2014-05-05 15:53 2799人阅读 评论(4) 收藏 举报 泛型通配符上界下界无界 目录(?)[+] 转自:http://www.linux ...
- 知识点一:OSI模型初识
OSI(开放系统)模型是一组协议的集合,它使得两个不同的系统之间能够互相通信,分为七层 第一层:物理层 物理层负责把逐个的比特(01)从一个节点移动到下个节点 具体体现在如何把比特转换成电或者光信号. ...
- ThreeJs 导入外部三维模型,并实现鼠标滚动放大缩小旋转效果
let i = ; function init() { // create a scene, that will hold all our elements such as objects, came ...
- Complete the Sequence HDU - 1121
题目大意: 输入两个数n和m,n表示有n个数,这n个数是一个多项式的前n项,让输出这个序列的n+1,n+2,..n+m项. 题解:差分规律,一直差分,直到全为0或者只剩下一个数.然后再递推回去. 给出 ...
- Windows Pains poj 2585
Boudreaux likes to multitask, especially when it comes to using his computer. Never satisfied with j ...
- Oracle数据库提权
一.执行java代码 简介 oracle提权漏洞集中存在于PL/SQL编写的函数.存储过程.包.触发器中.oracle存在提权漏洞的一个重要原因是PL/SQL定义的两种调用权限导致(定义者权限和调用者 ...
- Yii2.0 rules常用验证规则
设置一个修改方法,但是save(),没有成功,数据修改失败,查了好久,一般情况就是不符合rules规则,而我没有设置rules规则,重新设置了一个不能为空,然后就修改成功,rules里面什么也不写,也 ...
- 【考试总结】欢乐模拟赛_Day2
\(T1\) 题目描述 在仙界中有着 \(n\) 位神仙, 每位神仙用一个 \(1 ∼ n\) 的特异编号表示, 老祖 \(ChitongZ\) 的编号为 \(1\) . 除去至尊至圣, 统管仙界的老 ...
- search(8)- elastic4s-search-query模式
上篇提过query模式除对记录的筛选之外还对符合条件的记录进行了评分,即与条件的相似匹配程度.我们把评分放在后面的博文中讨论,这篇我们只介绍query查询. 查询可以分为绝对值查询和全文查询:绝对值查 ...