本文介绍在项目中时间类型、枚举类型的序列化和反序列化自定义的处理类,也可以使用注解。
建议枚举都实现一个统一的接口,方便处理。我这定义了一个Dict接口。
这种方式比较灵活,可以让枚举按照自己的方式序列化,可以序列化code值(推荐),也可以序列化对象。序列化为对象:对于前端来说是比较好的,前端接收到code值和中文label,页面显示label就行了,但是对于后端,服务之间调用,使用feign调用的时候,序列化为对象,被调用服务的controller层就接收不到这个枚举值了,因为接收的是枚举序列化的对象,无法反序列化成枚举对象了。除非使用自定义反序列加判断去处理,比较麻烦。参考改进枚举工具类
定义统一枚举接口
package com.common.interfaces;
public interface Dict {
String name();
default Integer getCode() {
return null;
}
default String getLabel() {
return null;
}
}
枚举代码
package com.common.enums.app;
import com.baomidou.mybatisplus.annotation.EnumValue;
import com.common.interfaces.Dict;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum DeliverDateModelEnum implements Dict {
SAME(0, "相同时间"),
DIFFERENT(2, "不同时间"),
;
// mybatis的处理注解
@EnumValue
// Jackson的序列化处理注解;序列化code值
@JsonValue
private final Integer code;
private final String label;
}
package com.common.enums.order;
import com.common.exception.E;
import com.common.interfaces.Dict;
import com.fasterxml.jackson.annotation.JsonCreator;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 订单采购方式枚举
*/
@Getter
@AllArgsConstructor
public enum OrderPurchaseMethodEnum implements Dict {
INQUIRY("询价"),
MALL("商城"),
;
private String label;
private static final Map MAP =
Arrays.stream(OrderPurchaseMethodEnum.values()).collect(Collectors.toMap(Enum::name, e -> e));
@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
public static OrderPurchaseMethodEnum resolve(String code) {
if (!MAP.containsKey(code)) {
throw new Exception("枚举类型错误");
}
return MAP.get(code);
}
}
实体类
package com.app.dto;
import com.common.enums.apply.DeliverDateModelEnum;
import com.common.enums.order.OrderPurchaseMethodEnum;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
@Data
@EqualsAndHashCode(callSuper = false)
public class ApplyInfoDTO2 implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "主键")
private Long id;
@ApiModelProperty(value = "标题", required = true)
private String title;
@NotNull(message = "时间方式不能为空")
private DeliverDateModelEnum deliverDateModel;
// 后期会用到
private OrderPurchaseMethodEnum purchaseMethod;
}
测试代码
@RestController
@Slf4j
@RequestMapping("/api/test")
public class TestController2 {
@PostMapping(value = "/hh")
public ApplyInfoDTO2 test9(@RequestBody ApplyInfoDTO2 applyInfoDTO) {
System.out.println("hhhhh");
applyInfoDTO.setId(55L);
System.out.println("----"+ JacksonUtils.toJson(applyInfoDTO));
return applyInfoDTO;
}
}
请求参数
{
"id":11,
"title": "dajf",
"deliverDateModel":2
}
响应结果
{
"id": 55,
"title": "dajf",
"deliverDateModel": 2,
"purchaseMethod": null
}
这里只改枚举就可以了
package com.common.enums.app;
import com.baomidou.mybatisplus.annotation.EnumValue;
import com.common.interfaces.Dict;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum DeliverDateModelEnum implements Dict {
SAME(0, "相同时间"),
DIFFERENT(2, "不同时间"),
;
// mybatis的处理注解
@EnumValue
// 由于上边注解标记序列化为对象了,这就不起作用了,可以注掉
// @JsonValue
private final Integer code;
private final String label;
}
结果
请求参数
{
"id":11,
"title": "dajf",
"deliverDateModel":0
}
响应结果
{
"id": 55,
"title": "dajf",
"deliverDateModel": {
"code": 0,
"label": "相同时间"
},
"purchaseMethod": null
}
这就是说,前端传code值0,后端可以对应到枚举字段上,默认的是使用下标ordinal来反序列化的,按照0,1,2,3...去对应上,如果中跳过了,接收值的时候就会报错。比如上边的枚举类DeliverDateModelEnum,传0不会报错,传2就找不到了,错误如下,
Caused by: com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `com.common.enums.apply.DeliverDateModelEnum` from number 2: index value outside legal index range [0..1]
at [Source: (PushbackInputStream); line: 4, column: 21] (through reference chain: com.cnpc.app.dto.ApplyInfoDTO2["deliverDateModel"])
解决方案:在枚举类加反序列化处理代码@JsonCreator
package com.common.enums.app;
import com.baomidou.mybatisplus.annotation.EnumValue;
import com.common.interfaces.Dict;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum DeliverDateModelEnum implements Dict {
SAME(0, "相同时间"),
DIFFERENT(2, "不同时间"),
;
// mybatis的处理注解
@EnumValue
// 由于上边注解标记序列化为对象了,这就不起作用了,可以注掉
// @JsonValue
private final Integer code;
private final String label;
private static final Map map =
Arrays.stream(DeliverDateModelEnum.values()).collect(Collectors.toMap(DeliverDateModelEnum::getCode, e -> e));
@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
public static DeliverDateModelEnum resolve(Integer code) {
if (!map.containsKey(code)) {
throw new E("找不到枚举");
}
return map.get(code);
}
}
以上就是注解的枚举处理方法,统一定义接口后,为了前端可以通过code值展示枚举中午label,所以又写了一个字典解析类,将所以实现Dict接口的枚举,都拿到,然后解析存储,再给前端提供一个请求路径。前端存储起来,去解析,退出的时候,可以清除前端缓存。
package com.dict.service;
import com.common.annotation.DictType;
import com.common.exception.E;
import com.common.interfaces.Dict;
import com.dict.vo.DictItemVO;
import com.dict.vo.DictVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.util.Strings;
import org.reflections.Reflections;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;
/**
* 系统字典Service
*/
@Slf4j
@Component
public class SystemDictService {
/**
* 反射要扫描的包路径
*/
@Value("${dict.scanPath:com.company}")
private String scanPath;
/**
* 字典集合
*/
private static Set DICT_SET = new HashSet<>();
/**
* 提供给外部获取字典集合的方法
*
* @author sun
*/
public Set getDictSet() {
return DICT_SET;
}
/**
* 启动时扫描
*/
@PostConstruct
public void initDict() {
DICT_SET = scanDict();
}
/**
* 扫描字典列表
*/
private Set scanDict() {
// 反射要扫描的包路径
Reflections reflections = new Reflections(scanPath);
// 反射获取所有实现 DictInterface 接口的枚举
Set> monitorClasses = reflections.getSubTypesOf(Dict.class);
/*
* 封装字典列表
* 过滤掉不是枚举的实现
* 反射调用枚举的 values 方法, 获取每个枚举内部的值列表
*/
return monitorClasses.stream()
.filter(Class::isEnum)
.map(sub -> {
DictVO vo = new DictVO();
try {
/* 这块没有使用到
DictType annotation = sub.getAnnotation(DictType.class);
if (Objects.nonNull(annotation) && Strings.isNotBlank(annotation.type())) {
// 有DictType注解并且type不是空白时使用注解中的值作为字典的类别
vo.setType(annotation.type());
} else {
// 否则使用类名作为字典的类别
vo.setType(sub.getSimpleName());
}*/
Method valuesMethod = sub.getMethod("values");
Object valuesObj = valuesMethod.invoke(sub);
Dict[] values = (Dict[]) valuesObj;
List collect = Arrays.stream(values)
.map(item -> {
// code和label都可以没有,全部以name为默认
String code = item.getCode() != null ? item.getCode().toString() : item.name();
String label = item.getLabel() != null ? item.getLabel() : item.name();
return new DictItemVO(code, label);
}).collect(Collectors.toList());
vo.setItems(collect);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
log.error("{}", e.getMessage(), e);
}
return vo;
})
// 这里转map是校验有没有重复的type, 如果存在重复的会报错
.collect(Collectors.toMap(item -> item, item -> item, (a1, a2) -> {throw new E("字典类型:" + a1.getType() + ", 有重复");}))
.keySet();
}
}
// controller层提供外部访问
@ApiOperation("系统字典列表查询")
@GetMapping(value = "/system")
public R> querySystemDict() {
return R.of(systemDictService.getDictSet());
}
package com.dict.vo;
import lombok.Data;
import java.util.List;
import java.util.Objects;
/**
* 字典VO
*
*/
@Data
public class DictVO {
/**
* 字典类别名称
*/
private String type;
/**
* 字典项列表
*/
private List items;
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
DictVO dictVO = (DictVO) o;
return Objects.equals(type, dictVO.type);
}
@Override
public int hashCode() {
return Objects.hash(type);
}
}
package com.dict.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 字典项
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class DictItemVO {
/**
* 编码
*/
private String code;
/**
* 标签
*/
private String label;
}
这种方式只能支持单个属性接收值,接收list枚举,map。set接收枚举都会报错。
由于上边规定所有的枚举都需要实现Dict接口,下面的反序列化只针对符合条件的处理
package com.common.config;
import com.common.interfaces.Dict;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/**
* 枚举匹配工具类
*/
public class EnumUtil {
private static final Map>, Map>> CLASS_ENUM_MAP =
new ConcurrentHashMap<>(16);
@SuppressWarnings("unchecked")
public static & Dict> E match(Class enumClass, Integer type) {
Map enumMap = CLASS_ENUM_MAP.get(enumClass);
if (Objects.isNull(enumMap)) {
// Map> unmodifiableMap = Arrays.stream(enumClass.getEnumConstants())
// 这种表达式写法会报错
// .collect(Collectors.toMap(Dict::getCode, v -> v));
Map> unmodifiableMap = Arrays.stream(enumClass.getEnumConstants())
.collect(Collectors.toMap(obj -> obj.getCode(), v -> v));
CLASS_ENUM_MAP.putIfAbsent(enumClass, unmodifiableMap);
return (E) unmodifiableMap.get(type);
}
return (E) enumMap.get(type);
}
}
package com.common.config;
import com.common.interfaces.Dict;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import java.io.IOException;
/**
* 枚举反序列化器
*/
public class DictEnumDeserializer extends StdDeserializer implements ContextualDeserializer {
public DictEnumDeserializer() {
super((JavaType) null);
}
public DictEnumDeserializer(JavaType valueType) {
super(valueType);
}
@Override
public JsonDeserializer> createContextual(DeserializationContext ctxt, BeanProperty property) {
return new DictEnumDeserializer(property.getType());
}
@Override
@SuppressWarnings("all")
public Dict deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
return (Dict) EnumUtil.match((Class) _valueClass, p.getIntValue());
}
}
由于项目中还配置有时间的序列化所以就把它们放一起了。建议使用的,把所有的序列化反序列化的都放这。
package com.cnpc.common.config;
import com.common.interfaces.Dict;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.module.SimpleDeserializers;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.type.ClassKey;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.Objects;
/**
* LocalDateTime配置
*/
@Configuration
// public class LocalDateTimeFormatConfig { 改了一个名字
public class WebCustomerConfig {
private static final String DEFAULT_DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
private static final String DEFAULT_DATE_PATTERN = "yyyy-MM-dd";
private static final String DEFAULT_TIME_PATTERN = "HH:mm:ss";
// 这里加@Primary,在项目启动的时候一共会加载12个convert,第7个是MappingJackson2HttpMessageConverter
// 处理json映射对象参数的类,会采用这个ObjectMapper,默认的是 new ObjectMapper();这里自己增加了一些序列化处理的方法
@Bean
@Primary
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
// 注册时间的序列化和反序列化处理
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_PATTERN)));
javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_PATTERN)));
javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_PATTERN)));
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_PATTERN)));
javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_PATTERN)));
javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_PATTERN)));
objectMapper.registerModule(javaTimeModule);
//忽略识别不了的属性
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// 注册枚举的处理序列化和反序列的方式
SimpleModule sm = new SimpleModule();
//自定义查找规则
sm.setDeserializers(new SimpleDeserializers() {
@Override
public JsonDeserializer> findEnumDeserializer(Class> type, DeserializationConfig config,
BeanDescription beanDesc) throws JsonMappingException {
JsonDeserializer enumDeserializer = super.findEnumDeserializer(type, config, beanDesc);
if (enumDeserializer != null) {
return enumDeserializer;
}
//遍历枚举实现的接口, 查找反序列化器
for (Class typeInterface : type.getInterfaces()) {
// 如果实现了Dict接口但是没有code值的就返回null,如果项目比较规范都有code值,或者没有code值的,没有实现Dict接口,可以去掉下面这个代码
if (typeInterface.equals(Dict.class) ){
Dict[] enumConstants = (Dict[]) type.getEnumConstants();
if (Objects.isNull(enumConstants[0].getCode())) {
return null;
}
}
enumDeserializer = this._classMappings.get(new ClassKey(typeInterface));
if (enumDeserializer != null) {
return enumDeserializer;
}
}
return null;
}
});
sm.addDeserializer(Dict.class, new TypeEnumDeserializer());
// 增加枚举的序列化方式
sm.addSerializer(Dict.class, new JsonSerializer() {
@Override
public void serialize(Dict value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
// 相当于在枚举类上加 @JsonFormat(shape = JsonFormat.Shape.OBJECT);返给页面的是一个对象code和label都返回
// gen.writeStartObject();
// gen.writeNumberField("code", value.getCode());
// gen.writeStringField("label", value.getLabel());
// gen.writeEndObject();
// 相当于在枚举code字段上加 @JsonValue 返回给页面一个code值
gen.writeNumber(value.getCode());
}
@Override
public void serializeWithType(Dict value, JsonGenerator gen, SerializerProvider serializers,
TypeSerializer typeSer) throws IOException {
WritableTypeId typeIdDef = typeSer.writeTypePrefix(gen, typeSer.typeId(value, JsonToken.VALUE_STRING));
serialize(value, gen, serializers);
typeSer.writeTypeSuffix(gen, typeIdDef);
}
});
objectMapper.registerModule(sm);
return objectMapper;
}
}
下面这种注入会影响项目已经有配置ObjectMapper的地方,建议都放一起。如果采用下面这种方式,需要自己创建一个MappingJackson2HttpMessageConverter,并将converter添加到list的第一个,如果添加到最后一个,他会先找list中前边的MappingJackson2HttpMessageConverter,大约是第7个位置,就不会用自己写的。
package com.common.config;
import com.common.interfaces.Dict;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.core.type.WritableTypeId;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.module.SimpleDeserializers;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.type.ClassKey;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
/**
* 装载枚举序列化器
*/
@Configuration
@Slf4j
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List> converters) {
StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter(StandardCharsets.UTF_8);
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setObjectMapper(objectMapperForWebConvert());
converters.add(0, stringHttpMessageConverter);
// 将这个MappingJackson2HttpMessageConverter添加到第一个,是为了优先找到,否则就有其他MappingJackson2HttpMessageConverter去处理了
converters.add(0, converter);
}
// 这个没有时间的处理配置。
public ObjectMapper objectMapperForWebConvert() {
ObjectMapper om = new ObjectMapper();
om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
SimpleModule sm = new SimpleModule();
//自定义查找规则
sm.setDeserializers(new SimpleDeserializers() {
@Override
public JsonDeserializer> findEnumDeserializer(Class> type, DeserializationConfig config,
BeanDescription beanDesc) throws JsonMappingException {
JsonDeserializer enumDeserializer = super.findEnumDeserializer(type, config, beanDesc);
if (enumDeserializer != null) {
return enumDeserializer;
}
//遍历枚举实现的接口, 查找反序列化器
for (Class typeInterface : type.getInterfaces()) {
// 如果实现了Dict接口但是没有code值的就返回null,如果项目比较规范都有code值,或者没有code值的,没有实现Dict接口,可以去掉下面这个代码
if (typeInterface.equals(Dict.class)){
Dict[] enumConstants = (Dict[]) type.getEnumConstants();
if (Objects.isNull(enumConstants[0].getCode())) {
return null;
}
}
// 这里的classKey不要导错包,必须是com.fasterxml.jackson.databind.type.ClassKey,否则会找不到,导致自定义的反序列化类不起作用
enumDeserializer = this._classMappings.get(new ClassKey(typeInterface));
if (enumDeserializer != null) {
return enumDeserializer;
}
// 上边的ClassKey导入包错误,找不到临时加的
// if (typeInterface.equals(Dict.class)){
// return new DictEnumDeserializer();
// }
// return new DictEnumDeserializer();
}
return null;
}
});
// 添加枚举反序列化处理器
sm.addDeserializer(Dict.class, new DictEnumDeserializer());
sm.addSerializer(Dict.class, new JsonSerializer() {
@Override
public void serialize(Dict value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
// 相当于在枚举类上加 @JsonFormat(shape = JsonFormat.Shape.OBJECT);返给页面的是一个对象code和label都返回
// gen.writeStartObject();
// gen.writeNumberField("code", value.getCode());
// gen.writeStringField("label", value.getLabel());
// gen.writeEndObject();
// 相当于在枚举code字段上加 @JsonValue 返回给页面一个code值
gen.writeNumber(value.getCode());
}
@Override
public void serializeWithType(Dict value, JsonGenerator gen, SerializerProvider serializers,
TypeSerializer typeSer) throws IOException {
WritableTypeId typeIdDef = typeSer.writeTypePrefix(gen, typeSer.typeId(value, JsonToken.VALUE_STRING));
serialize(value, gen, serializers);
typeSer.writeTypeSuffix(gen, typeIdDef);
}
});
om.registerModule(sm);
return om;
}
}
这样就可以测试了。没有实现Dict接口的枚举会采用下一个MappingJackson2HttpMessageConverter的new ObjectMapper()去处理,按照默认的去处理
第一次赋值
加载自定义的ObjectMapper
现在converter变成了14个了,因为这是注入方式2,自己往里面放了2个,
请求访问时,断点
org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver#readWithMessageConverters(org.springframework.http.HttpInputMessage, org.springframework.core.MethodParameter, java.lang.reflect.Type)
启动过程中会放入2个MappingJackson,会看到第7个下移到第8个了
第一次启动地址是15536
走到自己写的枚举处理
改进之后,即可已返回给前端枚举对象,也可以接收对象类型的,也可以接收数值,也可以接收字符串
package com.common.config;
import com.common.interfaces.Dict;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import java.io.IOException;
/**
* 枚举反序列化器
*/
public class DictEnumDeserializer extends StdDeserializer implements ContextualDeserializer {
public DictEnumDeserializer() {
super((JavaType) null);
}
public DictEnumDeserializer(JavaType valueType) {
super(valueType);
}
@Override
public JsonDeserializer> createContextual(DeserializationContext ctxt, BeanProperty property) {
return new DictEnumDeserializer(property.getType());
}
// 自定义的反序列化器这个方法,需要传JsonParser
@Override
@SuppressWarnings("all")
public Dict deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
return (Dict) EnumUtil.match((Class) _valueClass, p);
}
}
package com.common.config;
import com.common.interfaces.Dict;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import java.io.IOException;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/**
* 枚举匹配工具类
* 改进的枚举工具
*/
public class EnumUtil {
private static final Map>, Map>> CLASS_ENUM_MAP =
new ConcurrentHashMap<>(16);
@SuppressWarnings("unchecked")
public static & Dict> E match(Class enumClass, JsonParser jsonParser) throws IOException {
Integer code = null;
// 接收数值型的
if (jsonParser.currentToken() == JsonToken.VALUE_NUMBER_INT){
code = jsonParser.getValueAsInt();
// 接收OBJECT类型的参数
} else if (jsonParser.currentToken() == JsonToken.START_OBJECT){
while (jsonParser.nextToken() != JsonToken.END_OBJECT) {
String fieldname = jsonParser.getCurrentName();
if ("code".equals(fieldname)) {
jsonParser.nextToken();
code = jsonParser.getValueAsInt();
break;
}
}
// 接收字符串类型的,需要判断是纯数字
}else if (jsonParser.currentToken() == JsonToken.VALUE_STRING){
String codestr = jsonParser.getValueAsString();
if (codestr.matches("^[0-9]*$")) {
code = Integer.valueOf(codestr);
}
}
if (Objects.isNull(code)){
throw new RuntimeException("没有code找不到对应的枚举");
}
return getDictEnum(enumClass, code);
}
private static & Dict> E getDictEnum(Class enumClass, Integer type) {
Map enumMap = CLASS_ENUM_MAP.get(enumClass);
if (Objects.isNull(enumMap)) {
Map> unmodifiableMap = Arrays.stream(enumClass.getEnumConstants())
.collect(Collectors.toMap(obj -> obj.getCode(), v -> v));
CLASS_ENUM_MAP.putIfAbsent(enumClass, unmodifiableMap);
E e = (E) unmodifiableMap.get(code);
if (Objects.isNull(e)){
throw new RuntimeException("指定类型code找不到对应的枚举code = " + code);
}
return e;
}
E e = (E) enumMap.get(code);
if (Objects.isNull(e)){
throw new RuntimeException("指定类型code找不到对应的枚举code = " + code);
}
return e;
}
// 原来的方法,只能接收数值code
/*public static & Dict> E match(Class enumClass, Integer type) {
Map enumMap = CLASS_ENUM_MAP.get(enumClass);
if (Objects.isNull(enumMap)) {
Map> unmodifiableMap = Arrays.stream(enumClass.getEnumConstants())
.collect(Collectors.toMap(Dict::getCode, v -> v));
CLASS_ENUM_MAP.putIfAbsent(enumClass, unmodifiableMap);
return (E) unmodifiableMap.get(type);
}
return (E) enumMap.get(type);
}*/
}
https://developer.aliyun.com/article/979501
http://events.jianshu.io/p/33e537ea6f10
JsonParser的处理
https://blog.csdn.net/band_mmbx/article/details/126749515