Loading .gitignore 0 → 100644 +5 −0 Original line number Diff line number Diff line *.log target *.iml .idea build.sbt 0 → 100755 +52 −0 Original line number Diff line number Diff line lazy val commonSettings = Seq( organization := "com.kjetland", organizationName := "mbknor", version := "1.0.0-SNAPSHOT", scalaVersion := "2.11.8", publishMavenStyle := true, publishArtifact in Test := false, publishTo := { val nexus = "https://oss.sonatype.org/" if (isSnapshot.value) Some("snapshots" at nexus + "content/repositories/snapshots") else Some("releases" at nexus + "service/local/staging/deploy/maven2") }, homepage := Some(url("https://github.com/mbknor/mbknor-jackons-jsonSchema")), licenses := Seq("MIT" -> url("https://github.com/mbknor/mbknor-jackson-jsonSchema/blob/master/LICENSE.txt")), startYear := Some(2016), pomExtra := ( <scm> <url>git@github.com:mbknor/mbknor-jackson-jsonSchema.git</url> <connection>scm:git:git@github.com:mbknor/mbknor-jackson-jsonSchema.git</connection> </scm> <developers> <developer> <id>mbknor</id> <name>Morten Kjetland</name> <url>https://github.com/mbknor</url> </developer> </developers>), compileOrder in Test := CompileOrder.Mixed, javacOptions ++= Seq("-source", "1.8", "-target", "1.8"), scalacOptions ++= Seq("-unchecked", "-deprecation") ) val jacksonVersion = "2.7.5" val slf4jVersion = "1.7.7" lazy val deps = Seq( "com.fasterxml.jackson.core" % "jackson-databind" % jacksonVersion, "org.slf4j" % "slf4j-api" % slf4jVersion, "org.scalatest" % "scalatest_2.11" % "2.2.4" % "test", "ch.qos.logback" % "logback-classic" % "1.1.3" % "test" ) lazy val root = (project in file(".")) .settings(name := "mbknor-jackson-jsonSchema") .settings(commonSettings: _*) .settings(libraryDependencies ++= (deps)) src/main/scala/com/kjetland/jackson/jsonSchema/JsonSchemaGenerator.scala 0 → 100644 +162 −0 Original line number Diff line number Diff line package com.kjetland.jackson.jsonSchema import java.util import com.fasterxml.jackson.annotation.{JsonSubTypes, JsonTypeInfo} import com.fasterxml.jackson.core.JsonParser.NumberType import com.fasterxml.jackson.databind.jsonFormatVisitors._ import com.fasterxml.jackson.databind.ser.BeanPropertyWriter import com.fasterxml.jackson.databind._ import com.fasterxml.jackson.databind.node.{JsonNodeFactory, ObjectNode} import org.slf4j.LoggerFactory class JsonSchemaGenerator(rootObjectMapper:ObjectMapper) { val log = LoggerFactory.getLogger(getClass) trait MySerializerProvider { var provider:SerializerProvider = null def getProvider: SerializerProvider = provider def setProvider(provider: SerializerProvider): Unit = this.provider = provider } case class SubTypeAndTypeName[T](clazz:Class[T], subTypeName:String) class MyJsonFormatVisitorWrapper(objectMapper:ObjectMapper, indent:String = "", val node:ObjectNode = JsonNodeFactory.instance.objectNode()) extends JsonFormatVisitorWrapper with MySerializerProvider { def l(s:String): Unit = { println(indent + s) } def createChild(childNode:ObjectNode):MyJsonFormatVisitorWrapper = new MyJsonFormatVisitorWrapper(objectMapper, indent+ " ", childNode) override def expectStringFormat(_type: JavaType) = new JsonStringFormatVisitor { override def enumTypes(enums: util.Set[String]): Unit = l(s"enums: $enums") override def format(format: JsonValueFormat): Unit = l(s"format: $format") } override def expectArrayFormat(_type: JavaType) = new JsonArrayFormatVisitor with MySerializerProvider { override def itemsFormat(handler: JsonFormatVisitable, elementType: JavaType): Unit = l(s"expectArrayFormat - handler: $handler - elementType: $elementType") override def itemsFormat(format: JsonFormatTypes): Unit = l(s"itemsFormat - format: $format") } override def expectNumberFormat(_type: JavaType) = new JsonNumberFormatVisitor { override def numberType(_type: NumberType): Unit = ??? override def enumTypes(enums: util.Set[String]): Unit = ??? override def format(format: JsonValueFormat): Unit = ??? } override def expectAnyFormat(_type: JavaType) = new JsonAnyFormatVisitor { } override def expectIntegerFormat(_type: JavaType) = new JsonIntegerFormatVisitor { override def numberType(_type: NumberType): Unit = l(s"expectIntegerFormat - type = ${_type}") override def enumTypes(enums: util.Set[String]): Unit = ??? override def format(format: JsonValueFormat): Unit = ??? } override def expectNullFormat(_type: JavaType) = new JsonNullFormatVisitor { } override def expectObjectFormat(_type: JavaType) = { node.put("type", "object") val propertiesNode = JsonNodeFactory.instance.objectNode() node.set("properties", propertiesNode) new JsonObjectFormatVisitor with MySerializerProvider { override def optionalProperty(writer: BeanProperty): Unit = { val propertyName = writer.getName val propertyType = writer.getType l(s"${propertyName}: ${propertyType}") // check for polymorphism //val polymorphism:Boolean = !propertyType.isConcrete && !propertyType.isArrayType && !propertyType.isCollectionLikeType && !propertyType.isContainerType && propertyType.isAbstract val subTypes: List[SubTypeAndTypeName[_]] = Option(propertyType.getRawClass.getDeclaredAnnotation(classOf[JsonSubTypes])).map { ann: JsonSubTypes => ann.value().map { t: JsonSubTypes.Type => SubTypeAndTypeName(t.value(), t.name()) }.toList }.getOrElse(List()) if (subTypes.nonEmpty) { //l(s"polymorphism - subTypes: $subTypes") val anyOfNode = JsonNodeFactory.instance.objectNode() propertiesNode.set(propertyName, anyOfNode) val anyOfArrayNode = JsonNodeFactory.instance.arrayNode() anyOfNode.set("anyOf", anyOfArrayNode) val subTypeSpecifierPropertyName: String = propertyType.getRawClass.getDeclaredAnnotation(classOf[JsonTypeInfo]).property() subTypes.foreach { subType: SubTypeAndTypeName[_] => l(s"polymorphism - subType: $subType") val thisPropertyNode = JsonNodeFactory.instance.objectNode() anyOfArrayNode.add(thisPropertyNode) val childVisitor = createChild( thisPropertyNode ) objectMapper.acceptJsonFormatVisitor(subType.clazz, childVisitor) // must inject the 'type'-param and value as enum with only one possible value thisPropertyNode.get("properties").asInstanceOf[ObjectNode].put(subTypeSpecifierPropertyName, subType.subTypeName) } } else { val thisPropertyNode = JsonNodeFactory.instance.objectNode() propertiesNode.set(propertyName, thisPropertyNode) val childNode = JsonNodeFactory.instance.objectNode() objectMapper.acceptJsonFormatVisitor(propertyType, createChild(thisPropertyNode)) } } override def optionalProperty(name: String, handler: JsonFormatVisitable, propertyTypeHint: JavaType): Unit = ??? override def property(writer: BeanProperty): Unit = ??? override def property(name: String, handler: JsonFormatVisitable, propertyTypeHint: JavaType): Unit = ??? } } override def expectBooleanFormat(_type: JavaType) = new JsonBooleanFormatVisitor { override def enumTypes(enums: util.Set[String]): Unit = ??? override def format(format: JsonValueFormat): Unit = ??? } override def expectMapFormat(_type: JavaType) = new JsonMapFormatVisitor with MySerializerProvider { override def keyFormat(handler: JsonFormatVisitable, keyType: JavaType): Unit = ??? override def valueFormat(handler: JsonFormatVisitable, valueType: JavaType): Unit = ??? } } def generateJsonSchema[T <: Any](clazz:Class[T]):JsonNode = { val rootVisitor = new MyJsonFormatVisitorWrapper(rootObjectMapper) rootObjectMapper.acceptJsonFormatVisitor(clazz, rootVisitor) rootVisitor.node } } src/test/resources/logback-TEST.xml 0 → 100755 +15 −0 Original line number Diff line number Diff line <configuration> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <!-- encoders are assigned the type ch.qos.logback.classic.encoder.PatternLayoutEncoder by default --> <encoder> <pattern>%date{ISO8601} lvl=%level, %m | sId=%X{serviceId}, rId=%X{requestId}, akkaSrc=%X{akkaSource}, akkaThrd=%X{sourceThread}, thrd=%thread, lgr=%logger{36} %X{akkaPersistenceRecovering}%n</pattern> </encoder> </appender> <root level="INFO"> <appender-ref ref="STDOUT" /> </root> </configuration> No newline at end of file src/test/scala/com/kjetland/jackson/jsonSchema/JsonSchemaGeneratorTest.scala 0 → 100644 +69 −0 Original line number Diff line number Diff line package com.kjetland.jackson.jsonSchema import com.fasterxml.jackson.annotation.JsonValue import com.fasterxml.jackson.databind.{JsonNode, ObjectMapper} import com.kjetland.jackson.jsonSchema.testData.{Child1, Parent, PojoWithParent} import org.scalatest.{FunSuite, Matchers} class JsonSchemaGeneratorTest extends FunSuite with Matchers with TestData { val objectMapper = new ObjectMapper() val jsonSchemaGenerator = new JsonSchemaGenerator(objectMapper) def asPrettyJson(node:JsonNode):String = { objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(node) } def assertToFromJson(o:Any): Unit = { assertToFromJson(o, o.getClass) } def assertToFromJson(o:Any, deserType:Class[_]): Unit = { val json = objectMapper.writeValueAsString(o) println(s"json: $json") val r = objectMapper.readValue(json, deserType) assert( o == r) } test("polymorphism") { assertToFromJson(pojoWithParent) val schema = jsonSchemaGenerator.generateJsonSchema(pojoWithParent.getClass) val schemaAsJson = asPrettyJson(schema) println("--------------------------------------------") println(schemaAsJson) } test("polymorphism - first Level") { assertToFromJson(child1) assertToFromJson(child1, classOf[Parent]) val schema = jsonSchemaGenerator.generateJsonSchema(classOf[Parent]) val schemaAsJson = asPrettyJson(schema) println("--------------------------------------------") println(schemaAsJson) } } trait TestData { val child1 = { val c = new Child1() c.parentString = "pv" c.child1String = "cs" c } val pojoWithParent = { val p = new PojoWithParent p.pojoValue = true p.child = child1 p } } Loading
build.sbt 0 → 100755 +52 −0 Original line number Diff line number Diff line lazy val commonSettings = Seq( organization := "com.kjetland", organizationName := "mbknor", version := "1.0.0-SNAPSHOT", scalaVersion := "2.11.8", publishMavenStyle := true, publishArtifact in Test := false, publishTo := { val nexus = "https://oss.sonatype.org/" if (isSnapshot.value) Some("snapshots" at nexus + "content/repositories/snapshots") else Some("releases" at nexus + "service/local/staging/deploy/maven2") }, homepage := Some(url("https://github.com/mbknor/mbknor-jackons-jsonSchema")), licenses := Seq("MIT" -> url("https://github.com/mbknor/mbknor-jackson-jsonSchema/blob/master/LICENSE.txt")), startYear := Some(2016), pomExtra := ( <scm> <url>git@github.com:mbknor/mbknor-jackson-jsonSchema.git</url> <connection>scm:git:git@github.com:mbknor/mbknor-jackson-jsonSchema.git</connection> </scm> <developers> <developer> <id>mbknor</id> <name>Morten Kjetland</name> <url>https://github.com/mbknor</url> </developer> </developers>), compileOrder in Test := CompileOrder.Mixed, javacOptions ++= Seq("-source", "1.8", "-target", "1.8"), scalacOptions ++= Seq("-unchecked", "-deprecation") ) val jacksonVersion = "2.7.5" val slf4jVersion = "1.7.7" lazy val deps = Seq( "com.fasterxml.jackson.core" % "jackson-databind" % jacksonVersion, "org.slf4j" % "slf4j-api" % slf4jVersion, "org.scalatest" % "scalatest_2.11" % "2.2.4" % "test", "ch.qos.logback" % "logback-classic" % "1.1.3" % "test" ) lazy val root = (project in file(".")) .settings(name := "mbknor-jackson-jsonSchema") .settings(commonSettings: _*) .settings(libraryDependencies ++= (deps))
src/main/scala/com/kjetland/jackson/jsonSchema/JsonSchemaGenerator.scala 0 → 100644 +162 −0 Original line number Diff line number Diff line package com.kjetland.jackson.jsonSchema import java.util import com.fasterxml.jackson.annotation.{JsonSubTypes, JsonTypeInfo} import com.fasterxml.jackson.core.JsonParser.NumberType import com.fasterxml.jackson.databind.jsonFormatVisitors._ import com.fasterxml.jackson.databind.ser.BeanPropertyWriter import com.fasterxml.jackson.databind._ import com.fasterxml.jackson.databind.node.{JsonNodeFactory, ObjectNode} import org.slf4j.LoggerFactory class JsonSchemaGenerator(rootObjectMapper:ObjectMapper) { val log = LoggerFactory.getLogger(getClass) trait MySerializerProvider { var provider:SerializerProvider = null def getProvider: SerializerProvider = provider def setProvider(provider: SerializerProvider): Unit = this.provider = provider } case class SubTypeAndTypeName[T](clazz:Class[T], subTypeName:String) class MyJsonFormatVisitorWrapper(objectMapper:ObjectMapper, indent:String = "", val node:ObjectNode = JsonNodeFactory.instance.objectNode()) extends JsonFormatVisitorWrapper with MySerializerProvider { def l(s:String): Unit = { println(indent + s) } def createChild(childNode:ObjectNode):MyJsonFormatVisitorWrapper = new MyJsonFormatVisitorWrapper(objectMapper, indent+ " ", childNode) override def expectStringFormat(_type: JavaType) = new JsonStringFormatVisitor { override def enumTypes(enums: util.Set[String]): Unit = l(s"enums: $enums") override def format(format: JsonValueFormat): Unit = l(s"format: $format") } override def expectArrayFormat(_type: JavaType) = new JsonArrayFormatVisitor with MySerializerProvider { override def itemsFormat(handler: JsonFormatVisitable, elementType: JavaType): Unit = l(s"expectArrayFormat - handler: $handler - elementType: $elementType") override def itemsFormat(format: JsonFormatTypes): Unit = l(s"itemsFormat - format: $format") } override def expectNumberFormat(_type: JavaType) = new JsonNumberFormatVisitor { override def numberType(_type: NumberType): Unit = ??? override def enumTypes(enums: util.Set[String]): Unit = ??? override def format(format: JsonValueFormat): Unit = ??? } override def expectAnyFormat(_type: JavaType) = new JsonAnyFormatVisitor { } override def expectIntegerFormat(_type: JavaType) = new JsonIntegerFormatVisitor { override def numberType(_type: NumberType): Unit = l(s"expectIntegerFormat - type = ${_type}") override def enumTypes(enums: util.Set[String]): Unit = ??? override def format(format: JsonValueFormat): Unit = ??? } override def expectNullFormat(_type: JavaType) = new JsonNullFormatVisitor { } override def expectObjectFormat(_type: JavaType) = { node.put("type", "object") val propertiesNode = JsonNodeFactory.instance.objectNode() node.set("properties", propertiesNode) new JsonObjectFormatVisitor with MySerializerProvider { override def optionalProperty(writer: BeanProperty): Unit = { val propertyName = writer.getName val propertyType = writer.getType l(s"${propertyName}: ${propertyType}") // check for polymorphism //val polymorphism:Boolean = !propertyType.isConcrete && !propertyType.isArrayType && !propertyType.isCollectionLikeType && !propertyType.isContainerType && propertyType.isAbstract val subTypes: List[SubTypeAndTypeName[_]] = Option(propertyType.getRawClass.getDeclaredAnnotation(classOf[JsonSubTypes])).map { ann: JsonSubTypes => ann.value().map { t: JsonSubTypes.Type => SubTypeAndTypeName(t.value(), t.name()) }.toList }.getOrElse(List()) if (subTypes.nonEmpty) { //l(s"polymorphism - subTypes: $subTypes") val anyOfNode = JsonNodeFactory.instance.objectNode() propertiesNode.set(propertyName, anyOfNode) val anyOfArrayNode = JsonNodeFactory.instance.arrayNode() anyOfNode.set("anyOf", anyOfArrayNode) val subTypeSpecifierPropertyName: String = propertyType.getRawClass.getDeclaredAnnotation(classOf[JsonTypeInfo]).property() subTypes.foreach { subType: SubTypeAndTypeName[_] => l(s"polymorphism - subType: $subType") val thisPropertyNode = JsonNodeFactory.instance.objectNode() anyOfArrayNode.add(thisPropertyNode) val childVisitor = createChild( thisPropertyNode ) objectMapper.acceptJsonFormatVisitor(subType.clazz, childVisitor) // must inject the 'type'-param and value as enum with only one possible value thisPropertyNode.get("properties").asInstanceOf[ObjectNode].put(subTypeSpecifierPropertyName, subType.subTypeName) } } else { val thisPropertyNode = JsonNodeFactory.instance.objectNode() propertiesNode.set(propertyName, thisPropertyNode) val childNode = JsonNodeFactory.instance.objectNode() objectMapper.acceptJsonFormatVisitor(propertyType, createChild(thisPropertyNode)) } } override def optionalProperty(name: String, handler: JsonFormatVisitable, propertyTypeHint: JavaType): Unit = ??? override def property(writer: BeanProperty): Unit = ??? override def property(name: String, handler: JsonFormatVisitable, propertyTypeHint: JavaType): Unit = ??? } } override def expectBooleanFormat(_type: JavaType) = new JsonBooleanFormatVisitor { override def enumTypes(enums: util.Set[String]): Unit = ??? override def format(format: JsonValueFormat): Unit = ??? } override def expectMapFormat(_type: JavaType) = new JsonMapFormatVisitor with MySerializerProvider { override def keyFormat(handler: JsonFormatVisitable, keyType: JavaType): Unit = ??? override def valueFormat(handler: JsonFormatVisitable, valueType: JavaType): Unit = ??? } } def generateJsonSchema[T <: Any](clazz:Class[T]):JsonNode = { val rootVisitor = new MyJsonFormatVisitorWrapper(rootObjectMapper) rootObjectMapper.acceptJsonFormatVisitor(clazz, rootVisitor) rootVisitor.node } }
src/test/resources/logback-TEST.xml 0 → 100755 +15 −0 Original line number Diff line number Diff line <configuration> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <!-- encoders are assigned the type ch.qos.logback.classic.encoder.PatternLayoutEncoder by default --> <encoder> <pattern>%date{ISO8601} lvl=%level, %m | sId=%X{serviceId}, rId=%X{requestId}, akkaSrc=%X{akkaSource}, akkaThrd=%X{sourceThread}, thrd=%thread, lgr=%logger{36} %X{akkaPersistenceRecovering}%n</pattern> </encoder> </appender> <root level="INFO"> <appender-ref ref="STDOUT" /> </root> </configuration> No newline at end of file
src/test/scala/com/kjetland/jackson/jsonSchema/JsonSchemaGeneratorTest.scala 0 → 100644 +69 −0 Original line number Diff line number Diff line package com.kjetland.jackson.jsonSchema import com.fasterxml.jackson.annotation.JsonValue import com.fasterxml.jackson.databind.{JsonNode, ObjectMapper} import com.kjetland.jackson.jsonSchema.testData.{Child1, Parent, PojoWithParent} import org.scalatest.{FunSuite, Matchers} class JsonSchemaGeneratorTest extends FunSuite with Matchers with TestData { val objectMapper = new ObjectMapper() val jsonSchemaGenerator = new JsonSchemaGenerator(objectMapper) def asPrettyJson(node:JsonNode):String = { objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(node) } def assertToFromJson(o:Any): Unit = { assertToFromJson(o, o.getClass) } def assertToFromJson(o:Any, deserType:Class[_]): Unit = { val json = objectMapper.writeValueAsString(o) println(s"json: $json") val r = objectMapper.readValue(json, deserType) assert( o == r) } test("polymorphism") { assertToFromJson(pojoWithParent) val schema = jsonSchemaGenerator.generateJsonSchema(pojoWithParent.getClass) val schemaAsJson = asPrettyJson(schema) println("--------------------------------------------") println(schemaAsJson) } test("polymorphism - first Level") { assertToFromJson(child1) assertToFromJson(child1, classOf[Parent]) val schema = jsonSchemaGenerator.generateJsonSchema(classOf[Parent]) val schemaAsJson = asPrettyJson(schema) println("--------------------------------------------") println(schemaAsJson) } } trait TestData { val child1 = { val c = new Child1() c.parentString = "pv" c.child1String = "cs" c } val pojoWithParent = { val p = new PojoWithParent p.pojoValue = true p.child = child1 p } }