diff --git a/build.sbt b/build.sbt index 2cd3745e..6f0e46ee 100644 --- a/build.sbt +++ b/build.sbt @@ -116,13 +116,22 @@ lazy val `circe-json` = { project in file("circe-json") }.dependsOn(core, api).s } ) +lazy val `json4s-json` = { project in file("json4s-json") }.dependsOn(core, api).settings( + commonSettings, + + name := "scala-jsonschema-json4s-json", + + libraryDependencies += "org.json4s" %% "json4s-ast" % "3.6.2" +) + lazy val root = { project in file(".") }.aggregate( core, macros, api, `play-json`, `circe-json`, - `spray-json`).settings( + `spray-json`, + `json4s-json`).settings( commonSettings, diff --git a/json4s-json/src/main/scala/com/github/andyglow/jsonschema/AsJson4s.scala b/json4s-json/src/main/scala/com/github/andyglow/jsonschema/AsJson4s.scala new file mode 100644 index 00000000..86abcbc9 --- /dev/null +++ b/json4s-json/src/main/scala/com/github/andyglow/jsonschema/AsJson4s.scala @@ -0,0 +1,26 @@ +package com.github.andyglow.jsonschema + +import com.github.andyglow.json.Value._ +import com.github.andyglow.json._ +import json.Schema +import org.json4s.JsonAST._ + +object AsJson4s { + + def apply(value: Value): JValue = value match { + case `null` => JNull + case `true` => JBool(true) + case `false` => JBool(false) + case num(x) => JDecimal(x) + case str(x) => JString(x) + case arr(x) => JArray(x.map(AsJson4s.apply).toList) + case obj(x) => JObject(x.map { case (k, v) => JField(k, AsJson4s.apply(v)) }.toList) + } + + implicit class SchemaOps[T](val x: Schema[T]) extends AnyVal { + + def asJson4s( + title: Option[String] = None, + description: Option[String] = None): JValue = AsJson4s(AsValue.schema(x, title, description)) + } +} diff --git a/json4s-json/src/test/scala/com/github/andyglow/jsonschema/AsJson4sSpec.scala b/json4s-json/src/test/scala/com/github/andyglow/jsonschema/AsJson4sSpec.scala new file mode 100644 index 00000000..a6071f26 --- /dev/null +++ b/json4s-json/src/test/scala/com/github/andyglow/jsonschema/AsJson4sSpec.scala @@ -0,0 +1,53 @@ +package com.github.andyglow.jsonschema + +import org.scalatest._ +import org.scalatest.Matchers._ +import org.scalatest.prop.TableDrivenPropertyChecks._ +import com.github.andyglow.json.Value._ +import org.json4s.JsonAST._ + +class AsJson4sSpec extends PropSpec{ + import AsJson4sSpec._ + + private val examples = Table( + ("json" , "PlayJson"), + (`null` , JNull), + (`true` , JBool(true)), + (`false` , JBool(false)), + (str("foo") , JString("foo")), + (num(4) , JDecimal(4)), + (num(4.78) , JDecimal(4.78)), + (arr(1, 2, 3) , JArray(List(JDecimal(1), JDecimal(2), JDecimal(3)))), + (obj("foo" -> "foo", "bar" -> 15) , JObject("foo" -> JString("foo"), "bar" -> JDecimal(15))) + ) + + property("Check that AsJson4s translates internal representation of json to json4s Json") { + forAll(examples) { (internal, play) => AsJson4s(internal) shouldEqual play } + } + + property("Check Schema.asJson4s") { + import AsJson4s._ + + json.Json.schema[UserProfile].asJson4s() shouldEqual JObject( + f"$$schema" -> JString("http://json-schema.org/draft-04/schema#"), + "type" -> JString("object"), + "additionalProperties" -> JBool(false), + "properties" -> JObject( + "firstName" -> JObject("type" -> JString("string")), + "middleName" -> JObject("type" -> JString("string")), + "lastName" -> JObject("type" -> JString("string")), + "age" -> JObject("type" -> JString("integer")), + "active" -> JObject("type" -> JString("boolean"))), + "required" -> JArray(List(JString("age"), JString("lastName"), JString("firstName")))) + } +} + +object AsJson4sSpec { + + case class UserProfile( + firstName: String, + middleName: Option[String], + lastName: String, + age: Int, + active: Boolean = true) +} \ No newline at end of file