如何有效地支持JSR-303验证和Jackson JSON映射?

13
使用Spring MVC构建RESTful Web服务时,我在尝试结合Jackson JSON反序列化和JSR-303 Bean验证时遇到了棘手的情况。
JSR-303验证的一个好处是,可以返回与目标对象相关的所有验证错误,而不仅仅是在第一个约束违规时失败(虽然如果您想使用它,也有快速失败模式)。
想象一种场景,您有一个JSON有效负载,例如:
{
   "firstname": "Joe",
   "surname": "Bloggs",
   "age": 25
}

然后你需要映射的对象:

public class Person {

   @NotNull
   private String firstname;

   @NotNull
   private String surname;

   @Min(value = 0)
   private int age;

   ...
}

我对这种设置的问题在于,如果客户端将字符串作为“年龄”的值发送,则整个Jackson反序列化过程将失败,无法具体定位失败原因(即我们永远不会进行验证)。然后想象一下,这个有效负载:

{
   "age": "invalid"
}

在这种情况下,我想返回一个“错误”响应,例如:
{ 
   "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",
      }
   ]
}

解决这个问题的简单方法是将“age”字段指定为“String”类型,然后在传递给持久层时转换该值。但是,我不太喜欢使用字符串来处理数据传输对象的想法 - 从保持干净的设计角度来看,这种做法似乎不太合适。

我想到的另一个选择是创建一个类,例如:

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

我可以创建一些实现,例如IntegerValue、LongValue、DateTimeValue等,当然还需要为Jackson编写自定义类型适配器。然后我的Person类可以是这样的:

public class Person {

   @NotNull
   private StringValue firstname;

   @NotNull
   private StringValue surname;

   @Min(value = 0)
   private IntegerValue age;

   ...
}

这几乎是我想要的,但不幸的是,JSR-303 注释,如 @NotNull 和 @Min,将无法识别我的 BindableValue 实现。
那么,有人能解释一下我如何以更简洁的方式解决这个问题吗?
3个回答

2

我现在意识到,最好的方法可能是简单地使用字符串来表示所有字段,但封装这个事实并仍然提供特定类型的方法设置器和获取器。例如:

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

这样做可以确保我得到期望的行为,同时保持公共对象接口的"干净"。


1
另一种可能性是仅禁用Jackson对未识别属性的投诉(请参见this维基页面),以“删除”未识别的内容,并验证已绑定的内容。您还可以为未识别的属性定义侦听器。然后,您可以使用Bean验证来验证已绑定的数据。

0

好的,既然这还没有提到过...... DropWizard 是我认为最好的选择 - 它将 JAX-RS(Jersey)和 Jackson 与 Bean Validation(我想是 hibernate 实现)结合在一起,而且它们都“只要可以运行就可以了”。


我进行了简单的测试,如果您从drewzilla的第一篇帖子中发送一个非数字值的json,则会收到以下响应:{ "code": 400, "message": "无法处理JSON" }。因此,dropwizard无法解决这个问题。 - wjtk
我的评论是针对原始帖子的。DropWizard不会改变Bean Validation的语义,但它确实解决了如何启用其使用的问题。其他框架(如Spring)现在也可能提供此功能。但请注意原始评论的时间。 - StaxMan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接