Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More Flexible subtype resolver #139

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ object JsonSchemaConfig {
uniqueItemClasses:java.util.Set[Class[_]],
classTypeReMapping:java.util.Map[Class[_], Class[_]],
jsonSuppliers:java.util.Map[String, Supplier[JsonNode]],
subclassesResolver:SubclassesResolver,
subclassesResolver:SubTypeResolver,
failOnUnknownProperties:Boolean,
javaxValidationGroups:java.util.List[Class[_]]
):JsonSchemaConfig = {
Expand Down Expand Up @@ -148,11 +148,50 @@ object JsonSchemaConfig {
}
)
}
}

trait SubTypeResolver {
def getSubTypes(_type: JavaType, objectMapper: ObjectMapper):List[Class[_]]
}

trait SubclassesResolver {
trait SubclassesResolver extends SubTypeResolver {

import scala.collection.JavaConverters._

def getSubclasses(clazz:Class[_]):List[Class[_]]

def getSubTypes(_type: JavaType, objectMapper: ObjectMapper):List[Class[_]] = {

val ac = AnnotatedClassResolver.resolve(objectMapper.getDeserializationConfig, _type, objectMapper.getDeserializationConfig)

Option(ac.getAnnotation(classOf[JsonTypeInfo])).map {
jsonTypeInfo: JsonTypeInfo =>

jsonTypeInfo.use() match {
case JsonTypeInfo.Id.NAME =>
// First we try to resolve types via manually finding annotations (if success, it will preserve the order), if not we fallback to use collectAndResolveSubtypesByClass()
val subTypes: List[Class[_]] = Option(_type.getRawClass.getDeclaredAnnotation(classOf[JsonSubTypes])).map {
ann: JsonSubTypes =>
// We found it via @JsonSubTypes-annotation
ann.value().map {
t: JsonSubTypes.Type => t.value()
}.toList
}.getOrElse {
// We did not find it via @JsonSubTypes-annotation (Probably since it is using mixin's) => Must fallback to using collectAndResolveSubtypesByClass
val resolvedSubTypes = objectMapper.getSubtypeResolver.collectAndResolveSubtypesByClass(objectMapper.getDeserializationConfig, ac).asScala.toList
resolvedSubTypes.map( _.getType)
.filter( c => _type.getRawClass.isAssignableFrom(c) && _type.getRawClass != c)
}

subTypes

case _ =>
// Just find all sub type by ref
getSubclasses(_type.getRawClass)
}

}.getOrElse(List())
}
}

case class SubclassesResolverImpl
Expand All @@ -165,6 +204,7 @@ case class SubclassesResolverImpl

def this() = this(None, List(), List())


def withClassGraph(classGraph:ClassGraph):SubclassesResolverImpl = {
this.copy(classGraph = Option(classGraph))
}
Expand Down Expand Up @@ -239,7 +279,7 @@ case class JsonSchemaConfig
uniqueItemClasses:Set[Class[_]], // If rendering array and type is instanceOf class in this set, then we add 'uniqueItems": true' to schema - See // https://github.com/jdorn/json-editor for more info
classTypeReMapping:Map[Class[_], Class[_]], // Can be used to prevent rendering using polymorphism for specific classes.
jsonSuppliers:Map[String, Supplier[JsonNode]], // Suppliers in this map can be accessed using @JsonSchemaInject(jsonSupplierViaLookup = "lookupKey")
subclassesResolver:SubclassesResolver = new SubclassesResolverImpl(), // Using default impl that scans entire classpath
subclassesResolver:SubTypeResolver = new SubclassesResolverImpl(), // Using default impl that scans entire classpath
failOnUnknownProperties:Boolean = true,
javaxValidationGroups:Array[Class[_]] = Array(), // Used to match against different validation-groups (javax.validation.constraints)
jsonSchemaDraft:JsonSchemaDraft = JsonSchemaDraft.DRAFT_04
Expand All @@ -253,6 +293,10 @@ case class JsonSchemaConfig
this.copy( subclassesResolver = subclassesResolver )
}

def withSubTypeResolver(subTypeResolver: SubTypeResolver):JsonSchemaConfig = {
this.copy( subclassesResolver = subTypeResolver )
}

def withJavaxValidationGroups(javaxValidationGroups:Array[Class[_]]):JsonSchemaConfig = {
this.copy(javaxValidationGroups = javaxValidationGroups)
}
Expand Down Expand Up @@ -891,39 +935,6 @@ class JsonSchemaGenerator
}
}

private def extractSubTypes(_type: JavaType):List[Class[_]] = {

val ac = AnnotatedClassResolver.resolve(objectMapper.getDeserializationConfig, _type, objectMapper.getDeserializationConfig)

Option(ac.getAnnotation(classOf[JsonTypeInfo])).map {
jsonTypeInfo: JsonTypeInfo =>

jsonTypeInfo.use() match {
case JsonTypeInfo.Id.NAME =>
// First we try to resolve types via manually finding annotations (if success, it will preserve the order), if not we fallback to use collectAndResolveSubtypesByClass()
val subTypes: List[Class[_]] = Option(_type.getRawClass.getDeclaredAnnotation(classOf[JsonSubTypes])).map {
ann: JsonSubTypes =>
// We found it via @JsonSubTypes-annotation
ann.value().map {
t: JsonSubTypes.Type => t.value()
}.toList
}.getOrElse {
// We did not find it via @JsonSubTypes-annotation (Probably since it is using mixin's) => Must fallback to using collectAndResolveSubtypesByClass
val resolvedSubTypes = objectMapper.getSubtypeResolver.collectAndResolveSubtypesByClass(objectMapper.getDeserializationConfig, ac).asScala.toList
resolvedSubTypes.map( _.getType)
.filter( c => _type.getRawClass.isAssignableFrom(c) && _type.getRawClass != c)
}

subTypes

case _ =>
// Just find all subclasses
config.subclassesResolver.getSubclasses(_type.getRawClass)
}

}.getOrElse(List())
}

def tryToReMapType(originalClass: Class[_]):Class[_] = {
config.classTypeReMapping.get(originalClass).map {
mappedToClass:Class[_] =>
Expand Down Expand Up @@ -973,7 +984,7 @@ class JsonSchemaGenerator

override def expectObjectFormat(_type: JavaType) = {

val subTypes: List[Class[_]] = extractSubTypes(_type)
val subTypes: List[Class[_]] = config.subclassesResolver.getSubTypes(_type, objectMapper)

// Check if we have subtypes
if (subTypes.nonEmpty) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package com.kjetland.jackson.jsonSchema
import java.time.{LocalDate, LocalDateTime, OffsetDateTime}
import java.util
import java.util.{Collections, Optional, TimeZone}

import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.databind.node.{ArrayNode, MissingNode, ObjectNode}
import com.fasterxml.jackson.databind.{JavaType, JsonNode, ObjectMapper, SerializationFeature}
Expand All @@ -25,9 +24,12 @@ import com.kjetland.jackson.jsonSchema.testData.polymorphism4.{Child41, Child42}
import com.kjetland.jackson.jsonSchema.testData.polymorphism5.{Child51, Child52, Parent5}
import com.kjetland.jackson.jsonSchema.testDataScala._
import com.kjetland.jackson.jsonSchema.testData_issue_24.EntityWrapper
import io.github.classgraph.ClassGraph

import javax.validation.groups.Default
import org.scalatest.{FunSuite, Matchers}

import scala.::
import scala.collection.JavaConverters._

class JsonSchemaGeneratorTest extends FunSuite with Matchers {
Expand Down Expand Up @@ -1692,7 +1694,6 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers {

// Currently there are no differences in the generated jsonSchema other than the $schema-url
}

}

trait TestData {
Expand Down