Play framework JSON reads: How to read lines or Int?

The rest api JS client can send both int and string as the value of some field.

{
   field1: "123",
   field2: "456"
}

{
   field1: 123,
   field2: 456
}

Here is a game action with the case class to which you need to transform the body of the json request:

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

In case I send json values ​​with int values, it works fine. But in case field1 or field2 are strings ("123", "456"), it fails because request.body.validate expects an Int.

But the problem is that the JS client sends the values ​​from the input fields, and the input fields are converted to strings.

What is the best way to handle both ints and strings? (So ​​this action should convert json to dto in both cases)

+4
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)

: :

  • if field1 - , field2 - int , JsSuccess, JsError millhouse

  • , JsError, . millhouse .

+2

Dto - a Reads[Dto] Reads. "" ( ), 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, - , .

+1

Source: https://habr.com/ru/post/1682447/


All Articles