我有基于播放框架的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
}
}
答案 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: _*)
))
}