Spring Boot - How to validate incoming nested JSON request object fields with javax.validation

387 views Asked by At

I am using Spring Boot (2.7.6) and javax.validation and swagger v3.

My request JSON body of this form

{
    "itemDescription":"vehicle",
    "itemVersion":0,
    "itemCode":{
        "codeType":"1",
        "codeValue":"1111"
    }
}

The above JSON body is represented by 2 models ItemRequest and ItemCode as:

import io.swagger.v3.oas.annotations.media.Schema;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class ItemRequest {

    @NotNull
    @Schema(description = "Item description"
        , requiredMode = Schema.RequiredMode.REQUIRED)
    private String itemDescription;
    
    @NotNull
    @Schema(description = "Unique Item Code consisting of codeType integer and codeValue string"
        , requiredMode = Schema.RequiredMode.NOT_REQUIRED)  
    private ItemCode itemCode;
    
    @Min(value = 0)
    @Schema(description = "Item version'
        , requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    private int itemVersion;
}

, and its nested JSON model ItemCode:

import io.swagger.v3.oas.annotations.media.Schema;
import javax.validation.constraints.NotNull;

@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class ItemCode {

    @NotNull
    @Schema(description="codeType can be 1, 2, or 3"
        , requiredMode = Schema.RequiredMode.REQUIRED))
    private int codeType;
    
    @NotNull
    @Schema(description="Unique itemCode identifier. Can start with 0.  If codeType=1, 3-digits long, if codeType=2, 4-digits long, if codeTpye=3, 5-digits long.",
        , requiredMode = Schema.RequiredMode.REQUIRED)
    private String codeValue;  // for example 021, 5455, 05324
}

How do I validate ItemCode field in ItemRequest model so that validation does what descriptions for ItemCode state?

1

There are 1 answers

0
cd491415 On

The way I solved it thanks to suggestions from @Superchamp

Create a custom annotation

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = ItemCodeValidator.class)
@Documented
public @interface ValidItemCode {

    String message() default "Invalid ItemCode for ItemCode.codeType for ItemCode.codeValue: ${validatedValue}";
    Class<?>[] groups() default { };
    Class<? extends Payload>[] payload() default { };
}

Add custom validation logic:

public class ItemCodeValidator implements ConstraintValidator<ValidItemCode, ItemCode> {

    @Override
    public boolean isValid(ItemCode itemCode, ConstraintValidatorContext context) {

        if (itemCode == null) {
            return false;
        }

        switch (itemCode.getCodeType()) {
            case 1:
               return validateItemCode1(itemCode);
            case 2:
                return validateItemCode2(itemCode);
            case 3:
                return validateItemCode3(itemCode);
            default:
                return false;
        }
    }

    private boolean validateItemCode1(Key key) {
       // do your validation
    }

    private boolean validateItemCode2(Key key) {
        // do your validation
    }

    private boolean validateItemCode3(Key key) {
        // do your validation
    }
}

Add my custom annotation to the field in question:

...
@NotNull
@Schema(description = "Unique Item Code consisting of codeType integer and codeValue string"
        , requiredMode = Schema.RequiredMode.NOT_REQUIRED)
@ValidItemCode 
private ItemCode itemCode;
...