Home > Back-end >  How can I create custom validator on Java List type?
How can I create custom validator on Java List type?

Time:10-28

I have one NumberConstraint as follows:

@Constraint(validatedBy = { StringConstraintValidator.class, })
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.PARAMETER, })
public @interface StringConstraint {
   
    String message() default "'${validatedValue}' ist not valid Number. "  
            "A String is composed of 7 characters (digits and capital letters). Valid example: WBAVD13.";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

and it is validated by StringConstraintValidator as follows:

@Component
public class StringConstraintValidator implements ConstraintValidator<StringConstraint, String> {

    private static final String REGEX_String = "^[A-Z0-9]{7}$";

    @Override
    public void initialize(final StringConstraint annotation) {
        // noop
    }

    @Override
    public boolean isValid(final String value, final ConstraintValidatorContext context) {
        return isValid(value);
    }

    public boolean isValid(final String value) {
        // Number is not always null Checked via @NotNull
        LoggingContext.get().setString(value);
        if (value == null) {
            return false;
        }
        return Pattern.compile(REGEX_STRING).matcher(value).matches();
    }
}

I can apply this StringConstraint on single field of request object as follows:

@StringConstraint
private String number;

but if my request object contains a List of Strings then how can I use this constraint on entire List or do I have to define new on List type ?? Something like ConstraintValidator<StringConstraint, List ???>

My request object is:

    @JsonProperty(value = "items", required = true)
    @Schema(description = "List of items.", required = true)
    private List<String> items= new ArrayList<>();

So i want to apply my validator on all the strings in the list. How can i apply @StringConstraint on my list ?

CodePudding user response:

Yes, you can add more validators for one constraint by using a comma-separated list of validator classes in the validatedBy attribute of the @Constraint annotation. For example, you can write:

@Constraint(validatedBy = {StringConstraintValidator.class, BlablaValidot.class})
public @interface MyConstraint {
  // other attributes
}

Explanation

The @Constraint annotation is used to define a custom constraint annotation that can be applied to fields, methods, classes, etc. The validatedBy attribute specifies one or more classes that implement the ConstraintValidator interface and provide the logic to validate the annotated element. You can use multiple validators for the same constraint if you want to check different aspects or conditions of the value. For example, you can have one validator that checks the length of a string and another that checks the format of a string.

Examples

Here are some examples of custom constraint annotations with multiple validators:

  • A @PhoneNumber annotation that validates a phone number using two validators: one for the country code and one for the number format.
@Constraint(validatedBy = {CountryCodeValidator.class, PhoneNumberValidator.class})
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface PhoneNumber {
  String message() default "Invalid phone number";
  Class<?>[] groups() default {};
  Class<? extends Payload>[] payload() default {};
}
  • A @Password annotation that validates a password using three validators: one for the minimum length, one for the maximum length, and one for the presence of special characters.
@Constraint(validatedBy = {MinLengthValidator.class, MaxLengthValidator.class, SpecialCharValidator.class})
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface Password {
  String message() default "Invalid password";
  Class<?>[] groups() default {};
  Class<? extends Payload>[] payload() default {};
  int minLength() default 8;
  int maxLength() default 20;
  String specialChars() default "!@#$%^&*";
}

CodePudding user response:

A custom validator in Java is a way to define your own rules for validating the data of your objects or parameters. You can use the javax.validation API to create and use custom validators. The API consists of two main components: annotations and validators.

Annotations are used to declare the constraints that you want to apply on your data. They are usually placed on the fields or parameters that you want to validate. You can use the built-in annotations provided by the API, such as @NotNull, @Size, @Pattern, etc., or you can define your own annotations for your custom constraints.

Validators are classes that implement the ConstraintValidator interface and provide the logic for validating the data against the constraints. The interface has two generic parameters: A, which is the annotation type, and T, which is the data type. The interface has two methods: initialize and isValid. The initialize method is used to initialize the validator with the annotation attributes, and the isValid method is used to check if the data is valid or not according to the annotation.

To create a custom validator for a list type, you need to specify the list type as the second generic parameter of the ConstraintValidator interface, and implement the isValid method to iterate over the list elements and validate them individually. You can use any logic that suits your needs, such as checking the length, format, range, etc. of the elements. You can also use other annotations or validators inside your custom validator to reuse the existing validation rules.

Example

Here is an example of how to create a custom validator for a list of strings that checks if each element is a valid number.

Define the annotation

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

@Documented
@Constraint(validatedBy = StringConstraintValidator.class)
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface NumberConstraint {
    String message() default "Invalid number";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

Define the validator

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.List;

public class StringConstraintValidator implements ConstraintValidator<NumberConstraint, List<String>> {

    @Override
    public void initialize(NumberConstraint constraintAnnotation) {
        // You can use this method to initialize the validator with the annotation attributes
    }

    @Override
    public boolean isValid(List<String> value, ConstraintValidatorContext context) {
        // You can use this method to validate the list elements
        if (value == null || value.isEmpty()) {
            return true; // You can change this to false if you want to reject null or empty lists
        }
        for (String s : value) {
            try {
                Double.parseDouble(s); // Try to parse the string as a double
            } catch (NumberFormatException e) {
                return false; // If the string is not a valid number, return false
            }
        }
        return true; // If all the strings are valid numbers, return true
    }
}

Apply the annotation

import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.util.List;

public class Request {

    @NotNull
    @NumberConstraint
    private List<String> numbers;

    // Constructor, getters, setters, etc.

}

public class Controller {

    public void processRequest(@Valid Request request) {
        // Do something with the request
    }

}
  • Related