diff --git a/src/main/scala/com/kjetland/jackson/jsonSchema/JsonSchemaGenerator.scala b/src/main/scala/com/kjetland/jackson/jsonSchema/JsonSchemaGenerator.scala index 3632d09..97c1b20 100755 --- a/src/main/scala/com/kjetland/jackson/jsonSchema/JsonSchemaGenerator.scala +++ b/src/main/scala/com/kjetland/jackson/jsonSchema/JsonSchemaGenerator.scala @@ -443,34 +443,36 @@ class JsonSchemaGenerator DefinitionInfo(if(_type == w.typeInProgress) None else Some(ref), objectDefinitionBuilder(w.nodeInProgress)) } - case None => - - // new one - must build it - var retryCount = 0 - val definitionName = getDefinitionName(_type) - var shortRef = definitionName - var longRef = "#/definitions/" + definitionName - while( class2Ref.values.toList.contains(longRef)) { - retryCount = retryCount + 1 - shortRef = definitionName + "_" + retryCount - longRef = "#/definitions/" + definitionName + "_" + retryCount - } - class2Ref = class2Ref + (_type -> longRef) + case None => createDefinition(_type, objectDefinitionBuilder) + } + } - // create definition - val node = JsonNodeFactory.instance.objectNode() + def createDefinition(_type: JavaType, objectDefinitionBuilder: ObjectNode => Option[JsonObjectFormatVisitor]) = { + // new one - must build it + var retryCount = 0 + val definitionName = getDefinitionName(_type) + var shortRef = definitionName + var longRef = "#/definitions/" + definitionName + while (class2Ref.values.toList.contains(longRef)) { + retryCount = retryCount + 1 + shortRef = definitionName + "_" + retryCount + longRef = "#/definitions/" + definitionName + "_" + retryCount + } + class2Ref = class2Ref + (_type -> longRef) - // When processing polymorphism, we might get multiple recursive calls to getOrCreateDefinition - this is a wau to combine them - workInProgress = Some(WorkInProgress(_type, node)) + // create definition + val node = JsonNodeFactory.instance.objectNode() - definitionsNode.set(shortRef, node) + // When processing polymorphism, we might get multiple recursive calls to getOrCreateDefinition - this is a wau to combine them + workInProgress = Some(WorkInProgress(_type, node)) - val jsonObjectFormatVisitor = objectDefinitionBuilder.apply(node) + definitionsNode.set(shortRef, node) - workInProgress = None + val jsonObjectFormatVisitor = objectDefinitionBuilder.apply(node) - DefinitionInfo(Some(longRef), jsonObjectFormatVisitor) - } + workInProgress = None + + DefinitionInfo(Some(longRef), jsonObjectFormatVisitor) } def getFinalDefinitionsNode():Option[ObjectNode] = { @@ -870,7 +872,7 @@ class JsonSchemaGenerator private def extractPolymorphismInfo(_type:JavaType):Option[PolymorphismInfo] = { val maybeBaseType = ClassUtil.findSuperTypes(_type, null, false).asScala.find { cl => cl.getRawClass.isAnnotationPresent(classOf[JsonTypeInfo] ) - } orElse Option(_type.getSuperClass) + } orElse Option(_type) maybeBaseType.flatMap { baseType => val serializerOrNull = objectMapper @@ -975,350 +977,358 @@ class JsonSchemaGenerator } override def expectObjectFormat(_type: JavaType) = { - val subTypes: List[Class[_]] = extractSubTypes(_type) // Check if we have subtypes if (subTypes.nonEmpty) { - // We have subtypes - //l(s"polymorphism - subTypes: $subTypes") + generateSchemaWithSubtypes(_type, subTypes) + } else { + generateSchemaWithoutSubtypes(_type) + } + } - val anyOfArrayNode = JsonNodeFactory.instance.arrayNode() - node.set("oneOf", anyOfArrayNode) + def objectBuilderFor(_type: JavaType): ObjectNode => Option[JsonObjectFormatVisitor] = { + thisObjectNode:ObjectNode => - subTypes.foreach { - subType: Class[_] => - l(s"polymorphism - subType: $subType") - val definitionInfo: DefinitionInfo = definitionsHandler.getOrCreateDefinition(objectMapper.constructType(subType)){ - objectNode => + thisObjectNode.put("type", "object") + thisObjectNode.put("additionalProperties", !config.failOnUnknownProperties) - val childVisitor = createChild(objectNode, currentProperty = None) - objectMapper.acceptJsonFormatVisitor(tryToReMapType(subType), childVisitor) + // If class is annotated with JsonSchemaFormat, we should add it + val ac = AnnotatedClassResolver.resolve(objectMapper.getDeserializationConfig, _type, objectMapper.getDeserializationConfig) + resolvePropertyFormat(_type, objectMapper).foreach { + format => + setFormat(thisObjectNode, format) + } - None - } + // If class is annotated with JsonSchemaDescription, we should add it + Option(ac.getAnnotations.get(classOf[JsonSchemaDescription])).map(_.value()) + .orElse(Option(ac.getAnnotations.get(classOf[JsonPropertyDescription])).map(_.value)) + .foreach { + description: String => + thisObjectNode.put("description", description) + } - val thisOneOfNode = JsonNodeFactory.instance.objectNode() - thisOneOfNode.put("$ref", definitionInfo.ref.get) + // If class is annotated with JsonSchemaTitle, we should add it + Option(ac.getAnnotations.get(classOf[JsonSchemaTitle])).map(_.value()).foreach { + title => + thisObjectNode.put("title", title) + } - // If class is annotated with JsonSchemaTitle, we should add it - Option(subType.getDeclaredAnnotation(classOf[JsonSchemaTitle])).map(_.value()).foreach { - title => - thisOneOfNode.put("title", title) + // If class is annotated with JsonSchemaOptions, we should add it + Option(ac.getAnnotations.get(classOf[JsonSchemaOptions])).map(_.items()).foreach { + items => + val optionsNode = getOptionsNode(thisObjectNode) + items.foreach { + item => + optionsNode.put(item.name, item.value) } - - anyOfArrayNode.add(thisOneOfNode) - } - null // Returning null to stop jackson from visiting this object since we have done it manually - - } else { - // We do not have subtypes + // Optionally add JsonSchemaInject to top-level + val renderProps:Boolean = selectAnnotation(ac, classOf[JsonSchemaInject]).map { + a => + val merged = injectFromJsonSchemaInject(a, thisObjectNode) + merged == true // Continue to render props since we merged injection + }.getOrElse( true ) // nothing injected => of course we should render props + + if (renderProps) { + + val propertiesNode = getOrCreateObjectChild(thisObjectNode, "properties") + + extractPolymorphismInfo(_type).foreach { + 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 + // This is done to make sure the json generated from the schema using this oneOf + // contains the correct "type info" + val enumValuesNode = JsonNodeFactory.instance.arrayNode() + enumValuesNode.add(pi.subTypeName) + + val enumObjectNode = getOrCreateObjectChild(propertiesNode, pi.typePropertyName) + enumObjectNode.put("type", "string") + enumObjectNode.set("enum", enumValuesNode) + enumObjectNode.put("default", pi.subTypeName) + + if (config.hidePolymorphismTypeProperty) { + // Make sure the editor hides this polymorphism-specific property + val optionsNode = JsonNodeFactory.instance.objectNode() + enumObjectNode.set("options", optionsNode) + optionsNode.put("hidden", true) + } - val objectBuilder:ObjectNode => Option[JsonObjectFormatVisitor] = { - thisObjectNode:ObjectNode => + var found = false + val reqArrayNode = getRequiredArrayNode(thisObjectNode) + val iterator = reqArrayNode.elements() + while(iterator.hasNext) { + if(iterator.next().equals(TextNode.valueOf(pi.typePropertyName))) { + found = true + } + } + if(!found) { + reqArrayNode.add(pi.typePropertyName) + } - thisObjectNode.put("type", "object") - thisObjectNode.put("additionalProperties", !config.failOnUnknownProperties) + if (config.useMultipleEditorSelectViaProperty) { + // https://github.com/jdorn/json-editor/issues/709 + // Generate info to help generated editor to select correct oneOf-type + // when populating the gui/schema with existing data + val objectOptionsNode = getOrCreateObjectChild(thisObjectNode, "options") + val multipleEditorSelectViaPropertyNode = getOrCreateObjectChild(objectOptionsNode, "multiple_editor_select_via_property") + multipleEditorSelectViaPropertyNode.put("property", pi.typePropertyName) + multipleEditorSelectViaPropertyNode.put("value", pi.subTypeName) + () + } - // If class is annotated with JsonSchemaFormat, we should add it - val ac = AnnotatedClassResolver.resolve(objectMapper.getDeserializationConfig, _type, objectMapper.getDeserializationConfig) - resolvePropertyFormat(_type, objectMapper).foreach { - format => - setFormat(thisObjectNode, format) - } + } - // If class is annotated with JsonSchemaDescription, we should add it - Option(ac.getAnnotations.get(classOf[JsonSchemaDescription])).map(_.value()) - .orElse(Option(ac.getAnnotations.get(classOf[JsonPropertyDescription])).map(_.value)) - .foreach { - description: String => - thisObjectNode.put("description", description) - } + Some(new JsonObjectFormatVisitor with MySerializerProvider { - // If class is annotated with JsonSchemaTitle, we should add it - Option(ac.getAnnotations.get(classOf[JsonSchemaTitle])).map(_.value()).foreach { - title => - thisObjectNode.put("title", title) - } - // If class is annotated with JsonSchemaOptions, we should add it - Option(ac.getAnnotations.get(classOf[JsonSchemaOptions])).map(_.items()).foreach { - items => - val optionsNode = getOptionsNode(thisObjectNode) - items.foreach { - item => - optionsNode.put(item.name, item.value) - } - } + // Used when rendering schema using propertyOrdering as specified here: + // https://github.com/jdorn/json-editor#property-ordering + var nextPropertyOrderIndex = 1 - // Optionally add JsonSchemaInject to top-level - val renderProps:Boolean = selectAnnotation(ac, classOf[JsonSchemaInject]).map { - a => - val merged = injectFromJsonSchemaInject(a, thisObjectNode) - merged == true // Continue to render props since we merged injection - }.getOrElse( true ) // nothing injected => of course we should render props - - if (renderProps) { - - val propertiesNode = getOrCreateObjectChild(thisObjectNode, "properties") - - extractPolymorphismInfo(_type).foreach { - 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 - // This is done to make sure the json generated from the schema using this oneOf - // contains the correct "type info" - val enumValuesNode = JsonNodeFactory.instance.arrayNode() - enumValuesNode.add(pi.subTypeName) - - val enumObjectNode = getOrCreateObjectChild(propertiesNode, pi.typePropertyName) - enumObjectNode.put("type", "string") - enumObjectNode.set("enum", enumValuesNode) - enumObjectNode.put("default", pi.subTypeName) - - if (config.hidePolymorphismTypeProperty) { - // Make sure the editor hides this polymorphism-specific property - val optionsNode = JsonNodeFactory.instance.objectNode() - enumObjectNode.set("options", optionsNode) - optionsNode.put("hidden", true) - } + def myPropertyHandler(propertyName: String, propertyType: JavaType, prop: Option[BeanProperty], jsonPropertyRequired: Boolean): Unit = { + l(s"JsonObjectFormatVisitor - ${propertyName}: ${propertyType}") - var found = false - val reqArrayNode = getRequiredArrayNode(thisObjectNode) - val iterator = reqArrayNode.elements() - while(iterator.hasNext) { - if(iterator.next().equals(TextNode.valueOf(pi.typePropertyName))) { - found = true - } - } - if(!found) { - reqArrayNode.add(pi.typePropertyName) - } + if (propertiesNode.get(propertyName) != null) { + if (!config.disableWarnings) { + log.warn(s"Ignoring property '$propertyName' in $propertyType since it has already been added, probably as type-property using polymorphism") + } + return + } - if (config.useMultipleEditorSelectViaProperty) { - // https://github.com/jdorn/json-editor/issues/709 - // Generate info to help generated editor to select correct oneOf-type - // when populating the gui/schema with existing data - val objectOptionsNode = getOrCreateObjectChild(thisObjectNode, "options") - val multipleEditorSelectViaPropertyNode = getOrCreateObjectChild(objectOptionsNode, "multiple_editor_select_via_property") - multipleEditorSelectViaPropertyNode.put("property", pi.typePropertyName) - multipleEditorSelectViaPropertyNode.put("value", pi.subTypeName) - () - } + // Need to check for Option/Optional-special-case before we know what node to use here. + case class PropertyNode(main: ObjectNode, meta: ObjectNode) + // Check if we should set this property as required. Primitive types MUST have a value, as does anything + // with a @JsonProperty that has "required" set to true. Lastly, various javax.validation annotations also + // make this required. + val requiredProperty: Boolean = if (propertyType.getRawClass.isPrimitive || jsonPropertyRequired || validationAnnotationRequired(prop)) { + true + } else { + false } - Some(new JsonObjectFormatVisitor with MySerializerProvider { + val thisPropertyNode: PropertyNode = { + val thisPropertyNode = JsonNodeFactory.instance.objectNode() + propertiesNode.set(propertyName, thisPropertyNode) + if (config.usePropertyOrdering) { + thisPropertyNode.put("propertyOrder", nextPropertyOrderIndex) + nextPropertyOrderIndex = nextPropertyOrderIndex + 1 + } - // Used when rendering schema using propertyOrdering as specified here: - // https://github.com/jdorn/json-editor#property-ordering - var nextPropertyOrderIndex = 1 + // Figure out if the type is considered optional by either Java or Scala. + val optionalType: Boolean = classOf[Option[_]].isAssignableFrom(propertyType.getRawClass) || + classOf[Optional[_]].isAssignableFrom(propertyType.getRawClass) + + // If the property is not required, and our configuration allows it, let's go ahead and mark the type as nullable. + if (!requiredProperty && ((config.useOneOfForOption && optionalType) || + (config.useOneOfForNullables && !optionalType))) { + // We support this type being null, insert a oneOf consisting of a sentinel "null" and the real type. + val oneOfArray = JsonNodeFactory.instance.arrayNode() + thisPropertyNode.set("oneOf", oneOfArray) + + // Create our sentinel "null" value for the case no value is provided. + val oneOfNull = JsonNodeFactory.instance.objectNode() + oneOfNull.put("type", "null") + oneOfNull.put("title", "Not included") + oneOfArray.add(oneOfNull) + + // If our nullable/optional type has a value, it'll be this. + 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 { + // Our type must not be null: primitives, @NotNull annotations, @JsonProperty annotations marked required etc. + PropertyNode(thisPropertyNode, thisPropertyNode) + } + } - def myPropertyHandler(propertyName: String, propertyType: JavaType, prop: Option[BeanProperty], jsonPropertyRequired: Boolean): Unit = { - l(s"JsonObjectFormatVisitor - ${propertyName}: ${propertyType}") + // Continue processing this property + val childVisitor = createChild(thisPropertyNode.main, currentProperty = prop) - if (propertiesNode.get(propertyName) != null) { - if (!config.disableWarnings) { - log.warn(s"Ignoring property '$propertyName' in $propertyType since it has already been added, probably as type-property using polymorphism") - } - return - } - // Need to check for Option/Optional-special-case before we know what node to use here. - case class PropertyNode(main: ObjectNode, meta: ObjectNode) + // Push current work in progress since we're about to start working on a new class + definitionsHandler.pushWorkInProgress() - // Check if we should set this property as required. Primitive types MUST have a value, as does anything - // with a @JsonProperty that has "required" set to true. Lastly, various javax.validation annotations also - // make this required. - val requiredProperty: Boolean = if (propertyType.getRawClass.isPrimitive || jsonPropertyRequired || validationAnnotationRequired(prop)) { - true - } else { - false - } + if ((classOf[Option[_]].isAssignableFrom(propertyType.getRawClass) || classOf[Optional[_]].isAssignableFrom(propertyType.getRawClass)) && propertyType.containedTypeCount() >= 1) { - val thisPropertyNode: PropertyNode = { - val thisPropertyNode = JsonNodeFactory.instance.objectNode() - propertiesNode.set(propertyName, thisPropertyNode) - - if (config.usePropertyOrdering) { - thisPropertyNode.put("propertyOrder", nextPropertyOrderIndex) - nextPropertyOrderIndex = nextPropertyOrderIndex + 1 - } - - // Figure out if the type is considered optional by either Java or Scala. - val optionalType: Boolean = classOf[Option[_]].isAssignableFrom(propertyType.getRawClass) || - classOf[Optional[_]].isAssignableFrom(propertyType.getRawClass) - - // If the property is not required, and our configuration allows it, let's go ahead and mark the type as nullable. - if (!requiredProperty && ((config.useOneOfForOption && optionalType) || - (config.useOneOfForNullables && !optionalType))) { - // We support this type being null, insert a oneOf consisting of a sentinel "null" and the real type. - val oneOfArray = JsonNodeFactory.instance.arrayNode() - thisPropertyNode.set("oneOf", oneOfArray) - - // Create our sentinel "null" value for the case no value is provided. - val oneOfNull = JsonNodeFactory.instance.objectNode() - oneOfNull.put("type", "null") - oneOfNull.put("title", "Not included") - oneOfArray.add(oneOfNull) - - // If our nullable/optional type has a value, it'll be this. - 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 { - // Our type must not be null: primitives, @NotNull annotations, @JsonProperty annotations marked required etc. - PropertyNode(thisPropertyNode, thisPropertyNode) - } - } + // 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: + // https://github.com/FasterXML/jackson-module-scala/wiki/FAQ#deserializing-optionint-and-other-primitive-challenges - // Continue processing this property - val childVisitor = createChild(thisPropertyNode.main, currentProperty = prop) + val optionType: JavaType = resolveType(propertyType, prop, objectMapper) + objectMapper.acceptJsonFormatVisitor(tryToReMapType(optionType), childVisitor) - // Push current work in progress since we're about to start working on a new class - definitionsHandler.pushWorkInProgress() + } else { + objectMapper.acceptJsonFormatVisitor(tryToReMapType(propertyType), childVisitor) + } - if ((classOf[Option[_]].isAssignableFrom(propertyType.getRawClass) || classOf[Optional[_]].isAssignableFrom(propertyType.getRawClass)) && propertyType.containedTypeCount() >= 1) { + // Pop back the work we were working on.. + definitionsHandler.popworkInProgress() - // 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: - // https://github.com/FasterXML/jackson-module-scala/wiki/FAQ#deserializing-optionint-and-other-primitive-challenges + prop.flatMap(resolvePropertyFormat(_)).foreach { + format => + setFormat(thisPropertyNode.main, format) + } - val optionType: JavaType = resolveType(propertyType, prop, objectMapper) + // Optionally add description + prop.flatMap { + p: BeanProperty => + Option(p.getAnnotation(classOf[JsonSchemaDescription])).map(_.value()) + .orElse(Option(p.getAnnotation(classOf[JsonPropertyDescription])).map(_.value())) + }.map { + description => + thisPropertyNode.meta.put("description", description) + } - objectMapper.acceptJsonFormatVisitor(tryToReMapType(optionType), childVisitor) + // If this property is required, add it to our array of required properties. + if (requiredProperty) { + getRequiredArrayNode(thisObjectNode).add(propertyName) + } - } else { - objectMapper.acceptJsonFormatVisitor(tryToReMapType(propertyType), childVisitor) - } + // Optionally add title + prop.flatMap { + p: BeanProperty => + Option(p.getAnnotation(classOf[JsonSchemaTitle])) + }.map(_.value()) + .orElse { + if (config.autoGenerateTitleForProperties) { + // We should generate 'pretty-name' based on propertyName + Some(generateTitleFromPropertyName(propertyName)) + } else None + } + .map { + title => + thisPropertyNode.meta.put("title", title) + } - // Pop back the work we were working on.. - definitionsHandler.popworkInProgress() + // Optionally add options + prop.flatMap { + p: BeanProperty => + Option(p.getAnnotation(classOf[JsonSchemaOptions])) + }.map(_.items()).foreach { + items => + val optionsNode = getOptionsNode(thisPropertyNode.meta) + items.foreach { + item => + optionsNode.put(item.name, item.value) - prop.flatMap(resolvePropertyFormat(_)).foreach { - format => - setFormat(thisPropertyNode.main, format) } + } - // Optionally add description - prop.flatMap { - p: BeanProperty => - Option(p.getAnnotation(classOf[JsonSchemaDescription])).map(_.value()) - .orElse(Option(p.getAnnotation(classOf[JsonPropertyDescription])).map(_.value())) - }.map { - description => - thisPropertyNode.meta.put("description", description) + // Optionally add JsonSchemaInject + prop.flatMap { + p: BeanProperty => + selectAnnotation(p, classOf[JsonSchemaInject]) match { + case Some(a) => Some(a) + case None => + // Try to look at the class itself -- Looks like this is the only way to find it if the type is Enum + Option(p.getType.getRawClass.getAnnotation(classOf[JsonSchemaInject])) + .filter( annotationIsApplicable(_) ) } + }.foreach { + a => + injectFromJsonSchemaInject(a, thisPropertyNode.meta) + } + } - // If this property is required, add it to our array of required properties. - if (requiredProperty) { - getRequiredArrayNode(thisObjectNode).add(propertyName) - } + override def optionalProperty(prop: BeanProperty): Unit = { + l(s"JsonObjectFormatVisitor.optionalProperty: prop:${prop}") + myPropertyHandler(prop.getName, prop.getType, Some(prop), jsonPropertyRequired = false) + } - // Optionally add title - prop.flatMap { - p: BeanProperty => - Option(p.getAnnotation(classOf[JsonSchemaTitle])) - }.map(_.value()) - .orElse { - if (config.autoGenerateTitleForProperties) { - // We should generate 'pretty-name' based on propertyName - Some(generateTitleFromPropertyName(propertyName)) - } else None - } - .map { - title => - thisPropertyNode.meta.put("title", title) - } - - // Optionally add options - prop.flatMap { - p: BeanProperty => - Option(p.getAnnotation(classOf[JsonSchemaOptions])) - }.map(_.items()).foreach { - items => - val optionsNode = getOptionsNode(thisPropertyNode.meta) - items.foreach { - item => - optionsNode.put(item.name, item.value) - - } - } + override def optionalProperty(name: String, handler: JsonFormatVisitable, propertyTypeHint: JavaType): Unit = { + l(s"JsonObjectFormatVisitor.optionalProperty: name:${name} handler:${handler} propertyTypeHint:${propertyTypeHint}") + myPropertyHandler(name, propertyTypeHint, None, jsonPropertyRequired = false) + } - // Optionally add JsonSchemaInject - prop.flatMap { - p: BeanProperty => - selectAnnotation(p, classOf[JsonSchemaInject]) match { - case Some(a) => Some(a) - case None => - // Try to look at the class itself -- Looks like this is the only way to find it if the type is Enum - Option(p.getType.getRawClass.getAnnotation(classOf[JsonSchemaInject])) - .filter( annotationIsApplicable(_) ) - } - }.foreach { - a => - injectFromJsonSchemaInject(a, thisPropertyNode.meta) - } - } + override def property(prop: BeanProperty): Unit = { + l(s"JsonObjectFormatVisitor.property: prop:${prop}") + myPropertyHandler(prop.getName, prop.getType, Some(prop), jsonPropertyRequired = true) + } - override def optionalProperty(prop: BeanProperty): Unit = { - l(s"JsonObjectFormatVisitor.optionalProperty: prop:${prop}") - myPropertyHandler(prop.getName, prop.getType, Some(prop), jsonPropertyRequired = false) - } + override def property(name: String, handler: JsonFormatVisitable, propertyTypeHint: JavaType): Unit = { + l(s"JsonObjectFormatVisitor.property: name:${name} handler:${handler} propertyTypeHint:${propertyTypeHint}") + myPropertyHandler(name, propertyTypeHint, None, jsonPropertyRequired = true) + } - override def optionalProperty(name: String, handler: JsonFormatVisitable, propertyTypeHint: JavaType): Unit = { - l(s"JsonObjectFormatVisitor.optionalProperty: name:${name} handler:${handler} propertyTypeHint:${propertyTypeHint}") - myPropertyHandler(name, propertyTypeHint, None, jsonPropertyRequired = false) - } + // Checks to see if a javax.validation field that makes our field required is present. + private def validationAnnotationRequired(prop: Option[BeanProperty]): Boolean = { + prop.exists(p => selectAnnotation(p, classOf[NotNull]).isDefined || selectAnnotation(p, classOf[NotBlank]).isDefined || selectAnnotation(p, classOf[NotEmpty]).isDefined) + } + }) + } else None + } - override def property(prop: BeanProperty): Unit = { - l(s"JsonObjectFormatVisitor.property: prop:${prop}") - myPropertyHandler(prop.getName, prop.getType, Some(prop), jsonPropertyRequired = true) - } + private def generateSchemaWithoutSubtypes(_type: JavaType) = { + val objectBuilder:ObjectNode => Option[JsonObjectFormatVisitor] = objectBuilderFor(_type) - override def property(name: String, handler: JsonFormatVisitable, propertyTypeHint: JavaType): Unit = { - l(s"JsonObjectFormatVisitor.property: name:${name} handler:${handler} propertyTypeHint:${propertyTypeHint}") - myPropertyHandler(name, propertyTypeHint, None, jsonPropertyRequired = true) - } + if ( level == 0) { + // This is the first level - we must not use definitions + objectBuilder(node).orNull + } else { + val definitionInfo: DefinitionInfo = definitionsHandler.getOrCreateDefinition(_type)(objectBuilder) - // Checks to see if a javax.validation field that makes our field required is present. - private def validationAnnotationRequired(prop: Option[BeanProperty]): Boolean = { - prop.exists(p => selectAnnotation(p, classOf[NotNull]).isDefined || selectAnnotation(p, classOf[NotBlank]).isDefined || selectAnnotation(p, classOf[NotEmpty]).isDefined) - } - }) - } else None + definitionInfo.ref.foreach { + r => + // Must add ref to def at "this location" + node.put("$ref", r) } - if ( level == 0) { - // This is the first level - we must not use definitions - objectBuilder(node).orNull - } else { - val definitionInfo: DefinitionInfo = definitionsHandler.getOrCreateDefinition(_type)(objectBuilder) + definitionInfo.jsonObjectFormatVisitor.orNull + } + } - definitionInfo.ref.foreach { - r => - // Must add ref to def at "this location" - node.put("$ref", r) + private def generateSchemaWithSubtypes(_type: JavaType, subTypes: List[Class[_]]) = { + val anyOfArrayNode = JsonNodeFactory.instance.arrayNode() + node.set("anyOf", anyOfArrayNode) + + subTypes.foreach { + subType: Class[_] => + l(s"polymorphism - subType: $subType") + val definitionInfo: DefinitionInfo = definitionsHandler.getOrCreateDefinition(objectMapper.constructType(subType)) { + objectNode => + + val childVisitor = createChild(objectNode, currentProperty = None) + objectMapper.acceptJsonFormatVisitor(tryToReMapType(subType), childVisitor) + + None } - definitionInfo.jsonObjectFormatVisitor.orNull - } + val thisOneOfNode = JsonNodeFactory.instance.objectNode() + thisOneOfNode.put("$ref", definitionInfo.ref.get) + + // If class is annotated with JsonSchemaTitle, we should add it + Option(subType.getDeclaredAnnotation(classOf[JsonSchemaTitle])).map(_.value()).foreach { + title => + thisOneOfNode.put("title", title) + } + + anyOfArrayNode.add(thisOneOfNode) } - } + if (!_type.isAbstract) { + l(s"polymorphism - non abstract parent: ${_type}") + val info = definitionsHandler.createDefinition(_type, objectBuilderFor(_type)) + val thisOneOfNode = JsonNodeFactory.instance.objectNode() + thisOneOfNode.put("$ref", info.ref.get) + anyOfArrayNode.add(thisOneOfNode) + } + null // Returning null to stop jackson from visiting this object since we have done it manually + } } private def extractMinimalClassnameId(baseType: JavaType, child: JavaType) = { diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/JsonSchemaGeneratorTest.scala b/src/test/scala/com/kjetland/jackson/jsonSchema/JsonSchemaGeneratorTest.scala index 751f941..6310b95 100755 --- a/src/test/scala/com/kjetland/jackson/jsonSchema/JsonSchemaGeneratorTest.scala +++ b/src/test/scala/com/kjetland/jackson/jsonSchema/JsonSchemaGeneratorTest.scala @@ -24,6 +24,7 @@ import com.kjetland.jackson.jsonSchema.testData.polymorphism3.{Child31, Child32, import com.kjetland.jackson.jsonSchema.testData.polymorphism4.{Child41, Child42} import com.kjetland.jackson.jsonSchema.testData.polymorphism5.{Child51, Child52, Parent5} import com.kjetland.jackson.jsonSchema.testData.polymorphism6.{Child61, Parent6} +import com.kjetland.jackson.jsonSchema.testData.polymorphism7.{Child73, Parent7} import com.kjetland.jackson.jsonSchema.testDataScala._ import com.kjetland.jackson.jsonSchema.testData_issue_24.EntityWrapper import javax.validation.groups.Default @@ -329,8 +330,8 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers { assert(schema.at("/properties/pojoValue/type").asText() == "boolean") assertDefaultValues(schema) - assertChild1(schema, "/properties/child/oneOf") - assertChild2(schema, "/properties/child/oneOf") + assertChild1(schema, "/properties/child/anyOf") + assertChild2(schema, "/properties/child/anyOf") } // Java - html5 @@ -342,8 +343,8 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers { assert(schema.at("/properties/pojoValue/type").asText() == "boolean") assertDefaultValues(schema) - assertChild1(schema, "/properties/child/oneOf", html5Checks = true) - assertChild2(schema, "/properties/child/oneOf", html5Checks = true) + assertChild1(schema, "/properties/child/anyOf", html5Checks = true) + assertChild2(schema, "/properties/child/anyOf", html5Checks = true) } // Java - html5/nullable @@ -355,8 +356,8 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers { assertNullableType(schema, "/properties/pojoValue", "boolean") assertNullableDefaultValues(schema) - assertNullableChild1(schema, "/properties/child/oneOf/1/oneOf", html5Checks = true) - assertNullableChild2(schema, "/properties/child/oneOf/1/oneOf", html5Checks = true) + assertNullableChild1(schema, "/properties/child/oneOf/1/anyOf", html5Checks = true) + assertNullableChild2(schema, "/properties/child/oneOf/1/anyOf", html5Checks = true) } //Using fully-qualified class names @@ -368,8 +369,8 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers { assert(schema.at("/properties/pojoValue/type").asText() == "boolean") assertDefaultValues(schema) - assertChild1(schema, "/properties/child/oneOf", "com.kjetland.jackson.jsonSchema.testData.polymorphism1.Child1") - assertChild2(schema, "/properties/child/oneOf", "com.kjetland.jackson.jsonSchema.testData.polymorphism1.Child2") + assertChild1(schema, "/properties/child/anyOf", "com.kjetland.jackson.jsonSchema.testData.polymorphism1.Child1") + assertChild2(schema, "/properties/child/anyOf", "com.kjetland.jackson.jsonSchema.testData.polymorphism1.Child2") } // Using fully-qualified class names and nullable types @@ -381,8 +382,8 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers { assertNullableType(schema, "/properties/pojoValue", "boolean") assertNullableDefaultValues(schema) - assertNullableChild1(schema, "/properties/child/oneOf/1/oneOf", "com.kjetland.jackson.jsonSchema.testData.polymorphism1.Child1") - assertNullableChild2(schema, "/properties/child/oneOf/1/oneOf", "com.kjetland.jackson.jsonSchema.testData.polymorphism1.Child2") + assertNullableChild1(schema, "/properties/child/oneOf/1/anyOf", "com.kjetland.jackson.jsonSchema.testData.polymorphism1.Child1") + assertNullableChild2(schema, "/properties/child/oneOf/1/anyOf", "com.kjetland.jackson.jsonSchema.testData.polymorphism1.Child2") } // Scala @@ -394,8 +395,8 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers { assert(schema.at("/properties/pojoValue/type").asText() == "boolean") assertDefaultValues(schema) - assertChild1(schema, "/properties/child/oneOf", "Child1Scala") - assertChild2(schema, "/properties/child/oneOf", "Child2Scala") + assertChild1(schema, "/properties/child/anyOf", "Child1Scala") + assertChild2(schema, "/properties/child/anyOf", "Child2Scala") } } @@ -453,8 +454,8 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers { val schema = generateAndValidateSchema(jsonSchemaGenerator, classOf[Parent], Some(jsonNode)) - assertChild1(schema, "/oneOf") - assertChild2(schema, "/oneOf") + assertChild1(schema, "/anyOf") + assertChild2(schema, "/anyOf") } // Java + Nullables @@ -464,8 +465,8 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers { val schema = generateAndValidateSchema(jsonSchemaGenerator, classOf[Parent], Some(jsonNode)) - assertChild1(schema, "/oneOf") - assertChild2(schema, "/oneOf") + assertChild1(schema, "/anyOf") + assertChild2(schema, "/anyOf") } // Scala @@ -475,8 +476,8 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers { val schema = generateAndValidateSchema(jsonSchemaGeneratorScala, classOf[ParentScala], Some(jsonNode)) - assertChild1(schema, "/oneOf", "Child1Scala") - assertChild2(schema, "/oneOf", "Child2Scala") + assertChild1(schema, "/anyOf", "Child1Scala") + assertChild2(schema, "/anyOf", "Child2Scala") } } @@ -494,8 +495,8 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers { val schema = generateAndValidateSchema(g, classOf[Parent2], Some(jsonNode)) - assertChild1(schema, "/oneOf", "Child21", typeParamName = "clazz", typeName = "com.kjetland.jackson.jsonSchema.testData.polymorphism2.Child21") - assertChild2(schema, "/oneOf", "Child22", typeParamName = "clazz", typeName = "com.kjetland.jackson.jsonSchema.testData.polymorphism2.Child22") + assertChild1(schema, "/anyOf", "Child21", typeParamName = "clazz", typeName = "com.kjetland.jackson.jsonSchema.testData.polymorphism2.Child21") + assertChild2(schema, "/anyOf", "Child22", typeParamName = "clazz", typeName = "com.kjetland.jackson.jsonSchema.testData.polymorphism2.Child22") } } @@ -512,11 +513,11 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers { val schema = generateAndValidateSchema(g, classOf[Parent5], Some(jsonNode)) - assertChild1(schema, "/oneOf", "Child51", typeParamName = "clazz", typeName = ".Child51") - assertChild2(schema, "/oneOf", "Child52", typeParamName = "clazz", typeName = ".Child52") + assertChild1(schema, "/anyOf", "Child51", typeParamName = "clazz", typeName = ".Child51") + assertChild2(schema, "/anyOf", "Child52", typeParamName = "clazz", typeName = ".Child52") val embeddedTypeName = _objectMapper.valueToTree[ObjectNode](new Parent5.Child51InnerClass()).get("clazz").asText() - assertChild1(schema, "/oneOf", "Child51InnerClass", typeParamName = "clazz", typeName = embeddedTypeName) + assertChild1(schema, "/anyOf", "Child51InnerClass", typeParamName = "clazz", typeName = embeddedTypeName) } } @@ -532,8 +533,8 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers { val schema = generateAndValidateSchema(g, classOf[Parent6], Some(jsonNode)) - assertChild1(schema, "/oneOf", "Child61", typeParamName = "clazz", typeName = ".Child61") - assertChild2(schema, "/oneOf", "Child62", typeParamName = "clazz", typeName = ".Child62") + assertChild1(schema, "/anyOf", "Child61", typeParamName = "clazz", typeName = ".Child61") + assertChild2(schema, "/anyOf", "Child62", typeParamName = "clazz", typeName = ".Child62") } } @@ -547,8 +548,8 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers { val schema = generateAndValidateSchema(jsonSchemaGenerator, classOf[Parent3], Some(jsonNode)) - assertChild1(schema, "/oneOf", "Child31", typeName = "child31") - assertChild2(schema, "/oneOf", "Child32", typeName = "child32") + assertChild1(schema, "/anyOf", "Child31", typeName = "child31") + assertChild2(schema, "/anyOf", "Child32", typeName = "child32") } } @@ -568,6 +569,20 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers { } } + test("Generate schema for multiple levels of type hierarchy") { + + // Java + { + val config = JsonSchemaConfig.vanillaJsonSchemaDraft4 + val g = new JsonSchemaGenerator(_objectMapper, debug = true, config) + + val jsonNode = assertToFromJson(g, testData.child73) + assertToFromJson(g, testData.child73, classOf[Parent7]) + + val schema = generateAndValidateSchema(g, classOf[Parent7], Some(jsonNode)) + } + } + test("Generate schema for class containing generics with same base type but different type arguments") { { val config = JsonSchemaConfig.vanillaJsonSchemaDraft4 @@ -792,12 +807,12 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers { assert(schema.at("/properties/stringList/maxItems").asInt() == 10) assert(schema.at("/properties/polymorphismList/type").asText() == "array") - assertChild1(schema, "/properties/polymorphismList/items/oneOf", html5Checks = html5Checks) - assertChild2(schema, "/properties/polymorphismList/items/oneOf", html5Checks = html5Checks) + assertChild1(schema, "/properties/polymorphismList/items/anyOf", html5Checks = html5Checks) + assertChild2(schema, "/properties/polymorphismList/items/anyOf", html5Checks = html5Checks) assert(schema.at("/properties/polymorphismArray/type").asText() == "array") - assertChild1(schema, "/properties/polymorphismArray/items/oneOf", html5Checks = html5Checks) - assertChild2(schema, "/properties/polymorphismArray/items/oneOf", html5Checks = html5Checks) + assertChild1(schema, "/properties/polymorphismArray/items/anyOf", html5Checks = html5Checks) + assertChild2(schema, "/properties/polymorphismArray/items/anyOf", html5Checks = html5Checks) assert(schema.at("/properties/listOfListOfStrings/type").asText() == "array") assert(schema.at("/properties/listOfListOfStrings/items/type").asText() == "array") @@ -832,12 +847,12 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers { assert(schema.at("/properties/stringList/oneOf/1/items/type").asText() == "string") assertNullableType(schema, "/properties/polymorphismList", "array") - assertNullableChild1(schema, "/properties/polymorphismList/oneOf/1/items/oneOf") - assertNullableChild2(schema, "/properties/polymorphismList/oneOf/1/items/oneOf") + assertNullableChild1(schema, "/properties/polymorphismList/oneOf/1/items/anyOf") + assertNullableChild2(schema, "/properties/polymorphismList/oneOf/1/items/anyOf") assertNullableType(schema, "/properties/polymorphismArray", "array") - assertNullableChild1(schema, "/properties/polymorphismArray/oneOf/1/items/oneOf") - assertNullableChild2(schema, "/properties/polymorphismArray/oneOf/1/items/oneOf") + assertNullableChild1(schema, "/properties/polymorphismArray/oneOf/1/items/anyOf") + assertNullableChild2(schema, "/properties/polymorphismArray/oneOf/1/items/anyOf") assertNullableType(schema, "/properties/listOfListOfStrings", "array") assert(schema.at("/properties/listOfListOfStrings/oneOf/1/items/type").asText() == "array") @@ -893,8 +908,8 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers { assert(schema.at("/properties/string2String/additionalProperties/type").asText() == "string") assert(schema.at("/properties/string2PojoUsingJsonTypeInfo/type").asText() == "object") - assert(schema.at("/properties/string2PojoUsingJsonTypeInfo/additionalProperties/oneOf/0/$ref").asText() == "#/definitions/Child1") - assert(schema.at("/properties/string2PojoUsingJsonTypeInfo/additionalProperties/oneOf/1/$ref").asText() == "#/definitions/Child2") + assert(schema.at("/properties/string2PojoUsingJsonTypeInfo/additionalProperties/anyOf/0/$ref").asText() == "#/definitions/Child1") + assert(schema.at("/properties/string2PojoUsingJsonTypeInfo/additionalProperties/anyOf/1/$ref").asText() == "#/definitions/Child2") } // Try it with nullable types. @@ -909,8 +924,8 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers { assert(schema.at("/properties/string2String/oneOf/1/additionalProperties/type").asText() == "string") assertNullableType(schema, "/properties/string2PojoUsingJsonTypeInfo", "object") - assert(schema.at("/properties/string2PojoUsingJsonTypeInfo/oneOf/1/additionalProperties/oneOf/0/$ref").asText() == "#/definitions/Child1") - assert(schema.at("/properties/string2PojoUsingJsonTypeInfo/oneOf/1/additionalProperties/oneOf/1/$ref").asText() == "#/definitions/Child2") + assert(schema.at("/properties/string2PojoUsingJsonTypeInfo/oneOf/1/additionalProperties/anyOf/0/$ref").asText() == "#/definitions/Child1") + assert(schema.at("/properties/string2PojoUsingJsonTypeInfo/oneOf/1/additionalProperties/anyOf/1/$ref").asText() == "#/definitions/Child2") } } @@ -1404,8 +1419,8 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers { val schema = generateAndValidateSchema(jsonSchemaGenerator, classOf[MixinParent], Some(jsonNode)) - assertChild1(schema, "/oneOf", defName = "MixinChild1") - assertChild2(schema, "/oneOf", defName = "MixinChild2") + assertChild1(schema, "/anyOf", defName = "MixinChild1") + assertChild2(schema, "/anyOf", defName = "MixinChild2") } // Java + Nullable types @@ -1415,8 +1430,8 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers { val schema = generateAndValidateSchema(jsonSchemaGeneratorNullable, classOf[MixinParent], Some(jsonNode)) - assertNullableChild1(schema, "/oneOf", defName = "MixinChild1") - assertNullableChild2(schema, "/oneOf", defName = "MixinChild2") + assertNullableChild1(schema, "/anyOf", defName = "MixinChild1") + assertNullableChild2(schema, "/anyOf", defName = "MixinChild2") } } @@ -1425,10 +1440,10 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers { jsonSchemaGeneratorNullable.generateJsonSchema(classOf[EntityWrapper]) } - test("Polymorphism oneOf-ordering") { + test("Polymorphism anyOf-ordering") { val schema = generateAndValidateSchema(jsonSchemaGeneratorScalaHTML5, classOf[PolymorphismOrderingParentScala], None) - val oneOfList:List[String] = schema.at("/oneOf").asInstanceOf[ArrayNode].iterator().asScala.toList.map(_.at("/$ref").asText) - assert(List("#/definitions/PolymorphismOrderingChild3", "#/definitions/PolymorphismOrderingChild1", "#/definitions/PolymorphismOrderingChild4", "#/definitions/PolymorphismOrderingChild2") == oneOfList) + val anyOfList:List[String] = schema.at("/anyOf").asInstanceOf[ArrayNode].iterator().asScala.toList.map(_.at("/$ref").asText) + assert(List("#/definitions/PolymorphismOrderingChild3", "#/definitions/PolymorphismOrderingChild1", "#/definitions/PolymorphismOrderingChild4", "#/definitions/PolymorphismOrderingChild2") == anyOfList) } test("@NotNull annotations and nullable types") { @@ -1460,8 +1475,8 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers { println("--------------------------------------------") println(asPrettyJson(schema, jsonSchemaGeneratorScala.rootObjectMapper)) - assert( schema.at("/oneOf/0/$ref").asText() == "#/definitions/PolymorphismAndTitle1") - assert( schema.at("/oneOf/0/title").asText() == "CustomTitle1") + assert( schema.at("/anyOf/0/$ref").asText() == "#/definitions/PolymorphismAndTitle1") + assert( schema.at("/anyOf/0/title").asText() == "CustomTitle1") } test("UsingJsonSchemaOptions") { @@ -1557,7 +1572,7 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers { } // PojoWithParent has a property of type Parent (which uses polymorphism). - // Default rendering schema will make this property oneOf Child1 and Child2. + // Default rendering schema will make this property anyOf Child1 and Child2. // In this test we're preventing this by remapping Parent to Child1. // Now, when generating the schema, we should generate it as if the property where of type Child1 @@ -1794,6 +1809,15 @@ trait TestData { c.child1String3 = "cs3" c } + val child73 = { + val c = new Child73() + c.parentString = "pv" + c.child1String = "cs" + c.child1String2 = "cs2" + c.child1String3 = "cs3" + c.child3String = "cs4" + c + } val child2Scala = Child2Scala("pv", 12) val child1Scala = Child1Scala("pv", "cs", "cs2", "cs3") diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/polymorphism7/Child71.java b/src/test/scala/com/kjetland/jackson/jsonSchema/testData/polymorphism7/Child71.java new file mode 100644 index 0000000..d13630b --- /dev/null +++ b/src/test/scala/com/kjetland/jackson/jsonSchema/testData/polymorphism7/Child71.java @@ -0,0 +1,38 @@ +package com.kjetland.jackson.jsonSchema.testData.polymorphism7; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSubTypes; + +import java.util.Objects; + +@JsonSubTypes({ @JsonSubTypes.Type(value = Child73.class) }) +public class Child71 extends Parent7 { + + public String child1String; + + @JsonProperty("_child1String2") + public String child1String2; + + @JsonProperty(value = "_child1String3", required = true) + public String child1String3; + + public String parentString; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Child71)) return false; + + Child71 child1 = (Child71) o; + + return Objects.equals(child1String, child1.child1String) + && Objects.equals(child1String2, child1.child1String2) + && Objects.equals(child1String3, child1.child1String3) + && Objects.equals(parentString, child1.parentString); + } + + @Override + public int hashCode() { + return Objects.hash(child1String, child1String2, child1String3, parentString); + } +} diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/polymorphism7/Child72.java b/src/test/scala/com/kjetland/jackson/jsonSchema/testData/polymorphism7/Child72.java new file mode 100644 index 0000000..1b0536b --- /dev/null +++ b/src/test/scala/com/kjetland/jackson/jsonSchema/testData/polymorphism7/Child72.java @@ -0,0 +1,26 @@ +package com.kjetland.jackson.jsonSchema.testData.polymorphism7; + +import java.util.Objects; + +public class Child72 extends Parent7 { + + public Integer child2int; + + public String parentString; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Child72)) return false; + + Child72 child2 = (Child72) o; + + return Objects.equals(child2int, child2.child2int) + && Objects.equals(parentString, child2.parentString); + } + + @Override + public int hashCode() { + return Objects.hash(child2int, parentString); + } +} diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/polymorphism7/Child73.java b/src/test/scala/com/kjetland/jackson/jsonSchema/testData/polymorphism7/Child73.java new file mode 100644 index 0000000..9a3de69 --- /dev/null +++ b/src/test/scala/com/kjetland/jackson/jsonSchema/testData/polymorphism7/Child73.java @@ -0,0 +1,12 @@ +package com.kjetland.jackson.jsonSchema.testData.polymorphism7; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Objects; + +public class Child73 extends Child71 { + + @JsonProperty("_child3String") + public String child3String; + +} diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/polymorphism7/Parent7.java b/src/test/scala/com/kjetland/jackson/jsonSchema/testData/polymorphism7/Parent7.java new file mode 100644 index 0000000..ccfba4c --- /dev/null +++ b/src/test/scala/com/kjetland/jackson/jsonSchema/testData/polymorphism7/Parent7.java @@ -0,0 +1,15 @@ +package com.kjetland.jackson.jsonSchema.testData.polymorphism7; + +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonSubTypes; + +@JsonTypeInfo( + use = JsonTypeInfo.Id.CLASS, + include = JsonTypeInfo.As.PROPERTY, + property = "polymorphicType") +@JsonSubTypes({ + @JsonSubTypes.Type(value = Child71.class), + @JsonSubTypes.Type(value = Child72.class) }) +public class Parent7 { + +}