Commit 665a8061 authored by Morten Kjetland's avatar Morten Kjetland
Browse files

Added much test-code

parent abdb0ee5
Loading
Loading
Loading
Loading
+6 −2
Original line number Diff line number Diff line
@@ -11,6 +11,10 @@ import com.fasterxml.jackson.databind._
import com.fasterxml.jackson.databind.node.{ArrayNode, JsonNodeFactory, ObjectNode}
import org.slf4j.LoggerFactory

object JsonSchemaGenerator {
  val JSON_SCHEMA_DRAFT_4_URL = "http://json-schema.org/draft-04/schema#"
}

class JsonSchemaGenerator(rootObjectMapper: ObjectMapper, debug:Boolean = false) {

  import scala.collection.JavaConversions._
@@ -356,7 +360,7 @@ class JsonSchemaGenerator(rootObjectMapper: ObjectMapper, debug:Boolean = false)

                // Check if we should set this property as required
                val rawClass = prop.getType.getRawClass
                val requiredProperty:Boolean = if ( rawClass.isPrimitive && rawClass.isAssignableFrom(classOf[Boolean])) {
                val requiredProperty:Boolean = if ( rawClass.isPrimitive ) {
                  // primitive boolean MUST have a value
                  true
                } else if(prop.getAnnotation(classOf[NotNull]) != null) {
@@ -410,7 +414,7 @@ class JsonSchemaGenerator(rootObjectMapper: ObjectMapper, debug:Boolean = false)
    val rootNode = JsonNodeFactory.instance.objectNode()

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

    val definitionsHandler = new DefinitionsHandler
+141 −21
Original line number Diff line number Diff line
@@ -2,12 +2,14 @@ package com.kjetland.jackson.jsonSchema

import com.fasterxml.jackson.annotation.JsonValue
import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.databind.node.ArrayNode
import com.fasterxml.jackson.databind.{JsonNode, ObjectMapper}
import com.github.fge.jsonschema.main.JsonSchemaFactory
import com.kjetland.jackson.jsonSchema.testData._
import org.scalatest.{FunSuite, Matchers}
import scala.collection.JavaConversions._

class JsonSchemaGeneratorTest extends FunSuite with Matchers with TestData {
class JsonSchemaGeneratorTest extends FunSuite with Matchers {

  val objectMapper = new ObjectMapper()
  val simpleModule = new SimpleModule()
@@ -17,15 +19,20 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers with TestData {

  val jsonSchemaGenerator = new JsonSchemaGenerator(objectMapper)

  val testData = new TestData{}

  def asPrettyJson(node:JsonNode):String = {
    objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(node)
  }


  // Asserts that we're able to go from object => json => equal object
  def assertToFromJson(o:Any): JsonNode = {
    assertToFromJson(o, o.getClass)
  }

  // Asserts that we're able to go from object => json => equal object
  // deserType might be a class which o extends (polymorphism)
  def assertToFromJson(o:Any, deserType:Class[_]): JsonNode = {
    val json = objectMapper.writeValueAsString(o)
    println(s"json: $json")
@@ -47,58 +54,165 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers with TestData {
    }
  }

  def generateAndValidateSchema(clazz:Class[_], jsonToTestAgainstSchema:Option[JsonNode] = None):String = {
  // Generates schema, validates the schema using external schema validator and
  // Optionally tries to validate json against the schema.
  def generateAndValidateSchema(clazz:Class[_], jsonToTestAgainstSchema:Option[JsonNode] = None):JsonNode = {
    val schema = jsonSchemaGenerator.generateJsonSchema(clazz)

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

    assert( JsonSchemaGenerator.JSON_SCHEMA_DRAFT_4_URL == schema.at("/$schema").asText())

    useSchema(schema, jsonToTestAgainstSchema)

    asPrettyJson(schema)
    schema
  }

  def assertJsonSubTypesInfo(node:JsonNode, typeParamName:String, typeName:String): Unit ={
    /*
      "properties" : {
        "type" : {
          "type" : "string",
          "enum" : [ "child1" ],
          "default" : "child1"
        },
      },
      "title" : "child1",
      "required" : [ "type" ]
    */
    assert( node.at(s"/properties/$typeParamName/type").asText() == "string" )
    assert( node.at(s"/properties/$typeParamName/enum/0").asText() == typeName)
    assert( node.at(s"/properties/$typeParamName/default").asText() == typeName)
    assert( node.at(s"/title").asText() == typeName)
    assert( getRequiredList(node).contains(typeParamName))

  }

  def getArrayNodeAsListOfStrings(node:JsonNode):List[String] = {
    node.asInstanceOf[ArrayNode].iterator().toList.map(_.asText())
  }

  def getRequiredList(node:JsonNode):List[String] = {
    getArrayNodeAsListOfStrings(node.at(s"/required"))
  }

  test("regular object") {
    val jsonNode = assertToFromJson(child1)
  def getNodeViaArrayOfRefs(root:JsonNode, pathToArrayOfRefs:String, definitionName:String):JsonNode = {
    val nodeWhereArrayOfRefsIs:ArrayNode = root.at(pathToArrayOfRefs).asInstanceOf[ArrayNode]
    val arrayItemNodes = nodeWhereArrayOfRefsIs.iterator().toList
    val ref = arrayItemNodes.map(_.get("$ref").asText()).find( _.endsWith(s"/$definitionName")).get
    // use ref to look the node up
    val fixedRef = ref.substring(1) // Removing starting #
    root.at(fixedRef)
  }

  test("Generate scheme for plain class not using @JsonTypeInfo") {
    val jsonNode = assertToFromJson(testData.classNotExtendingAnything)

    val schema = generateAndValidateSchema((testData.classNotExtendingAnything).getClass, Some(jsonNode))

    val schemaAsJson = generateAndValidateSchema(child1.getClass, Some(jsonNode))
    assert( false == schema.at("/additionalProperties").asBoolean())
    assert( schema.at("/properties/someString/type").asText() == "string")

  }

  test("polymorphism") {
    val jsonNode = assertToFromJson(pojoWithParent)
  test("Generating schema for concrete class which happens to extend class using @JsonTypeInfo") {
    val jsonNode = assertToFromJson(testData.child1)

    val schemaAsJson = generateAndValidateSchema(pojoWithParent.getClass, Some(jsonNode))
    val schema = generateAndValidateSchema(testData.child1.getClass, Some(jsonNode))

    assert( false == schema.at("/additionalProperties").asBoolean())
    assert( schema.at("/properties/parentString/type").asText() == "string")
    assertJsonSubTypesInfo(schema, "type", "child1")

  }

  test("polymorphism - first Level") {
    val jsonNode = assertToFromJson(child1)
    assertToFromJson(child1, classOf[Parent])
  test("Generate schema for regular class which has a property of class annotated with @JsonTypeInfo") {
    val jsonNode = assertToFromJson(testData.pojoWithParent)
    val schema = generateAndValidateSchema(testData.pojoWithParent.getClass, Some(jsonNode))

    assert( false == schema.at("/additionalProperties").asBoolean())
    assert( schema.at("/properties/pojoValue/type").asText() == "boolean")

    assertChild1(schema, "/properties/child/oneOf")
    assertChild2(schema, "/properties/child/oneOf")


  }

  def assertChild1(node:JsonNode, path:String): Unit ={
    val child1 = getNodeViaArrayOfRefs(node, path, "Child1")
    assertJsonSubTypesInfo(child1, "type", "child1")
    assert( child1.at("/properties/parentString/type").asText() == "string" )
    assert( child1.at("/properties/child1String/type").asText() == "string" )
  }

  def assertChild2(node:JsonNode, path:String): Unit ={
    val child2 = getNodeViaArrayOfRefs(node, path, "Child2")
    assertJsonSubTypesInfo(child2, "type", "child2")
    assert( child2.at("/properties/parentString/type").asText() == "string" )
    assert( child2.at("/properties/child2int/type").asText() == "integer" )
  }

  test("Generate schema for super class annotated with @JsonTypeInfo") {
    val jsonNode = assertToFromJson(testData.child1)
    assertToFromJson(testData.child1, classOf[Parent])

    val schema = generateAndValidateSchema(classOf[Parent], Some(jsonNode))

    assertChild1(schema, "/oneOf")
    assertChild2(schema, "/oneOf")

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

  test("primitives") {
    val jsonNode = assertToFromJson(manyPrimitives)
    val schemaAsJson = generateAndValidateSchema(manyPrimitives.getClass, Some(jsonNode))
    val jsonNode = assertToFromJson(testData.manyPrimitives)
    val schema = generateAndValidateSchema(testData.manyPrimitives.getClass, Some(jsonNode))

    assert( schema.at("/properties/_string/type").asText() == "string" )

    assert( schema.at("/properties/_integer/type").asText() == "integer" )
    assert( !getRequiredList(schema).contains("_integer")) // Should allow null by default

    assert( schema.at("/properties/_int/type").asText() == "integer" )
    assert( getRequiredList(schema).contains("_int")) // Must have a value

    assert( schema.at("/properties/_booleanObject/type").asText() == "boolean" )
    assert( !getRequiredList(schema).contains("_booleanObject")) // Should allow null by default

    assert( schema.at("/properties/_booleanPrimitive/type").asText() == "boolean" )
    assert( getRequiredList(schema).contains("_booleanPrimitive")) // Must be required since it must have true or false - not null

    assert( schema.at("/properties/_booleanObjectWithNotNull/type").asText() == "boolean" )
    assert( getRequiredList(schema).contains("_booleanObjectWithNotNull"))

    assert( schema.at("/properties/_doubleObject/type").asText() == "number" )
    assert( !getRequiredList(schema).contains("_doubleObject")) // Should allow null by default

    assert( schema.at("/properties/_doublePrimitive/type").asText() == "number" )
    assert( getRequiredList(schema).contains("_doublePrimitive")) // Must be required since it must have a value - not null

    assert( schema.at("/properties/myEnum/type").asText() == "string")
    assert( getArrayNodeAsListOfStrings(schema.at("/properties/myEnum/enum")) == List("A", "B", "C") )


  }

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

    val jsonNode = assertToFromJson(pojoWithCustomSerializer)
    val schemaAsJson = generateAndValidateSchema(pojoWithCustomSerializer.getClass, Some(jsonNode))
    val jsonNode = assertToFromJson(testData.pojoWithCustomSerializer)
    val schema = generateAndValidateSchema(testData.pojoWithCustomSerializer.getClass, Some(jsonNode))
  }

  test("pojoWithArrays") {

    val jsonNode = assertToFromJson(pojoWithArrays)
    val schemaAsJson = generateAndValidateSchema(pojoWithArrays.getClass, Some(jsonNode))
    val jsonNode = assertToFromJson(testData.pojoWithArrays)
    val schema = generateAndValidateSchema(testData.pojoWithArrays.getClass, Some(jsonNode))
  }

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

  }

@@ -128,6 +242,12 @@ trait TestData {
    p
  }

  val classNotExtendingAnything = {
    val o = new ClassNotExtendingAnything
    o.someString = "Something"
    o
  }

  val manyPrimitives = new ManyPrimitives("s1", 1, 2, true, false, true, 0.1, 0.2, MyEnum.B)

  val pojoWithCustomSerializer = {
+22 −0
Original line number Diff line number Diff line
package com.kjetland.jackson.jsonSchema.testData;

public class ClassNotExtendingAnything {

    public String someString;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        ClassNotExtendingAnything child1 = (ClassNotExtendingAnything) o;

        return someString != null ? someString.equals(child1.someString) : child1.someString == null;

    }

    @Override
    public int hashCode() {
        return someString != null ? someString.hashCode() : 0;
    }
}
+21 −21
Original line number Diff line number Diff line
@@ -6,27 +6,27 @@ public class ManyPrimitives {
    public String _string;
    public Integer _integer;
    public int _int;
    public Boolean _boolean;
    public boolean _boolean2;
    public Boolean _booleanObject;
    public boolean _booleanPrimitive;

    @NotNull
    public Boolean _boolean3;
    public Double _double;
    public double _double2;
    public Boolean _booleanObjectWithNotNull;
    public Double _doubleObject;
    public double _doublePrimitive;
    public MyEnum myEnum;

    public ManyPrimitives() {
    }

    public ManyPrimitives(String _string, Integer _integer, int _int, Boolean _boolean, boolean _boolean2, Boolean _boolean3, Double _double, double _double2, MyEnum myEnum) {
    public ManyPrimitives(String _string, Integer _integer, int _int, Boolean _booleanObject, boolean _booleanPrimitive, Boolean _booleanObjectWithNotNull, Double _doubleObject, double _doublePrimitive, MyEnum myEnum) {
        this._string = _string;
        this._integer = _integer;
        this._int = _int;
        this._boolean = _boolean;
        this._boolean2 = _boolean2;
        this._boolean3 = _boolean3;
        this._double = _double;
        this._double2 = _double2;
        this._booleanObject = _booleanObject;
        this._booleanPrimitive = _booleanPrimitive;
        this._booleanObjectWithNotNull = _booleanObjectWithNotNull;
        this._doubleObject = _doubleObject;
        this._doublePrimitive = _doublePrimitive;
        this.myEnum = myEnum;
    }

@@ -38,13 +38,13 @@ public class ManyPrimitives {
        ManyPrimitives that = (ManyPrimitives) o;

        if (_int != that._int) return false;
        if (_boolean2 != that._boolean2) return false;
        if (Double.compare(that._double2, _double2) != 0) return false;
        if (_booleanPrimitive != that._booleanPrimitive) return false;
        if (Double.compare(that._doublePrimitive, _doublePrimitive) != 0) return false;
        if (_string != null ? !_string.equals(that._string) : that._string != null) return false;
        if (_integer != null ? !_integer.equals(that._integer) : that._integer != null) return false;
        if (_boolean != null ? !_boolean.equals(that._boolean) : that._boolean != null) return false;
        if (_boolean3 != null ? !_boolean3.equals(that._boolean3) : that._boolean3 != null) return false;
        if (_double != null ? !_double.equals(that._double) : that._double != null) return false;
        if (_booleanObject != null ? !_booleanObject.equals(that._booleanObject) : that._booleanObject != null) return false;
        if (_booleanObjectWithNotNull != null ? !_booleanObjectWithNotNull.equals(that._booleanObjectWithNotNull) : that._booleanObjectWithNotNull != null) return false;
        if (_doubleObject != null ? !_doubleObject.equals(that._doubleObject) : that._doubleObject != null) return false;
        return myEnum == that.myEnum;

    }
@@ -56,11 +56,11 @@ public class ManyPrimitives {
        result = _string != null ? _string.hashCode() : 0;
        result = 31 * result + (_integer != null ? _integer.hashCode() : 0);
        result = 31 * result + _int;
        result = 31 * result + (_boolean != null ? _boolean.hashCode() : 0);
        result = 31 * result + (_boolean2 ? 1 : 0);
        result = 31 * result + (_boolean3 != null ? _boolean3.hashCode() : 0);
        result = 31 * result + (_double != null ? _double.hashCode() : 0);
        temp = Double.doubleToLongBits(_double2);
        result = 31 * result + (_booleanObject != null ? _booleanObject.hashCode() : 0);
        result = 31 * result + (_booleanPrimitive ? 1 : 0);
        result = 31 * result + (_booleanObjectWithNotNull != null ? _booleanObjectWithNotNull.hashCode() : 0);
        result = 31 * result + (_doubleObject != null ? _doubleObject.hashCode() : 0);
        temp = Double.doubleToLongBits(_doublePrimitive);
        result = 31 * result + (int) (temp ^ (temp >>> 32));
        result = 31 * result + (myEnum != null ? myEnum.hashCode() : 0);
        return result;