kondaurov-json / json_schema

Build your own object schemas. This is like standard json schemas but not only for validation

Github

Build Status Coverage Status Download Maven Central

Motivation

We have raw json:

{
  "modelName": "c3po",
  "price": "500",
  "is_working": "yes",
  "previous_price": 250
}

We have case class:

case class Robot(
    model: String,
    price: Int,
    isWorking: Boolean,
    previousPrice: Option[Int]
)

We want to cast input json as an instance of Robot

There are few ways to do this:

  1. You can create reads that are created by play_json library. You just need to create companion object with implicit reads.
import play.api.libs.json._

object Robot {
 implicit val reads: Reads[Robot] = Json.reads[Robot]
}

This reads won't work with our raw json because

  • there's no model, isWorking, previousPrice fields.
  • "500" isn't a number but string
  1. You can create create reads with custom reads:
 import play.api.libs.json._
 import play.api.libs.functional.syntax._

object Robot {
  implicit val reads = (
    (__ \ "model").read[String] ~
    (__ \ "price").read[String].map(_.toInt) ~
    (__ \ "is_working").read[String].map(s => s.equalsIgnoreCase("yes")) ~
    (__ \ "previous_price").readNullable[String]
  )(Robot.apply _)
}

This is too much boilerplate code... Moreover you might get runtime exception if price field can't be cast to Int

  1. You can create json schema, validate json over it and cast it to Robot. But this wont work with our raw json either because of problems described in 1

json_schema

Here json schema comes in play:

You can describe json schema like that:

import com.github.kondaurovdev.json_schema.types.Api._
import com.github.kondaurovdev.json_schema.Implicits._


object Robot {

  val schema: iSchemaWrapper = objVal(
    prop("model", strVal()),
    prop("price", numVal()),
    prop("is_working", boolVal(), "isWorking"),
    prop("previous_price", numVal(), "previousPrice", required = false)
  )
  
  val reads = schema.getReadsBySchema(Json.reads[Robot])

}

Or you can describe your schemas in file and provide it via application config

application.conf

json_schema {
  provider {
    fromDir {
      dir = ${HOME}/json_schemas
    }
  }
}

${HOME}/json_schemas/app.yaml

public:
 obj:
  props:
    - { path: model, type: str }
    - { path: price, type: number }
    - { path: is_working, alias: isWorking }
    - { path: previous_price, alias: previousPrice }

Now we can get rid of big schema in our application and use link to it

object Robot {

import com.github.kondaurovdev.json_schema.types.Api._
import com.github.kondaurovdev.json_schema.types.SchemaPath
import com.github.kondaurovdev.json_schema.Implicits._

object Robot {

  val schema: iSchemaWrapper = SchemaPath("app", "robot", ns = Some("public"))
  
  val reads = schema.getReadsBySchema(Json.reads[Robot])

}

}

Some thoughts

There is a json schema specification (http://json-schema.org/)

Purpose of json schema is validate json over schema. It can only validate This means:

  • you can't specify default values for fields
  • you can't cast values (for eg. "123" is string and you cast it to number )

So i want a little bit more: validate and transform json while validating. Get pure json from schema