自定义json序列化和反序列化

一、LocalDateTime反序列化异常

首先我们定义一个java POJO实体类,其中关键的成员变量时birthDate,我们没有采用Date数据类型,而是采用了Java8 新的日期类型LocalDateTime,使用LocalDateTime的好处我就不多说了,有很多的文章解释说明。我们把精力放回到Jackson的JSON格式序列化与反序列化内容上来。

@Data
public class PlayerStar4 {private String name; //姓名private LocalDateTime birthDate; //出生日期
}

下面的代码,我们首先定义了一个PlayerStar4类的对象player,然后

  • 使用writeValueAsString方法将player对象序列化为JSON字符串jsonString
  • 然后使用readValue方法将JSON字符串jsonString ,反序列化为PlayerStar4类的对象
@Test
void testJSON2Object() throws IOException {ObjectMapper mapper = new ObjectMapper();PlayerStar4 player = new PlayerStar4();player.setName("curry");//我并不知道库里的生日,这里是编造的player.setBirthDate(LocalDateTime.of(1986,4,5,12,50));//将player对象以JSON格式进行序列化为String对象String jsonString = mapper.writeValueAsString(player);System.out.println(jsonString);//将JSON字符串反序列化为java对象PlayerStar4 curry = mapper.readValue(jsonString, PlayerStar4.class);System.out.println(curry);}

但是上面的代码报错了,从下图中可以看出

  • 将player对象序列化为JSON字符串jsonString 的过程被正常执行了,但是LocalDateTime序列化之后的结果,是图中”黄框中的黄框“内容。
  • 将JSON字符串反序列化的过程报错了,因为Jackson默认情况下,根本不认识图中”黄框中的黄框“内容这种LocalDateTime序列化之后的JSON字符串数据结构。无法把它反序列化为java对象。

怎么办?我们需要自定义序列化及反序列化类型转换器,有两种方法

  • 继承StdConverter类,自定义实现String与LocalDateTime相互转换
  • 继承JsonSerializer和JsonDeserializer类,自定义实现String与LocalDateTime相互转换

二、方法一:继承StdConverter类

继承StdConverter类,将LocalDateTime序列化为String数据类型

public class LocalDateTimeToStringConverter extends StdConverter<LocalDateTime, String> {static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM);@Overridepublic String convert(LocalDateTime value) {return value.format(DATE_FORMATTER);}
}

继承StdConverter类,将String数据类型反序列化为LocalDateTime

public class StringToLocalDatetimeConverter extends StdConverter<String, LocalDateTime> {@Overridepublic LocalDateTime convert(String value) {return LocalDateTime.parse(value, LocalDateTimeToStringConverter.DATE_FORMATTER);}
}

自定义的转换器完成之后,我们就可以在对应的成员变量上,使用@JsonSerialize指定序列化转换器,@JsonDeserialize指定反序列化转换器。

  @JsonSerialize(converter = LocalDateTimeToStringConverter.class)@JsonDeserialize(converter = StringToLocalDatetimeConverter.class)private LocalDateTime birthDate;

然后调用第一小节中的测试用例,就不会出现异常了。控制台打印输出结果如下,第一行是序列化结果JSON格式字符串,第二行是Java 对象的toString()方法的打印结果。

{"name":"curry","birthDate":"1986-4-5 12:50:00"}
PlayerStar4(name=curry, birthDate=1986-04-05T12:50)

三、方法二:继承JsonSerializer和JsonDeserializer类

继承JsonSerializer<LocalDateTime>类,将LocalDateTime序列化为String数据类型

public class LocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM);@Overridepublic void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider provider)throws IOException {String s = value.format(DATE_FORMATTER);gen.writeString(s);}
}

继承JsonDeserializer<LocalDateTime>类,将String数据类型反序列化为LocalDateTime

public class LocalDatetimeDeserializer extends JsonDeserializer<LocalDateTime> {@Overridepublic LocalDateTime deserialize(JsonParser p, DeserializationContext ctx)throws IOException {String str = p.getText();return LocalDateTime.parse(str, LocalDateTimeSerializer.DATE_FORMATTER);}
}

四、如果上面的你都没看懂

对于相对小白的读者,上面的自定义序列化及反序列化转换过程你都没懂,对于LocalDateTime的异常你也不要慌,Jackson已经给出了解决方案。

需要特别和大家强调的是LocalDateTimeSerializer和LocalDateTimeDeserializer其实并不需要我们自己去定义,因为Jackson已经帮我们定义好了。 之所以我还做了自定义的实现的介绍,是因为要为大家讲解这个自定义序列化和反序列化类型转换的实现过程,以后你再遇到其他的特殊的数据类型转换,或者LocalDateTime类型的特殊日期格式等,都可以自己来定义JsonSerialize和JsonDeserialize来实现数据类型的转换。

下面的这两个类就是Jackson已经帮我们定义好的LocalDateTimeSerializer和LocalDateTimeDeserializer。

import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;

使用方法是在对应的成员变量上,使用@JsonSerialize指定序列化转换器,@JsonDeserialize指定反序列化转换器。

@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
private LocalDateTime birthDate;

执行之后的序列化和反序列化结果,和方法一、方法二自定义的实现效果是一样的。

以下是解决类型为Date

日常Web开发中对日期格式的序列化与反序列化是必不可少,在微服务下若没有一套完善且统一的配置,会出现各种奇奇怪怪的问题,如@JsonFormat(pattern = "yyyy-MM-dd")默认的是GMT时区,而中国是GMT+8的东八区,若不声明时区会少一个小时,又比如若两服务序列化配置不一致,会导致远程调用失败等

需求点

  1. 接口入参无论是yyyy-MM-dd还是yyyy-MM-dd HH:mm:ss 均支持反序列化
  2. 反参序列化,默认为yyyy-MM-dd HH:mm:ss ,但支持某些字段以@JsonFormat(pattern = "yyyy-MM-dd")定义
  3. 不会有时区问题

方案1:无任何配置

  1. 默认返回的是时间戳,时区是系统自带的时区
  2. 需在每一个字段都加上@JsonFormat 进行配置

虽说这样做没有问题,但需要在每一个dto上面的日期字段加注解,肯定不科学

方案2:使用配置文件指定

spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8
  1. 指定后,序列化和反序列化都只能是一个格式
  2. 若入参是yyyy-MM-dd,会报错,就算使用@JsonFormat(pattern = "yyyy-MM-dd")也无济于事,此注解对反序列化无效

# 方案3:拓展 DateFormat


@Bean
public ObjectMapper getObjectMapper() {ObjectMapper objectMapper = new ObjectMapper();objectMapper.setDateFormat(new ObjectMapperDateFormat());return objectMapper;
}/*** 扩展jackson日期格式化支持格式*/
public static class ObjectMapperDateFormat extends DateFormat {/*** 序列化*/@Overridepublic StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition) {return new StringBuffer(DateUtil.formatDateTime(date));}/*** 反序列化*/@Overridepublic Date parse(String source, ParsePosition pos) {source = source.trim();pos.setIndex(source.length());return DateUtil.parse(source);}@Overridepublic Object clone() {return new WebConfig.ObjectMapperDateFormat();}/*** 此方法无效,不止何解*/@Overridepublic TimeZone getTimeZone() {return TimeZone.getTimeZone("GMT+8");}
}
  1. 这样做后入参的反序列化可以自行拓展,比如支持yyyy-MM-ddHH:mm:ss
  2. 序列化只能一种格式,若想支持多种而使用@JsonFormat自定义格式化的话,会有时区问题!,必须显式指定时区:@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")

关于第二点这个坑,我研究了一上午想全局指定时区,但好像不太行,尝试的方法:

  1. 在配置文件指定时区,不行,因为配置文件其实已经无用了
  2. objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+8")); 会抛异常,自定义的DateFormat就是会抛异常,百思不得其解,但使用其自带的SimpleDateFormat,就正常的
  3. 那我在想会不会拓展的DateFormat自己可以指定时区?(上面代码的getTimeZone 方法),尝试了也是不行的,根据断点可知使用@JsonFormat后,其序列化是不会走拓展的DateFormat,而是走自带的StdDateFormat.java

所以该方案,如果想在不同接口返回不同的日期格式,一定要指定时区,除了这点,倒也没其他问题,但是一点都不优雅

方案4:自定义序列化、反序列化的处理器(完美方案)

@Bean
public ObjectMapper getObjectMapper() {SimpleModule simpleModule = new SimpleModule();simpleModule.addDeserializer(Date.class, new MyJsonDeserializer());simpleModule.addSerializer(Date.class, new MyJsonSerializer());ObjectMapper objectMapper = new ObjectMapper();objectMapper.registerModule(simpleModule);return objectMapper;
}/*** 自定义反序列化处理器* 支持yyyy-MM-dd、yyyy-MM-dd HH:mm:ss*/
public static class MyJsonDeserializer extends JsonDeserializer<Date> {@Overridepublic Date deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {String source = p.getText().trim();try {return DateUtil.parse(source);} catch (Exception e) {e.printStackTrace();return null;}}
}/*** 自定义序列化处理器*/
@NoArgsConstructor
@AllArgsConstructor
public static class MyJsonSerializer extends JsonSerializer<Date> implements ContextualSerializer {private JsonFormat jsonFormat;/*** 默认序列化yyyy-MM-dd HH:mm:ss* 若存在@JsonFormat(pattern = "xxx") 则根据具体其表达式序列化*/@Overridepublic void serialize(Date value, JsonGenerator gen, SerializerProvider serializers) throws IOException {if (value == null) {gen.writeNull();return;}String pattern = jsonFormat == null ? DatePattern.NORM_DATETIME_PATTERN : jsonFormat.pattern();gen.writeString(DateUtil.format(value, pattern));}/*** 通过字段已知的上下文信息定制 JsonSerializer* 若字段上存在@JsonFormat(pattern = "xxx") 则根据上面的表达式进行序列化*/@Overridepublic JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) {JsonFormat ann = property.getAnnotation(JsonFormat.class);if (ann != null) {return new MyJsonSerializer(ann);}return this;}
}

此方案可完美解决文章头部提到的需求点,序列化时,通过实现ContextualSerializer 获取字段已知的上下文信息,即获取@JsonFormat中的表达式进行格式化,且不会有时区问题

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/46114.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java常见JUC并发工具类

Lock 并发编程领域的两大核心问题&#xff1a; 一个是 互斥&#xff0c;即同一时刻只允许一个线程访问共享资源 另一个是 同步&#xff0c;即线程之间如何通信、协作 这两大问题&#xff0c;管程&#xff08;synchronized&#xff09;都是能够解决的。Java SDK并发包通过Lock和…

【Linux】进程控制的详细介绍

前言 在此之前&#xff0c;我们学过进程的概念&#xff0c;进程的状态&#xff0c;进程地址空间等一系列进程相关的问题。本章我们继续学习进程&#xff0c;我们要来学习一下进程的控制&#xff0c;关于进程等待&#xff0c;进程替换等问题。 目录 1.再次认识Fork函数1.1 fork…

internet download manager(IDM下载器) 6.42.8.2下载安装使用指南

internet download manager(IDM下载器) 6.42.8.2Z是一款功能强大的下载加速工具&#xff0c;能够显著提升您的下载速度&#xff0c;最高可达500%。它不仅能够加速下载&#xff0c;还能对下载任务进行智能调度&#xff0c;并具备恢复中断下载的能力。根据用户评价&#xff0c;无…

初识C++(命名空间、缺省参数)

初识C 命名空间namespace关键字命名空间的使用 缺省参数 命名空间 namespace关键字 在C中&#xff0c;为了尽可能避免命名冲突&#xff0c;需要对各个变量进行域作用限定&#xff0c;这就需要使用到namespace关键字&#xff0c;namespace可以定义一个命名空间&#xff0c;即命…

LabVIEW红外热波图像缺陷检

开发使用LabVIEW开发的红外热波图像缺陷检测系统。该系统结合红外热像仪、工业相机和高效的数据采集硬件&#xff0c;实现对工件表面缺陷的自动检测和分析。通过LabVIEW的强大功能&#xff0c;系统能够实时采集、处理和显示红外热波图像&#xff0c;有效提高了检测的精度和效率…

vue:标签属性绑定Vue实例【ref,reactive,内置指令v-bind,v-on】,预定义变量、方法【$methods,$computed】

Vue2、3组件通信、双向绑定、插槽slot、内置指令_组件双向绑定-CSDN博客​Vue2&#xff0c;3响应式原理&#xff0c;ref和reactive&#xff0c;toRef和toRefs&#xff0c;shallowRef和shallowRefs_vue2 shallowref-CSDN博客 vue2【Options 选项API、mixin混入】&#xff0c;vu…

WAF基础介绍

WAF 一、WAF是什么&#xff1f;WAF能够做什么 二 waf的部署三、WAF的工作原理 一、WAF是什么&#xff1f; WAF的全称是&#xff08;Web Application Firewall&#xff09;即Web应用防火墙&#xff0c;简称WAF。 国际上公认的一种说法是&#xff1a;Web应用防火墙是通过执行一…

免开steam 脱离steam 进行游戏的小工具

链接&#xff1a;https://pan.baidu.com/s/1k2C8b4jEqKIGLtLZp8YCgA?pwd6666 提取码&#xff1a;6666 我们只需选择游戏根目录 然后输入AppID 点击底部按钮 进行就可以了 关于AppID在&#xff1a;

机器学习——L1 L2 范数 —>L1 L2正则化

1、L1范数和L2范数是机器学习和数据分析中经常使用的两种范数&#xff0c;它们之间存在多个方面的区别。 以下是关于L1范数和L2范数区别的详细解释&#xff1a; 一、定义差异 L1范数&#xff1a;也被称为曼哈顿范数&#xff0c;是向量元素的绝对值之和。对于一个n维向量x&am…

酒店管理系统小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;酒店管理员管理&#xff0c;房间类型管理&#xff0c;房间信息管理&#xff0c;订单信息管理&#xff0c;系统管理 微信端账号功能包括&#xff1a;系统首页&#xff0c;房间信息…

Linux介绍与常用命令详解

目录 一、Linux概述 1.Linux发行版 2.Linux目录结构 二、Linux特点 三、Linux用途 四、Linux常用的命令 1.cd指令&#xff08;跳转位置&#xff09; 2.显示目录文件 3.对文件进行操作 4.rm指令&#xff08;删除文件夹指令&#xff09; 5.mv指令 6.查看文件命令 7.进程命令…

【云岚到家】-day05-6-项目迁移-门户-CMS

【云岚到家】-day05-6-项目迁移-门户-CMS 4 项目迁移-门户4.1 迁移目标4.2 能力基础4.2.1 缓存方案设计与应用能力4.2.2 静态化技术应用能力 4.3 需求分析4.3.1 界面原型 4.4 系统设计4.4.1 表设计4.4.2 接口与方案4.4.2.1 首页信息查询接口4.4.3.1 数据缓存方案4.4.3.2 页面静…

Ubuntu20.04 编译安装FFmpeg,出错分析以及解决方案

最近工程上需要对FFmpeg底层源码进行修改&#xff0c;需要重新编译&#xff0c;遇见不少坑&#xff0c;出篇教程记录一下。 文章目录 1.FFmpeg源码下载地址2.编译环境配置3.编译FFmpeg4.配置FFmpeg运行环境 1.FFmpeg源码下载地址 官方下载地址:Index of /releases (ffmpeg.or…

Java | Leetcode Java题解之第232题用栈实现队列

题目&#xff1a; 题解&#xff1a; class MyQueue {Deque<Integer> inStack;Deque<Integer> outStack;public MyQueue() {inStack new ArrayDeque<Integer>();outStack new ArrayDeque<Integer>();}public void push(int x) {inStack.push(x);}pub…

springboot1——快速构建项目

需求 第一步&#xff1a;创建maven工程(非web项目) 第二步&#xff1a;导入起步依赖 点击&#xff1a; 下拉复制&#xff1a; 粘贴&#xff1a;&#xff01;&#xff01;这是springboot工程需要继承的父工程 下拉复制&#xff1a; 粘贴&#xff1a;&#xff01;&#xf…

Nodejs 第八十章(Kafka高级)

kafka前置知识在前几章章讲过了 不再复述 Kafka集群操作 1.创建多个kafka服务 拷贝一份kafka完整目录改名为kafka2 修改配置文件 kafka2/config/server.properties 这个文件 broker.id1 //唯一broker port9093 //切换端口 listenersPLAINTEXT://:9093 //切换监听源启动zooKe…

常见问题记录(持续更新)

备注&#xff1a; 在7月10日记录之前遇到的问题及解决方法: 一&#xff1a;常见的访问问题&#xff1a; 403 Forbidden&#xff1a;&#xff08;未有请求权限&#xff09; 表示服务器理解请求但是拒绝执行它。这通常是由于服务器上的文件或资源没有正确的读、写或执行权限&…

python接口自动化(二十四)--unittest断言——中(详解)

1.简介 上一篇通过简单的案例给小伙伴们介绍了一下unittest断言&#xff0c;这篇我们将通过结合和围绕实际的工作来进行unittest的断言。这里以获取城市天气预报的接口为例&#xff0c;设计了 2 个用例&#xff0c;一个是查询北京的天气&#xff0c;一个是查询 南京为例&#…

[MySQL][数据类型]详细讲解

目录 1.说明1.数据类型分类2.数值类型1.int类型2.bit类型3.浮点数类型1.float2.decimal 3.字符串类型1.char2.varchar3.char和varchar比较 4.日期和时间类型5.enum和set1.基本语法2.查询数据 1.说明 MySQL表中建立属性列&#xff0c;列名称 类型 num tinyint unsigned;与C/C语…

优化Cocos Creator 包体体积

优化Cocos Creator 包体体积 引言一、优化图片文件体积&#xff1a;二、优化声音文件体积&#xff1a;三、优化引擎代码体积&#xff1a;四、 优化字体字库文件大小&#xff1a; 引言 优化Cocos Creator项目的包体体积是一个常见且重要的任务&#xff0c;尤其是在移动设备和网…