简体   繁体   中英

Defining cross-attribute Json Validators for a case class in Play Framework 2.3.x (Scala)

I know it's possible to define Case classes that we want to match some given json against (we use JsValue.validate[T] ):

For example:

case class UpdateDashboardModel(id: Long,
                            maybeName: Option[String],
                            containers: Option[List[UpdateContainerModel]],
                            description: Option[String])

And then we must write a Reads[T] to define how to actually turn a json object into an instance of our case class (and optionally define a few custom validators for individual attributes):

  val exists: Reads[Long] =
    Reads.LongReads.filter(ValidationError("Dashboard does not exist"))(long => Dashboard.findById(long).isDefined)


  implicit val reads: Reads[UpdateDashboardModel] = (
    (JsPath \ "id").read[Long](exists) and
    (JsPath \ "name").readNullable[String] and
    (JsPath \ "containers").readNullable[List[UpdateContainerModel]] and
    (JsPath \ "description").readNullable[String]) (UpdateDashboardModel.apply _ )

In this example, I run a simple validation for the given id -> it must exist in the database otherwise I have to throw an error.

Problem is, I can't seem to be able to write a validator for something that requires two attributes.

For example, I would like to write a short validator that takes the id and the name attributes because I want to check whether that name is not already in use by another dashboard (if it's the current dashboard, it's ok).

Can anybody think of a way to do this?

Thanks in advance.

Well, it's not pretty, but I think something like this should work... Let's assume isNameAvailable is a function that ensures that the name is not already in use by another dashboard, and returns true if name is available (false otherwise).

import play.api.data.validation.ValidationError

implicit val reads: Reads[UpdateDashboardModel] = (
  (JsPath \ "id").read[Long](exists) and
  (
    (JsPath \ "id").read[Long] and
    (JsPath \ "name").readNullable[String]
  ).tupled.filter(
    ValidationError("Name is already in use")
  )
  { case (id, name) => isNameAvailable(name, id) }.map(t => t._2) and
  (JsPath \ "containers").readNullable[List[UpdateContainerModel]] and
  (JsPath \ "description").readNullable[String]) (UpdateDashboardModel.apply _ )

Personally I find the tupled.filter syntax a little strange, but this is how I have been able to overcome the issue you're having.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM