Scala play json api:无法隐式将JsArray转换为JsValueWrapper

时间:2013-07-23 13:20:43

标签: json scala playframework implicit-conversion

我有基于播放框架的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,Scala 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
    }
}

2 个答案:

答案 0 :(得分:4)

您只需在Map[String, Seq[String]]中定义错误,然后使用Json.toJson在Json中对其进行转换(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"]}}

答案 1 :(得分: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会返回JsArray,但obj需要JsValueWrapper。 Scala在构造JsArray的参数时能够将JsValueWrapper转换为obj。但是它无法将Seq[(String, JsArray)]转换为`Seq [(String,JsValueWrapper)]。

如果提前提供序列的预期类型,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: _*)
  ))
}