22

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.

Jerome Dalbert
  • 10,067
  • 6
  • 56
  • 64
Leon
  • 699
  • 2
  • 7
  • 10

4 Answers4

28

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.

Uresh K
  • 1,136
  • 11
  • 16
  • 4
    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. – JohnStrong Mar 10 '16 at 17:11
  • Glad it helped. My intention for posting this was because it was modular and easy to maintain. – Uresh K Aug 29 '16 at 17:36
  • 1
    Any idea how to report back which row had the error? – Drew Oct 27 '16 at 11:25
  • Will this work when `TypeA` have a property `List` (nested)? If I have a class which is `RuleCondition`... I may have a field of `List` which is nested. I wonder if this also works on this scenario – Borgy Manotoy Sep 13 '21 at 15:27
  • ^ please disregard my question, I tried it and it works on my given scenario. Thanks! – Borgy Manotoy Sep 13 '21 at 16:18
  • `org.springframework.beans.NotReadablePropertyException: Invalid property 'listObjects[0]' of bean class [java.util.ArrayList]: Bean property 'listObjects[0]' is not readable or has an invalid getter method: Does the return type of the getter match the parameter type of the setter? ` will get this exception when implement – Yu Tian Toby Jan 05 '22 at 12:39
20
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 :

Jerome Dalbert
  • 10,067
  • 6
  • 56
  • 64
  • 3
    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 Oct 02 '12 at 01:26
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++;
    }
}
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() {}
}
rougou
  • 956
  • 9
  • 14
sanz
  • 1,069
  • 1
  • 10
  • 26