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

news2024/9/25 1:21:44

一、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);

  @Override
  public String convert(LocalDateTime value) {
      return value.format(DATE_FORMATTER);
  }
}

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

public class StringToLocalDatetimeConverter extends StdConverter<String, LocalDateTime> {
  @Override
  public 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);

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

  @Override
  public 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 {
    /**
     * 序列化
     */
    @Override
    public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition) {
        return new StringBuffer(DateUtil.formatDateTime(date));
    }

    /**
     * 反序列化
     */
    @Override
    public Date parse(String source, ParsePosition pos) {
        source = source.trim();
        pos.setIndex(source.length());
        return DateUtil.parse(source);
    }

    @Override
    public Object clone() {
        return new WebConfig.ObjectMapperDateFormat();
    }

    /**
     * 此方法无效,不止何解
     */
    @Override
    public 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> {
    @Override
    public 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") 则根据具体其表达式序列化
     */
    @Override
    public 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") 则根据上面的表达式进行序列化
     */
    @Override
    public 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.coloradmin.cn/o/1920675.html

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

相关文章

技术成神之路:设计模式(五)抽象工厂模式

1.介绍 抽象工厂模式&#xff08;Abstract Factory Pattern&#xff09;是一种创建型设计模式&#xff0c;它提供了一种创建一系列相关或相互依赖对象的接口&#xff0c;而无需指定其具体类。这种模式属于工厂模式的一种扩展&#xff0c;它通过引入抽象层来实现工厂方法的组合&…

vue3 - vue项目自动检测更新

GitHub Demo 地址 在线预览 web项目当页面检测到需要更新&#xff0c;然后弹框提示是否更新&#xff08;刷新页面&#xff09;这种可以通过纯前端实现也可以通过接口实现 接口实现&#xff1a;通过调用接口轮询和本地的版本号比较&#xff0c;检查是否需要弹框提示更新纯前端实…

RAFT RAG GraphRAG

解读人类语言真实意图是一门不完美的学问。相关搜索是一个认知迷宫&#xff0c;即使是最先进的 AI 也无法(完全)解决&#xff01; # RAG Retrieval-Augmented Generation # Retrieval Augmented FineTuning&#xff08;RAFT&#xff09; RAFT的核心思想是结合监督式微调&#…

云WAF | 云waf保护你的网络安全

随着时代的发展&#xff0c;云计算与网络安全成为当今社会的热点问题。由于网络环境的日益复杂&#xff0c;网络安全问题日益突出&#xff0c;网络安全问题日益突出。近年来&#xff0c;各类网络安全工具与技术层出不穷&#xff0c;以保障用户信息及企业财产安全。云服务防火墙…

机器学习——决策树(笔记)

目录 一、认识决策树 1. 介绍 2. 决策树生成过程 二、sklearn中的决策树 1. tree.DecisionTreeClassifier&#xff08;分类树&#xff09; &#xff08;1&#xff09;模型基本参数 &#xff08;2&#xff09;模型属性 &#xff08;3&#xff09;接口 2. tree.Decision…

289个地级市-资源型城市划分数据

资源型城市&#xff1a;经济地理的独特现象与可持续发展的挑战 资源型城市是指那些以丰富的自然资源为基础&#xff0c;对国家经济和工业化进程有着重要影响的城市。这些城市在国家现代化建设中扮演着关键角色&#xff0c;其发展状况直接关系到区域经济的繁荣与社会的稳定。 资…

Go-知识测试-模糊测试

Go-知识测试-模糊测试 1. 定义2. 例子3. 数据结构4. tesing.F.Add5. 模糊测试的执行6. testing.InternalFuzzTarget7. testing.runFuzzing8. testing.fRunner9. FuzzXyz10. RunFuzzWorker11. CoordinateFuzzing12. 总结 建议先看&#xff1a;https://blog.csdn.net/a1879272183…

GitHub连接超时问题 Recv failure: Connection was reset

用手机热点WIF拉取git项目的时候&#xff0c;遇到Recv failure: Connection was reset问题。 解决办法 一、手动开启本地代理 二、在终端&#xff08;cmd&#xff09;输入命令 git config --global http.proxy http://127.0.0.1:7890 git config --global https.proxy https:…

QT实现自定义带有提示信息的透明环形进度条

1. 概述 做界面开发的童鞋可能都会遇到这样的需求&#xff0c;就是有一些界面点击了之后比较耗时的操作&#xff0c;需要界面给出一个环形进度条的进度反馈信息. 如何来实现这样的需求呢&#xff0c;话不多说&#xff0c;上效果 透明进度条 2. 代码实现 waitfeedbackprogressba…

2006-2021年 291个地级市资源错配指数、劳动和资本相对扭曲指数do文件和结果

资源错配指数&#xff1a;衡量生产要素配置效率的关键指标 资源错配指数&#xff08;Misallocation Index&#xff09;是一个衡量资源配置效率的指标&#xff0c;它反映了生产要素是否得到了合理配置&#xff0c;以及是否达到了生产效率的最优状态。一个较高的资源错配指数意味…

three-tile: 一个开源的轻量级三维瓦片库

three-tile 介绍 three-tile 是一个开源的轻量级三维瓦片库&#xff0c;它基于threejs使用typescript开发&#xff0c;提供一个三维地形模型&#xff0c;能轻松给你的应用增加三维瓦片地图。 https://blog.csdn.net/HZGJF/article/details/140280844 源码&#xff1a;https:/…

malloc与free函数的用法(精简全面 · 一看即懂)

前言&#xff1a;Hello大家好&#x1f618;&#xff0c;我是心跳sy&#xff0c;今天为大家带来malloc函数与free函数的用法&#xff0c;我们一起来看看吧&#xff01; 目录 一、malloc函数 &#x1f4ab; 1、⭐️malloc函数对应的头文件⭐️ 2、⭐️malloc函数的作用⭐️ 3…

高职计算机网络实训室

一、高职计算机网络实训室建设的背景 如今&#xff0c;数字化发展已成为国家发展的战略方向&#xff0c;是推动社会进步和经济发展的重要动力。在这一时代背景下&#xff0c;计算机网络技术作为数字化发展的基础设施&#xff0c;其地位和作用愈发凸显。因此&#xff0c;高职院…

Windows中nvm的安装配置和卸载

文章目录 Windows中安装配置&#xff0c;和卸载nvm一、卸载node二、安装vnm三、配置环境&#xff08;配置路径和下载源&#xff09;四、使用nvm安装node五、nvm常见的命令六、nvm的卸载 Windows中安装配置&#xff0c;和卸载nvm 一、卸载node 如果没有node可以直接了跳过该步…

<数据集>Udacity交通目标识别数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;13239张&#xff08;实际有15000张&#xff0c;但其中1761张无标签的图片被我去除掉&#xff09; 标注数量(xml文件个数)&#xff1a;13239 标注数量(txt文件个数)&#xff1a;13239 标注类别数&#xff1a;11 标注…

全开源TikTok跨境商城源码/TikTok内嵌商城+搭建教程/前端uniapp+后端

多语言跨境电商外贸商城 TikTok内嵌商城&#xff0c;商家入驻一键铺货一键提货 全开源完美运营 海外版抖音TikTok商城系统源码&#xff0c;TikToK内嵌商城&#xff0c;跨境商城系统源码 接在tiktok里面的商城。tiktok内嵌&#xff0c;也可单独分开出来当独立站运营 二十一种…

Tomcat下载安装配置教程(零基础超详细)

「作者简介」&#xff1a;冬奥会网络安全中国代表队&#xff0c;CSDN Top100&#xff0c;就职奇安信多年&#xff0c;以实战工作为基础著作 《网络安全自学教程》&#xff0c;适合基础薄弱的同学系统化的学习网络安全&#xff0c;用最短的时间掌握最核心的技术。 Tomcat 1、下载…

Docker 镜像构建报 exec xxx.sh: no such file or directory

问题记录 场景&#xff1a; 处于对nacos docker 部署最新版本的探究&#xff0c;但是nacos/nacos-server镜像拉取不到最新版本&#xff0c;官网也是给出自己构建镜像的方案。 具体步骤很简单&#xff0c;先clone项目&#xff0c;然后签出你要的nacos版本&#xff0c;通过docke…

Java——IO流(二)-(7/7):补充知识:IO框架(什么是框架、什么是IO框架、Commons-io)

目录 什么是框架 什么是IO框架 Commons-io 常用方法 框架下载 框架导入 实例演示 什么是框架 解决某类问题&#xff0c;编写的一套类、接口等&#xff0c;可以理解成一个半成品&#xff0c;大多框架都是第三方研发的。好处&#xff1a;在框架的基础上开发&#xff0c;可…

edge 学习工具包 math solver

简介 推荐微软推出的学习工具中的两项工具&#xff1a;数学求解器和 pdf 阅读器。 打开 edge 学习工具包的方法 &#xff1a;右上角三点-更多工具-学习工具包。 math solver 除了基础的计算求解外&#xff0c;还用图标展示公式&#xff0c;清晰直观。 地址&#xff1a;求解…