优雅的实现后端接收字典枚举值

3,286 阅读3分钟

「这是我参与2022首次更文挑战的第3天,活动详情查看:2022首次更文挑战」。

引言

Hello 大家好,这里是Anyin。

继上一篇 优雅的实现前端回显字典枚举值 我们"优雅"的实现了前端字典枚举值的回显,当数据库查询返回值是一个code值的时候,我们能够在spring mvc进行序列化的时候根据注解信息,自动把code值转换为一个包含textcode值的对象返回给前端,让前端方便的取到对应的text值进行页面渲染。

这是在查询的场景下,那在编辑或者新增的场景下会怎样呢?

在编辑或者新增的场景下,前端会给后端一个code值,而后端一般使用String字符串接收该枚举值,然后在业务代码把它转为对应的枚举对象,最后在进行对应的业务操作。转换的逻辑一般如下:

    public static SexEnum get(String code){
        for(SexEnum item : values()) {
            if(code.equals(item.getCode())){
                return item;
            }
        }
        // 返回默认值或者抛出异常
        return SexEnum.MAN;
    }

这样子会有2个问题:

  1. 所有前端传递的枚举对象都需要该转换方法
  2. 前端传递的枚举值无法校验正确与否

思路

以上2个问题其实会有不同的解法,例如问题1,可以通过继承父类,抽象get方法,让所有的枚举类都继承该父类即可;问题2可以通过注解+AOP的形式去校验枚举值是否正确。

当然,这里我们分享下另外一个解法。在上篇我们通过序列化解决,那这里我们当然通过反序列化解决。在JSON字符串进行反序列化的时候我们会拿到当前枚举字段的Class对象,然后配合值从而取到具体的枚举对象,最后进行返回。

实现

其实整体代码很简单,我们实现一个StringAsDictDeserializer类,它继承了JsonDeserializer,其泛型为BaseEnum,这是我们定义所有枚举对象的抽象接口。最后,还需要实现下ContextualDeserializer接口,通过该接口才可以拿到当前枚举字段的Class对象。

代码如下:

@Slf4j
@JacksonStdImpl
public class StringAsDictDeserializer extends JsonDeserializer<BaseEnum> implements ContextualDeserializer {

    public StringAsDictDeserializer(){}

    private Class<?> clazz;

    public StringAsDictDeserializer(Class<?> clazz){
        this.clazz = clazz;
    }

    @Override
    public BaseEnum deserialize(JsonParser p, DeserializationContext ctx) throws IOException {
        // 前端传递的值
        String value = p.getValueAsString();
        // 通过遍历,获取对应的枚举对象
        BaseEnum type = Arrays.stream(clazz.getEnumConstants()).map(t -> (BaseEnum) t)
                .filter(t -> Objects.equals(t.getCode(), value))
                .findAny().orElse(null);
        if(type == null){
            throw new DictSerializerException("value is error, not found enum type");
        }
        return type;
    }

    @Override
    public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException {
        // 自定义反序列化器
        Class<?> typeClazz = property.getType().getRawClass();
        return new StringAsDictDeserializer(typeClazz);
    }
}

以上,有一个很重要的注解@JacksonStdImpl, 只有添加了该注解,ContextualDeserializer接口的createContextual方法才会执行,也只有这样子,我们才可以拿到BeanProperty对象,从而拿到目标枚举类的Class对象。

测试

接下来,我们来测试下我们的代码。在controller接收实体类中,我们添加上对应的注解配置,如下:

    @Data
    public static class TestForm{
        @JsonDeserialize(using = StringAsDictDeserializer.class)
        private SexEnum sex;

        @JsonDeserialize(using = StringAsDictDeserializer.class)
        private StatusEnum status;
    }

使用postman的请求参数:

image.png

控制台打印结果:

image.png

最后

以上,我们实现后端接收字典枚举值,你学会了吗?

相关源码地址 Anyin Cloud