使用 Jackson 将枚举序列化为字符串以进行验证

Serialize Enum as String for validation using Jackson

我正在尝试使用自定义验证器验证枚举,在我的自定义验证器中,当枚举值中不存在参数时,我正在尝试return自定义消息。

下面是我的枚举

public enum Type {
    MISSING_SITE,
    INACTIVE_SITE;
}

吼我的PostMapping方法

@PostMapping(value = "/line-kpi", produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Find Kpis by one or more customer property")
public ResponseEntity<List<KpiDTO>> findKPILineByCustomer(@RequestBody @ValidCustomerParameter CustomerParameter customerParameter, @RequestParam @ValidExtractionDate String extractionDate) {
    var linesKpi = Optional.ofNullable(
            kpiService.findKPILineByCustomer(
                    Optional.ofNullable(customerParameter.getEntityPerimeter()).orElse(List.of()),
                    Optional.ofNullable(customerParameter.getName()).orElse(List.of()),
                    Optional.ofNullable(customerParameter.getIc01()).orElse(List.of()),
                    Optional.ofNullable(customerParameter.getSiren()).orElse(List.of()),
                    Optional.ofNullable(customerParameter.getEnterpriseId()).orElse(List.of()),
                    LocalDate.parse(extractionDate)
            )
    );
    return linesKpi.map(ResponseEntity::ok).orElseThrow(() -> new ResourceNotFoundException(KPIS));
}

我无法在方法本身中将枚举类型切换为字符串,因为我使用的是 swagger,它显示了一个很好的枚举选择列表。

不幸的是,当我尝试为 Type 提供不同的值时,它 return 是一个错误的请求,我的验证器没有被触发。

所以我试图序列化我的枚举,以便在它到达控制器时将其解释为字符串,为此我需要使用 Jackson,我试图寻找解决方案,但找不到合适的解决方案一个适合我的情况。

下面是我的验证器

public class ReportTypeValidator implements ConstraintValidator<ValidReportType, Type> {
    private String globalMessage;

    @Override
    public void initialize(ValidReportType constraintAnnotation) {
        ConstraintValidator.super.initialize(constraintAnnotation);
        globalMessage = constraintAnnotation.message();
    }

    @Override
    public boolean isValid(Type type, ConstraintValidatorContext constraintValidatorContext) {
        if (Arrays.stream(Type.values()).filter(type1 -> type1.equals(type)).toList().isEmpty()) {
            constraintValidatorContext
                    .buildConstraintViolationWithTemplate(globalMessage + ", report type does not exist")
                    .addConstraintViolation();
            return false;
        }
        return true;
    }
}
@Constraint(validatedBy = ReportTypeValidator.class)
@Target( { ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@Valid
public @interface ValidReportType {
    String message() default "Invalid value for report type";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

谁能告诉我如何将我的枚举转换为字符串以便我的验证器可以处理它?

添加一个特殊的枚举常量,指示请求 JSON 包含无效的枚举常量名称。请求 JSON 不应该实际包含此枚举常量的名称。还要添加 Jackson 将在反序列化以将 JSON 字符串转换为枚举常量时调用的方法。如果 JSON 字符串不是已知的枚举常量名称,则此方法 returns 特殊枚举常量。

public enum Type {
    MISSING_SITE,
    INACTIVE_SITE,

    @JsonProperty("SHOULD NEVER ACTUALLY APPEAR IN REQUEST JSON")
    INVALID;

    /**
     * Converts enum constant name to enum constant.
     *
     * @param name
     *         enum constant name
     * @return enum constant, or {@link #INVALID} if there is no enum constant with that name
     */
    @JsonCreator
    public static Type valueOfOrInvalid(String name) {
        try {
            return Type.valueOf(name);
        } catch (IllegalArgumentException e) {
            return INVALID;
        }
    }
}

ReportTypeValidator.isValid( 方法中,检查枚举常量是 INVALID.

if (type == Type.INVALID) {
    // Add constraint violation.

我找到了,我能够通过实现一个新的转换器来做到这一点,它将字符串转换为有效的枚举值或无效值:

public class TypeConverter implements Converter<String, Type> {
    @Override
    public Type convert(String source) {
        if (Arrays.stream(Type.values()).filter(type -> Objects.equals(type.toString(), source)).toList().isEmpty()) {
            return Type.INVALID;
        }
        return Type.valueOf(source.toUpperCase());
    }
}

之后我为我的转换器添加了一个新配置:

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new TypeConverter());
    }
}

此外,我还必须通过添加 @Schema 注释来隐藏我的枚举的无效值:

@Schema(allowableValues = {"MISSING_SITE","INACTIVE_SITE"}, type = "String")
public enum Type {
    MISSING_SITE,
    INACTIVE_SITE,
    INVALID
}

最后在验证器中,我应该拒绝无效值并显示自定义消息:

public class ReportTypeValidator implements ConstraintValidator<ValidReportType, Type> {
    private String globalMessage;

    @Override
    public void initialize(ValidReportType constraintAnnotation) {
        ConstraintValidator.super.initialize(constraintAnnotation);
        globalMessage = constraintAnnotation.message();
    }

    @Override
    public boolean isValid(Type type, ConstraintValidatorContext constraintValidatorContext) {
        if (type == Type.INVALID) {
            constraintValidatorContext
                    .buildConstraintViolationWithTemplate(globalMessage)
                    .addConstraintViolation();
            return false;
        }
        return true;
    }

}

前一个验证器的注解:

@Constraint(validatedBy = ReportTypeValidator.class)
@Target( { ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@Valid
public @interface ValidReportType {
    String message() default "Invalid value for report type";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}