j3d
j3d

Reputation: 9734

Play Framework: How to Validate Optional Fields When Parsing JSON

Given the following JSON...

{
     "nickname": "mj",
     "firstname": "Mike",
     "lastName": "Jordan",
     "trash": "ignore"
}

... I need to parse and validate it so that

  1. any field except nickname, firstName, and lastName are filtered out – in my example above trash has to be removed
  2. if defined, nickname (which is optional) must be at least 3 characters long

Here below is my code:

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

def orEmpty = reads | __.json.put(Json.obj())    

val js = Json.parse("""{ "nickname": "mj", "firstname": "Mike", "lastName": "Jordan" }""")

val validateUser = (
  ((__ \ 'nickname).json.pickBranch(Reads.of[JsString] <~ Reads.minLength[String](3)) orEmpty) ~
  ((__ \ 'firstName).json.pickBranch) ~
  ((__ \ 'lastName).json.pickBranch)
)

validateUser.reads(js).fold(
  valid = { validated => JsSuccess(js) },
  invalid => { errors => JsError(errors) }
)

The problem is that if nickname is invalid because shorter than 3 characters, orEmpty applies and no error is reported. What I need is to keep nickname optional (that's why I defined orEmpty), but when defined the validation should succeed if and only if nickanme passes the Reads.minLength check.

Upvotes: 1

Views: 1635

Answers (1)

Cem Catikkas
Cem Catikkas

Reputation: 7174

Assuming Play Framework 2.4.x. I believe reading nullable fields is a little different in 2.3.x but it's the same idea.

It would be easier to reason about this if you assume that JSON validators and transformers are really just Reads[T <: JsValue].

Overall, what you need to consider is that the nickname field is actually optional and you need to represent it as such. Basically, what you're looking for is to compose the nullableReads with Reads.minLength to read nickname as Option[String].

So, you can represent the structure you want to validate as:

case class ToValidate(nickname: Option[String], firstname: String, lastname: String)

And define you're Reader:

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

implicit val toValidateReads: Reads[ToValidate] = (
  (__ \ "nickname").readNullable[String](minLength(3)) ~
  (__ \ "firstname").read[String] ~
  (__ \ "lastname").read[String]
)(ToValidate.apply _)

Then you can vaildate your input:

val js = Json.parse("""{ "nickname": "mj", "firstname": "Mike", "lastname": "Jordan" }""")
val v = js.validate[ToValidate]
println(v) // JsError(List((/nickname,List(ValidationError(List(error.minLength),WrappedArray(3))))))

Upvotes: 2

Related Questions