Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to validate a Map<String, String> using Spring Validator programmatically

I have a Map that I receive from a browser redirection back from a third party to my Spring Controller as below -

@RequestMapping(value = "/capture", method = RequestMethod.POST, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
    public void capture(@RequestParam
    final Map<String, String> response)
    {
        // TODO : perform validations first.
        captureResponse(response);
    }

Before using this payload, I need to do non-trivial validation, involving first checking for non-null values of a map, and then using those values in a checksum validation. So, I would like to validate my payload programmatically using the Spring Validator interface. However, I could not find any validator example for validating a Map.

For validating a Java Object, I understand how a Validator is invoked by passing the object and a BeanPropertyBindingResult to contain the errors to the Validator as below -

final Errors errors = new BeanPropertyBindingResult(object, objectName);
myValidator.validate(object, errors);
if (errors.hasErrors())
{
    throw new MyWebserviceValidationException(errors);
}

For a Map, I can see that there is a MapBindingResult class that extends AbstractBindingResult. Should I simply use it, and pass my map in the Object object and in the validator cast it back to a Map? Also, how would the Validator method of supports(final Class<?> clazz) be implemented in my validator? Would it simply be like below code snippet where there can only be one validator supporting this generic class of HashMap? Somehow doesn't feel right. (Although this does not matter to me as I will be injecting my validator and use it directly and not through a validator registry, but still curious.)

@Override
public boolean supports(final Class<?> clazz)
{
    return HashMap.class.equals(clazz);
}

Since, there is a MapBindingResult, I'm positive that Spring must be supporting Maps for validation, would like to know how. So would like to know if this is the way to go, or am I heading in the wrong direction and there is a better way of doing this.

Please note I would like to do this programmatically and not via annotations.

like image 541
Anurag Avatar asked Jul 14 '19 11:07

Anurag


People also ask

How do you validate a map in Spring boot?

Validate the parameters inside the map For the validation of your Map following a specific mapping you will need a custom validator. As this may be the usecase for some, validation of @RequestParam can be done using org. springframework. validation annotations, e.g.

What is the use of @validated annotation?

The @Validated annotation is a class-level annotation that we can use to tell Spring to validate parameters that are passed into a method of the annotated class. We'll learn more about how to use it in the section about validating path variables and request parameters.

What is @valid annotation in Spring boot?

The @Valid annotation will tell spring to go and validate the data passed into the controller by checking to see that the integer numberBetweenOneAndTen is between 1 and 10 inclusive because of those min and max annotations.


1 Answers

Just like I thought, Spring Validator org.springframework.validation.Validator does support validation of a Map. I tried it out myself, and it works!

I created a org.springframework.validation.MapBindingResult by passing in the map I need to validate and an identifier name for that map (for global/root-level error messages). This Errors object is passed in the validator, along with the map to be validated as shown in the snippet below.

final Errors errors = new MapBindingResult(responseMap, "responseMap");
myValidator.validate(responseMap, errors);
if (errors.hasErrors())
{
    throw new MyWebserviceValidationException(errors);
}

The MapBindingResult extends AbstractBindingResult and overrides the method getActualFieldValue to give it's own implementation to get field from a map being validated.

private final Map<?, ?> target;

@Override
protected Object getActualFieldValue(String field) {
    return this.target.get(field);
}

So, inside the Validator I was able to use all the useful utility methods provided in org.springframework.validation.ValidationUtils just like we use in a standard object bean validator. For example -

ValidationUtils.rejectIfEmpty(errors, "checksum", "field.required");

where "checksum" is a key in my map. Ah, the beauty of inheritance! :)

For the other non-trivial validations, I simply cast the Object to Map and wrote my custom validation code.

So the validator looks something like -

@Override
public boolean supports(final Class<?> clazz)
{
    return HashMap.class.equals(clazz);
}
@Override
public void validate(final Object target, final Errors errors)
{
    ValidationUtils.rejectIfEmpty(errors, "transactionId", "field.required");
    ValidationUtils.rejectIfEmpty(errors, "checksum", "field.required");

    final Map<String, String> response = (HashMap<String, String>) target;
    // do custom validations with the map's attributes
    // ....
    // if validation fails, reject the whole map - 
         errors.reject("response.map.invalid");
}
like image 164
Anurag Avatar answered Sep 28 '22 01:09

Anurag