Skip to content

Instantly share code, notes, and snippets.

@jakzal
Created January 10, 2020 15:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jakzal/8a993a9f30237f627876cad8f989c279 to your computer and use it in GitHub Desktop.
Save jakzal/8a993a9f30237f627876cad8f989c279 to your computer and use it in GitHub Desktop.
Register Jackson subtypes automatically for a given list of parent classes
package pl.zalas.acme.config
import com.fasterxml.jackson.databind.Module
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import pl.zalas.jackson.module.subtype.SubTypeModule
@Configuration
class JacksonConfig {
@Bean
fun subTypeModule(): Module {
return SubTypeModule("pl.zalas.acme", listOf(Foo::class, Bar::class))
}
}
package pl.zalas.jackson.module.subtype
import com.fasterxml.jackson.annotation.JsonTypeName
import com.fasterxml.jackson.core.Version
import com.fasterxml.jackson.databind.Module
import com.fasterxml.jackson.databind.jsontype.NamedType
import io.github.classgraph.ClassGraph
import io.github.classgraph.ClassInfo
import io.github.classgraph.ScanResult
import org.slf4j.LoggerFactory
import kotlin.reflect.KClass
import kotlin.reflect.full.findAnnotation
/**
* Finds all children of types given in the constructor and registers them as Jackson subtypes.
*
* Subtypes need to have the standard `@JsonTypeName` annotation.
* It will register types that would normally not be registered by Jackson.
* Specifically, subtypes can be put in any namespace and don't need to be sub classes of a sealed class.
*/
class SubTypeModule(private val prefix: String, private val parentTypes: List<KClass<*>>) : Module() {
companion object {
private val logger = LoggerFactory.getLogger(SubTypeModule::class.java)
}
override fun getModuleName(): String = "SubType"
override fun version(): Version = Version(1, 0, 0, "", "pl.zalas.jackson.module", "subtype")
override fun setupModule(context: SetupContext) {
context.registerSubtypes(*findJsonSubTypes().toTypedArray())
}
private fun findJsonSubTypes(): List<NamedType> {
val classes: ScanResult = scanClasses()
val subTypes = parentTypes.flatMap { classes.filterJsonSubTypes(it) }
logTypes(subTypes)
return subTypes
}
private fun scanClasses(): ScanResult = ClassGraph().enableClassInfo().whitelistPackages(prefix).scan()
private fun ScanResult.filterJsonSubTypes(type: KClass<*>): Iterable<NamedType> =
getSubclasses(type.java.name)
.map(ClassInfo::loadClass)
.map {
NamedType(it, it.findJsonTypeAnnotation())
}
private fun Class<*>.findJsonTypeAnnotation(): String = kotlin.findAnnotation<JsonTypeName>()?.value ?: "unknown"
private fun logTypes(subTypes: List<NamedType>) = subTypes.forEach {
logger.info("Registering json subtype ${it.name}: ${it.type.kotlin.qualifiedName} ")
}
}
package pl.zalas.jackson.module.subtype
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.annotation.JsonTypeInfo
import com.fasterxml.jackson.annotation.JsonTypeName
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
class SubTypeModuleTests {
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type", visible = true)
abstract class Animal(@JsonProperty val type: String)
@JsonTypeName("dog")
data class Dog(@JsonProperty val name: String) : Animal("dog")
@JsonTypeName("cat")
data class Cat(@JsonProperty val name: String) : Animal("cat")
@Test
fun `it register subtypes of given parent types`() {
val objectMapper = jacksonObjectMapper().registerModule(SubTypeModule("pl.zalas.jackson.module.subtype", listOf(Animal::class)))
val dog = objectMapper.readValue(objectMapper.writeValueAsString(Dog("Albert")), Animal::class.java)
val cat = objectMapper.readValue(objectMapper.writeValueAsString(Cat("Lucy")), Animal::class.java)
assertEquals(Dog("Albert"), dog)
assertEquals(Cat("Lucy"), cat)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment