Scalaz验证时的错误累积

时间:2015-08-31 13:53:31

标签: json scala playframework scalaz play-json

我有一个复杂的JSON,它在数据库中持久存在。它的复杂性是"隔离" in" blocks",如下:

整个JSON:

{
    "block1" : {
        "param1" : "val1",
        "param2" : "val2"
    },
    "block2" : {
        "param3" : "val3",
        "param4" : "val4"
    },
    ...
}

在数据库中,每个块都被单独存储和处理:

坚持阻止

"block1" : {
    "param1" : "val1",
    "param2" : "val2"
}

"block2" : {
    "param3" : "val3",
    "param4" : "val4"
}

每个块都有业务含义,因此,每个块都映射到一个案例类。 我正在构建一个存储,更新和检索此JSON结构的Play API,并且我想验证是否有人为了完整性而更改了它的数据。

我按如下方式对每个块进行检索(解析和验证):

val block1 = Json.parse(block1).validate[Block1].get
val block2 = Json.parse(block2).validate[Block2].get
...

案例类:

trait Block
sealed case class Block1 (param1: String, param2: String, ...) extends Block
sealed case class Block2 (param3: String, param4: String, ...) extends Block
sealed case class Request (block1: Block1, block2: Block2, ...)

使用当前结构,如果某些字段被更改并且与定义的类型不匹配,则Play会抛出此异常:

  

[NoSuchElementException:JsError.get]

因此,我希望使用Scalaz和Validation构建一个累积错误结构,以捕获所有可能的解析和验证错误。我看过thisthis,所以我用这种方式对验证进行了编码:

def build(block1: String, block2: String, ...): Validation[NonEmptyList[String], Request] = {
    val block1 = Option(Json.parse(block1).validate[Block1].get).toSuccess("Error").toValidationNel
    val block2 = Option(Json.parse(block2).validate[Block2].get).toSuccess("Error").toValidationNel
    ...

    val request = (Request.apply _).curried

    blockn <*> (... <*> (... <*> (...<*> (block2 <*> (block1 map request)))))   
}

请注意,我正在使用应用仿函数<*>,因为Request有20个字段(使用该语法构成括号 - 混乱),|@|仅适用仿函数最多12个参数的案例类。

该代码适用于快乐路径,但是,当我修改某些字段时,Play会抛出稍后描述的执行异常。

问题 :我希望在解析每个块时累积Play可以检测到的所有可能的结构错误。我怎么能这样做?

注意:如果Shapeless以某种方式与此有关,我可以使用它(我已经在使用它了)。

1 个答案:

答案 0 :(得分:2)

如果多次验证是将Scalaz添加到项目中的唯一原因,那么为什么不考虑一种替代方案,不需要您将Play项目调整为Scalaz强迫您解决问题的monadic方式(这可能是一个好的方法)事情,但不一定,如果唯一的原因是多次验证)。

相反,请考虑使用标准的Scala方法scala.util.Try

val block1 = Try(Json.parse(block1).validate[Block1].get)
val block2 = Try(Json.parse(block2).validate[Block2].get)
...

val allBlocks : List[Try[Block]] = List(block1, block2, ...)
val failures : List[Failure[Block]] = allBlocks.collect { case f : Failure[Block] => f }

通过这种方式,您仍然可以对标准scala集合进行操作,以检索要进一步处理的失败列表。此方法也不会限制您的块数。