Commit c0cce8d0 authored by mokj's avatar mokj
Browse files

Now using definitions and refs

parent e7c20d94
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -2,7 +2,7 @@
lazy val commonSettings = Seq(
  organization := "com.kjetland",
  organizationName := "mbknor",
  version := "1.0.0-build-5-SNAPSHOT",
  version := "1.0.0-build-6-SNAPSHOT",
  scalaVersion := "2.11.8",
  publishMavenStyle := true,
  publishArtifact in Test := false,
+145 −42
Original line number Diff line number Diff line
@@ -45,13 +45,80 @@ class JsonSchemaGenerator(rootObjectMapper: ObjectMapper) {

  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 {
  case class DefinitionInfo(ref:Option[String], jsonObjectFormatVisitor: Option[JsonObjectFormatVisitor])

  class DefinitionsHandler() {
    var class2Ref = Map[Class[_], String]()
    val definitionsNode = JsonNodeFactory.instance.objectNode()


    case class WorkInProgress(classInProgress:Class[_], nodeInProgress:ObjectNode)

    var workInProgress:Option[WorkInProgress] = None

    // returns ref
    def getOrCreateDefinition(clazz:Class[_])(objectDefinitionBuilder:(ObjectNode) => Option[JsonObjectFormatVisitor]):DefinitionInfo = {

      class2Ref.get(clazz) match {
        case Some(ref) =>

          workInProgress match {
            case None =>
              DefinitionInfo(Some(ref), None)

            case Some(w) =>
              // this is a recursive polymorphism call
              if ( clazz != w.classInProgress) throw new Exception(s"Wrong class - working on ${w.classInProgress} - got $clazz")

              DefinitionInfo(None, objectDefinitionBuilder(w.nodeInProgress))
          }

        case None =>

          // new one - must build it
          var retryCount = 0
          var shortRef = clazz.getSimpleName
          var longRef = "#/definitions/"+clazz.getSimpleName
          while( class2Ref.values.contains(longRef)) {
            retryCount = retryCount + 1
            shortRef = clazz.getSimpleName + "_" + retryCount
            longRef = "#/definitions/"+clazz.getSimpleName + "_" + retryCount
          }
          class2Ref = class2Ref + (clazz -> longRef)

          // create definition
          val node = JsonNodeFactory.instance.objectNode()

          // When processing polymorphism, we might get multiple recursive calls to getOrCreateDefinition - this is a wau to combine them
          workInProgress = Some(WorkInProgress(clazz, node))

          definitionsNode.set(shortRef, node)

          val jsonObjectFormatVisitor = objectDefinitionBuilder.apply(node)

          workInProgress = None

          DefinitionInfo(Some(longRef), jsonObjectFormatVisitor)
      }
    }

    def getFinalDefinitionsNode():Option[ObjectNode] = {
      if (class2Ref.isEmpty) None else Some(definitionsNode)
    }

  }

  class MyJsonFormatVisitorWrapper(objectMapper: ObjectMapper, level:Int = 0, val node: ObjectNode = JsonNodeFactory.instance.objectNode(), val definitionsHandler:DefinitionsHandler) extends JsonFormatVisitorWrapper with MySerializerProvider {

    def l(s: String): Unit = {
      var indent = ""
      for( i <- 0 until level) {
        indent = indent + "  "
      }
      println(indent + s)
    }

    def createChild(childNode: ObjectNode): MyJsonFormatVisitorWrapper = new MyJsonFormatVisitorWrapper(objectMapper, indent + "   ", childNode)
    def createChild(childNode: ObjectNode): MyJsonFormatVisitorWrapper = new MyJsonFormatVisitorWrapper(objectMapper, level + 1, node = childNode, definitionsHandler = definitionsHandler)

    override def expectStringFormat(_type: JavaType) = {
      l(s"expectStringFormat - _type: ${_type}")
@@ -169,18 +236,17 @@ class JsonSchemaGenerator(rootObjectMapper: ObjectMapper) {
          subType: SubTypeAndTypeName[_] =>
            l(s"polymorphism - subType: $subType")

            val thisOneOfNode = JsonNodeFactory.instance.objectNode()
            val definitionInfo: DefinitionInfo = definitionsHandler.getOrCreateDefinition(subType.clazz){
              objectNode =>

                // Set the title = subTypeName
            thisOneOfNode.put("title", subType.subTypeName)

            anyOfArrayNode.add(thisOneOfNode)
                objectNode.put("title", subType.subTypeName)

            val childVisitor = createChild(thisOneOfNode)
                val childVisitor = createChild(objectNode)
                objectMapper.acceptJsonFormatVisitor(subType.clazz, childVisitor)

                // must inject the 'type'-param and value as enum with only one possible value
            val propertiesNode = thisOneOfNode.get("properties").asInstanceOf[ObjectNode]
                val propertiesNode = objectNode.get("properties").asInstanceOf[ObjectNode]

                val enumValuesNode = JsonNodeFactory.instance.arrayNode()
                enumValuesNode.add(subType.subTypeName)
@@ -194,23 +260,38 @@ class JsonSchemaGenerator(rootObjectMapper: ObjectMapper) {

                val requiredNode:ArrayNode = Option(propertiesNode.get("required")).map(_.asInstanceOf[ArrayNode]).getOrElse {
                  val rn = JsonNodeFactory.instance.arrayNode()
              thisOneOfNode.set("required", rn)
                  objectNode.set("required", rn)
                  rn
                }

                requiredNode.add(subTypeSpecifierPropertyName)

                None
            }

            val thisOneOfNode = JsonNodeFactory.instance.objectNode()
            thisOneOfNode.put("$ref", definitionInfo.ref.get)
            anyOfArrayNode.add(thisOneOfNode)



        }

        null // Returning null to stop jackson from visiting this object since we have done it manually

      } else {
        node.put("type", "object")


        val objectBuilder:ObjectNode => Option[JsonObjectFormatVisitor] = {
          thisObjectNode:ObjectNode =>

            thisObjectNode.put("type", "object")
            thisObjectNode.put("additionalProperties", false)

            val propertiesNode = JsonNodeFactory.instance.objectNode()
        node.set("properties", propertiesNode)
            thisObjectNode.set("properties", propertiesNode)

        new JsonObjectFormatVisitor with MySerializerProvider {
            Some(new JsonObjectFormatVisitor with MySerializerProvider {
              override def optionalProperty(writer: BeanProperty): Unit = {
                val propertyName = writer.getName
                val propertyType = writer.getType
@@ -234,6 +315,22 @@ class JsonSchemaGenerator(rootObjectMapper: ObjectMapper) {
              override def property(name: String, handler: JsonFormatVisitable, propertyTypeHint: JavaType): Unit = {
                l(s"JsonObjectFormatVisitor.property: name:${name} handler:${handler} propertyTypeHint:${propertyTypeHint}")
              }
            })
        }

        if ( level == 0) {
          // This is the first level - we must not use definitions
          objectBuilder(node).orNull
        } else {
          val definitionInfo: DefinitionInfo = definitionsHandler.getOrCreateDefinition(_type.getRawClass)(objectBuilder)

          definitionInfo.ref.foreach {
            r =>
              // Must add ref to def at "this location"
              node.put("$ref", r)
          }

          definitionInfo.jsonObjectFormatVisitor.orNull
        }

      }
@@ -249,10 +346,16 @@ class JsonSchemaGenerator(rootObjectMapper: ObjectMapper) {

    // Specify that this is a v4 json schema
    rootNode.put("$schema", "http://json-schema.org/draft-04/schema#")
    //rootNode.put("id", "http://my.site/myschema#")

    val rootVisitor = new MyJsonFormatVisitorWrapper(rootObjectMapper, node = rootNode)
    val definitionsHandler = new DefinitionsHandler
    val rootVisitor = new MyJsonFormatVisitorWrapper(rootObjectMapper, node = rootNode, definitionsHandler = definitionsHandler)
    rootObjectMapper.acceptJsonFormatVisitor(clazz, rootVisitor)

    definitionsHandler.getFinalDefinitionsNode().foreach {
      definitionsNode => rootNode.set("definitions", definitionsNode)
    }

    rootNode
  }

+19 −0
Original line number Diff line number Diff line
@@ -49,11 +49,21 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers with TestData {

  def generateAndValidateSchema(clazz:Class[_], jsonToTestAgainstSchema:Option[JsonNode] = None):String = {
    val schema = jsonSchemaGenerator.generateJsonSchema(clazz)
    //println(asPrettyJson(schema))
    useSchema(schema, jsonToTestAgainstSchema)

    asPrettyJson(schema)
  }

  test("regular object") {
    val jsonNode = assertToFromJson(child1)

    val schemaAsJson = generateAndValidateSchema(child1.getClass, Some(jsonNode))
    println("--------------------------------------------")
    println(schemaAsJson)

  }

  test("polymorphism") {
    val jsonNode = assertToFromJson(pojoWithParent)

@@ -95,6 +105,13 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers with TestData {
    println(schemaAsJson)
  }

  test("recursivePojo") {
    val jsonNode = assertToFromJson(recursivePojo)
    val schemaAsJson = generateAndValidateSchema(recursivePojo.getClass, Some(jsonNode))
    println("--------------------------------------------")
    println(schemaAsJson)
  }


}

@@ -137,4 +154,6 @@ trait TestData {
    List(child1, child2).toArray

  )

  val recursivePojo = new RecursivePojo("t1", List(new RecursivePojo("c1", null)))
}
+37 −0
Original line number Diff line number Diff line
package com.kjetland.jackson.jsonSchema.testData;

import java.util.List;

public class RecursivePojo {

    public String myText;

    public List<RecursivePojo> children;

    public RecursivePojo() {
    }

    public RecursivePojo(String myText, List<RecursivePojo> children) {
        this.myText = myText;
        this.children = children;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof RecursivePojo)) return false;

        RecursivePojo that = (RecursivePojo) o;

        if (myText != null ? !myText.equals(that.myText) : that.myText != null) return false;
        return children != null ? children.equals(that.children) : that.children == null;

    }

    @Override
    public int hashCode() {
        int result = myText != null ? myText.hashCode() : 0;
        result = 31 * result + (children != null ? children.hashCode() : 0);
        return result;
    }
}