Created
January 10, 2020 15:31
-
-
Save jakzal/8a993a9f30237f627876cad8f989c279 to your computer and use it in GitHub Desktop.
Register Jackson subtypes automatically for a given list of parent classes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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} ") | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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