自定义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);

  @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中的表达式进行格式化,且不会有时区问题, End ~

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

图鸟UI框架在uni-app多端应用开发中的实践与应用

摘要&#xff1a; 随着移动互联网的蓬勃发展&#xff0c;跨平台应用开发已成为行业趋势。本文将探讨图鸟UI框架如何在uni-app开发环境下助力开发者高效构建多端应用&#xff0c;并通过具体案例展示其在实际项目中的应用效果。 一、引言 在移动应用开发领域&#xff0c;跨平台…

高职软件测试实训室

一、高职软件测试实训室建设背景 随着《中华人民共和国国民经济和社会发展第十四个五年规划和2035年远景目标纲要》的深入实施&#xff0c;我国正在以不可阻挡的势头迈进数字化新时代。在这个波澜壮阔的时代背景下&#xff0c;软件作为数字经济的核心驱动力&#xff0c;其质量…

变量和常量(局部变量和全局变量)

常变的值叫变量&#xff0c;不变的值叫常量 变量分为局部变量和全局变量 在同一范围内&#xff0c;变量只能定义一次&#xff0c;否则就会报错 全部变量和局部变量是可以同时存在的&#xff0c;不过使用的时候是局部优先 变量如果你不给他初始化&#xff0c;那么他放得就是一…

【UML用户指南】-33-对体系结构建模-系统和模型

目录 1、系统和子系统 2、模型和视图 3、跟踪 4、常用建模技术 4.1、对系统的体系结构建模 4.2、对系统的系统建模 模型是对现实世界的简化——即对系统的抽象&#xff0c;建立模型的目的是为了更好地理解系统。 1、系统和子系统 一个系统可能被分解成一组子系统&#…

SAP ABAP ME21N 采购订单行项目屏幕增强

一、事务代码&#xff1a;SMOD 增强点&#xff1a;MM06E005 1.在CI_EKPODB 组建中添加自定义字段 2.事务码&#xff1a;SE11 进入CI_EKPODB 二、事务码&#xff1a;SE38 ZXM06TOP 定义结构 创建子屏幕 1.代码如下&#xff1a; TABLES:ci_ekpodb. DATA:EDIT_MODE TYPE cha…

Cocos如何跟iOS通信?

点击上方亿元程序员+关注和★星标 引言 Cocos如何跟iOS通信 大家好,相信小伙伴们通过阅读笔者前几期的文章**《你那么牛,怎么不教我打iOS包?安排!》,对Cocos如何打iOS**包有了一定的了解。 但是,除了把iOS包打出来,另外还有一个重要的就是要能够调用iOS提供的OC方法以…

2024数据挖掘实战

1、项目案例描述 沃尔玛全年都会举办几次促销减价活动。这些减价活动都是在重要节假日之前进行的&#xff0c;其中最大的四个节假日是超级碗、劳动节、感恩节和圣诞节。包括这些节假日在内的几周在评估中的权重是非节假日周的五倍。在缺乏完整/理想历史数据的情况下&#xff0…

《低碳世界》知网收录吗?如何投稿?

《低碳世界》知网收录吗&#xff1f;如何投稿&#xff1f; 《低碳世界》第一批学术期刊&#xff0c;月刊&#xff0c;知网、万方、维普、超星收录&#xff0c;要求&#xff1a;三版约5300字符 《低碳世界》是中国学术期刊&#xff08;光盘版&#xff09;全文收录期刊&#xf…

《A++ 敏捷开发》- 10 二八原则

团队成员协作&#xff0c;利用项目数据&#xff0c;分析根本原因&#xff0c;制定纠正措施&#xff0c;并立马尝试&#xff0c;判断是否有效&#xff0c;是改善的“基本功”。10-12章会探索里面的注意事项&#xff0c;13章会看两家公司的实施情况和常见问题。 如果已经获得高层…

CSS技巧专栏:一日一例 3.纯CSS实现炫酷多彩按钮特效

大家好,今天是 CSS技巧专栏:一日一例 第三篇《纯CSS实现炫酷多彩按钮特效》 先看图: 开工前的准备工作 正如昨日所讲,为了案例的表现,也处于书写的习惯,在今天的案例开工前,先把昨天的准备工作重做一遍。 清除浏览器的默认样式定义页面基本颜色设定body的样式清除butt…

AI普及时代即将来临,我们如何提升自我竞争力?

自ChatGPT发布以来&#xff0c;形形色色的AI工具形同雨后春笋&#xff0c;令人眼花缭乱&#xff0c;不知所措。 许多听说过AI的人&#xff0c;或者使用过AI工具&#xff0c;如 文心一言&#xff0c;通义千问&#xff0c;ChatGPT等等也只会提一些简单的问题。那么&#xff0c;面…

数据结构与算法基础-学习-37-平衡二叉树(Avl树)之删除节点

目录 一、知识点回顾 1、二叉搜索树&#xff08;BST&#xff09; 2、平衡二叉树&#xff08;Avl树&#xff09;之查找 二、环境信息 三、实现思路 1、示例图 2、查询 3、删除 &#xff08;1&#xff09;叶子节点&#xff08;无子树节点&#xff09; &#xff08;2&am…

macOS系统下载navicat安装包

链接: https://pan.baidu.com/s/1SqTIXNL-B8ZMJxIBu1DfIw?pwdc1z8 提取码: c1z8 安装后效果

嘉立创EDA学习笔记

嘉立创EDA学习笔记 PCB原理图设计PCB原理图设计顺序器件选型 PCB引线一、设计规则间距安全间距其他间距 物理导线网络长度差分对过孔尺寸 平面铺铜 二、PCB布线三、DRC检查四、PCB布线优化 打样白嫖生产进度追踪 作为一个嵌入式开发潜力工程师&#xff0c;咱们必须得学会如何绘…

[终端安全]-7 后量子密码算法

本文参考资料来源&#xff1a;NSA Releases Future Quantum-Resistant (QR) Algorithm Requirements for National Security Systems > National Security Agency/Central Security Service > Article Commercial National Security Algorithm Suite 2.0” (CNSA 2.0) C…

赛迪顾问ITSS服务经理发布多项2024年IT趋势报告

在深入探讨算力、工业控制系统、网络安全、数据治理、人工智能、数字化转型、5G通信等12大IT关键领域的基础上&#xff0c;赛迪顾问ITSS服务经理于1月16日以“乘势而上&#xff0c;及锋而试”为主题&#xff0c;成功举办了2024年IT趋势发布会。 会议聚焦IT行业的新技术、新模式…

PTK是如何加密WLAN单播数据帧的?

1. References WLAN 4-Way Handshake如何生成PTK&#xff1f;-CSDN博客 2. 概述 在Wi-Fi网络中&#xff0c;单播、组播和广播帧的加密算法是由AP决定的。其中单播帧的加密使用PTK密钥&#xff0c;其PTK的密钥结构如下图所示&#xff1a; PTK的组成如上图所示&#xff0c;由K…

2023 年 GitHub 上最受欢迎的编程语言

JavaScript 仍然是使用最多的编程语言&#xff0c;在 Web 开发中占据重要地位。 被广泛用于 Web 应用程序开发&#xff0c;框架如 React 和 Angular 促进了单页应用程序的开发。 Node.js 的出现使其在后端编程中同样重要。 Python 由于其简单性和广泛的库&#xff0c;Pyth…

python同级目录下调用其他.py文件

现有PCSE6.0.6&#xff0c;其中需要调用w81.py文件中的函数 就是必须是这两文件层级一致 当我把w81移到下一级文件中就找不到了

p14数组(2)

数组作为函数的参数 冒泡排序 两两比较 void bubble_sort(int arr[],int sz) {int i0;for(i0;i<sz-1;i){//每一趟冒泡排序int j0;for(j0;j<sz-1-i;j){if(arr[j]>arr[j1]){int tmparr[j];arr[j]arr[j1];arr[j1]tmp;}}} } int main(){int arr[]{9,8,7,6,5,4,3,2,1,0}…