4

I'm using play 2.2.0 Reads for validating incoming request in my application.

I'm trying to implement a very simple thing with json API. I have a json like this:

{
  "field1": "some value",
  "field2": "some another value"
}

I already have Reads that checks for other stuff like minimal length

case class SomeObject(field1: String, field2: String)
implicit val someObjectReads = (
  (__ \ "field1").read(minLength[String](3)) ~
  (__ \ "field2").read(minLength[String](3))
)(SomeObject)

I want to create a parser combinator that will match values of two fields and return JsSuccess if values are equal and otherwise JsError and combine it with existing Reads.

How can I achieve this?

UPDATE: clarified the question and changed the code.

Andrey Neverov
  • 2,135
  • 1
  • 12
  • 21
  • IMHO, this kind of validation should be done by the API client before sending the data to the API. – Ionuț G. Stan Jan 09 '14 at 15:07
  • @IonuțG.Stan Yes, you're right (client actually does), but I want to implement both client and server side validation for this. – Andrey Neverov Jan 09 '14 at 15:21
  • 1
    Yeah, I understand, but if your API will be used by 3rd party developers, you can't enforce them to actually show users a "confirm password" field. They can just as well use the value from a single input field to populate both `password` and `passwordConfirm`. Anyway, it's obviously your choice in the end. I just wanted to present a different view of the problem. – Ionuț G. Stan Jan 09 '14 at 15:26
  • @IonuțG.Stan Hm it's actually a good point. I think I'll remove this validation from the server in the future (api is not public yet so it's safe). Nevertheless, I'd love to see an example of parser combinator because I want to learn how to write such things in the future. – Andrey Neverov Jan 09 '14 at 15:33
  • `Reads` is monadic, and some combination of `flatMap` and `verifying` should be relatively straightforward. – Travis Brown Jan 09 '14 at 16:05
  • IMHO, either passwordConfirm may be deleted, or you should not check equality here. The json is valid (3 fields of type String min lenght 3). – Isammoc Jan 09 '14 at 23:07
  • Ok, I see I asked a question in the wrong way. What I'm asking about is how to build combinators, not about api design (btw thanks for advice guys!). I will update the post. – Andrey Neverov Jan 10 '14 at 08:07

1 Answers1

8

You can use filter to do further validation on parsed values:

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

case class SomeObject(field1: String, field2: String)
implicit val someObjectReads = (
  (__ \ "field1").read(minLength[String](3)) ~
  (__ \ "field2").read(minLength[String](3))
)(SomeObject).filter(
  ValidationError("field1 and field2 must be equal")
) { someObject =>
  someObject.field1 == someObject.field2
}

If you want the error message to be listed against each field, then you'll have to use flatMap, eg:

implicit val someObjectReads = (
  (__ \ "field1").read(minLength[String](3)) ~
  (__ \ "field2").read(minLength[String](3))
)(SomeObject).flatMap { someObject =>
  Reads { _ =>
    if (someObject.field1 == someObject.field2) {
      JsSuccess(someObject)
    } else {
      JsError(Seq(
        JsPath("field1") -> Seq(ValidationError("field1 and field2 must be equal")),
        JsPath("field2") -> Seq(ValidationError("field1 and field2 must be equal"))
      ))
    }
  }
}
James Roper
  • 12,695
  • 46
  • 45
  • James, thank you very much for the answer! Could you please elaborate on the format of the validation error type message? I'd like to get the error as something in format of like { "field1": "field1 and field2 must be equal", "field2": "field1 and field2 must be equal" } and your example gives something like List((,List(field1 and field2 must be equal))). – Andrey Neverov Jan 10 '14 at 21:40
  • ValidationError is just a message key and some arguments, in the the above I haven't bothered to use a message key, but you could say ValidationError("validation.equals, "field1", "field2"), and define in your messages file "validation.equals={0} and {1} must be equal". – James Roper Jan 10 '14 at 21:54
  • `ValidationError` was deprecated, instead `JsibValidationError` should be used. But very nice answer! – Tomer Shetah Jan 11 '21 at 20:22