2017-07-27 4 views
4

JSクライアントの残りのAPIは、intとstringの両方をいくつかのフィールドの値として送ることができます。ここでPlayフレームワークJSONの読み込み:Stringまたは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) 
    } 
    } 

場合、私はint型の値でJSON値を送信する場合、それは[OK]を動作します。しかし、field1またはfield2が文字列( "123"、 "456")の場合、それは失敗します。なぜなら、request.body.validateはIntを期待しているからです。

しかし、問題は、JSクライアントが入力フィールドから値を送信し、入力フィールドが文字列に変換されることです。

intまたは文字列を処理する最も良い方法は何ですか? (この場合、両方のケースで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は「幸せなケース」で仕事をしてもらいます。それでも問題が解決しない場合は、最後にIntに変換する前に、フィールドをStringインスタンスとして扱うようにしてください。

可能であれば、「誰か」によって作成された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[Dto]を定義するために、あなたより寛容Reads[Int]を使用します。

implicit val DtoReads = 
    (JsPath \ "field1").read[Int](readInt) and 
    (JsPath \ "field2").read[Int](readInt) 

EDIT:ミルハウスのソリューションとの違い:field1場合

  • 文字列あるとfield2はあなたが取得します。このソリューションでint型あるJsSuccessが、ミルハウスのソリューションとJsError

  • の場合両方のフィールドがこのソリューションでは無効です。JsErrorには、各フィールドに1つのエラーが含まれています。 Millhouseのソリューションでは、最初のエラーが発生します。

+0

再生時には、 'ValidationError'の代わりに' JsonValidationError'を使います。 – ulric260

関連する問題