Commit 77ecc2ec authored by mokj's avatar mokj
Browse files

Simplified polymorphism support

parent be3571ae
Loading
Loading
Loading
Loading
+12 −4
Original line number Diff line number Diff line
@@ -2,17 +2,25 @@
lazy val commonSettings = Seq(
  organization := "com.kjetland",
  organizationName := "mbknor",
  version := "1.0.0-build-6-SNAPSHOT",
  version := "1.0.0-build-7-SNAPSHOT",
  scalaVersion := "2.11.8",
  publishMavenStyle := true,
  publishArtifact in Test := false,

  publishTo := {
    val nexus = "https://oss.sonatype.org/"
    val nexus = "http://nexus.nextgentel.net/content/repositories/"
    if (isSnapshot.value)
      Some("snapshots" at nexus + "content/repositories/snapshots")
      Some("snapshots" at nexus + "snapshots/")
    else
      Some("releases" at nexus + "service/local/staging/deploy/maven2")
      Some("releases"  at nexus + "thirdparty/")
  },
//  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-jackson-jsonSchema")),
  licenses := Seq("MIT" -> url("https://github.com/mbknor/mbknor-jackson-jsonSchema/blob/master/LICENSE.txt")),
  startYear := Some(2016),
+70 −25
Original line number Diff line number Diff line
@@ -44,8 +44,6 @@ class JsonSchemaGenerator(rootObjectMapper: ObjectMapper) {
  }


  case class SubTypeAndTypeName[T](clazz: Class[T], subTypeName: String)

  case class DefinitionInfo(ref:Option[String], jsonObjectFormatVisitor: Option[JsonObjectFormatVisitor])

  class DefinitionsHandler() {
@@ -224,12 +222,55 @@ class JsonSchemaGenerator(rootObjectMapper: ObjectMapper) {
      }
    }

    case class PolymorphismInfo(typePropertyName:String, subTypeName:String)

    private def extractPolymorphismInfo(clazz:Class[_]):Option[PolymorphismInfo] = {
      // look for @JsonTypeInfo
      var superClass = clazz
      var jsonTypeInfo:JsonTypeInfo = null

      while( jsonTypeInfo == null && superClass != classOf[Object]) {
        jsonTypeInfo = Option(superClass.getDeclaredAnnotation(classOf[JsonTypeInfo])).map {
          ann:JsonTypeInfo => ann
        }.orNull
        if ( jsonTypeInfo == null ) {
          superClass = superClass.getSuperclass
        }
      }

      if ( jsonTypeInfo == null) {
        return None
      }


      if ( jsonTypeInfo.include() != JsonTypeInfo.As.PROPERTY) throw new Exception("We only support polymorphism using jsonTypeInfo.include() == JsonTypeInfo.As.PROPERTY")
      if ( jsonTypeInfo.use != JsonTypeInfo.Id.NAME) throw new Exception("We only support polymorphism using jsonTypeInfo.use == JsonTypeInfo.Id.NAME")


      val propertyName = jsonTypeInfo.property()

      // must look at the @JsonSubTypes to find what this current class should be called

      val subTypeName:String = Option(superClass.getDeclaredAnnotation(classOf[JsonSubTypes])).map {
        ann: JsonSubTypes => ann.value()
          .find {
            t: JsonSubTypes.Type =>
              t.value() == clazz
          }.map(_.name()).getOrElse(throw new Exception(s"Did not find info about the class $clazz in @JsonSubTypes on $superClass"))
      }.getOrElse(throw new Exception(s"Did not find @JsonSubTypes on $superClass"))


      Some(PolymorphismInfo(propertyName, subTypeName))



    }

    override def expectObjectFormat(_type: JavaType) = {

      val subTypes: List[SubTypeAndTypeName[_]] = Option(_type.getRawClass.getDeclaredAnnotation(classOf[JsonSubTypes])).map {
      val subTypes: List[Class[_]] = Option(_type.getRawClass.getDeclaredAnnotation(classOf[JsonSubTypes])).map {
        ann: JsonSubTypes => ann.value().map {
          t: JsonSubTypes.Type =>
            SubTypeAndTypeName(t.value(), t.name())
          t: JsonSubTypes.Type => t.value()
        }.toList
      }.getOrElse(List())

@@ -239,36 +280,19 @@ class JsonSchemaGenerator(rootObjectMapper: ObjectMapper) {
        val anyOfArrayNode = JsonNodeFactory.instance.arrayNode()
        node.set("oneOf", anyOfArrayNode)

        val subTypeSpecifierPropertyName: String = _type.getRawClass.getDeclaredAnnotation(classOf[JsonTypeInfo]).property()

        subTypes.foreach {
          subType: SubTypeAndTypeName[_] =>
          subType: Class[_] =>
            l(s"polymorphism - subType: $subType")

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

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

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

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

                val enumValuesNode = JsonNodeFactory.instance.arrayNode()
                enumValuesNode.add(subType.subTypeName)

                val enumObjectNode = JsonNodeFactory.instance.objectNode()
                enumObjectNode.put("type", "string")
                enumObjectNode.set("enum", enumValuesNode)
                enumObjectNode.put("default", subType.subTypeName)

                propertiesNode.set(subTypeSpecifierPropertyName, enumObjectNode)

                getRequiredArrayNode(objectNode).add(subTypeSpecifierPropertyName)

                None
            }

@@ -294,6 +318,27 @@ class JsonSchemaGenerator(rootObjectMapper: ObjectMapper) {
            val propertiesNode = JsonNodeFactory.instance.objectNode()
            thisObjectNode.set("properties", propertiesNode)

            extractPolymorphismInfo(_type.getRawClass).map {
              case pi:PolymorphismInfo =>
                // This class is a child in a polymorphism config..
                // Set the title = subTypeName
                thisObjectNode.put("title", pi.subTypeName)

                // must inject the 'type'-param and value as enum with only one possible value
                val enumValuesNode = JsonNodeFactory.instance.arrayNode()
                enumValuesNode.add(pi.subTypeName)

                val enumObjectNode = JsonNodeFactory.instance.objectNode()
                enumObjectNode.put("type", "string")
                enumObjectNode.set("enum", enumValuesNode)
                enumObjectNode.put("default", pi.subTypeName)

                propertiesNode.set(pi.typePropertyName, enumObjectNode)

                getRequiredArrayNode(thisObjectNode).add(pi.typePropertyName)

            }

            Some(new JsonObjectFormatVisitor with MySerializerProvider {
              override def optionalProperty(prop: BeanProperty): Unit = {
                val propertyName = prop.getName
+5 −15
Original line number Diff line number Diff line
@@ -49,7 +49,10 @@ 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))

    println("--------------------------------------------")
    println(asPrettyJson(schema))

    useSchema(schema, jsonToTestAgainstSchema)

    asPrettyJson(schema)
@@ -59,8 +62,6 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers with TestData {
    val jsonNode = assertToFromJson(child1)

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

  }

@@ -68,8 +69,6 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers with TestData {
    val jsonNode = assertToFromJson(pojoWithParent)

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

  }

@@ -78,38 +77,29 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers with TestData {
    assertToFromJson(child1, classOf[Parent])

    val schemaAsJson = generateAndValidateSchema(classOf[Parent], Some(jsonNode))
    println("--------------------------------------------")
    println(schemaAsJson)
  }

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

  test("custom serializer not overriding JsonSerializer.acceptJsonFormatVisitor") {

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

  test("pojoWithArrays") {

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

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

  }


+3 −0
Original line number Diff line number Diff line
package com.kjetland.jackson.jsonSchema.testData;

import com.fasterxml.jackson.annotation.JsonInclude;

import java.util.List;

public class RecursivePojo {

    public String myText;

    @JsonInclude(JsonInclude.Include.NON_NULL)
    public List<RecursivePojo> children;

    public RecursivePojo() {