1

我有基于播放框架的 json rest api 应用程序,并且希望在解析传入请求时接收有关验证错误的信息。除了从 json 数组到 json 值的转换之外,一切正常。我要实现的Json结构:

{
  "errors": {
    "name": ["invalid", "tooshort"],
    "email": ["invalid"]
  }
}

当我尝试实现一个示例时,它运行良好:

def error = Action {
  BadRequest(obj(
    "errors" -> obj(
      "name" -> arr("invalid", "tooshort"),
      "email" -> arr("invalid")
    )
  ))
}

当我试图像这样提取变化的部分时:

def error = Action {
  val e = Seq("name" -> arr("invalid", "tooshort"), "email" -> arr("invalid"))
  // in real app it will be Seq[(JsPath, Seq[ValidationError])] 
  BadRequest(obj(
    "errors" -> obj(e: _*)
  ))
}

我得到一个编译器错误:

类型不匹配; 找到:Seq[(String, play.api.libs.json.JsArray)] 需要:Seq[(String, play.api.libs.json.Json.JsValueWrapper)]

也许我缺少从 JsArray 到 JsValueWrapper 的一些隐式转换?但是,为什么示例在具有相同导入的同一文件中工作正常?

玩 2.1.1,斯卡拉 2.10.0

更新:由于 Julien Lafont 最终代码解决了问题:

implicit val errorsWrites = new Writes[Seq[(JsPath, Seq[ValidationError])]] {
  /**
   * Maps validation result of Ember.js json request to json validation object, which Ember can understand and parse as DS.Model 'errors' field.
  *
  * @param errors errors collected after json validation
  * @return json in following format:
  *
  * {
  *   "errors": {
  *     "error1": ["message1", "message2", ...],
  *     "error2": ["message3", "message4", ...],
  *     ...
  *   }
  * }
  */
 def writes(errors: Seq[(JsPath, Seq[ValidationError])]) = {
   val mappedErrors = errors.map {
     e =>
       val fieldName = e._1.toString().split("/").last // take only last part of the path, which contains real field name
       val messages = e._2.map(_.message)
       fieldName -> messages
   }

   obj("errors" -> toJson(mappedErrors.toMap)) // Ember requires root "errors" object
  }
}

用法:

def create = Action(parse.json) { // method in play controller
  request =>
    fromJson(request.body) match {
      case JsSuccess(pet, path) => Ok(obj("pet" -> Pets.create(pet)))
      case JsError(errors) => UnprocessableEntity(toJson(errors)) // Ember.js expects 422 error code in case of errors
    }
}
4

2 回答 2

4

您可以简单地在 a 中定义您的错误Map[String, Seq[String]],并在 Json 中转换它Json.toJson(有内置的和的编写Map[String,Y]Seq[X]

scala> val e = Map("name" -> Seq("invalid", "tooshort"), "email" -> Seq("invalid"))
e: scala.collection.immutable.Map[String,Seq[String]] = Map(name -> List(invalid, tooshort), email -> List(invalid))

scala> Json.toJson(e)
res0: play.api.libs.json.JsValue = {"name":["invalid","tooshort"],"email":["invalid"]}

scala> Json.obj("errors" -> Json.toJson(e))
res1: play.api.libs.json.JsObject = {"errors":{"name":["invalid","tooshort"],"email":["invalid"]}}
于 2013-07-23T19:45:10.610 回答
2

长手版本有效的原因是因为 scala 的自动类型推断触发了隐式转换toJsFieldJsValueWrapper

例如

scala> import play.api.libs.json._
import play.api.libs.functional.syntax._
import play.api.libs.json._

scala> import play.api.libs.functional.syntax._
import play.api.libs.functional.syntax._

scala> import Json._
import Json._

scala> arr("invalid", "tooshort")
res0: play.api.libs.json.JsArray = ["invalid","tooshort"]

scala> obj("name" -> arr("invalid", "tooshort"))
res1: play.api.libs.json.JsObject = {"name":["invalid","tooshort"]}

scala> obj _
res3: Seq[(String, play.api.libs.json.Json.JsValueWrapper)] => play.api.libs.json.JsObject = <function1>

请注意,arr返回 a JsArray,但obj需要 a JsValueWrapper。Scala在构造 的参数时能够将 转换JsArray为。但是它不能将 a 转换为 `Seq[(String, JsValueWrapper)]。JsValueWrapperobjSeq[(String, JsArray)]

如果您提前提供序列的预期类型 when ,Scala 编译器的类型推断器将在创建序列时执行转换。

scala> Seq[(String, JsValueWrapper)]("name" -> arr("invalid", "tooshort"), "email" -> arr("invalid"))
res4: Seq[(String, play.api.libs.json.Json.JsValueWrapper)] = List((name,JsValueWrapperImpl(["invalid","tooshort"])), (email,JsValueWrapperImpl(["invalid"])))

但是,一旦创建了序列,它就不能被转换,除非在范围内有可以转换序列的隐式转换。

最终的代码片段如下所示:

def error = Action {
  val e = Seq[(String, JsValueWrapper)]("name" -> arr("invalid", "tooshort"), "email" -> arr("invalid"))
  // in real app it will be Seq[(JsPath, Seq[ValidationError])] 
  BadRequest(obj(
    "errors" -> obj(e: _*)
  ))
}
于 2013-07-24T11:58:20.723 回答