When using Hibernate Validator (via the javax.validation
interfaces), I'm seeing a strange phenomenon in the property paths of nested objects when there are violations.
This demonstrates it:
@Documented
@Retention(RUNTIME)
@Target({ METHOD, FIELD, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Constraint(validatedBy = AddressLineValidator.class)
public @interface ValidAddressLine {
String message() default "Address line is invalid.";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
public class AddressLineValidator implements ConstraintValidator<ValidAddressLine, String> {
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return value != null && value.length() >= 5;
}
}
public class ValidationPathTest {
@lombok.Builder
static class Person {
private List<@Valid Address> addresses ;
}
@lombok.Builder
static class Address {
private List<@ValidAddressLine String> lines;
}
public static void main(String[] args) {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Address address = Address.builder()
.lines(Arrays.asList("221 Baker St.", "B"))
.build();
Person person = Person.builder()
.addresses(Arrays.asList(address))
.build();
Set<ConstraintViolation<Person>> violations = validator.validate(person);
violations.forEach(v -> System.out.println(v.getPropertyPath() + " : " + v.getMessage()));
}
}
This program produces the output
addresses[0].lines[1].<list element> : Address line is invalid.
My question is, why is <list element>
being appended to the property path, and only after the lines[1]
node? The logical follow-up question is, is there some way I can stop or prevent it? Having this in the output out an API endpoint confuses the API users, and I'd like to eliminate it.
This comes from the BV specification on value extractors, see built-in value extractors section in particular:
java.util.List; indexedValue() must be invoked for each contained element, passing the string literal as node name
You probably could try to provide your own value extractor for the List
. Something like:
class CustomListValueExtractor implements ValueExtractor<List<@ExtractedValue ?>> {
@Override
public void extractValues(List<?> originalValue, ValueReceiver receiver) {
for ( int i = 0; i < originalValue.size(); i++ ) {
// not passing the node name, so it won't appear in the path
receiver.indexedValue( null, i, originalValue.get( i ) );
}
}
}
and then pass it to the validator:
Validator validator = Validation.byDefaultProvider().configure()
.addValueExtractor( new CustomListValueExtractor() )
.buildValidatorFactory()
.getValidator();
this should override the default extractor. But as this is not an intended behavior - please make sure you test that there are no issues caused by such change.