Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Play framework JSON reads: How to read either String or Int?

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

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

{
   field1: 123,
   field2: 456
}

Here is play action with case class to which json request body should be converted:

  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 if I send json values with int values, it works ok. But in case if field1 or field2 are strings ("123", "456"), it fails, because request.body.validate expects Int.

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

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

like image 475
Teimuraz Avatar asked Jul 27 '17 19:07

Teimuraz


1 Answers

You could also define a more tolerant Reads[Int]. And use it to define your Reads[Dto]

1) Define a more tolerant 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)

Examples:

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) Use your more tolerant Reads[Int] to define your Reads[Dto]:

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

EDIT: Differences with millhouse's solution:

  • if field1 is a string and field2 is an int with this solution you'll get a JsSuccess but a JsError with millhouse's solution

  • If both field are invalid with this solution you'll get a JsError containing one error for each field. With millhouse's solution you'll get the first error.

like image 162
ulric260 Avatar answered Nov 15 '22 22:11

ulric260