Home > Software design >  Spring list validation - get field from invalid object
Spring list validation - get field from invalid object

Time:07-18

I'm trying to understand if it's possible to get the index of invalids objects inside a list that is validated with @Valid.

I already have validation in place where I send a response like this

{
    "status": "BAD_REQUEST",
    "message": "Validation Error",
    "detailedMessage": "There are errors in the entered data",
    "timestamp": 1657896844868,
    "validationErrors": [
        {
            "field": "items[0].name",
            "message": "The name is mandatory"
        },
        {
            "field": "items[1].surname",
            "message": "The surname is mandatory"
        }
    ]
}

The problem is that in the frontend I need to know exactly which objects in the array "items" have problems so I can highlight the corret input. What I'm doing right now is getting the index from the string "items[0].name" using a regex but I really dislike this kind of behavior and I would like to exctract the index of the invalid item and put it in the response.

Ideally I would like to not have the array index but a specific field of the invalid object. What I mean is that every item has an "id" field and I would like to extract that one and send something like this in response

 {
    "status": "BAD_REQUEST",
    "message": "Validation Error",
    "detailedMessage": "There are errors in the entered data",
    "timestamp": 1657896844868,
    "validationErrors": [
        {
            "field": "items[12345].name",
            "message": "The name is mandatory",
            "itemId": 12345 
        },
        {
            "field": "items[12346].surname",
            "message": "The surname is mandatory",
            "itemId": 12346
        }
    ]
}

In this way I would be able to know exactly which object is invalid in the frontend without having to rely on array indexes or regex to extract the index from a string. Of course having the array index as "itemIndex" field would also be better than what I have right now.

Following you can find my request class, that is used in the controller as @Valid @RequestBody, and my ControllerAdvice where I build my response when a MethodArgumentNotValidException happens.

Request

public class Request {
    @Valid
    @NotEmpty(message = "You must insert at least one item")
    private List<@Valid @NotNull Item> items;
}

ControllerAdvice

@RestControllerAdvice
public class BaseExceptionHandler extends ResponseEntityExceptionHandler {

    @Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
        ErrorResponse errorResponse = new ErrorResponse(HttpStatus.BAD_REQUEST, "Validation Error", "There are errors in the entered data");
        List<ValidationError> errors = new ArrayList<>();
        ex.getBindingResult().getAllErrors().forEach(error -> {
            String fieldName = ((FieldError) error).getField();
            String errorMessage = error.getDefaultMessage();
            ValidationError validationError = new ValidationError(fieldName, errorMessage);
            errors.add(validationError);
        });
        errorResponse.setValidationErrors(errors);
        return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
    }
}

CodePudding user response:

You can access information about objects that violated the constraint by unwrapping your ObjectError or FieldError to ConstraintViolation (https://docs.oracle.com/javaee/7/api/javax/validation/ConstraintViolation.html).

Here is how you can access array index

Path nodes = error.unwrap(ConstraintViolation.class).getPropertyPath();
for (Path.Node node : nodes) {
  if (node.isInIterable()) {
    node.getIndex() // for items[12346].surname it will return 123456
  }
}

Accessing an element that has thrown an error is also possible using unwrap to ConstraintViolation.

Item item = (Item) error.unwrap(ConstraintViolation.class).getLeafBean(); // don't use cast with no checks in your code

This will return an item which triggered constraint violation.

  • Related