Maxence Cramet
Maxence Cramet

Reputation: 594

Scala Play 2.6: Parse json with constraint between fields

I'd like to validate a field depending on the value of another field when parsing a json.

For example when I'm reading a range, I'd like to verify min < max:

import org.scalatest.{FlatSpec, Matchers}
import play.api.libs.json.{JsError, Json, Reads}

class JsonReadsTest extends FlatSpec with Matchers {

  "Json" should "be reads" in {
    val reads: Reads[Range] = ???
    val json = Json.obj("min" -> 3, "max" -> 2)
    reads.reads(json) shouldBe JsError("max should be superior to min")
  }
}

case class Range(min: Int, max: Int)

Upvotes: 0

Views: 246

Answers (1)

Pahomov Dmitry
Pahomov Dmitry

Reputation: 111

Stright-forward-oop solution

You can create an object that extends Reads[T] and implement reads method directly without fuctional builders (this approach is not fully covered in docs but you can find many exambles in source code)

val reads1 = new Reads[Range] {
  def reads(json: JsValue): JsResult[Range] = {
    (for {
      min <- (json \ "min").validate[Int]
      max <- (json \ "max").validate[Int]
    } yield (min, max)).flatMap {
      case (min, max) if max > min =>
        JsSuccess(Range(min, max))
      case _ =>
        JsError(Seq(JsPath ->
          Seq(JsonValidationError("error.expected.range"))))
    }
  }
}

Functional-builder solution

val reads2 = (
  (__ \ "min").read[Int] and
  (__ \ "max").read[Int]
).tupled
.filter(JsonValidationError("error.expected.range")){ case (min, max) => max > min}
.map{ case (min, max) => Range(min, max)}

Best practice

You are trying to mix reading and validation and it can be a problem when you have different validation rules in different modules. There is a practice to parse json object to an presentational-model-class like

case class RangeInput(min: Int, max: Int)

and then convert it to business model class performing validation

def validate(input: RangeInput): Option[Range] = 
  input.filter(i => i.max > i.min).map(i => Range(i.min, i.max))

If you need to aggregate validation errors things like cats validated can help you with that

Upvotes: 2

Related Questions