Skip to content
Advertisement

Serialize Enum as String for validation using Jackson

I’m trying to validate an Enum using the custom validator, in my custom validator I’m trying to return a custom message when a parameter does not exist in the enum values.

Bellow my enum

public enum Type {
    MISSING_SITE,
    INACTIVE_SITE;
}

Bellow my PostMapping method

@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));
}

I can’t switch the type of enum to string in the method itself because I’m using swagger which displays a nice selection list for enums.

Unfortunately, when I try to give a different value for Type, it returns a bad request and my validators are not triggered.

So I’m trying to serialize my enum to be interpreted as String when it arrives at the controller and to do that I need to use Jackson, I tried to look for a solution but I can’t find a good one for my case.

Bellow are my validators

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 {};
}

Could anyone tell me how can I turn my enum as a string so my validator could handle it?

Advertisement

Answer

I found it, I was able to do it by implementing a new converter, which will convert string to a valid enum value or an INVALID value:

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());
    }
}

After that I added a new Configuration for my converter:

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

Also I had to hide the INVALID value of my enum from swagger by adding the @Schema annotation:

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

Finally in the validators, I should reject the INVALID value and display a custom message:

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;
    }

}

The annotation for the previous validator:

@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 {};
}
User contributions licensed under: CC BY-SA
4 People found this is helpful
Advertisement