2011-12-11 8 views
13

Cuando construyo un servicio web RESTful usando Spring MVC, siento que he encontrado una situación difícil cuando intento combinar la deserialización de Jackson JSON y la validación de beans JSR-303.¿Cómo puedo admitir la validación JSR-303 y el mapeo Jackson JSON juntos de manera efectiva?

Una de las cosas buenas de la validación JSR-303 es que uno puede devolver todos los errores de validación asociados con un objeto de destino en lugar de simplemente fallar en la primera violación de restricción (aunque hay un modo de falla rápida si desea usarlo)

Imagine un escenario en el que tienen una carga útil JSON como:

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

Y entonces usted tiene el objeto que desea mapear:

public class Person { 

    @NotNull 
    private String firstname; 

    @NotNull 
    private String surname; 

    @Min(value = 0) 
    private int age; 

    ... 
} 

Mi problema con esta disposición es que si el cliente envía un String como el valor de "edad", todo el proceso de deserialización de Jackson fallará sin poder apuntar específicamente al motivo por el cual falló (es decir, nunca llegaremos a validar). Imagínese, entonces, esta carga útil:

{ 
    "age": "invalid" 
} 

En este caso, lo que me gustaría volver sería una respuesta "errores", tales como:

{ 
    "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", 
     } 
    ] 
} 

La forma más sencilla de solucionar este problema sería simplemente especifique el campo "edad" como tipo "Cadena" y luego convierta el valor al pasarlo a, por ejemplo, la capa de persistencia. Sin embargo, realmente no me gusta la idea de recurrir a objetos de transferencia de datos que usan cadenas de forma generalizada; parece incorrecto hacerlo en términos de preservar un diseño limpio.

Otra opción pensé sería la creación de una clase como:

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); 
} 

pude luego crear implementaciones como IntegerValue, longValue, DateTimeValue etc., por supuesto, también la escritura adaptadores de tipo personalizado para Jackson. Y entonces mi clase de persona puede ser:

public class Person { 

    @NotNull 
    private StringValue firstname; 

    @NotNull 
    private StringValue surname; 

    @Min(value = 0) 
    private IntegerValue age; 

    ... 
} 

Esto es casi lo que quiera pero, por desgracia, los JSR-303 anotaciones tales como @NotNull y @Min no van a reconocer mis implementaciones BindableValue.

Entonces, ¿alguien puede explicar una forma en que podría resolver este problema de una manera más limpia?

Respuesta

2

Ahora me doy cuenta de que la mejor manera de hacer esto puede ser simplemente usar cadenas para todos los campos, pero encapsular ese hecho y seguir proporcionando adaptadores de método y getters específicos de tipo. p.ej.

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; 
    } 
} 

De esta forma, obtengo el comportamiento que busco mientras aseguro que la interfaz del objeto público todavía está "limpia".

1

Otra posibilidad es simplemente deshabilitar las quejas de Jackson sobre las propiedades no reconocidas (ver la página wiki this), "soltar" las cosas no reconocidas y validar las cosas que se vinculen. Alternativamente, también puede definir un oyente para propiedades no reconocidas. Y luego podría usar Bean Validation para verificar los datos que se vinculen.

0

Ok, ya que esto aún no se ha mencionado ....DropWizard es el camino a seguir, creo que combina JAX-RS (Jersey) en Jackson con Bean Validation (creo que hibernate impl), y todo "simplemente funciona".

+0

hice prueba simple y si envía JSON desde el primer puesto de drewzilla sin un valor numérico que se obtiene: { "código": 400, "mensaje" : "No se puede procesar JSON" }. Entonces, dropwizard no resuelve este problema. – wjtk

+0

Mi comentario fue en relación con la publicación en su forma original. DropWizard no cambia la semántica de Bean Validation, pero resuelve el problema de cómo uno permite su uso. Otros marcos como Spring pueden proporcionar esto también, en este punto. Pero tenga en cuenta el momento del comentario original. – StaxMan

Cuestiones relacionadas