SpringBoot枚举序列化方案

前言

在使用 JSON 传值的时候,对于枚举类型的处理是比较麻烦的,因为基本上默认都只会按照枚举的名称来序列化和反序列化。先来描述一下需求。

有一个性别枚举:

MALE(1, "男"),
FEMALE(2, "女");

返回时以对象的形式序列化枚举,格式大致为:

{
  "gender": {
    "code": 1,
    "name": "男"
  }
}

入参时以枚举码的形式反序列化枚举,格式为:

{
  "genderCode": 1
}



SpringBoot Jackson 方案

枚举

@JsonFormat(shape = JsonFormat.Shape.OBJECT)
@Getter
@AllArgsConstructor
public enum GenderEnum {
    MALE(1, "男"),
    FEMALE(2, "女");

    @ApiModelProperty("性别码")
    @EnumValue
    private final int code;

    @ApiModelProperty("性别名")
    private final String name;

    @JsonCreator
    public static GenderEnum ofCode(Integer code) {
        for (GenderEnum value : values()) {
            if (code == value.getCode()) {
                return value;
            }
        }
        return null;
    }
}
  1. 使用@JsonFormat(shape = JsonFormat.Shape.OBJECT)注解来让枚举按照类(对象)的格式进行序列化。
  2. 使用@JsonCreator注解标记一个通过枚举码来查询枚举的方法,Jackson 会使用这个有参构造器进行反序列化。

返回DTO

@ApiModel("用户基本返回对象")
@Data
@Accessors(chain = true)
public class UserOutDTO {

    @ApiModelProperty("性别")
    @JsonProperty(access = JsonProperty.Access.READ_ONLY)
    private GenderEnum gender;

}

入参DTO及参数校验

@ApiModel("用户基本入参对象")
@Data
@Accessors(chain = true)
public class UserInDTO {

    @ApiModelProperty(value = "性别码", name = "genderCode", dataType = "java.lang.Integer")
    @NotNull(groups = ValidationGroup.Create.class, message = "创建时性别码错误")
    @JsonProperty(value = "genderCode", access = JsonProperty.Access.WRITE_ONLY)
    private GenderEnum gender;

}
  1. 使用JsonProperty来指定反序列化时接收的属性名
  2. 使用ApiModelProperty来指定文档中的属性名和类型

警告

这样做虽然解决了枚举序列化和反序列化的问题。但是同时又引入了另一个问题:

  • 在校验时我们指定了创建时分组,可以有效的校验入参数据是合法值。
  • 但是如果采用 NOT_NULL 策略更新,那么就不能使用非空校验。
  • 因此除了 NULL 值,不存在的枚举码也会导致该字段不进行更新,而且获取失败信息还比较麻烦。

测试

@Api(tags = "用户管理接口")
@RestController
@RequestMapping("/user")
public class UserController {

    @ApiOperation("测试")
    @PostMapping("/test")
    public ResponseEntity<Result<UserOutDTO>> test(@Validated({ValidationGroup.Create.class, Default.class}) @RequestBody UserInDTO in) {
        UserOutDTO out = new UserOutDTO().setGender(in.getGender());
        Result<UserOutDTO> body = Result.ok(out);
        return ResponseEntity.ok(body);
    }
}

SpringBoot枚举序列化方案_第1张图片

SpringBoot枚举序列化方案_第2张图片
SpringBoot枚举序列化方案_第3张图片



PS

问题的根源还是参数校验,大致经历了如下几个阶段:

  1. 没有相关枚举校验的注解:DTO 转换时校验,自定义参数校验异常,和 validation 校验异常一起处理。
  2. 想找一份可复用的校验规则。
  3. 没找到:自定义一些校验规则。
  4. 不想自定义:在序列化上想办法。

关于自定义校验规则的念头源于@NotEmpty@NotBlank这两个非常非常常用的注解:

  • 别的注解都:null elements are considered valid.
  • 这两个注解:The annotated element must not be null.

这也就导致了维护一个大入参DTO,可以通过分组 + @NotNull 来实现的 NOT_NULL 策略更新实际上麻烦的一批。待测的解决方案为:

  1. 自定义 JSON 反序列化规则自动 trim,使用@Size 校验字符串。
  2. 使用Pattern校验字符串。

PS

如果有比较好用的 validation 扩展,还请提示一下哈

你可能感兴趣的:(Java)