Play框架Scala JSON验证异常

4

我在使用play framework 2.2.2中的Actor检查JsValue对象。当我尝试使用validate方法时,我收到了异常而不是结果对象:

try {
      val result = data.validate[EventConfig]
      Logger.debug("Result: "+result")
    } catch {
        case e =>
           Logger.error("Exception: "+e)
    }

以下是此异常信息:

Exception: play.api.libs.json.JsResultException: JsResultException(errors:List((,List(ValidationError(error.expected.jsnumber,WrappedArray())))))

为什么会发生这种情况,我应该如何使用验证方法?
====== 更新
我正在使用以下Reads实现:
implicit val EventConfig_reads = new Reads[EventConfig] {
    def reads(json: JsValue): JsResult[EventConfig] = {
        JsSuccess(new
            EventConfig((json \ ConfigEventAttrs.PARAM).as[Int],
              (json \ ConfigEventAttrs.PERIOD).as[Int],
              (json \ ConfigEventAttrs.THRESHOLD).as[Int],
              (json \ ConfigEventAttrs.TOGGLE).as[Boolean]))
    }
  }

解决方案是添加catch子句:
implicit val EventConfig_reads = new Reads[EventConfig] {
    def reads(json: JsValue): JsResult[EventConfig] = {
      try {
        JsSuccess(new
            EventConfig((json \ ConfigEventAttrs.PARAM).as[Int],
              (json \ ConfigEventAttrs.PERIOD).as[Int],
              (json \ ConfigEventAttrs.THRESHOLD).as[Int],
              (json \ ConfigEventAttrs.TOGGLE).as[Boolean]))
      } catch {
        case e: JsResultException =>
          JsError(e.errors)
      }
    }
  }
2个回答

17
这不是正确使用validate的方法。我认为文档应该更加强调它的重要性,但它在这里被解释了,称为Using Validationdata.validate[EventConfig]返回JsResult不是EventConfig。处理错误的首选方式是将结果fold
data.validate[EventConfig].fold(
   error => {
       // There were validation errors, handle them here.
   },
   config => {
       // `EventConfig` has validated, and is now in the scope as `config`, proceed as usual.
   }
)

让我们仔细研究一下。在 JsResult 上,fold 的签名如下:

fold[X](invalid: (Seq[(JsPath, Seq[ValidationError])]) ⇒ X, valid: (A) ⇒ X): X

它接受两个函数作为参数,这两个函数都返回相同类型的结果。第一个函数是Seq[(JsPath, Seq[ValidationError])]) => X。在上面的代码中,error的类型为Seq[(JsPath, Seq[ValidationError])]),本质上只是一个包含json路径和验证错误的序列。在此处,您可以分解这些错误并相应地返回适当的错误消息,或在失败时执行其他任何所需操作。
第二个函数将A => X映射到A类型为JsResult已经被验证为EventConfig的情况下。在这里,您将能够直接处理您的EventConfig类型。
引起和捕获异常不是处理这种情况的方法(很少使用),因为您将失去所有累积的验证错误。
编辑:由于OP已经更新了有关他定义的Reads的其他信息。
那里定义的Reads的问题在于它们正在使用as[T]。调用as时,您正在尝试将给定的json路径强制转换为类型T,如果无法强制转换,则会抛出异常。因此,一旦到达第一个验证错误,就会抛出异常,并且您将失去所有后续错误。但是,您的用例相对简单,因此我认为最好采用更现代化的Reads
import play.api.libs.json._
import play.api.libs.functional.syntax._

case class EventConfig(param: Int, period: Int, threshold: Int, toggle: Boolean)

object EventConfig {

    implicit val jsonReads: Reads[EventConfig] = (
        (__ \ ConfigEventAttrs.PARAM).read[Int] and 
        (__ \ ConfigEventAttrs.PERIOD).read[Int] and 
        (__ \ ConfigEventAttrs.THRESHOLD).read[Int] and 
        (__ \ ConfigEventAttrs.TOGGLE).read[Boolean]
    )(EventConfig.apply _)

}

这样更加简洁,使用函数语法将所有验证错误累积到JsResult中,而不是抛出异常。
编辑2: 为了解决OP对不同apply方法的需求。
如果你用于从JSON构建对象的参数与你的case类不同,请定义一个函数来代替EventConfig.apply用于JSON Reads。假设你的EventConfig在JSON中实际上是这样的:
(time: Long, param: Int)    

但是你希望它变成这样:
case class EventConfig(time: Date, param: Int)

定义一个函数,从原始参数创建一个 EventConfig

def buildConfig(time: Long, param: Int) = EventConfig(DateUtils.timeSecToDate(time), param)

在您的Reads中使用buildConfig代替EventConfig.apply

implicit val jsonReads: Reads[EventConfig] = (
    (__ \ "time").read[Long] and 
    (__ \ "param").read[Int]
)(buildConfig _)

我简化了这个例子,但是buildConfig可以是任何返回EventConfig的函数,并且参数与您要验证的JSON对象的参数匹配。


在我的代码中,我正在使用fold方法,我以这种方式编写是为了简单。我只想打印结果,而不是EventConfig。然而,我仍然遇到了这个异常。问题出在错误的Reads实现上,它会抛出JsResultException。即使您使用fold,它仍会抛出此异常,我将在更新中添加这个Reads实现。谢谢您的解释。 - Michał Jurczuk
@JMichal 我已经更新了我的答案,以解决“Reads”的问题。 - Michael Zajac
在我的“真实”代码中,我不得不在apply中使用额外的参数,这就是为什么我使用了这样的结构,或者你知道如何在这个语法中添加它们吗? - Michał Jurczuk
“Additional parameters”是什么意思?它们与此有何不同? - Michael Zajac
EventConfig有一个参数time,我将转换后的时间以秒为单位传递给毫秒:DateUtils.timeSecToDate((json \ ConfigEventAttrs.TIME).as[Long]) - Michał Jurczuk
这可以通过使用不同于apply的函数来解决,正如我在我的编辑中所解释的那样,这对于评论来说有点过于复杂。 - Michael Zajac

0

验证取决于您的Reads方法,我在那里遇到了问题。我应该在我的reads中捕获此异常。


这应该被添加为注释,而不是答案。 - Vikas Pandya
但这是一个答案。 - Michał Jurczuk

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