Create an account

Very important

  • To access the important data of the forums, you must be active in each forum and especially in the leaks and database leaks section, send data and after sending the data and activity, data and important content will be opened and visible for you.
  • You will only see chat messages from people who are at or below your level.
  • More than 500,000 database leaks and millions of account leaks are waiting for you, so access and view with more activity.
  • Many important data are inactive and inaccessible for you, so open them with activity. (This will be done automatically)


Thread Rating:
  • 1019 Vote(s) - 3.48 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Validation of a list of objects in Spring

#1
I have the following controller method:

@RequestMapping(value="/map/update", method=RequestMethod.POST, produces = "application/json; charset=utf-8")
@ResponseBody
public ResponseEntityWrapper updateMapTheme(
HttpServletRequest request,
@RequestBody @Valid List<CompanyTag> categories,
HttpServletResponse response
) throws ResourceNotFoundException, AuthorizationException {
...
}

CompanyTag is defined this way:

public class CompanyTag {
@StringUUIDValidation String key;
String value;
String color;
String icon;
Icon iconObj;

public String getKey() {
return key;
}

public void setKey(String key) {
this.key = key;
}
...
}

The problem is that validation is not triggered, the CompanyTag list is not validated, the "StringUUIDValidation" validator is never called.

If I remove the List and only try to send a single CompanyTag, i.e. instead of:

@RequestBody @Valid List<CompanyTag> categories,
use:

@RequestBody @Valid CompanyTag category,

it works as expected, so apparently Spring does not like to validate lists of things (tried with array instead, that did not work either).

Anybody have any idea what's missing?
Reply

#2
Validating a collection does not work directly.

For example: what should it do if multiple elements fail the validation? Stop after first validation? Validate all (if so what is to be done with the collection of messages)?

If in your configuration Spring delegates to a Bean Validator provider like Hibernate Validator, you should look up for ways of implementing a collection validator there.


For Hibernate, a similar problem is discussed [here][1]


[1]:

[To see links please register here]

Reply

#3
I would suggest to wrap your List<CompanyTag> categories into some DTO bean and validate it. Beside of working validation you will benefit from more flexible API.

@RequestMapping(value="/map/update", method=RequestMethod.POST, produces = "application/json; charset=utf-8")
@ResponseBody
public ResponseEntityWrapper updateMapTheme(
HttpServletRequest request,
@RequestBody @Valid TagRequest tagRequest,
HttpServletResponse response
) throws ResourceNotFoundException, AuthorizationException {
...
}

public static class TagRequest {
@Valid
List<CompanyTag> categories;
// Gettes setters
}

Reply

#4
I found another approach that works. The basic problem is that you want to have a list as your input payload for your service, but javax.validation won't validate a list, only a JavaBean. The trick is to use a custom list class that functions as both a List **and** a JavaBean:

@RequestBody @Valid List<CompanyTag> categories

Change to:

@RequestBody @Valid ValidList<CompanyTag> categories

Your list subclass would look something like this:

public class ValidList<E> implements List<E> {

@Valid
private List<E> list;

public ValidList() {
this.list = new ArrayList<E>();
}

public ValidList(List<E> list) {
this.list = list;
}

// Bean-like methods, used by javax.validation but ignored by JSON parsing

public List<E> getList() {
return list;
}

public void setList(List<E> list) {
this.list = list;
}

// List-like methods, used by JSON parsing but ignored by javax.validation

@Override
public int size() {
return list.size();
}

@Override
public boolean isEmpty() {
return list.isEmpty();
}

// Other list methods ...
}
Reply

#5
I think the most elegant solution is to create a custom Validator for Collection and a @ControllerAdvice that registers that Validator in the WebDataBinders, take a look to

[To see links please register here]

Reply

#6
@Paul Strack's [great solution][1] mixed with Lombok magic:


@Data
public class ValidList<E> implements List<E> {
@Valid
@Delegate
private List<E> list = new ArrayList<>();
}

Usage (swap List for ValidList):

public ResponseEntityWrapper updateMapTheme(
@RequestBody @Valid ValidList<CompanyTag> categories, ...)


*(Needs [Lombok][2], but if you don't use it already you really want to try it out)*


[1]:

[To see links please register here]

[2]:

[To see links please register here]

Reply

#7
I'm using spring-boot 1.5.19.RELEASE

I annotate my service with `@validated` and then apply `@Valid` to the `List` parameter in the method and items in my list get validated.

Model

@Data
@ApiModel
@Validated
public class SubscriptionRequest {
@NotBlank()
private String soldToBpn;

@NotNull
@Size(min = 1)
@Valid
private ArrayList<DataProducts> dataProducts;

private String country;

@NotNull
@Size(min = 1)
@Valid
private ArrayList<Contact> contacts;
}


Service Interface (or use on concrete type if no interface)

@Validated
public interface SubscriptionService {
List<SubscriptionCreateResult> addSubscriptions(@NonNull @Size(min = 1) @Valid List<SubscriptionRequest> subscriptionRequestList)
throws IOException;
}

Global Exception Handler method (ApiError Type is not my design)

@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = ConstraintViolationException.class)
@ResponseBody
public ApiError[] handleConstraintViolationException(ConstraintViolationException exception) {
List<InvalidField> invalidFields = exception.getConstraintViolations().stream()
.map(constraintViolation -> new InvalidField(constraintViolation.getPropertyPath().toString(),
constraintViolation.getMessage(),
constraintViolation.getInvalidValue()))
.collect(Collectors.toList());
return new ApiError[] {new ApiError(ErrorCodes.INVALID_PARAMETER, "Validation Error", invalidFields)};
}

example bad method call from a controller

LinkedList<SubscriptionRequest> list = new LinkedList<>();
list.add(new SubscriptionRequest());
return subscriptionService.addSubscriptions(list);

Response body (note the index [0])

[
{
"errorCode": "invalid.parameter",
"errorMessage": "Validation Error",
"invalidFields": [
{
"name": "addSubscriptions.arg0[0].soldToBpn",
"message": "may not be empty",
"value": null
},
{
"name": "addSubscriptions.arg0[0].dataProducts",
"message": "may not be null",
"value": null
},
{
"name": "addSubscriptions.arg0[0].contacts",
"message": "may not be null",
"value": null
}
]
}
]
Reply

#8
use **@Validated** annotate controller<br/>
use **@Valid** annotate @RequestBody
Reply

#9
create entity class:
```
import javax.validation.Valid;
import java.util.List;

public class ValidList<E> {

@Valid
private List<E> list;

public List<E> getList() {
return list;
}

public void setList(List<E> list) {
this.list = list;
}
}
```
use Controller

```
@RequestMapping(value = "/sku", method = RequestMethod.POST)
public JsonResult createSKU(@Valid @RequestBody ValidList<Entity> entityList, BindingResult bindingResult) {
if (bindingResult.hasErrors())
return ErrorTools.build().handlerError(bindingResult);
return new JsonResult(200, "result");
}
```
Reply

#10
The `@Valid` annotation can be used inside the diamond operator:

private List<@Valid MyType> types;

or

@Valid
private List<MyType> types;

Now, every list item will be validated.
Reply



Forum Jump:


Users browsing this thread:
1 Guest(s)

©0Day  2016 - 2023 | All Rights Reserved.  Made with    for the community. Connected through