Kunal Pradhan
Kunal Pradhan

Reputation: 522

Scala Play 2.3 framework Json validate recursively

I have json which is having same structure in field repeating.

case class RuleJson(`type`: String, attribute: Int, operator: Option[String], value: String, is_value_processed: String, aggregator: String, conditions: RuleJson)


object RuleJson  {  
  implicit val reads = Json.reads[RuleJson]
}

So the conditions key will have same RuleJson case structure. (though optional)

I am getting "No implicit Reads for models.RuleJson available." error.

My JSON is

{
    "type": "salesrule/rule_condition_combine",
    "attribute": null,
    "operator": null,
    "value": "1",
    "is_value_processed": null,
    "aggregator": "all",
    "conditions": [
        {
            "type": "salesrule/rule_condition_product_subselect",
            "attribute": "qty",
            "operator": "==",
            "value": "5",
            "is_value_processed": null,
            "aggregator": "all",
            "conditions": [
                {
                    "type": "salesrule/rule_condition_product",
                    "attribute": "quote_item_price",
                    "operator": "==",
                    "value": "200",
                    "is_value_processed": false
                }
            ]
        }
    ]
}

So if you see condition field is repeating, how do I validate such JSON in play scala 2.3?

Upvotes: 2

Views: 926

Answers (1)

Mikesname
Mikesname

Reputation: 8901

The key here is to change your case class so it actually matches the data you want to read (e.g. make optional values Option[String] etc) and then to use the lazyRead[T] functions to read values using the Reads you are currently constructing. Here's a case class (altered as appropriate) which works for your data:

case class RuleJson(
  `type`: String,
  attribute: Option[String],
  operator: Option[String],
  value: String,
  isValueProcessed: Option[Boolean],
  aggregator: Option[String],
  conditions: Seq[RuleJson]
)

object RuleJson {
  implicit val reads: Reads[RuleJson] = (
    (__ \ "type").read[String] and
    (__ \ "attribute").readNullable[String] and
    (__ \ "operator").readNullable[String] and
    (__ \ "value").read[String] and
    (__ \ "is_value_processed").readNullable[Boolean] and
    (__ \ "aggregator").readNullable[String] and
    (__ \ "conditions").lazyReadNullable[Seq[RuleJson]](Reads.seq(RuleJson.reads))
      .map(opt => opt.toSeq.flatten)
  )(RuleJson.apply _)
}

Two things to notice here:

  • conditions is defined as a Seq[RuleJson], but since it might not exist at all in the source JSON it's read as an optional value and the empty case mapped to an empty sequence
  • since we're defining a reader for a recursive structure we use lazyReadNullable for the list of conditions and pass it an explicit reference to the Reads we are currently constructing. If we were not to use the lazy... variant the reads might crash with a null pointer error, since it makes a reference to something not yet defined (the lazy variants basically use Scala call-by-name parameters which aren't evaluated until they're needed.)

Upvotes: 1

Related Questions