[英]Play framework JSON reads: How to read either String or Int?
rest api的JS客戶端可以將int和string作為某個字段的值發送。
{
field1: "123",
field2: "456"
}
{
field1: 123,
field2: 456
}
以下是應該轉換json請求正文的case類的play動作:
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)
您還可以定義更寬容的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 JsSuccess
但JsError
與米爾豪斯的解決方案
如果此解決方案的兩個字段都無效,您將獲得包含每個字段一個錯誤的JsError
。 使用millhouse的解決方案,您將收到第一個錯誤。
您需要為Dto
實現自定義Reads
實現 - 即Reads[Dto]
。 我總是喜歡從Json.reads[Dto]
獲得的“內置”(宏生成)開始 - 然后從那里開始; 例如:
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
,我們在由“其他人”創建的JsResult
范圍內工作,因此我們將快速失敗。
它實際上非常容易使用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 _)
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.