rest api的JS客户端可以将int和string作为某个字段的值发送。
{
field1: "123",
field2: "456"
}
{
field1: 123,
field2: 456
}
以下是应该转换json请求体的案例类的播放操作:
case class Dto(field1: Int, field2: Int)
object Dto {
implicit val reads = Json.reads[Dto]
}
def create = Action.async(BodyParsers.parse.json) { implicit request =>
request.body.validate[Dto].map {
dto => someService.doStuff(dto).map(result => Ok(Json.toJson(result)))
}.recoverTotal {
e => jsErrorToBadRequest(e)
}
}
如果我发送带有int值的json值,它可以正常工作。但是如果field1或field2是字符串(“123”,“456”),它就会失败,因为request.body.validate需要Int。
但问题是JS客户端从输入字段发送值,输入字段转换为字符串。
处理整数或字符串的最佳方法是什么? (因此,在这两种情况下,此操作都应将json转换为dto)
答案 0 :(得分:6)
您还可以定义更宽容的Reads[Int]
。
并使用它来定义您的Reads[Dto]
1)定义更宽容的Reads[Int]
:
import play.api.data.validation.ValidationError
import play.api.libs.json._
import scala.util.{Success, Try}
// Define a more tolerant Reads[Int]
val readIntFromString: Reads[Int] = implicitly[Reads[String]]
.map(x => Try(x.toInt))
.collect (ValidationError(Seq("Parsing error"))){
case Success(a) => a
}
val readInt: Reads[Int] = implicitly[Reads[Int]].orElse(readIntFromString)
示例:
readInt.reads(JsNumber(1))
// JsSuccess(1,)
readInt.reads(JsString("1"))
// JsSuccess(1,)
readInt.reads(JsString("1x"))
// JsError(List((,List(ValidationError(List(Parsing error),WrappedArray())))
2)使用更宽容的Reads[Int]
来定义Reads[Dto]
:
implicit val DtoReads =
(JsPath \ "field1").read[Int](readInt) and
(JsPath \ "field2").read[Int](readInt)
编辑:与millhouse的解决方案的差异:
如果field1
是字符串且field2
是 int ,使用此解决方案,您将获得{{1}但是JsSuccess
与millhouse的解决方案
如果此解决方案的两个字段无效,您将获得JsError
,其中包含每个字段的一个错误。使用millhouse的解决方案,您将收到第一个错误。
答案 1 :(得分:2)
您需要Reads
的自定义Dto
实施,即Reads[Dto]
。我总是喜欢从Json.reads[Dto]
获得的“内置”(宏生成)开始 - 然后从那里开始; e.g:
object Dto {
val basicReads = Json.reads[Dto]
implicit val typeCorrectingReads = new Reads[Dto]{
def reads(json: JsValue): JsResult[Dto] = {
def readAsInteger(fieldName:String):JsResult[Int] = {
(json \ fieldName).validate[String].flatMap { s =>
// We've got a String, but it might not be convertible to an int...
Try(s.toInt).map(JsSuccess(_)).getOrElse {
JsError(JsPath \ fieldName, s"Couldn't convert string $s to an integer")
}
}
}
basicReads.reads(json).orElse {
for {
f1 <- readAsInteger("field1")
f2 <- readAsInteger("field2")
} yield {
Dto(f1, f2)
}
}
}
}
}
通过这种方式,你可以让basicReads
在“快乐案例”中完成工作。如果它不成功,我们会尝试将字段视为String
个实例,然后再尝试转换为Int
。
请注意,尽管如此,我们仍然在“其他人”创建的JsResult
范围内工作,因此我们会快速失败。
答案 2 :(得分:1)
使用or
组合器(此处记录在https://www.playframework.com/documentation/2.6.x/ScalaJsonCombinators上)和很少的读取功能实际上很容易。
case class Customer(name: String, number: Int)
object Customer {
val readIntFromString: Reads[Int] = implicitly[Reads[String]]
.map(x => x.toInt)
import play.api.libs.functional.syntax._
import play.api.libs.json._
implicit val routeReads: Reads[Customer] =
((__ \ "name").read[String] and
((__ \ "number").read[Int] or
(__ \ "number").read[Int](readIntFromString)))(Customer.apply _)
}