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.
在使用Spring MVC构建RESTful Web服务时,我觉得在尝试组合Jackson JSON反序列化和JSR-303 bean验证时遇到了棘手的情况。
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).
关于JSR-303验证的一个有趣的事情是,可以返回与目标对象关联的所有验证错误,而不是仅在第一个约束违规时失败(尽管如果要使用它,则存在失败快速模式)。
Imagine a scenario where you have a JSON payload such as:
想象一下你有一个JSON有效载荷的场景,例如:
{
"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.
解决这个问题的简单方法是简单地将“age”字段指定为“String”类型,然后在将值传递给持久层时将其转换。但是,我并不喜欢采用以全面方式使用字符串的数据传输对象的想法 - 在保留简洁设计方面这样做似乎是错误的。
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:
然后,我可以创建诸如IntegerValue,LongValue,DateTimeValue等实现,当然还要为Jackson编写自定义类型适配器。然后我的Person类可能是:
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.
这几乎是我想要的,但不幸的是,@ NTNull和@Min等JSR-303注释不会识别我的BindableValue实现。
So, can anyone explain a way I could solve this problem in a cleaner way?
那么,任何人都能解释一种我能以更清洁的方式解决这个问题的方法吗?
3 个解决方案
#1
2
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.
我现在意识到,这样做的最好方法可能就是简单地将字符串用于所有字段,但是封装了这个事实,并且仍然提供特定于类型的方法设置器和getter。例如
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".
通过这种方式,我得到了我所追求的行为,同时确保公共对象接口仍然“干净”。
#2
1
Another possibility is to just disable Jackson's complaints on unrecognized properties (see this wiki page), to "drop" unrecognized things, and validate things that did get bound. You can alternatively also define a listener for unrecognized properties. And then you could use Bean Validation for verifying data that did get bound.
另一种可能性是禁用杰克逊对无法识别的属性的投诉(请参阅此维基页面),“删除”无法识别的内容,并验证确实受到限制的内容。您也可以为无法识别的属性定义侦听器。然后,您可以使用Bean Validation来验证确实受到限制的数据。
#3
0
Ok, since this was not yet mentioned.... DropWizard is the way to go I think -- it combines JAX-RS (Jersey) on Jackson with Bean Validation (hibernate impl I think), and it all "just works".
好吧,因为还没有提到...... DropWizard是我认为的方式 - 它将杰克逊的JAX-RS(泽西岛)与Bean验证(我认为是hibernate impl)结合起来,而且它都“正常”。
#1
2
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.
我现在意识到,这样做的最好方法可能就是简单地将字符串用于所有字段,但是封装了这个事实,并且仍然提供特定于类型的方法设置器和getter。例如
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".
通过这种方式,我得到了我所追求的行为,同时确保公共对象接口仍然“干净”。
#2
1
Another possibility is to just disable Jackson's complaints on unrecognized properties (see this wiki page), to "drop" unrecognized things, and validate things that did get bound. You can alternatively also define a listener for unrecognized properties. And then you could use Bean Validation for verifying data that did get bound.
另一种可能性是禁用杰克逊对无法识别的属性的投诉(请参阅此维基页面),“删除”无法识别的内容,并验证确实受到限制的内容。您也可以为无法识别的属性定义侦听器。然后,您可以使用Bean Validation来验证确实受到限制的数据。
#3
0
Ok, since this was not yet mentioned.... DropWizard is the way to go I think -- it combines JAX-RS (Jersey) on Jackson with Bean Validation (hibernate impl I think), and it all "just works".
好吧,因为还没有提到...... DropWizard是我认为的方式 - 它将杰克逊的JAX-RS(泽西岛)与Bean验证(我认为是hibernate impl)结合起来,而且它都“正常”。