2017-07-27 178 views
4

rest api的JS客戶端可以發送int和string作爲某個字段的值。播放框架JSON讀取:如何讀取字符串或Int?

{ 
    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) 
    } 
    } 

在情況下,如果我發送的JSON值與int類型,它工作正常。但是,如果field1或field2是字符串(「123」,「456」),它會失敗,因爲request.body.validate需要Int。

但問題是,JS客戶端從輸入字段發送值,並且輸入字段被轉換爲字符串。

處理整數或字符串的最佳方法是什麼? (所以這個動作應該在兩種情況下將json轉換爲dto)

回答

1

您需要爲您的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範圍內工作,所以我們將快速失敗。

2

你也可以定義一個更寬容的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) 

編輯:差異與米爾豪斯的解決方案:

  • 如果field1field2INT這個解決方案,你會得到一個JsSuccess但米爾豪斯的解決方案JsError

  • 如果這兩個字段都是無效的這個解決方案,你會得到一個JsError每個字段包含一個錯誤。用millhouse的解決方案,你會得到第一個錯誤。

+0

在play 2.6使用'JsonValidationError'而不是'ValidationError' – ulric260