Spring form validation with conversion - spring

In a form I take a comma separated list of integers and convert them to an ArrayList<Integer>. This works fine. My form-backing object is:
public class FormDto {
#NotNull
private ArrayList<Integer> nList;
// getters and setters
}
In the form I have:
<input type="text" class="form-control" id="nList" th:field="*{nList}" required="required"/>
Apparently spring automatically converts the string into an ArrayList. Just in case I also created a Converter:
public class CSVStringToListConverter implements Converter<String, ArrayList<Integer>>{
#Override
public ArrayList<Integer> convert(String arg0) {
ArrayList<Integer> list = new ArrayList<Integer>();
for (String s : arg0.split(",")) {
list.add(Integer.parseInt(s));
}
return list;
}
}
which I add in
#Configuration
#EnableWebMvc
public class MvcConfig extends WebMvcConfigurerAdapter implements ApplicationContextAware {
#Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new CSVStringToListConverter());
}
}
Is this all I have to do in order to make converters work? As I wrote, the conversion also works without this converter, so I am unsure whether my converter is actually used or whether it still uses the built-in converter.
My main question however is how to validate the input String (not the ArrayList)? For form fields without conversion I just do that in the controller with:
#PostMapping("/submitForm")
public String formSubmit(#ModelAttribute #Valid SortingExplicitDto sortingExplicitDto, BindingResult result, Model model) {
if(result.hasErrors()) {
model.addAttribute("formDto", new formDto());
return "submitForm";
}
}
Is it possible to validate the input String in the same way, or do I have to do that with Javascript or in the converter?
addition
After a few more tests, I noticed that it does use my custom converter, which does answer my first question. Still I am wondering how to deal with the validation. If I enter some random string which are not integers separated by comma, I receive an exception:
Failed to convert property value of type [java.lang.String] to required type [java.util.ArrayList] for property nList; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [#javax.validation.constraints.NotNull java.util.ArrayList<java.lang.Integer>] for value ,2,3; nested exception is java.lang.NumberFormatException: For input string:
Also I can do all my validation inside the CSVStringToListConverter and throw an exception if validation fails. I believe I should then catch the exception in the controller and somehow display a nice human readable message in the form. In the html, I display errors (including the one above) with:
<span id="errornList" class="alert alert-danger" th:if="${#fields.hasErrors('nList')}" th:errors="*{nList}">NList error</span>
How do I catch the exception in the controller and put a nice error message there?

Your converter can be this way:
#Component
public class CSVStringToListConverter implements Converter<String, List<Integer>> {
#Autowired
private Errors errors;
#Override
public List<Integer> convert(String arg0) {
List<Integer> list = new ArrayList<Integer>();
for (String s : arg0.split(",")) {
try {
list.add(Integer.parseInt(s));
} catch (NumberFormatException e) {
list = Collections.emptyList();
errors.rejectValue("fieldName", "fieldName.invalid", "Invalid integer in list");
}
}
return list;
}}
Register the Converter in a Configuration class
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new CSVStringToListConverter());
}
}
I only Injected the "Errors" class in order to display "nice human readable message in the form" as you wish to have.
Use "org.springframework.validation.BindException" implementation of Errors class, since Errors is an interface, preferably in a configuration class and then the injection should work.

Related

JSON field Desrializing to lowercase in Spring Boot

I have a Spring Boot Controller -
#RestController
public class UserController {
#PostMapping
#ResponseStatus(CREATED)
public UserResponse register( #Valid #RequestBody UserRequest userRequest) {
//return ....
}
}
Below is UserRequest.java
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder
public class UserRequest {
private String email;
//other property
}
I am sending below json in request body -
{
"email" : "TEST#Example.com",
//some other fields.
}
Sometime client send email in uppercase or in camel case so in userRquest I want to change value of email field to lowercase like test#example.com while de serializing to UserRequest Object.
Is there any easy way to do this. Can I introduce my own annotation like #ToLowerCase how I can create my own annotation and use that at field level in UserRequest.
There is no easy way just by introducing a new annotation #ToLowerCase,
because then you would also need to implement some annotation processor
for doing the real conversion work.
But you can achieve your goal in a slightly different way.
In your UserRequest class annotate the email property
with #JsonDeserialize and specify a converter there.
#JsonDeserialize(converter = ToLowerCaseConverter.class)
private String email;
You need to implement the converter class by yourself,
but it is easy by extending it from StdConverter.
public class ToLowerCaseConverter extends StdConverter<String, String> {
#Override
public String convert(String value) {
return value.toLowerCase();
}
}
Jackson will use the setter methods in your class.
Perform the conversion to lower case in the setter.
For example
public void setEmail(String newValue)
{
email = StringUtils.lowerCase(newValue);
}
StringUtils is an apache commons class.
You can make a general StringDeserializer and register it in ObjectMapper as shown below:-
StringDeserializer class
public final class StringDeserializer extends StdDeserializer<String> {
public StringDeserializer() {
super((Class<String>) null);
}
#Override
public String deserialize(JsonParser parser, DeserializationContext context) throws IOException {
JsonToken token = parser.getCurrentToken();
if (token == JsonToken.VALUE_STRING) {
String text = parser.getText();
return text == null ? null : text.toLowerCase().trim();
}
return null;
}
}
JacksonConfiguration class
#Configuration
public class JacksonConfiguration {
#Autowired
void mapper(ObjectMapper mapper) {
mapper.registerModule(initModule());
}
private Module initModule() {
SimpleModule module = new SimpleModule();
module.addDeserializer(String.class, new StringDeserializer());
return module;
}
}
The above code makes jackson deserialize all strings to lowercase and trimmed.

How to set different type for variable in POJO than expected while deserializing json using gson in android(see example)

Bear with my English. I have a simple json,
{
"Hint2": "L"
}
this is the POJO that works.
public class Hints {
#SerializedName("Hint2")
#Expose
private String Hint2;
public void setHint1(Object Hint2) {
this.Hint2 = (Hint2);
}
}
i want to change it to
public class Hints {
#SerializedName("Hint2")
#Expose
public final ObservableField<String> Hint2 = new ObservableField<>();
public void setHint2(String Hint2) {
this.Hint2.set(Hint2);
}
}
both class has same setter method, same #SerializedName annotation tag. only type of Hint2 object is changed. but the latter one throws exception shown below
Caused by: com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at..
so i believe deserialization depends on what kind of variable "Hint2" is.
Is there a way to make it work with ObservableField rather than using String?
The reason i'm trying this is android binding library, which supports binding objects directly to xml files. and the ObservableField automatically updates UI when corresponding value in POJO is changed.
Update:
gson design document has this
Using fields vs getters to indicate Json elements
Some Json libraries use the getters of a type to deduce the Json elements. We chose to use all fields (up the inheritance hierarchy) that are not transient, static, or synthetic. We did this because not all classes are written with suitably named getters. Moreover, getXXX or isXXX might be semantic rather than indicating properties.
However, there are good arguments to support properties as well. We intend to enhance Gson in a latter version to support properties as an alternate mapping for indicating Json fields. For now, Gson is fields-based.
so this indicates that Gson is fields-based. this pretty much answers my question but still waiting if anyone has someway around this.
I came across the same requirements, and resolved it finally, here are the steps:
create the class GsonUtils:
public class GsonUtils {
// code following
//...
}
following code are in this class
write a customized serializer & deserializer:
private static class ObservableFieldSerializerDeserializer implements JsonSerializer>, JsonDeserializer> {
#Override
public JsonElement serialize(ObservableField src, Type typeOfSrc, JsonSerializationContext context) {
return context.serialize(src.get());
}
#Override
public ObservableField deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
final Type type = ((ParameterizedType) typeOfT).getActualTypeArguments()[0];
return new ObservableField((T) GsonUtils.getGson().fromJson(json, type));
}
}
you need to register ObservableField types to Gson:
private static GsonBuilder createGsonBuilder() {
final GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(new TypeToken&ltObservableField&ltString&gt&gt(){}.getType(), new ObservableFieldSerializerDeserializer&ltString&gt());
...// register more types which are wrapped by ObservableFields
return gsonBuilder;
}
create the Gson which is used by the deserializer
private static final Gson sGson = createGson();
private static Gson createGson() {
return createGsonBuilder().create();
}
// this is used by the deserializer
public static Gson getGson() {
return sGson;
}
that's all, hope it helps
I just ran into what I think is the same issue, and here is a JUnit4 test showing how I solved it with Jackson for a POJO, but of course String would work as well.
public class ObservableDeserializationTest {
private static class ObservableDeserializer extends JsonDeserializer<ObservableField> implements ContextualDeserializer {
private Class<?> mTargetClass;
#Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException {
mTargetClass = property.getType().containedType(0).getRawClass();
return this;
}
#Override
public ObservableField deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
ObservableField result = new ObservableField();
result.set(p.readValueAs(mTargetClass));
return result;
}
}
private static class SomePojo {
public String id;
public String name;
}
private static class ObservableTestClass {
#JsonDeserialize(using = ObservableDeserializer.class)
public ObservableField<SomePojo> testObj = new ObservableField<>();
}
#Test
public void DeserializingAnObservableObjectShouldSetValueCorrectly() {
ObservableTestClass tc = null;
try {
tc = new ObjectMapper().readValue("{\"testObj\":{\"name\":\"TestName\",\"id\":\"TestId\"}}", ObservableTestClass.class);
} catch (IOException e) {
e.printStackTrace();
}
Assert.assertEquals("TestName", tc.testObj.get().name);
Assert.assertEquals("TestId", tc.testObj.get().id);
}
}
The key is the ContextualDeserializer interface that allows extracting the contained class type. Jackson provides several options for registering a custom deserializer, so this is but one way of doing it. Also, it would probably be a good idea to override getNullValue as well in the deserializer if you would use this for real.

Spring Validation rejectValue for inherited fields

I get Exception
org.springframework.beans.NotReadablePropertyException: Invalid property 'entries[0].reason' of bean class [my.company.data.SDROrder]: Bean property 'entries[0].reason' is not readable or has an invalid getter method: Does the return type of the getter match the parameter type of the setter?
from the following code snippet:
Errors errors = new BeanPropertyBindingResult(new SDROrder(), "sdr");
orderValidator.validate(order, errors);
for validator:
public class OrderValidator implements Validator
{
#Override
public boolean supports(Class<?> clazz)
{
return Order.class.isAssignableFrom(clazz);
}
#Override
public void validate(final Object target, final Errors errors)
{
errors.rejectValue("entries[0].reason", "Wrong Reason");
}
}
where we have such data hierarchy
public class Order
{
private List<AbstractOrderEntry> entries;
public List<AbstractOrderEntry> getEntries()
{
return entries;
}
public void setEntries(List<AbstractOrderEntry> entries)
{
this.entries = entries;
}
}
public class SDROrder extends Order
{
}
public class AbstractOrderEntry
{
}
public class SDROrderEntry extends AbstractOrderEntry
{
private String reason;
public String getReason()
{
return reason;
}
public void setReason(String reason)
{
this.reason = reason;
}
}
Please see working example here: here
Update 1: Just to clarify. The problem is I try to rejectValue on object that has Collection of objects where each element has specific attribute at Runtime but has not it at Compile time. Spring uses Bean's properties to resolve these fields and can't find inherited attribute. The question is: can I explain Spring to resolve inherited fields somehow?
I found the solution here.
The trick is at
org.springframework.validation.Errors.pushNestedPath(String)
and
org.springframework.validation.Errors.popNestedPath()
methods.
The correct validation should be done as follow:
errors.pushNestedPath("entries[0]");
errors.rejectValue("reason", "Wrong Reason");
errors.popNestedPath();

How to validate Spring MVC #PathVariable values?

For a simple RESTful JSON api implemented in Spring MVC, can I use Bean Validation (JSR-303) to validate the path variables passed into the handler method?
For example:
#RequestMapping(value = "/number/{customerNumber}")
#ResponseBody
public ResponseObject searchByNumber(#PathVariable("customerNumber") String customerNumber) {
...
}
Here, I need to validate the customerNumber variables's length using Bean validation. Is this possible with Spring MVC v3.x.x? If not, what's the best approach for this type of validations?
Thanks.
Spring does not support #javax.validation.Valid on #PathVariable annotated parameters in handler methods. There was an Improvement request, but it is still unresolved.
Your best bet is to just do your custom validation in the handler method body or consider using org.springframework.validation.annotation.Validated as suggested in other answers.
You can use like this:
use org.springframework.validation.annotation.Validated to valid RequestParam or PathVariable.
*
* Variant of JSR-303's {#link javax.validation.Valid}, supporting the
* specification of validation groups. Designed for convenient use with
* Spring's JSR-303 support but not JSR-303 specific.
*
step.1 init ValidationConfig
#Configuration
public class ValidationConfig {
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
return processor;
}
}
step.2 Add #Validated to your controller handler class, Like:
#RequestMapping(value = "poo/foo")
#Validated
public class FooController {
...
}
step.3 Add validators to your handler method:
#RequestMapping(value = "{id}", method = RequestMethod.DELETE)
public ResponseEntity<Foo> delete(
#PathVariable("id") #Size(min = 1) #CustomerValidator int id) throws RestException {
// do something
return new ResponseEntity(HttpStatus.OK);
}
final step. Add exception resolver to your context:
#Component
public class BindExceptionResolver implements HandlerExceptionResolver {
#Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
if (ex.getClass().equals(BindException.class)) {
BindException exception = (BindException) ex;
List<FieldError> fieldErrors = exception.getFieldErrors();
return new ModelAndView(new MappingJackson2JsonView(), buildErrorModel(request, response, fieldErrors));
}
}
}
The solution is simple:
#GetMapping(value = {"/", "/{hash:[a-fA-F0-9]{40}}"})
public String request(#PathVariable(value = "hash", required = false) String historyHash)
{
// Accepted requests: either "/" or "/{40 character long hash}"
}
And yes, PathVariables are ment to be validated, like any user input.
Instead of using #PathVariable, you can take advantage of Spring MVC ability to map path variables into a bean:
#RestController
#RequestMapping("/user")
public class UserController {
#GetMapping("/{id}")
public void get(#Valid GetDto dto) {
// dto.getId() is the path variable
}
}
And the bean contains the actual validation rules:
#Data
public class GetDto {
#Min(1) #Max(99)
private long id;
}
Make sure that your path variables ({id}) correspond to the bean fields (id);
#PathVariable is not meant to be validated in order to send back a readable message to the user. As principle a pathVariable should never be invalid. If a pathVariable is invalid the reason can be:
a bug generated a bad url (an href in jsp for example). No #Valid is
needed and no message is needed, just fix the code;
"the user" is manipulating the url.
Again, no #Valid is needed, no meaningful message to the user should
be given.
In both cases just leave an exception bubble up until it is catched by
the usual Spring ExceptionHandlers in order to generate a nice
error page or a meaningful json response indicating the error. In
order to get this result you can do some validation using custom editors.
Create a CustomerNumber class, possibly as immutable (implementing a CharSequence is not needed but allows you to use it basically as if it were a String)
public class CustomerNumber implements CharSequence {
private String customerNumber;
public CustomerNumber(String customerNumber) {
this.customerNumber = customerNumber;
}
#Override
public String toString() {
return customerNumber == null ? null : customerNumber.toString();
}
#Override
public int length() {
return customerNumber.length();
}
#Override
public char charAt(int index) {
return customerNumber.charAt(index);
}
#Override
public CharSequence subSequence(int start, int end) {
return customerNumber.subSequence(start, end);
}
#Override
public boolean equals(Object obj) {
return customerNumber.equals(obj);
}
#Override
public int hashCode() {
return customerNumber.hashCode();
}
}
Create an editor implementing your validation logic (in this case no whitespaces and fixed length, just as an example)
public class CustomerNumberEditor extends PropertyEditorSupport {
#Override
public void setAsText(String text) throws IllegalArgumentException {
if (StringUtils.hasText(text) && !StringUtils.containsWhitespace(text) && text.length() == YOUR_LENGTH) {
setValue(new CustomerNumber(text));
} else {
throw new IllegalArgumentException();
// you could also subclass and throw IllegalArgumentException
// in order to manage a more detailed error message
}
}
#Override
public String getAsText() {
return ((CustomerNumber) this.getValue()).toString();
}
}
Register the editor in the Controller
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(CustomerNumber.class, new CustomerNumberEditor());
// ... other editors
}
Change the signature of your controller method accepting CustomerNumber instead of String (whatever your ResponseObject is ...)
#RequestMapping(value = "/number/{customerNumber}")
#ResponseBody
public ResponseObject searchByNumber(#PathVariable("customerNumber") CustomerNumber customerNumber) {
...
}
You can create the answer you want by using the fields in the ConstraintViolationException with the following method;
#ExceptionHandler(ConstraintViolationException.class)
protected ResponseEntity<Object> handlePathVariableError(final ConstraintViolationException exception) {
log.error(exception.getMessage(), exception);
final List<SisSubError> subErrors = new ArrayList<>();
exception.getConstraintViolations().forEach(constraintViolation -> subErrors.add(generateSubError(constraintViolation)));
final SisError error = generateErrorWithSubErrors(VALIDATION_ERROR, HttpStatus.BAD_REQUEST, subErrors);
return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
}
You need to added an #Validated annotation to Controller class and any validation annotation before path variable field
Path variable may not be linked with any bean in your system. What do you want to annotate with JSR-303 annotations?
To validate path variable you should use this approach Problem validating #PathVariable url on spring 3 mvc
Actually there is a very simple solution to this. Add or override the same controller method with its request mapping not having the placeholder for the path variable and throw ResponseStatusException from it. Code given below
#RequestMapping(value = "/number")
#ResponseBody
public ResponseObject searchByNumber() {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST,"customer number missing")
}

Spring InitBinder: bind empty or null values of a float field as 0

I'm just wondering if it's possible to tell an #InitBinder that empty float values in a form would be converted to 0.
I know that float is a primitive data type but I'd still like to convert null or empty values to 0.
If that is possible, how can i achieve that?
Otherwise I'll just make a workaround using a String instead of a float
Define a subclsss of CustomNumberEditor as
import org.springframework.beans.propertyeditors.CustomNumberEditor;
import org.springframework.util.StringUtils;
public class MyCustomNumberEditor extends CustomNumberEditor {
public MyCustomNumberEditor(Class<? extends Number> numberClass) throws IllegalArgumentException {
super(numberClass, true);
}
#Override
public void setAsText(String text) throws IllegalArgumentException {
if (!StringUtils.hasText(text)) {
setValue(0);
}else {
super.setAsText(text.trim());
}
}
}
Then in your controller class (I create a BaseController for all my application controllers, I need this behavior for all the numeric primitive types in my application, so I simply define this in my BaseController), register binders for the various primitive types.
Note that the constructor parameter of MyCustomNumberEditor must be a subclass of Number, instead of primitive class type.
#InitBinder
public void registerCustomerBinder(WebDataBinder binder) {
binder.registerCustomEditor(double.class, new MyCustomNumberEditor(Double.class));
binder.registerCustomEditor(float.class, new MyCustomNumberEditor(Float.class));
binder.registerCustomEditor(long.class, new MyCustomNumberEditor(Long.class));
binder.registerCustomEditor(int.class, new MyCustomNumberEditor(Integer.class));
....
}
Yes you could always do that .Spring have a CustomNumberEditor which is a customizable property editor for any Number subclass like Integer, Long, Float, Double.It is registered by default by BeanWrapperImpl,but, can be overridden by registering custom instance of it as custom editor.It means you could extend a class like this
public class MyCustomNumberEditor extends CustomNumberEditor{
public MyCustomNumberEditor(Class<? extends Number> numberClass, NumberFormat numberFormat, boolean allowEmpty) throws IllegalArgumentException {
super(numberClass, numberFormat, allowEmpty);
}
public MyCustomNumberEditor(Class<? extends Number> numberClass, boolean allowEmpty) throws IllegalArgumentException {
super(numberClass, allowEmpty);
}
#Override
public String getAsText() {
//return super.getAsText();
return "Your desired text";
}
#Override
public void setAsText(String text) throws IllegalArgumentException {
super.setAsText("set your desired text");
}
}
And then register it normally in you controller:
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(Float.class,new MyCustomNumberEditor(Float.class, true));
}
This should do the task.

Resources