簡體   English   中英

Play框架JSON讀取:如何讀取String或Int?

[英]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 JsSuccessJsError與米爾豪斯的解決方案

  • 如果此解決方案的兩個字段都無效,您將獲得包含每個字段一個錯誤的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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM