Commit d75ef347 authored by Morten Kjetland's avatar Morten Kjetland
Browse files

Startingpoint

parents
Loading
Loading
Loading
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))
+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
  }

}
+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
+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
  }
}