Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I support JSR-303 validation and Jackson JSON mapping together effectively?

When building a RESTful web service using Spring MVC, I feel I've encountered a tricky situation when trying to combine Jackson JSON deserialization and JSR-303 bean validation.

One of the neat things about JSR-303 validation is that one can return all validation errors associated with a target object rather than just fail on the first constraint violation (though there is a fail fast mode if you want to use it).

Imagine a scenario where you have a JSON payload such as:

{
   "firstname": "Joe",
   "surname": "Bloggs",
   "age": 25
}

And then you have the object you wish to map to:

public class Person {

   @NotNull
   private String firstname;

   @NotNull
   private String surname;

   @Min(value = 0)
   private int age;

   ...
}

My issue with this setup is that if the client sends a String as the value for "age", the whole Jackson deserialization process will fail without being able to specifically target the reason why it failed (i.e. we'll never get as far as validating). Imagine, then, this payload:

{
   "age": "invalid"
}

In this case, what I'd like to return would be an "errors" response such as:

{ 
   "errors" : [
      "error": {
         "field": "firstname",
         "message": "Must not be null",
      },
      "error": {
         "field": "surname",
         "message": "Must not be null",
      },
      "error": {
         "field": "age",
         "message": "Must be numeric value greater than or equal 0",
      }
   ]
}

The simple way around this problem would be to simply specify the "age" field as type "String" and then convert the value when passing it on to, say, the persistence layer. However, I don't really like the idea of resorting to data transfer objects that use Strings in a blanket fashion - it just seems wrong to do that in terms of preserving a clean design.

Another option I thought of would be to create a class such as:

public abstract class BindableValue<T> {

   private String stringValue;

   private T value;

   public T getValue() {
      if(value == null) {
         value = fromString(stringValue);
      }
      return value;
   }

   protected abstract T fromString(String stringValue);
}

I could then create implementations such as IntegerValue, LongValue, DateTimeValue etc. of course also writing custom type adapters for Jackson. And then my Person class could be:

public class Person {

   @NotNull
   private StringValue firstname;

   @NotNull
   private StringValue surname;

   @Min(value = 0)
   private IntegerValue age;

   ...
}

This is almost what I want but, unfortunately, the JSR-303 annotations such as @NotNull and @Min are not going to recognise my BindableValue implementations.

So, can anyone explain a way I could solve this problem in a cleaner way?

like image 456
DrewEaster Avatar asked Dec 11 '11 10:12

DrewEaster


1 Answers

I realise now that the best way to do this may well be to simply use Strings for all fields but encapsulate that fact and still provide type specific method setters and getters. e.g.

public class Person {

    @NotNull
    private String name;

    @NotNull
    @Min(value = 0)
    private String age;

    public String getName() {
        return name;
    }

    public Integer getAge() {
        return age != null ? new Integer(age) : null;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(Integer age) {
        this.age = age == null ? null : String.valueOf(age);
    }

    @JsonSetter
    private void setAge(String age) {
        this.age = age;
    }
 }

In that way, I get the behaviour I'm after whilst ensuring the public object interface is still "clean".

like image 169
DrewEaster Avatar answered Sep 23 '22 06:09

DrewEaster