jsonscalaplayframework-2.4

Play JSON combinator validates at least a field is specified


I have a scenario that when parsing a json to a case class like this

implicit val userRead: Reads[User] = (
    (__ \ "name").read[String] ~
      (__ \ "email").readNullable[String] ~
      (__ \ "phone").readNullable[String] ~
      Reads.pure(None)
    )(User.apply _)

I don't require both email and phone to be available, but at least one of them have to be available.

In my case class definition, I can prevent the case that both of them are empty with

case class User(name: String, email: Option[String], phone: Option[String], id: Option[Long] = None) {
  require(email.nonEmpty || phone.nonEmpty, "must have at least an email or phone number")
}

However, doing this will generate an exception, and a 500 status is responded, when this should be a 400 error due to user input.

I can of course manually perform the verification in my controller, but I wonder if there is a cleaner way to do this.


Solution

  • I can only suggest writing you own "reads" manually

    case class A(i: Option[Int], j: Option[Int])
    
    implicit val reads: Reads[A] = new Reads[A] { 
      override def reads(json: JsValue): JsResult[A] = { 
        (for {
          i <- (json \ "i").validateOpt[Int] 
          j <-  (json \ "j").validateOpt[Int]
         } yield A(i, j))
           .filter(JsError("both values can't be empty"))(a ⇒ a.j.nonEmpty || a.i.nonEmpty)  
        } 
      }
    

    And to test:

    scala> Json.parse("""{ "i" : 1} """).validate[A]
    res4: play.api.libs.json.JsResult[A] = JsSuccess(A(Some(1),None),)
    
    
    scala> Json.parse("""{} """).validate[A]
    res5: play.api.libs.json.JsResult[A] = JsError(List((,List(ValidationError(List(from and to in range can't be both empty),WrappedArray())))))