当前位置: 首页 > news >正文

Spring后端直接用枚举类接收参数,自定义通用枚举类反序列化器

在使用枚举类做参数时,一般会让前端传数字,后端将数字转为枚举类,当枚举类很多时,很可能不知道这个code该对应哪个枚举类。能不能后端直接使用枚举类接收参数呢,可以,但是受限。
Spring反序列默认使用的是Jacskon,反序列化枚举类时,可以根据枚举类的name或ordinal属性进行反序列化,这是因为Jacskon内置了EnumDeserializer,它可以根据name或ordinal属性进行反序列化。

public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException{if (p.hasToken(JsonToken.VALUE_STRING)) {return _fromString(p, ctxt, p.getText());}// But let's consider int acceptable as well (if within ordinal range)if (p.hasToken(JsonToken.VALUE_NUMBER_INT)) {if (_isFromIntValue) {// 根据namereturn _fromString(p, ctxt, p.getText());}// 根据ordinalreturn _fromInteger(p, ctxt, p.getIntValue());}if (p.isExpectedStartObjectToken()) {return _fromString(p, ctxt,ctxt.extractScalarFromObject(p, this, _valueClass));}return _deserializeOther(p, ctxt);}

比如我们有一个接口:

    @PostMapping("/product")@ResponseBodypublic void product(@RequestBody Product product) {System.out.println(product.getStatus());System.out.println("ok");}public class Product {private Status status;private String name;// getter and setter
}public enum Status {ON_LINE(1000, "在线"),OFF_LINE(2000, "下线");private int code;private String desc;Status(int code, String desc) {this.code = code;this.desc = desc;}
}

PostMan请求
在这里插入图片描述

这两种写法都可以。
status使用数字方式,只能是0或1,即枚举类的ordinal值.但是这种方式和我们的使用习惯不同,我们一般会自定义code,而不是使用ordinal。如果向根据自定义code反序列化枚举类,该如何实现呢?

参照Spring的方式,大概思路应该是自定义一个反序列化器,然后再需要使用自定义反序列化器的对象上加上@JsonDeserialize以覆盖Spring默认使用的EnumDeserializer。

自定义枚举类反序列化器

public class StatusDeser extends JsonDeserializer<Status> {@Overridepublic Status deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException {final int code = p.getIntValue();return Status.getByCode(code);}
}

给Status增加一个getByCode,且指定反序列化器。

@JsonDeserialize(using = StatusDeser.class)
public enum Status {ON_LINE(1000, "在线"),OFF_LINE(2000, "下线");private int code;private String desc;Status(int code, String desc) {this.code = code;this.desc = desc;}public static Status getByCode(int code) {final Status[] values = Status.values();for (int i = 0; i < values.length; i++) {if (values[i].code == code) {return values[i];}}throw new RuntimeException("不合法的code值");}
}

此时就可以这样传值了
在这里插入图片描述
注意,如果直接使用枚举类做接收参数,接口和body应该这样写

    @PostMapping("/status")@ResponseBodypublic void status(@RequestBody Status status) {System.out.println(status);System.out.println("ok");}

在这里插入图片描述

统一处理枚举类

上面自定义的反序列化器可以处理某一种枚举,如果枚举很多,每写一个枚举类都要写一个与之对应的反序列化器,有点麻烦,而且一旦忘记写或不知道要写就麻烦了。如何对枚举类做统一处理呢?可以让枚举类都继承一个接口,我们的反序列化器对接口类型处理。

定义统一接口

public interface BaseEnum {Integer getCode();
}

统一接口中有一个返回code值的方法,前端传这个code值,我们根据这个code值反序列化出对应的枚举对象。

定义统一反序列化器

因为我们的接口下可以有多种实现类枚举,那我们在反序列化的时候要反序列化成哪种枚举类呢?怎么在运行时知道我们的目标枚举类呢?这个时候要使用StdDeserializer和ContextualDeserializer。
StdDeserializer中有目标对象类型,ContextualDeserializer可以在反序列化时获取目标对象类型信息。两者结合就可以在运行时获取到目标对象类型信息,动态创建反序列化器。
在这里插入图片描述

public class BaseEnumDeserial extends StdDeserializer<BaseEnum> implements ContextualDeserializer {protected BaseEnumDeserial(Class<?> vc) {super(vc);}// Jackson通过反射创建BaseEnumDeserial对象,这个对象不会用于正真的反序列化,因为它没有真实的类信息// 真正用于反序列化的对象会通过createContextual重新创建public BaseEnumDeserial() {this(BaseEnum.class);}@Overridepublic BaseEnum deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException {// 反序列目标对象继承自BaseEnum且是枚举类型if (BaseEnum.class.isAssignableFrom(_valueClass) && Enum.class.isAssignableFrom(_valueClass)) {final int code = p.getIntValue();BaseEnum[] enumConstants = (BaseEnum[]) _valueClass.getEnumConstants();for (int i = 0; i < enumConstants.length; i++) {if (code == enumConstants[i].getCode()) {return enumConstants[i];}}}return null;}@Overridepublic JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException {// 从上下文获取目标对象类型final Class<?> rawClass = ctxt.getContextualType().getRawClass();// new出来的反序列化器不用我们缓存,一种类型的反序列化器的createContextual方法只会执行一次,执行后的结果Jackson自己会缓存return new BaseEnumDeserial(rawClass);}
}

注意,有的地方文章使用property获取目标对象类型,只有在枚举类作为其他对象的属性被反序列化时,property才有值,如果时直接反序列化枚举对象,则property是null,所以还是直接从上下文中取类型比较好。

// property可能为null
final Class<?> rawClass = property.getType().getRawClass();

枚举类改造

// 反序列器加在接口上,就不用在每个枚举类上加了
@JsonDeserialize(using = BaseEnumDeserial.class)
public interface BaseEnum {Integer getCode();
}
// 实现BaseEnum 接口
public enum Status implements BaseEnum {ON_LINE(1000, "在线"),OFF_LINE(2000, "下线");private int code;private String desc;Status(int code, String desc) {this.code = code;this.desc = desc;}// 删除了getByCode方法@Overridepublic Integer getCode() {return code;}
}

定义接口

    @PostMapping("/product")@ResponseBody// 枚举类在其他对象中public void product(@RequestBody Product product) {System.out.println(product.getStatus());System.out.println("ok");}@PostMapping("/status")@ResponseBody// 直接反序列化枚举类public void status(@RequestBody Status status) {System.out.println(status);System.out.println("ok");}

在这里插入图片描述
此时,就可以前端传数字,后端直接用枚举类型接收,不用再做数字到枚举类型的转化了。改造之后,下面这种方式就不行了,只能使用code反序列化,而不能使用name反序列化了。
在这里插入图片描述

相关文章:

  • 极狐GitLab 重要安全版本:17.3.3, 17.2.7, 17.1.8, 17.0.8, 16.11.10
  • 计算机网络第四章——网络层
  • mtk平台编译出来的cust.dtsi有什么作用
  • JVM频繁Full GC问题的排查与解决方案
  • SpringBoot使用validation进行自参数校验
  • 未来数字世界相关技术:数字人、元宇宙、全息显示
  • 2024新动态:低代码开发占领新常态市场
  • 智能抠图怎么使用?4个快速消除图片背景的小技巧
  • FPGA随记-二进制转格雷码
  • pytorch 加载模型参数后 如何测试数据,应用模型预测数据,然后连续变量转换成 list 或者numpy.array padans并保存到csv文件中
  • 单链表的实现(C语言)
  • Android 去掉SIM卡插拔出现的重启弹窗提示
  • STM32 的 SDIO 接口(基于STM32F429HAL库)
  • 【xhs截流软件】爬取小红书关键词笔记下的筛选评论
  • ComfyUI 节点、插件的基本指南
  • “大数据应用场景”之隔壁老王(连载四)
  • 「译」Node.js Streams 基础
  • 07.Android之多媒体问题
  • CentOS从零开始部署Nodejs项目
  • Docker 1.12实践:Docker Service、Stack与分布式应用捆绑包
  • eclipse(luna)创建web工程
  • gcc介绍及安装
  • JavaScript异步流程控制的前世今生
  • Java教程_软件开发基础
  • Linux编程学习笔记 | Linux多线程学习[2] - 线程的同步
  • PHP面试之三:MySQL数据库
  • REST架构的思考
  • VuePress 静态网站生成
  • Windows Containers 大冒险: 容器网络
  • 闭包,sync使用细节
  • 翻译--Thinking in React
  • 关于Android中设置闹钟的相对比较完善的解决方案
  • 力扣(LeetCode)22
  • 如何编写一个可升级的智能合约
  • 如何邀请好友注册您的网站(模拟百度网盘)
  • 使用Maven插件构建SpringBoot项目,生成Docker镜像push到DockerHub上
  • 小程序上传图片到七牛云(支持多张上传,预览,删除)
  • 正则学习笔记
  • k8s使用glusterfs实现动态持久化存储
  • ​ 全球云科技基础设施:亚马逊云科技的海外服务器网络如何演进
  • ​LeetCode解法汇总307. 区域和检索 - 数组可修改
  • # include “ “ 和 # include < >两者的区别
  • # linux 中使用 visudo 命令,怎么保存退出?
  • #include到底该写在哪
  • $.proxy和$.extend
  • (2020)Java后端开发----(面试题和笔试题)
  • (done) 声音信号处理基础知识(2) (重点知识:pitch)(Sound Waveforms)
  • (pojstep1.3.1)1017(构造法模拟)
  • (安卓)跳转应用市场APP详情页的方式
  • (动态规划)5. 最长回文子串 java解决
  • (回溯) LeetCode 77. 组合
  • (六)激光线扫描-三维重建
  • (五)关系数据库标准语言SQL
  • (原)本想说脏话,奈何已放下
  • (转)EOS中账户、钱包和密钥的关系