Loading src/main/scala/com/kjetland/jackson/jsonSchema/JsonSchemaGenerator.scala +53 −14 Original line number Diff line number Diff line Loading @@ -3,6 +3,7 @@ package com.kjetland.jackson.jsonSchema import java.lang.reflect.{Field, Method, ParameterizedType} import java.time.{LocalDate, LocalDateTime, LocalTime, OffsetDateTime} import java.util import java.util.Optional import javax.validation.constraints.NotNull import com.fasterxml.jackson.annotation.{JsonSubTypes, JsonTypeInfo} Loading @@ -25,7 +26,9 @@ object JsonSchemaConfig { val vanillaJsonSchemaDraft4 = JsonSchemaConfig( useHTML5DateTimeLocal = false, autoGenerateTitleForProperties = false, defaultArrayFormat = None) defaultArrayFormat = None, useOneOfForOption = false ) /** * Use this configuration if using the JsonSchema to generate HTML5 GUI, eg. by using https://github.com/jdorn/json-editor Loading @@ -38,14 +41,17 @@ object JsonSchemaConfig { val html5EnabledSchema = JsonSchemaConfig( useHTML5DateTimeLocal = true, autoGenerateTitleForProperties = true, defaultArrayFormat = Some("table")) defaultArrayFormat = Some("table"), useOneOfForOption = true ) } case class JsonSchemaConfig ( useHTML5DateTimeLocal:Boolean = false, autoGenerateTitleForProperties:Boolean = true, defaultArrayFormat:Option[String] = Some("table") useHTML5DateTimeLocal:Boolean, autoGenerateTitleForProperties:Boolean, defaultArrayFormat:Option[String], useOneOfForOption:Boolean ) Loading Loading @@ -440,13 +446,46 @@ class JsonSchemaGenerator val propertyType = prop.getType l(s"JsonObjectFormatVisitor - ${propertyName}: ${propertyType}") // Need to check for Option/Optional-special-case before we know what node to use here. case class PropertyNode(main:ObjectNode, meta:ObjectNode) val thisPropertyNode:PropertyNode = { val thisPropertyNode = JsonNodeFactory.instance.objectNode() propertiesNode.set(propertyName, thisPropertyNode) val childNode = JsonNodeFactory.instance.objectNode() // Check for Option/Optional-special-case if ( config.useOneOfForOption && ( classOf[Option[_]].isAssignableFrom(propertyType.getRawClass) || classOf[Optional[_]].isAssignableFrom(propertyType.getRawClass)) ) { // Need to special-case for property using Option/Optional // Should insert oneOf between 'real one' and 'null' val oneOfArray = JsonNodeFactory.instance.arrayNode() thisPropertyNode.set("oneOf", oneOfArray) // Create the one used when Option is empty val oneOfNull = JsonNodeFactory.instance.objectNode() oneOfNull.put("type", "null") oneOfNull.put("title", "Not included") oneOfArray.add(oneOfNull) // Create the one used when Option is defined with the real value val oneOfReal = JsonNodeFactory.instance.objectNode() oneOfArray.add(oneOfReal) // Return oneOfReal which, from now on, will be used as the node representing this property PropertyNode(oneOfReal, thisPropertyNode) } else { // Not special-casing - using thisPropertyNode as is PropertyNode(thisPropertyNode, thisPropertyNode) } } // Continue processing this property val childVisitor = createChild(thisPropertyNode) val childVisitor = createChild(thisPropertyNode.main) // Workaround for scala lists and so on if ( (propertyType.isArrayType || propertyType.isCollectionLikeType) && !classOf[Option[_]].isAssignableFrom(propertyType.getRawClass) && propertyType.containedTypeCount() >= 1) { Loading @@ -458,9 +497,9 @@ class JsonSchemaGenerator val itemType:JavaType = resolveType(prop, objectMapper) childVisitor.expectArrayFormat(itemType).itemsFormat(null, itemType) } else if(classOf[Option[_]].isAssignableFrom(propertyType.getRawClass) && propertyType.containedTypeCount() >= 1) { } else if( (classOf[Option[_]].isAssignableFrom(propertyType.getRawClass) || classOf[Optional[_]].isAssignableFrom(propertyType.getRawClass) ) && propertyType.containedTypeCount() >= 1) { // Property is scala Option. // Property is scala Option or Java Optional. // // Due to Java's Type Erasure, the type behind Option is lost. // To workaround this, we use the same workaround as jackson-scala-module described here: Loading Loading @@ -491,13 +530,13 @@ class JsonSchemaGenerator resolvePropertyFormat(prop).foreach { format => setFormat(thisPropertyNode, format) setFormat(thisPropertyNode.main, format) } // Optionally add description Option(prop.getAnnotation(classOf[JsonSchemaDescription])).map { jsonSchemaDescription => thisPropertyNode.put("description", jsonSchemaDescription.value()) thisPropertyNode.meta.put("description", jsonSchemaDescription.value()) } // Optionally add title Loading @@ -510,7 +549,7 @@ class JsonSchemaGenerator } .map { title => thisPropertyNode.put("title", title) thisPropertyNode.meta.put("title", title) } } Loading src/test/scala/com/kjetland/jackson/jsonSchema/JsonSchemaGeneratorTest.scala +105 −3 Original line number Diff line number Diff line package com.kjetland.jackson.jsonSchema import java.time.OffsetDateTime import java.util.TimeZone import java.util import java.util.{Optional, TimeZone} import com.fasterxml.jackson.annotation.{JsonSubTypes, JsonTypeInfo, JsonValue} import com.fasterxml.jackson.databind.annotation.JsonDeserialize Loading Loading @@ -40,8 +41,9 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers { val jsonSchemaGenerator = new JsonSchemaGenerator(_objectMapper, debug = true) val jsonSchemaGeneratorHTML5Date = new JsonSchemaGenerator(_objectMapper, debug = true, config = JsonSchemaConfig.html5EnabledSchema) val jsonSchemaGeneratorHTML5 = new JsonSchemaGenerator(_objectMapper, debug = true, config = JsonSchemaConfig.html5EnabledSchema) val jsonSchemaGeneratorScala = new JsonSchemaGenerator(_objectMapperScala, debug = true) val jsonSchemaGeneratorScalaHTML5 = new JsonSchemaGenerator(_objectMapperScala, debug = true, config = JsonSchemaConfig.html5EnabledSchema) val testData = new TestData{} Loading Loading @@ -334,6 +336,27 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers { } test("java using option") { val jsonNode = assertToFromJson(jsonSchemaGenerator, testData.pojoUsingOptionalJava) val schema = generateAndValidateSchema(jsonSchemaGenerator, testData.pojoUsingOptionalJava.getClass, Some(jsonNode)) assert(schema.at("/properties/_string/type").asText() == "string") assert(!getRequiredList(schema).contains("_string")) // Should allow null by default assert(schema.at("/properties/_integer/type").asText() == "integer") assert(!getRequiredList(schema).contains("_integer")) // Should allow null by default val child1 = getNodeViaRefs(schema, schema.at("/properties/child1"), "Child1") assertJsonSubTypesInfo(child1, "type", "child1") assert( child1.at("/properties/parentString/type").asText() == "string" ) assert( child1.at("/properties/child1String/type").asText() == "string" ) assert(schema.at("/properties/optionalList/type").asText() == "array") assert(schema.at("/properties/optionalList/items/$ref").asText() == "#/definitions/ClassNotExtendingAnything") } test("custom serializer not overriding JsonSerializer.acceptJsonFormatVisitor") { val jsonNode = assertToFromJson(jsonSchemaGenerator, testData.pojoWithCustomSerializer) Loading Loading @@ -414,7 +437,7 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers { test("pojo Using Custom Annotations") { val jsonNode = assertToFromJson(jsonSchemaGenerator, testData.pojoUsingFormat) val schema = generateAndValidateSchema(jsonSchemaGenerator, testData.pojoUsingFormat.getClass, Some(jsonNode)) val schemaHTML5Date = generateAndValidateSchema(jsonSchemaGeneratorHTML5Date, testData.pojoUsingFormat.getClass, Some(jsonNode)) val schemaHTML5Date = generateAndValidateSchema(jsonSchemaGeneratorHTML5, testData.pojoUsingFormat.getClass, Some(jsonNode)) assert( schema.at("/format").asText() == "grid") assert( schema.at("/description").asText() == "This is our pojo") Loading Loading @@ -443,6 +466,83 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers { } test("scala using option with HTML5") { val jsonNode = assertToFromJson(jsonSchemaGeneratorScalaHTML5, testData.pojoUsingOptionScala) val schema = generateAndValidateSchema(jsonSchemaGeneratorScalaHTML5, testData.pojoUsingOptionScala.getClass, Some(jsonNode)) assert(schema.at("/properties/_string/oneOf/0/type").asText() == "null") assert(schema.at("/properties/_string/oneOf/0/title").asText() == "Not included") assert(schema.at("/properties/_string/oneOf/1/type").asText() == "string") assert(!getRequiredList(schema).contains("_string")) // Should allow null by default assert(schema.at("/properties/_string/title").asText() == "_string") assert(schema.at("/properties/_integer/oneOf/0/type").asText() == "null") assert(schema.at("/properties/_integer/oneOf/0/title").asText() == "Not included") assert(schema.at("/properties/_integer/oneOf/1/type").asText() == "integer") assert(!getRequiredList(schema).contains("_integer")) // Should allow null by default assert(schema.at("/properties/_integer/title").asText() == "_integer") assert(schema.at("/properties/_boolean/oneOf/0/type").asText() == "null") assert(schema.at("/properties/_boolean/oneOf/0/title").asText() == "Not included") assert(schema.at("/properties/_boolean/oneOf/1/type").asText() == "boolean") assert(!getRequiredList(schema).contains("_boolean")) // Should allow null by default assert(schema.at("/properties/_boolean/title").asText() == "_boolean") assert(schema.at("/properties/_double/oneOf/0/type").asText() == "null") assert(schema.at("/properties/_double/oneOf/0/title").asText() == "Not included") assert(schema.at("/properties/_double/oneOf/1/type").asText() == "number") assert(!getRequiredList(schema).contains("_double")) // Should allow null by default assert(schema.at("/properties/_double/title").asText() == "_double") assert(schema.at("/properties/child1/oneOf/0/type").asText() == "null") assert(schema.at("/properties/child1/oneOf/0/title").asText() == "Not included") val child1 = getNodeViaRefs(schema, schema.at("/properties/child1/oneOf/1"), "Child1Scala") assert(schema.at("/properties/child1/title").asText() == "Child 1") assertJsonSubTypesInfo(child1, "type", "child1") assert( child1.at("/properties/parentString/type").asText() == "string" ) assert( child1.at("/properties/child1String/type").asText() == "string" ) assert(schema.at("/properties/optionalList/oneOf/0/type").asText() == "null") assert(schema.at("/properties/optionalList/oneOf/0/title").asText() == "Not included") assert(schema.at("/properties/optionalList/oneOf/1/type").asText() == "array") assert(schema.at("/properties/optionalList/oneOf/1/items/$ref").asText() == "#/definitions/ClassNotExtendingAnythingScala") assert(schema.at("/properties/optionalList/title").asText() == "Optional List") } test("java using optional with HTML5") { val jsonNode = assertToFromJson(jsonSchemaGeneratorHTML5, testData.pojoUsingOptionalJava) val schema = generateAndValidateSchema(jsonSchemaGeneratorHTML5, testData.pojoUsingOptionalJava.getClass, Some(jsonNode)) assert(schema.at("/properties/_string/oneOf/0/type").asText() == "null") assert(schema.at("/properties/_string/oneOf/0/title").asText() == "Not included") assert(schema.at("/properties/_string/oneOf/1/type").asText() == "string") assert(!getRequiredList(schema).contains("_string")) // Should allow null by default assert(schema.at("/properties/_string/title").asText() == "_string") assert(schema.at("/properties/_integer/oneOf/0/type").asText() == "null") assert(schema.at("/properties/_integer/oneOf/0/title").asText() == "Not included") assert(schema.at("/properties/_integer/oneOf/1/type").asText() == "integer") assert(!getRequiredList(schema).contains("_integer")) // Should allow null by default assert(schema.at("/properties/_integer/title").asText() == "_integer") assert(schema.at("/properties/child1/oneOf/0/type").asText() == "null") assert(schema.at("/properties/child1/oneOf/0/title").asText() == "Not included") val child1 = getNodeViaRefs(schema, schema.at("/properties/child1/oneOf/1"), "Child1") assert(schema.at("/properties/child1/title").asText() == "Child 1") assertJsonSubTypesInfo(child1, "type", "child1") assert( child1.at("/properties/parentString/type").asText() == "string" ) assert( child1.at("/properties/child1String/type").asText() == "string" ) assert(schema.at("/properties/optionalList/oneOf/0/type").asText() == "null") assert(schema.at("/properties/optionalList/oneOf/0/title").asText() == "Not included") assert(schema.at("/properties/optionalList/oneOf/1/type").asText() == "array") assert(schema.at("/properties/optionalList/oneOf/1/items/$ref").asText() == "#/definitions/ClassNotExtendingAnything") assert(schema.at("/properties/optionalList/title").asText() == "Optional List") } } trait TestData { Loading Loading @@ -489,6 +589,8 @@ trait TestData { val pojoUsingOptionScala = PojoUsingOptionScala(Some("s1"), Some(1), Some(true), Some(0.1), Some(child1Scala), Some(List(classNotExtendingAnythingScala))) val pojoUsingOptionalJava = new PojoUsingOptionalJava(Optional.of("s"), Optional.of(1), Optional.of(child1), Optional.of(util.Arrays.asList(classNotExtendingAnything))) val pojoWithCustomSerializer = { val p = new PojoWithCustomSerializer p.myString = "xxx" Loading src/test/scala/com/kjetland/jackson/jsonSchema/testData/PojoUsingOptionalJava.java 0 → 100644 +55 −0 Original line number Diff line number Diff line package com.kjetland.jackson.jsonSchema.testData; import java.util.List; import java.util.Optional; public class PojoUsingOptionalJava { public Optional<String> _string; public Optional<Integer> _integer; public Optional<Child1> child1; public Optional<List<ClassNotExtendingAnything>> optionalList; public PojoUsingOptionalJava() { } public PojoUsingOptionalJava(Optional<String> _string, Optional<Integer> _integer, Optional<Child1> child1, Optional<List<ClassNotExtendingAnything>> optionalList) { this._string = _string; this._integer = _integer; this.child1 = child1; this.optionalList = optionalList; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; PojoUsingOptionalJava that = (PojoUsingOptionalJava) o; 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 (child1 != null ? !child1.equals(that.child1) : that.child1 != null) return false; return optionalList != null ? optionalList.equals(that.optionalList) : that.optionalList == null; } @Override public int hashCode() { int result = _string != null ? _string.hashCode() : 0; result = 31 * result + (_integer != null ? _integer.hashCode() : 0); result = 31 * result + (child1 != null ? child1.hashCode() : 0); result = 31 * result + (optionalList != null ? optionalList.hashCode() : 0); return result; } @Override public String toString() { return "PojoUsingOptionalJava{" + "_string=" + _string + ", _integer=" + _integer + ", child1=" + child1 + ", optionalList=" + optionalList + '}'; } } Loading
src/main/scala/com/kjetland/jackson/jsonSchema/JsonSchemaGenerator.scala +53 −14 Original line number Diff line number Diff line Loading @@ -3,6 +3,7 @@ package com.kjetland.jackson.jsonSchema import java.lang.reflect.{Field, Method, ParameterizedType} import java.time.{LocalDate, LocalDateTime, LocalTime, OffsetDateTime} import java.util import java.util.Optional import javax.validation.constraints.NotNull import com.fasterxml.jackson.annotation.{JsonSubTypes, JsonTypeInfo} Loading @@ -25,7 +26,9 @@ object JsonSchemaConfig { val vanillaJsonSchemaDraft4 = JsonSchemaConfig( useHTML5DateTimeLocal = false, autoGenerateTitleForProperties = false, defaultArrayFormat = None) defaultArrayFormat = None, useOneOfForOption = false ) /** * Use this configuration if using the JsonSchema to generate HTML5 GUI, eg. by using https://github.com/jdorn/json-editor Loading @@ -38,14 +41,17 @@ object JsonSchemaConfig { val html5EnabledSchema = JsonSchemaConfig( useHTML5DateTimeLocal = true, autoGenerateTitleForProperties = true, defaultArrayFormat = Some("table")) defaultArrayFormat = Some("table"), useOneOfForOption = true ) } case class JsonSchemaConfig ( useHTML5DateTimeLocal:Boolean = false, autoGenerateTitleForProperties:Boolean = true, defaultArrayFormat:Option[String] = Some("table") useHTML5DateTimeLocal:Boolean, autoGenerateTitleForProperties:Boolean, defaultArrayFormat:Option[String], useOneOfForOption:Boolean ) Loading Loading @@ -440,13 +446,46 @@ class JsonSchemaGenerator val propertyType = prop.getType l(s"JsonObjectFormatVisitor - ${propertyName}: ${propertyType}") // Need to check for Option/Optional-special-case before we know what node to use here. case class PropertyNode(main:ObjectNode, meta:ObjectNode) val thisPropertyNode:PropertyNode = { val thisPropertyNode = JsonNodeFactory.instance.objectNode() propertiesNode.set(propertyName, thisPropertyNode) val childNode = JsonNodeFactory.instance.objectNode() // Check for Option/Optional-special-case if ( config.useOneOfForOption && ( classOf[Option[_]].isAssignableFrom(propertyType.getRawClass) || classOf[Optional[_]].isAssignableFrom(propertyType.getRawClass)) ) { // Need to special-case for property using Option/Optional // Should insert oneOf between 'real one' and 'null' val oneOfArray = JsonNodeFactory.instance.arrayNode() thisPropertyNode.set("oneOf", oneOfArray) // Create the one used when Option is empty val oneOfNull = JsonNodeFactory.instance.objectNode() oneOfNull.put("type", "null") oneOfNull.put("title", "Not included") oneOfArray.add(oneOfNull) // Create the one used when Option is defined with the real value val oneOfReal = JsonNodeFactory.instance.objectNode() oneOfArray.add(oneOfReal) // Return oneOfReal which, from now on, will be used as the node representing this property PropertyNode(oneOfReal, thisPropertyNode) } else { // Not special-casing - using thisPropertyNode as is PropertyNode(thisPropertyNode, thisPropertyNode) } } // Continue processing this property val childVisitor = createChild(thisPropertyNode) val childVisitor = createChild(thisPropertyNode.main) // Workaround for scala lists and so on if ( (propertyType.isArrayType || propertyType.isCollectionLikeType) && !classOf[Option[_]].isAssignableFrom(propertyType.getRawClass) && propertyType.containedTypeCount() >= 1) { Loading @@ -458,9 +497,9 @@ class JsonSchemaGenerator val itemType:JavaType = resolveType(prop, objectMapper) childVisitor.expectArrayFormat(itemType).itemsFormat(null, itemType) } else if(classOf[Option[_]].isAssignableFrom(propertyType.getRawClass) && propertyType.containedTypeCount() >= 1) { } else if( (classOf[Option[_]].isAssignableFrom(propertyType.getRawClass) || classOf[Optional[_]].isAssignableFrom(propertyType.getRawClass) ) && propertyType.containedTypeCount() >= 1) { // Property is scala Option. // Property is scala Option or Java Optional. // // Due to Java's Type Erasure, the type behind Option is lost. // To workaround this, we use the same workaround as jackson-scala-module described here: Loading Loading @@ -491,13 +530,13 @@ class JsonSchemaGenerator resolvePropertyFormat(prop).foreach { format => setFormat(thisPropertyNode, format) setFormat(thisPropertyNode.main, format) } // Optionally add description Option(prop.getAnnotation(classOf[JsonSchemaDescription])).map { jsonSchemaDescription => thisPropertyNode.put("description", jsonSchemaDescription.value()) thisPropertyNode.meta.put("description", jsonSchemaDescription.value()) } // Optionally add title Loading @@ -510,7 +549,7 @@ class JsonSchemaGenerator } .map { title => thisPropertyNode.put("title", title) thisPropertyNode.meta.put("title", title) } } Loading
src/test/scala/com/kjetland/jackson/jsonSchema/JsonSchemaGeneratorTest.scala +105 −3 Original line number Diff line number Diff line package com.kjetland.jackson.jsonSchema import java.time.OffsetDateTime import java.util.TimeZone import java.util import java.util.{Optional, TimeZone} import com.fasterxml.jackson.annotation.{JsonSubTypes, JsonTypeInfo, JsonValue} import com.fasterxml.jackson.databind.annotation.JsonDeserialize Loading Loading @@ -40,8 +41,9 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers { val jsonSchemaGenerator = new JsonSchemaGenerator(_objectMapper, debug = true) val jsonSchemaGeneratorHTML5Date = new JsonSchemaGenerator(_objectMapper, debug = true, config = JsonSchemaConfig.html5EnabledSchema) val jsonSchemaGeneratorHTML5 = new JsonSchemaGenerator(_objectMapper, debug = true, config = JsonSchemaConfig.html5EnabledSchema) val jsonSchemaGeneratorScala = new JsonSchemaGenerator(_objectMapperScala, debug = true) val jsonSchemaGeneratorScalaHTML5 = new JsonSchemaGenerator(_objectMapperScala, debug = true, config = JsonSchemaConfig.html5EnabledSchema) val testData = new TestData{} Loading Loading @@ -334,6 +336,27 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers { } test("java using option") { val jsonNode = assertToFromJson(jsonSchemaGenerator, testData.pojoUsingOptionalJava) val schema = generateAndValidateSchema(jsonSchemaGenerator, testData.pojoUsingOptionalJava.getClass, Some(jsonNode)) assert(schema.at("/properties/_string/type").asText() == "string") assert(!getRequiredList(schema).contains("_string")) // Should allow null by default assert(schema.at("/properties/_integer/type").asText() == "integer") assert(!getRequiredList(schema).contains("_integer")) // Should allow null by default val child1 = getNodeViaRefs(schema, schema.at("/properties/child1"), "Child1") assertJsonSubTypesInfo(child1, "type", "child1") assert( child1.at("/properties/parentString/type").asText() == "string" ) assert( child1.at("/properties/child1String/type").asText() == "string" ) assert(schema.at("/properties/optionalList/type").asText() == "array") assert(schema.at("/properties/optionalList/items/$ref").asText() == "#/definitions/ClassNotExtendingAnything") } test("custom serializer not overriding JsonSerializer.acceptJsonFormatVisitor") { val jsonNode = assertToFromJson(jsonSchemaGenerator, testData.pojoWithCustomSerializer) Loading Loading @@ -414,7 +437,7 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers { test("pojo Using Custom Annotations") { val jsonNode = assertToFromJson(jsonSchemaGenerator, testData.pojoUsingFormat) val schema = generateAndValidateSchema(jsonSchemaGenerator, testData.pojoUsingFormat.getClass, Some(jsonNode)) val schemaHTML5Date = generateAndValidateSchema(jsonSchemaGeneratorHTML5Date, testData.pojoUsingFormat.getClass, Some(jsonNode)) val schemaHTML5Date = generateAndValidateSchema(jsonSchemaGeneratorHTML5, testData.pojoUsingFormat.getClass, Some(jsonNode)) assert( schema.at("/format").asText() == "grid") assert( schema.at("/description").asText() == "This is our pojo") Loading Loading @@ -443,6 +466,83 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers { } test("scala using option with HTML5") { val jsonNode = assertToFromJson(jsonSchemaGeneratorScalaHTML5, testData.pojoUsingOptionScala) val schema = generateAndValidateSchema(jsonSchemaGeneratorScalaHTML5, testData.pojoUsingOptionScala.getClass, Some(jsonNode)) assert(schema.at("/properties/_string/oneOf/0/type").asText() == "null") assert(schema.at("/properties/_string/oneOf/0/title").asText() == "Not included") assert(schema.at("/properties/_string/oneOf/1/type").asText() == "string") assert(!getRequiredList(schema).contains("_string")) // Should allow null by default assert(schema.at("/properties/_string/title").asText() == "_string") assert(schema.at("/properties/_integer/oneOf/0/type").asText() == "null") assert(schema.at("/properties/_integer/oneOf/0/title").asText() == "Not included") assert(schema.at("/properties/_integer/oneOf/1/type").asText() == "integer") assert(!getRequiredList(schema).contains("_integer")) // Should allow null by default assert(schema.at("/properties/_integer/title").asText() == "_integer") assert(schema.at("/properties/_boolean/oneOf/0/type").asText() == "null") assert(schema.at("/properties/_boolean/oneOf/0/title").asText() == "Not included") assert(schema.at("/properties/_boolean/oneOf/1/type").asText() == "boolean") assert(!getRequiredList(schema).contains("_boolean")) // Should allow null by default assert(schema.at("/properties/_boolean/title").asText() == "_boolean") assert(schema.at("/properties/_double/oneOf/0/type").asText() == "null") assert(schema.at("/properties/_double/oneOf/0/title").asText() == "Not included") assert(schema.at("/properties/_double/oneOf/1/type").asText() == "number") assert(!getRequiredList(schema).contains("_double")) // Should allow null by default assert(schema.at("/properties/_double/title").asText() == "_double") assert(schema.at("/properties/child1/oneOf/0/type").asText() == "null") assert(schema.at("/properties/child1/oneOf/0/title").asText() == "Not included") val child1 = getNodeViaRefs(schema, schema.at("/properties/child1/oneOf/1"), "Child1Scala") assert(schema.at("/properties/child1/title").asText() == "Child 1") assertJsonSubTypesInfo(child1, "type", "child1") assert( child1.at("/properties/parentString/type").asText() == "string" ) assert( child1.at("/properties/child1String/type").asText() == "string" ) assert(schema.at("/properties/optionalList/oneOf/0/type").asText() == "null") assert(schema.at("/properties/optionalList/oneOf/0/title").asText() == "Not included") assert(schema.at("/properties/optionalList/oneOf/1/type").asText() == "array") assert(schema.at("/properties/optionalList/oneOf/1/items/$ref").asText() == "#/definitions/ClassNotExtendingAnythingScala") assert(schema.at("/properties/optionalList/title").asText() == "Optional List") } test("java using optional with HTML5") { val jsonNode = assertToFromJson(jsonSchemaGeneratorHTML5, testData.pojoUsingOptionalJava) val schema = generateAndValidateSchema(jsonSchemaGeneratorHTML5, testData.pojoUsingOptionalJava.getClass, Some(jsonNode)) assert(schema.at("/properties/_string/oneOf/0/type").asText() == "null") assert(schema.at("/properties/_string/oneOf/0/title").asText() == "Not included") assert(schema.at("/properties/_string/oneOf/1/type").asText() == "string") assert(!getRequiredList(schema).contains("_string")) // Should allow null by default assert(schema.at("/properties/_string/title").asText() == "_string") assert(schema.at("/properties/_integer/oneOf/0/type").asText() == "null") assert(schema.at("/properties/_integer/oneOf/0/title").asText() == "Not included") assert(schema.at("/properties/_integer/oneOf/1/type").asText() == "integer") assert(!getRequiredList(schema).contains("_integer")) // Should allow null by default assert(schema.at("/properties/_integer/title").asText() == "_integer") assert(schema.at("/properties/child1/oneOf/0/type").asText() == "null") assert(schema.at("/properties/child1/oneOf/0/title").asText() == "Not included") val child1 = getNodeViaRefs(schema, schema.at("/properties/child1/oneOf/1"), "Child1") assert(schema.at("/properties/child1/title").asText() == "Child 1") assertJsonSubTypesInfo(child1, "type", "child1") assert( child1.at("/properties/parentString/type").asText() == "string" ) assert( child1.at("/properties/child1String/type").asText() == "string" ) assert(schema.at("/properties/optionalList/oneOf/0/type").asText() == "null") assert(schema.at("/properties/optionalList/oneOf/0/title").asText() == "Not included") assert(schema.at("/properties/optionalList/oneOf/1/type").asText() == "array") assert(schema.at("/properties/optionalList/oneOf/1/items/$ref").asText() == "#/definitions/ClassNotExtendingAnything") assert(schema.at("/properties/optionalList/title").asText() == "Optional List") } } trait TestData { Loading Loading @@ -489,6 +589,8 @@ trait TestData { val pojoUsingOptionScala = PojoUsingOptionScala(Some("s1"), Some(1), Some(true), Some(0.1), Some(child1Scala), Some(List(classNotExtendingAnythingScala))) val pojoUsingOptionalJava = new PojoUsingOptionalJava(Optional.of("s"), Optional.of(1), Optional.of(child1), Optional.of(util.Arrays.asList(classNotExtendingAnything))) val pojoWithCustomSerializer = { val p = new PojoWithCustomSerializer p.myString = "xxx" Loading
src/test/scala/com/kjetland/jackson/jsonSchema/testData/PojoUsingOptionalJava.java 0 → 100644 +55 −0 Original line number Diff line number Diff line package com.kjetland.jackson.jsonSchema.testData; import java.util.List; import java.util.Optional; public class PojoUsingOptionalJava { public Optional<String> _string; public Optional<Integer> _integer; public Optional<Child1> child1; public Optional<List<ClassNotExtendingAnything>> optionalList; public PojoUsingOptionalJava() { } public PojoUsingOptionalJava(Optional<String> _string, Optional<Integer> _integer, Optional<Child1> child1, Optional<List<ClassNotExtendingAnything>> optionalList) { this._string = _string; this._integer = _integer; this.child1 = child1; this.optionalList = optionalList; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; PojoUsingOptionalJava that = (PojoUsingOptionalJava) o; 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 (child1 != null ? !child1.equals(that.child1) : that.child1 != null) return false; return optionalList != null ? optionalList.equals(that.optionalList) : that.optionalList == null; } @Override public int hashCode() { int result = _string != null ? _string.hashCode() : 0; result = 31 * result + (_integer != null ? _integer.hashCode() : 0); result = 31 * result + (child1 != null ? child1.hashCode() : 0); result = 31 * result + (optionalList != null ? optionalList.hashCode() : 0); return result; } @Override public String toString() { return "PojoUsingOptionalJava{" + "_string=" + _string + ", _integer=" + _integer + ", child1=" + child1 + ", optionalList=" + optionalList + '}'; } }