前言
我们在日常开发中经常会遇到,实体类中的枚举属性,在通过@ResponseBody
注解返回到前端后,默认被解析成了枚举对象的name
值,当我们需要获取枚举类的详细属性时就会变得非常麻烦,今天给大家分享几种常用的获取枚举类属性的方式。
例子
我们先通过一个小例子讲解这个问题,首先我们要准备一个简单的接口,用于查询用户信息,用户实体中包含性别
、用户类型
和用户状态
三种枚举属性,我们下面的例子中, 将会分别拿这三个枚举类进行测试。
代码如下:
用户实体User.java
@Data
public class User {
private String username;
private String site;
private String[] git;
private GenderEnum gender;
private TypeEnum type;
private StatusEnum status;
}
枚举类内容如下:GenderEnum.java
public enum GenderEnum {
MALE("男"),
FAMALE("女");
private String desc;
GenderEnum(String desc) {
this.desc = desc;
}
public String getDesc() {
return desc;
}
}
TypeEnum.java
public enum TypeEnum {
ADMIN("管理员"),
USER("普通用户");
private String desc;
TypeEnum(String desc) {
this.desc = desc;
}
public String getDesc() {
return desc;
}
}
StatusEnum.java
public enum StatusEnum {
INUSE("使用中"),
UNUSED("未使用"),
DISABLED("已禁用");
private String desc;
StatusEnum(String desc) {
this.desc = desc;
}
public String getDesc() {
return desc;
}
}
Controller类RestApiController.java
@RestController
public class RestApiController {
@RequestMapping("/users")
public Object users() {
List<User> userList = new ArrayList<>();
User user = null;
for (int i = 0; i < 2; i++) {
user = new User();
user.setGender(i % 2 == 0 ? GenderEnum.MALE : GenderEnum.FAMALE);
user.setType(i % 2 == 0 ? TypeEnum.ADMIN : TypeEnum.USER);
user.setStatus(i % 2 == 0 ? StatusEnum.INUSE : StatusEnum.DISABLED);
user.setUsername("user_" + i);
user.setSite("https://www.zhyd.me");
user.setGit(new String[]{"https://gitee.com/yadong.zhang", "https://github.com/zhangyd-c"});
userList.add(user);
}
return userList;
}
}
接口返回数据的原始内容如下:
[
{
"username": "user_0",
"site": "https://www.zhyd.me",
"git": [
"https://gitee.com/yadong.zhang",
"https://github.com/zhangyd-c"
],
"gender": "MALE",
"type": "ADMIN",
"status": "INUSE",
},
{
"username": "user_1",
"site": "https://www.zhyd.me",
"git": [
"https://gitee.com/yadong.zhang",
"https://github.com/zhangyd-c"
],
"gender": "FAMALE",
"type": "USER",
"status": "DISABLED"
}
]
从接口数据中可以清楚的看到,枚举类属性被解析成了字符串。接下来我们将分别通过几种方式,完成对枚举类属性的获取。
第一种:新增Get方法
这种方式,需要我们在User.java
实体类中,为用户类型
枚举属性添加对应的get方法:
@Data
public class User {
private String username;
private String site;
private String[] git;
private GenderEnum gender;
private TypeEnum type;
private StatusEnum status;
public String getTypeEnumDesc() {
return null == type ? null : type.getDesc();
}
}
接口响应内容:
[
{
"username": "user_0",
"site": "https://www.zhyd.me",
"git": [
"https://gitee.com/yadong.zhang",
"https://github.com/zhangyd-c"
],
"gender": "MALE",
"type": "ADMIN",
"status": "INUSE",
"typeEnumDesc": "管理员"
},
{
"username": "user_1",
"site": "https://www.zhyd.me",
"git": [
"https://gitee.com/yadong.zhang",
"https://github.com/zhangyd-c"
],
"gender": "FAMALE",
"type": "USER",
"status": "DISABLED",
"typeEnumDesc": "普通用户"
}
]
这种方式的优缺点:
- 优点
- 简单,不需要集成其他代码
- 缺点
- 扩展性差,如果一个枚举类存在多个属性,那我们就要在业务bean中为每个枚举属性单独增加
get
方法,想想都觉得头疼。
- 扩展性差,如果一个枚举类存在多个属性,那我们就要在业务bean中为每个枚举属性单独增加
一般用来应急
第二种:通过自定义FastJsonHttpMessageConverter
完成对Enum的转换
注:本人开发环境的fastjson版本为1.2.37
, 另外测试了1.2.34
和1.2.52
,其他版本可能有所差异
改进枚举类
本小节只以TypeEnum.java
枚举类为例,重写枚举类的toString
方法
TypeEnu.java
public enum TypeEnum {
ADMIN("管理员"),
USER("普通用户");
private String desc;
TypeEnum(String desc) {
this.desc = desc;
}
public String getDesc() {
return desc;
}
@Override
public String toString() {
return "{\\\"name\\\": \\\"" + this.name() + "\\\", \\\"desc\\\": \\\"" + this.getDesc() + "\\\"}";
}
}
新增配置类
@Configuration
public class WebMvcConf implements WebMvcConfigurer {
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.forEach(System.out::println);
}
/**
* 自定义FastJsonHttpMessageConverter处理api中的枚举
*/
@Configuration
@ConditionalOnClass({FastJsonHttpMessageConverter.class})
@ConditionalOnProperty(
name = {"spring.http.converters.preferred-json-mapper"},
havingValue = "fastjson",
matchIfMissing = true
)
public class FastJsonHttpMessageConvertersConfiguration {
@Bean
@ConditionalOnMissingBean({FastJsonHttpMessageConverter.class})
public FastJsonHttpMessageConverter fastJsonHttpMessageConverter() {
FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
FastJsonConfig fastJsonConfig = new FastJsonConfig();
fastJsonConfig.setSerializerFeatures(
SerializerFeature.PrettyFormat,
SerializerFeature.WriteEnumUsingToString
);
converter.setFastJsonConfig(fastJsonConfig);
return converter;
}
}
}
注:
@ConditionalOnClass
表示当存在FastJsonHttpMessageConverter
类时,才会加载该配置@@ConditionalOnProperty
表示当spring.http.converters.preferred-json-mapper
配置项的值为fastjson
时,才会加载该配置,并且即使没有(matchIfMissing
)该property也会正常加载@ConditionalOnMissingBean
表示当没有注入FastJsonHttpMessageConverter
类时,才会执行当前的注入操作
需要注意的是,一定要在SerializerFeature
中添加SerializerFeature.WriteEnumUsingToString
,表示在对枚举类序列化的时候,返回枚举类的toString()
方法的值。关于SerializerFeature
其他参数的说明,请参考https://blog.csdn.net/u010246789/article/details/52539576
重新访问接口,返回内容如下:
[
{
"gender":"MALE",
"status": "INUSE",
"git":["https:\/\/gitee.com\/yadong.zhang","https:\/\/github.com\/zhangyd-c"],
"site":"https:\/\/www.zhyd.me",
"type":"{"name": "ADMIN", "desc": "绠$悊鍛�"}",
"typeEnumDesc":"绠$悊鍛�",
"username":"user_0"
},
{
"gender":"FAMALE",
"status": "DISABLED",
"git":["https:\/\/gitee.com\/yadong.zhang","https:\/\/github.com\/zhangyd-c"],
"site":"https:\/\/www.zhyd.me",
"type":"{"name": "USER", "desc": "鏅€氱敤鎴�"}",
"typeEnumDesc":"鏅€氱敤鎴�",
"username":"user_1"
}
]
汉字全部乱码了,通过Debug
发现,FastJsonHttpMessageConverter
的supportedMediaTypes
属性默认为*/*
,这样就会造成浏览器在解析的时候无法识别具体的编码而导致解析失败,出现乱码问题。
我们修改一下fastJsonHttpMessageConverter
方法,重新配置一下supportedMediaTypes
,增加以下代码:
List<MediaType> mediaTypes = new ArrayList<>();
mediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
converter.setSupportedMediaTypes(mediaTypes);
响应数据如下:
[
{
"gender": "MALE",
"status": "INUSE",
"git": [
"https://gitee.com/yadong.zhang",
"https://github.com/zhangyd-c"
],
"site": "https://www.zhyd.me",
"type": "{\"name\": \"ADMIN\", \"desc\": \"管理员\"}",
"typeEnumDesc": "管理员",
"username": "user_0"
},
{
"gender": "FAMALE",
"status": "DISABLED",
"git": [
"https://gitee.com/yadong.zhang",
"https://github.com/zhangyd-c"
],
"site": "https://www.zhyd.me",
"type": "{\"name\": \"USER\", \"desc\": \"普通用户\"}",
"typeEnumDesc": "普通用户",
"username": "user_1"
}
]
到这儿还有一个问题就是:接口返回的枚举属性,是一个字符串,前端使用时,并不能直接使用,需要通过JSON.parse(x)
格式化成json串后才能使用,我们在后台处理一下,使接口直接返回map形式的内容。
改进
修改枚举类(TypeEnum
)的toString
方法,将枚举类的属性通过map包装成JSON
@Override
public String toString() {
Map<String, Object> map = new HashMap<>();
map.put("name", this.name());
map.put("desc", this.getDesc());
return JSONObject.toJSONString(map);
}
然后修改配置类的fastJsonHttpMessageConverter
方法,通过ValueFilter
处理序列化的参数
ValueFilter valueFilter = (o, s, o1) -> {
if (o1 instanceof Enum) {
String str = String.valueOf(o1);
if (isJson(str)) {
o1 = JSONObject.parseObject(String.valueOf(o1), Map.class);
}
}
return o1;
};
fastJsonConfig.setSerializeFilters(valueFilter);
代码很好理解:当序列化参数为枚举类型时,将json字符串转为map(此处不是多余,因为SerializerFeature
中针对枚举的序列化方式,只有两种(name
ortoString
),所以我们必须先将map转成字符串,然后再转成map)。
注:鉴于编码过程中可能会漏掉重写一些枚举类的toString
方法(或者重写toString
方法时并没有返回正确的json格式字符串),而枚举类默认的toString
方法返回的并不是json形式的字符串,所以,需要使用isJson(str)
判断当前枚举序列化的结果是否为json字符串。
WebMvcConf.java
详细代码如下:
@Configuration
public class WebMvcConf implements WebMvcConfigurer {
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.forEach(System.out::println);
}
private boolean isJson(String str) {
return !StringUtils.isEmpty(str) && (str.charAt(0) == '{' && str.charAt(str.length() - 1) == '}');
}
/**
* 自定义FastJsonHttpMessageConverter处理api中的枚举
*/
@Configuration
@ConditionalOnClass({FastJsonHttpMessageConverter.class})
@ConditionalOnProperty(
name = {"spring.http.converters.preferred-json-mapper"},
havingValue = "fastjson",
matchIfMissing = true
)
public class FastJsonHttpMessageConvertersConfiguration {
@Bean
@ConditionalOnMissingBean({FastJsonHttpMessageConverter.class})
public FastJsonHttpMessageConverter fastJsonHttpMessageConverter() {
FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
FastJsonConfig fastJsonConfig = new FastJsonConfig();
fastJsonConfig.setSerializerFeatures(
SerializerFeature.PrettyFormat,
SerializerFeature.WriteEnumUsingToString
);
List<MediaType> mediaTypes = new ArrayList<>();
mediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
converter.setSupportedMediaTypes(mediaTypes);
ValueFilter valueFilter = (o, s, o1) -> {
if (o1 instanceof Enum) {
String str = String.valueOf(o1);
if (isJson(str)) {
o1 = JSONObject.parseObject(String.valueOf(o1), Map.class);
}
}
return o1;
};
fastJsonConfig.setSerializeFilters(valueFilter);
converter.setFastJsonConfig(fastJsonConfig);
return converter;
}
}
}
响应数据如下:
[
{
"gender": "MALE",
"status": "INUSE",
"git": [
"https://gitee.com/yadong.zhang",
"https://github.com/zhangyd-c"
],
"site": "https://www.zhyd.me",
"type": {
"name": "ADMIN",
"desc": "管理员"
},
"typeEnumDesc": "管理员",
"username": "user_0"
},
{
"gender": "FAMALE",
"status": "DISABLED",
"git": [
"https://gitee.com/yadong.zhang",
"https://github.com/zhangyd-c"
],
"site": "https://www.zhyd.me",
"type": {
"name": "USER",
"desc": "普通用户"
},
"typeEnumDesc": "普通用户",
"username": "user_1"
}
]
这种方式的优缺点:
- 优点
- 不需要对业务bean进行定制化,减少不必要的代码冗余和耦合
- 扩展性好,枚举类中可以自定义多个属性
- 缺点
- 代码量大,新增了一个配置类
- 每个枚举类中都需要Override
toString
方法,对代码的维护造成一定的影响
第三种:使用fastjson的@JSONType
注解
因为Springboot默认使用的jackson的Converter
,而@JSONType
是fastjson的注解,所以这种方式也是基于FastJsonHttpMessageConverter
的。
我们以StatusEnum.java
为例,演示该注解的用法
首先修改StatusEnum.java
为以下内容:
@JSONType(serializeEnumAsJavaBean = true)
public enum StatusEnum {
INUSE("使用中"),
UNUSED("未使用"),
DISABLED("已禁用");
private String desc;
StatusEnum(String desc) {
this.desc = desc;
}
public String getDesc() {
return desc;
}
public String getName() {
return this.name();
}
}
注:如果此处存在反序列化异常,请参考:https://github.com/alibaba/fastjson/issues/1452
响应结果:
[
{
"gender": "MALE",
"git": [
"https://gitee.com/yadong.zhang",
"https://github.com/zhangyd-c"
],
"site": "https://www.zhyd.me",
"status": {
"desc": "使用中",
"name": "INUSE"
},
"type": {
"name": "ADMIN",
"desc": "管理员"
},
"typeEnumDesc": "管理员",
"username": "user_0"
},
{
"gender": "FAMALE",
"git": [
"https://gitee.com/yadong.zhang",
"https://github.com/zhangyd-c"
],
"site": "https://www.zhyd.me",
"status": {
"desc": "已禁用",
"name": "DISABLED"
},
"type": {
"name": "USER",
"desc": "普通用户"
},
"typeEnumDesc": "普通用户",
"username": "user_1"
}
]
效果和第二步的一致。
这种方式的优缺点:
- 优点
- 不需要对业务bean进行定制化,减少不必要的代码冗余和耦合
- 扩展性好,枚举类中可以自定义多个属性
- 不用去定制
toString
方法 - 使用注解配置枚举类,方便快捷
- 缺点
- 依赖
FastJsonHttpMessageConverter
,需要单独对HttpMessageConverter
进行增强
- 依赖
第四种:使用jackson的@JsonFormat
注解
这一步开始,我们先把第二种
方案中添加的FastJsonHttpMessageConvertersConfiguration
类注掉,这样能更清楚的对比效果。
我们修改GenderEnum
类内容如下:
@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum GenderEnum {
MALE("男"),
FAMALE("女");
private String desc;
GenderEnum(String desc) {
this.desc = desc;
}
public String getDesc() {
return desc;
}
}
访问接口返回的数据结构如下:
[
{
"username": "user_0",
"site": "https://www.zhyd.me",
"git": [
"https://gitee.com/yadong.zhang",
"https://github.com/zhangyd-c"
],
"gender": {
"desc": "男"
},
"type": "ADMIN",
"status": "INUSE",
"typeEnumDesc": "管理员"
},
{
"username": "user_1",
"site": "https://www.zhyd.me",
"git": [
"https://gitee.com/yadong.zhang",
"https://github.com/zhangyd-c"
],
"gender": {
"desc": "女"
},
"type": "USER",
"status": "DISABLED",
"typeEnumDesc": "普通用户"
}
]
已经成功序列化了枚举,因为我们枚举类中只有一个getDesc
方法,所以序列化出来的枚举内容,也只有这一个参数。当实际生产环境中的枚举类存在多个属性时,只需要分别加上getXxx
方法即可。
这种方式的优缺点:
- 优点
- 不需要对业务bean进行定制化,减少不必要的代码冗余和耦合
- 扩展性好,枚举类中可以自定义多个属性
- 不用去定制
toString
方法 - 使用注解配置枚举类,方便快捷
- Jackson自带注解,不依赖自定义的
HttpMessageConverter
,减少代码量
- 缺点
- 暂无
测试中碰到的问题
作者在写测试用例的时候,因不小心,碰到过几个问题,在这儿记录一下,防止后面有朋友继续碰到。
JSONType
是fastjson的注解,JsonFormat
是jackson的注解,在使用自定义的FastJsonHttpMessageConverter
组件时,JsonFormat
是不可用的,同理在禁用掉FastJsonHttpMessageConverter
组件后,JSONType
不可用- 其他问题,多见于参考资料的链接
项目源码
参考资料
- https://www.jianshu.com/p/68a75c093023
- https://blog.csdn.net/gottst0113/article/details/80978966
- https://blog.csdn.net/u010246789/article/details/52539576
- https://blog.csdn.net/mickjoust/article/details/51671060
- https://blog.csdn.net/qq_41599820/article/details/86607029
- https://blog.csdn.net/zhangfengaiCQ/article/details/82657926
- https://github.com/alibaba/fastjson/issues/1452
其他开源作品
- blog-hunter,一款简单好用并且支持多个平台的博客爬取工具
- OneBlog,一个简洁美观、功能强大并且自适应的Java博客
- JustAuth,史上最全的整合第三方登录的工具,目前已支持Github、Gitee、微博、钉钉、百度、Coding、腾讯云开发者平台、OSChina、支付宝、QQ、微信、淘宝、Google、Facebook、抖音、领英、小米、微软和今日头条等第三方平台的授权登录。Login, so easy!