24

I want to know how to validate a list of nested objects in my form with Spring Validator (not annotation) in Spring MVC application.

class MyForm() {
    String myName;
    List<TypeA> listObjects;
}
class TypeA() {
    String number;
    String value;
}

How can I create a MyFormValidator to validate the listObjects and add error message for number and value of TypeA.

4 Answers 4

29

For the nested validation, you can do as below:

public class MyFormValidator implements Validator {

    private TypeAValidator typeAValidator;

    @Override
    public boolean supports(Class clazz) {
        return MyForm.class.equals(clazz);
    }

    @Override
    public void validate(Object target, Errors errors) {
        MyForm myForm = (MyForm) target;
        typeAValidator = new TypeAValidator();

        int idx = 0;
        for (TypeA item : myForm.getListObjects()) {

            errors.pushNestedPath("listObjects[" + idx + "]");
            ValidationUtils.invokeValidator(this.typeAValidator, item, errors);
            errors.popNestedPath();
            idx++;

            ...
        }

        ...
    }
}

public class TypeAValidator implements Validator{

    @Override
    public boolean supports(Class<?> clazz) {
        return TypeA.class.isAssignableFrom(clazz);
    }

    @Override
    public void validate(Object target, Errors errors) {
        TypeA objTypeA = (TypeA)target;

        ValidationUtils.rejectIfEmpty(errors, "number", "number.notEmpty");
    }
}

Hope this helps.

Sign up to request clarification or add additional context in comments.

6 Comments

I would recommend this solution. Just came across a similar issue in a project that I am working on. This solution allows you to keep the code modular, splitting your validation into multiple different validators if needs be.
Glad it helped. My intention for posting this was because it was modular and easy to maintain.
Any idea how to report back which row had the error?
Will this work when TypeA have a property List<TypeA> (nested)? If I have a class which is RuleCondition... I may have a field of List<RuleCondition> which is nested. I wonder if this also works on this scenario
^ please disregard my question, I tried it and it works on my given scenario. Thanks!
|
21
public class MyFormValidator implements Validator {

    @Override
    public boolean supports(Class clazz) {
        return MyForm.class.equals(clazz);
    }

    @Override
    public void validate(Object target, Errors errors) {
        MyForm myForm = (MyForm) target;

        for (int i = 0; i < myForm.getListObjects().size(); i++) {
            TypeA typeA = myForm.getListObjects().get(i);

            if(typeAHasAnErrorOnNumber) {
                errors.rejectValue("listObjects[" + i + "].number", "your_error_code");
            }

            ...
        }

        ...
    }

}

Interesting links :

5 Comments

Thanks, Jerome. If TypeA has its own validator, how can I use the validator in MyFormValidator? And how can I just display one error message on the form page if there are more than one errors?
@Leon - check my answer above.
Also note that Spring Validator officially supports nested lists and maps and automatically creates several error codes both with and without the index or map key, so that you can still use messages.properties to produce localized messages for the respective validation problem. Use applicationContext.getMessage(messageSourceResolvable, request.getLocale()) to resolve the Spring Validation error to a message.
A map with String keys can be traversed using myMap[stringKey].
The Validator produces a number of error codes like this: ["errorCode.objectName.myMap[stringKey]", "errorCode.objectName.myMap", "errorCode.myMap[stringKey]", "errorCode.myMap", "errorCode"] from which you can choose when defining your messages.properties entry. The messageSourceResolvable can e.g. be a FieldError.
3

You can use this anywhere in the project

import org.springframework.validation.ValidationUtils;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.collections.CollectionUtils;

    public static void invokeValidatorForNestedCollection(Validator validator,
                                                      Object obj,
                                                      String collectionPath,
                                                      Errors errors) {

    Collection collection;
    try {
        collection = (Collection) PropertyUtils.getProperty(obj, collectionPath);
    } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
        throw new RuntimeException(e);
    }

    if (CollectionUtils.isEmpty(collection)) return;
    int counter = 0;
    for (Object elem : collection) {
        errors.pushNestedPath(String.format(collectionPath + "[%d]", counter));
        ValidationUtils.invokeValidator(validator, elem, errors);
        errors.popNestedPath();
        counter++;
    }
}

Comments

2

A handy helper class that I use -

public final class ValidationHelper {

    public static <TEntity> void invokeNestedValidator(Validator validator, TEntity entity, Errors errors, String subPath) {
        try {
            errors.pushNestedPath(subPath);
            ValidationUtils.invokeValidator(validator, entity, errors);
        } finally {
            errors.popNestedPath();
        }
    }

    public static <TEntity> void invokeNestedValidatorForList(Validator validator, List<TEntity> entities, Errors errors, String subPathRoot) {
        for (int index = 0; index < entities.size(); index++) {
            invokeNestedValidator(validator, entities.get(index), errors, subPathRoot + "[" + index + "]");
        }
    }

    private ValidationHelper() {}
}

1 Comment

How to use ValidationHelper class?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.