springboot序列化和反序列化器配置方法
作者:wangfenglei123456
介绍
本文介绍在项目中时间类型、枚举类型的序列化和反序列化自定义的处理类,也可以使用注解。
建议枚举都实现一个统一的接口,方便处理。我这定义了一个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; } }
序列化code值
枚举代码
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<String, OrderPurchaseMethodEnum> 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<Integer, DeliverDateModelEnum> 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<DictVO> DICT_SET = new HashSet<>(); /** * 提供给外部获取字典集合的方法 * * @author sun */ public Set<DictVO> getDictSet() { return DICT_SET; } /** * 启动时扫描 */ @PostConstruct public void initDict() { DICT_SET = scanDict(); } /** * 扫描字典列表 */ private Set<DictVO> scanDict() { // 反射要扫描的包路径 Reflections reflections = new Reflections(scanPath); // 反射获取所有实现 DictInterface 接口的枚举 Set<Class<? extends Dict>> 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<DictItemVO> 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<Set<DictVO>> 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<DictItemVO> 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<Class<? extends Enum<?>>, Map<Integer, ? extends Enum<? extends Dict>>> CLASS_ENUM_MAP = new ConcurrentHashMap<>(16); @SuppressWarnings("unchecked") public static <E extends Enum<E> & Dict> E match(Class<E> enumClass, Integer type) { Map enumMap = CLASS_ENUM_MAP.get(enumClass); if (Objects.isNull(enumMap)) { // Map<Integer, ? extends Enum<? extends Dict>> unmodifiableMap = Arrays.stream(enumClass.getEnumConstants()) // 这种表达式写法会报错 // .collect(Collectors.toMap(Dict::getCode, v -> v)); Map<Integer, ? extends Enum<? extends Dict>> 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<Dict> 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()); } }
注入到spring中
注入方式1
由于项目中还配置有时间的序列化所以就把它们放一起了。建议使用的,把所有的序列化反序列化的都放这。
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<Dict>() { @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; } }
注入方式2
下面这种注入会影响项目已经有配置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<HttpMessageConverter<?>> 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<Dict>() { @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)
注入方式1断点测试
启动过程中会放入2个MappingJackson,会看到第7个下移到第8个了
注入方式2断点测试
第一次启动地址是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<Dict> 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<Class<? extends Enum<?>>, Map<Integer, ? extends Enum<? extends Dict>>> CLASS_ENUM_MAP = new ConcurrentHashMap<>(16); @SuppressWarnings("unchecked") public static <E extends Enum<E> & Dict> E match(Class<E> 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 <E extends Enum<E> & Dict> E getDictEnum(Class<E> enumClass, Integer type) { Map enumMap = CLASS_ENUM_MAP.get(enumClass); if (Objects.isNull(enumMap)) { Map<Integer, ? extends Enum<? extends Dict>> 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 <E extends Enum<E> & Dict> E match(Class<E> enumClass, Integer type) { Map enumMap = CLASS_ENUM_MAP.get(enumClass); if (Objects.isNull(enumMap)) { Map<Integer, ? extends Enum<? extends Dict>> 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
到此这篇关于springboot序列化和反序列化器配置方法的文章就介绍到这了,更多相关springboot序列化和反序列化器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!