Loading README.md +1 −1 Original line number Diff line number Diff line Loading @@ -10,7 +10,7 @@ Current version: *1.0.7* **Highlights** * JSON Schema Draft v4 * Supports polymorphism using **@JsonTypeInfo** and **oneOf** * Supports polymorphism (**@JsonTypeInfo**, **MixIn**, and **registerSubtypes()**) using JsonSchema's **oneOf**-feature. * Supports schema customization using **@JsonSchemaDescription**, **@JsonSchemaFormat** and **@JsonSchemaTitle** * Supports many Javax-validation @Annotations * Works well with Generated GUI's using [https://github.com/jdorn/json-editor](https://github.com/jdorn/json-editor) Loading src/main/scala/com/kjetland/jackson/jsonSchema/JsonSchemaGenerator.scala +9 −15 Original line number Diff line number Diff line Loading @@ -431,16 +431,11 @@ class JsonSchemaGenerator val propertyName = jsonTypeInfo.property() // must look at the @JsonSubTypes to find what this current class should be called val subTypeName:String = Option(ac.getAnnotations.get(classOf[JsonSubTypes])).map { ann: JsonSubTypes => ann.value() .find { t: JsonSubTypes.Type => t.value() == _type.getRawClass }.map(_.name()).getOrElse(throw new Exception(s"Did not find info about the class ${_type.getRawClass} in @JsonSubTypes")) }.getOrElse(throw new Exception(s"Did not find @JsonSubTypes")) // Must find out what this current class should be called val subTypeName: String = objectMapper.getSubtypeResolver.collectAndResolveSubtypesByClass(objectMapper.getDeserializationConfig, ac).toList .filter(_.getType == _type.getRawClass) .find(p => true) // find first .get.getName PolymorphismInfo(propertyName, subTypeName) } Loading @@ -449,11 +444,10 @@ class JsonSchemaGenerator override def expectObjectFormat(_type: JavaType) = { val subTypes: List[Class[_]] = Option(_type.getRawClass.getDeclaredAnnotation(classOf[JsonSubTypes])).map { ann: JsonSubTypes => ann.value().map { t: JsonSubTypes.Type => t.value() }.toList }.getOrElse(List()) val ac = AnnotatedClass.construct(_type, objectMapper.getDeserializationConfig()) val resolvedSubTypes = objectMapper.getSubtypeResolver.collectAndResolveSubtypesByClass(objectMapper.getDeserializationConfig, ac).toList val subTypes: List[Class[_]] = resolvedSubTypes.map( _.getType) .filter( c => _type.getRawClass.isAssignableFrom(c) && _type.getRawClass != c) if (subTypes.nonEmpty) { //l(s"polymorphism - subTypes: $subTypes") Loading src/test/scala/com/kjetland/jackson/jsonSchema/JsonSchemaGeneratorTest.scala +27 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule import com.fasterxml.jackson.module.scala.DefaultScalaModule import com.github.fge.jsonschema.main.JsonSchemaFactory import com.kjetland.jackson.jsonSchema.testData._ import com.kjetland.jackson.jsonSchema.testData.mixin.{MixinChild1, MixinModule, MixinParent} import org.scalatest.{FunSuite, Matchers} import scala.collection.JavaConversions._ Loading @@ -37,6 +38,9 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers { om.registerModule(new Jdk8Module ) om.registerModule(new JodaModule) // For the mixin-test om.registerModule( new MixinModule) om.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) om.setTimeZone(TimeZone.getDefault()) } Loading Loading @@ -629,6 +633,20 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers { assert(schema.at("/properties/doubleMax/maximum").asInt() == 10) } test("Polymorphism using mixin") { // Java { val jsonNode = assertToFromJson(jsonSchemaGenerator, testData.mixinChild1) assertToFromJson(jsonSchemaGenerator, testData.mixinChild1, classOf[MixinParent]) val schema = generateAndValidateSchema(jsonSchemaGenerator, classOf[MixinParent], Some(jsonNode)) assertChild1(schema, "/oneOf", defName = "MixinChild1") assertChild2(schema, "/oneOf", defName = "MixinChild2") } } } trait TestData { Loading Loading @@ -720,6 +738,15 @@ trait TestData { "_stringUsingNotNull", "_stringUsingSize", "_stringUsingSizeOnlyMin", "_stringUsingSizeOnlyMax", "_stringUsingPatternA", 1, 2, 1.0, 2.0 ) val mixinChild1 = { val c = new MixinChild1() c.parentString = "pv" c.child1String = "cs" c.child1String2 = "cs2" c.child1String3 = "cs3" c } } Loading src/test/scala/com/kjetland/jackson/jsonSchema/testData/mixin/MixinChild1.java 0 → 100755 +39 −0 Original line number Diff line number Diff line package com.kjetland.jackson.jsonSchema.testData.mixin; import com.fasterxml.jackson.annotation.JsonProperty; public class MixinChild1 extends MixinParent { public String child1String; @JsonProperty("_child1String2") public String child1String2; @JsonProperty(value = "_child1String3", required = true) public String child1String3; @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof MixinChild1)) return false; if (!super.equals(o)) return false; MixinChild1 child1 = (MixinChild1) o; if (child1String != null ? !child1String.equals(child1.child1String) : child1.child1String != null) return false; if (child1String2 != null ? !child1String2.equals(child1.child1String2) : child1.child1String2 != null) return false; return child1String3 != null ? child1String3.equals(child1.child1String3) : child1.child1String3 == null; } @Override public int hashCode() { int result = super.hashCode(); result = 31 * result + (child1String != null ? child1String.hashCode() : 0); result = 31 * result + (child1String2 != null ? child1String2.hashCode() : 0); result = 31 * result + (child1String3 != null ? child1String3.hashCode() : 0); return result; } } src/test/scala/com/kjetland/jackson/jsonSchema/testData/mixin/MixinChild2.java 0 → 100644 +22 −0 Original line number Diff line number Diff line package com.kjetland.jackson.jsonSchema.testData.mixin; public class MixinChild2 extends MixinParent { public Integer child2int; @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; MixinChild2 child2 = (MixinChild2) o; return child2int != null ? child2int.equals(child2.child2int) : child2.child2int == null; } @Override public int hashCode() { return child2int != null ? child2int.hashCode() : 0; } } Loading
README.md +1 −1 Original line number Diff line number Diff line Loading @@ -10,7 +10,7 @@ Current version: *1.0.7* **Highlights** * JSON Schema Draft v4 * Supports polymorphism using **@JsonTypeInfo** and **oneOf** * Supports polymorphism (**@JsonTypeInfo**, **MixIn**, and **registerSubtypes()**) using JsonSchema's **oneOf**-feature. * Supports schema customization using **@JsonSchemaDescription**, **@JsonSchemaFormat** and **@JsonSchemaTitle** * Supports many Javax-validation @Annotations * Works well with Generated GUI's using [https://github.com/jdorn/json-editor](https://github.com/jdorn/json-editor) Loading
src/main/scala/com/kjetland/jackson/jsonSchema/JsonSchemaGenerator.scala +9 −15 Original line number Diff line number Diff line Loading @@ -431,16 +431,11 @@ class JsonSchemaGenerator val propertyName = jsonTypeInfo.property() // must look at the @JsonSubTypes to find what this current class should be called val subTypeName:String = Option(ac.getAnnotations.get(classOf[JsonSubTypes])).map { ann: JsonSubTypes => ann.value() .find { t: JsonSubTypes.Type => t.value() == _type.getRawClass }.map(_.name()).getOrElse(throw new Exception(s"Did not find info about the class ${_type.getRawClass} in @JsonSubTypes")) }.getOrElse(throw new Exception(s"Did not find @JsonSubTypes")) // Must find out what this current class should be called val subTypeName: String = objectMapper.getSubtypeResolver.collectAndResolveSubtypesByClass(objectMapper.getDeserializationConfig, ac).toList .filter(_.getType == _type.getRawClass) .find(p => true) // find first .get.getName PolymorphismInfo(propertyName, subTypeName) } Loading @@ -449,11 +444,10 @@ class JsonSchemaGenerator override def expectObjectFormat(_type: JavaType) = { val subTypes: List[Class[_]] = Option(_type.getRawClass.getDeclaredAnnotation(classOf[JsonSubTypes])).map { ann: JsonSubTypes => ann.value().map { t: JsonSubTypes.Type => t.value() }.toList }.getOrElse(List()) val ac = AnnotatedClass.construct(_type, objectMapper.getDeserializationConfig()) val resolvedSubTypes = objectMapper.getSubtypeResolver.collectAndResolveSubtypesByClass(objectMapper.getDeserializationConfig, ac).toList val subTypes: List[Class[_]] = resolvedSubTypes.map( _.getType) .filter( c => _type.getRawClass.isAssignableFrom(c) && _type.getRawClass != c) if (subTypes.nonEmpty) { //l(s"polymorphism - subTypes: $subTypes") Loading
src/test/scala/com/kjetland/jackson/jsonSchema/JsonSchemaGeneratorTest.scala +27 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule import com.fasterxml.jackson.module.scala.DefaultScalaModule import com.github.fge.jsonschema.main.JsonSchemaFactory import com.kjetland.jackson.jsonSchema.testData._ import com.kjetland.jackson.jsonSchema.testData.mixin.{MixinChild1, MixinModule, MixinParent} import org.scalatest.{FunSuite, Matchers} import scala.collection.JavaConversions._ Loading @@ -37,6 +38,9 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers { om.registerModule(new Jdk8Module ) om.registerModule(new JodaModule) // For the mixin-test om.registerModule( new MixinModule) om.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) om.setTimeZone(TimeZone.getDefault()) } Loading Loading @@ -629,6 +633,20 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers { assert(schema.at("/properties/doubleMax/maximum").asInt() == 10) } test("Polymorphism using mixin") { // Java { val jsonNode = assertToFromJson(jsonSchemaGenerator, testData.mixinChild1) assertToFromJson(jsonSchemaGenerator, testData.mixinChild1, classOf[MixinParent]) val schema = generateAndValidateSchema(jsonSchemaGenerator, classOf[MixinParent], Some(jsonNode)) assertChild1(schema, "/oneOf", defName = "MixinChild1") assertChild2(schema, "/oneOf", defName = "MixinChild2") } } } trait TestData { Loading Loading @@ -720,6 +738,15 @@ trait TestData { "_stringUsingNotNull", "_stringUsingSize", "_stringUsingSizeOnlyMin", "_stringUsingSizeOnlyMax", "_stringUsingPatternA", 1, 2, 1.0, 2.0 ) val mixinChild1 = { val c = new MixinChild1() c.parentString = "pv" c.child1String = "cs" c.child1String2 = "cs2" c.child1String3 = "cs3" c } } Loading
src/test/scala/com/kjetland/jackson/jsonSchema/testData/mixin/MixinChild1.java 0 → 100755 +39 −0 Original line number Diff line number Diff line package com.kjetland.jackson.jsonSchema.testData.mixin; import com.fasterxml.jackson.annotation.JsonProperty; public class MixinChild1 extends MixinParent { public String child1String; @JsonProperty("_child1String2") public String child1String2; @JsonProperty(value = "_child1String3", required = true) public String child1String3; @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof MixinChild1)) return false; if (!super.equals(o)) return false; MixinChild1 child1 = (MixinChild1) o; if (child1String != null ? !child1String.equals(child1.child1String) : child1.child1String != null) return false; if (child1String2 != null ? !child1String2.equals(child1.child1String2) : child1.child1String2 != null) return false; return child1String3 != null ? child1String3.equals(child1.child1String3) : child1.child1String3 == null; } @Override public int hashCode() { int result = super.hashCode(); result = 31 * result + (child1String != null ? child1String.hashCode() : 0); result = 31 * result + (child1String2 != null ? child1String2.hashCode() : 0); result = 31 * result + (child1String3 != null ? child1String3.hashCode() : 0); return result; } }
src/test/scala/com/kjetland/jackson/jsonSchema/testData/mixin/MixinChild2.java 0 → 100644 +22 −0 Original line number Diff line number Diff line package com.kjetland.jackson.jsonSchema.testData.mixin; public class MixinChild2 extends MixinParent { public Integer child2int; @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; MixinChild2 child2 = (MixinChild2) o; return child2int != null ? child2int.equals(child2.child2int) : child2.child2int == null; } @Override public int hashCode() { return child2int != null ? child2int.hashCode() : 0; } }