1

I have the following scala code which works to parse my JSON into my case class UserLocation.

Here usLocationDuration or a nonUSLocationDuration are optional; but what I want is a way to enforce that that the JSON requires a usLocationDuration or a nonUSLocationDuration, not both or not neither?

import play.api.libs.functional.syntax._
import play.api.libs.json._

final case class UserLocation(username: String, usLocationDuration: Option[Int], nonUSLocationDuration: Option[Int])

object UserLocation {
  implicit val userReads: Reads[UserLocation] = (
    (JsPath \ "username").read[String] and
    (JsPath \ "usLocationDuration").readNullable[Int] and
    (JsPath \ "nonUSLocationDuration").readNullable[Int]
  ) (UserLocation.apply _)

  def main(args: Array[String]): Unit = {
    val json = s"""{"username": "rs", "usLocationDuration": 10}"""
    val user = Json.parse(json).as[UserLocation]
    println(user)
  }
}
1
  • One thing you could do is simply enforce it in case class like final case class UserLocation(username: String, usLocationDuration: Option[Int], nonUSLocationDuration: Option[Int]) { require(usLocationDuration.nonEmpty != nonUSLocationDuration.nonEmpty ) }
    – curious
    Commented Mar 28, 2018 at 23:18

1 Answer 1

1

One way to solve that is with the Either type. See play-scala-json-format-for-either.

In your case:

import play.api.libs.functional.syntax._
import play.api.libs.json.Reads._
import play.api.libs.json._

final case class UserLocation(username: String, locationDuration: Either[Int, Int])


object UserLocation {

  implicit val userReads: Reads[UserLocation] = (
    (JsPath \ "username").read[String] and
      ((JsPath \ "usLocationDuration").read[Int].map(dur => Left(dur): Either[Int, Int]) or
        (JsPath \ "nonUSLocationDuration").read[Int].map(dur => Right(dur): Either[Int, Int]))
    ) (UserLocation.apply _)

}

You can test this with:

  println(Json.parse("""
                       | {
                       |   "username": "admin"
                       |   , "usLocationDuration": 123
                       | }
                     """.stripMargin).validate[UserLocation])

  println(Json.parse("""
                       | {
                       |   "username": "admin"
                       |   , "nonUSLocationDuration": 123
                       | }
                     """.stripMargin).validate[UserLocation])

  println(Json.parse("""
                       | {
                       |   "username": "admin"
                       | }
                     """.stripMargin).validate[UserLocation])

This gives you:

JsSuccess(UserLocation(admin,Left(123)),)
JsSuccess(UserLocation(admin,Right(123)),)
JsError(List((/usLocationDuration,List(JsonValidationError(List(error.path.missing),WrappedArray()))), (/nonUSLocationDuration,List(JsonValidationError(List(error.path.missing),WrappedArray())))))

Check the whole example here: ScalaFiddle

1
  • That worked except I had to change to syntax of the map calls ((JsPath \ "usLocationDuration").read[Int].map(dur => Left(dur) : Either[Int, Int]) or (JsPath \ "nonUSLocationDuration").read[Int].map(dur => Right(dur) : Either[Int, Int]))
    – richs
    Commented Apr 1, 2018 at 0:37

Not the answer you're looking for? Browse other questions tagged or ask your own question.