Last active
May 11, 2026 08:09
-
-
Save Anton3/348e639b6d46c3598f3311b9feca8578 to your computer and use it in GitHub Desktop.
This file contains hidden or 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 name.anton3.vkapi.generator.json | |
| import com.fasterxml.jackson.annotation.JsonUnwrapped | |
| import com.fasterxml.jackson.core.JsonParser | |
| import com.fasterxml.jackson.databind.* | |
| import com.fasterxml.jackson.databind.deser.ContextualDeserializer | |
| import com.fasterxml.jackson.databind.deser.ResolvableDeserializer | |
| import com.fasterxml.jackson.databind.deser.std.StdDeserializer | |
| import com.fasterxml.jackson.databind.node.ObjectNode | |
| import com.fasterxml.jackson.databind.node.TreeTraversingParser | |
| import com.fasterxml.jackson.databind.util.NameTransformer | |
| class SinglePolyUnwrappedDeserializer<T : Any> : JsonDeserializer<T>(), ContextualDeserializer { | |
| override fun deserialize(p: JsonParser, ctxt: DeserializationContext): T = error("Not implemented") | |
| override fun createContextual(ctxt: DeserializationContext, property: BeanProperty?): JsonDeserializer<T> = | |
| SinglePolyUnwrappedDeserializerImpl(ctxt) | |
| } | |
| private class SinglePolyUnwrappedDeserializerImpl<T : Any>(ctxt: DeserializationContext) : | |
| StdDeserializer<T>(null as JavaType?) { | |
| private val type: JavaType = ctxt.contextualType | |
| private val beanDeserializer: JsonDeserializer<T> | |
| private val ownPropertyNames: Set<String> | |
| private val unwrappedType: JavaType | |
| private val unwrappedPropertyName: String | |
| private val nameTransformer: NameTransformer | |
| init { | |
| val description: BeanDescription = ctxt.config.introspect(type) | |
| var tempUnwrappedAnnotation: JsonUnwrapped? = null | |
| val unwrappedProperties = description.findProperties().filter { prop -> | |
| listOfNotNull(prop.constructorParameter, prop.mutator, prop.field).any { member -> | |
| val unwrappedAnnotation: JsonUnwrapped? = member.getAnnotation(JsonUnwrapped::class.java) | |
| if (unwrappedAnnotation != null) { | |
| tempUnwrappedAnnotation = unwrappedAnnotation | |
| member.allAnnotations.add(notUnwrappedAnnotation) | |
| } | |
| unwrappedAnnotation != null | |
| } | |
| } | |
| val unwrappedProperty = when (unwrappedProperties.size) { | |
| 0 -> error("@JsonUnwrapped properties not found in ${type.typeName}") | |
| 1 -> unwrappedProperties.single() | |
| else -> error("Multiple @JsonUnwrapped properties found in ${type.typeName}") | |
| } | |
| nameTransformer = tempUnwrappedAnnotation!!.run { NameTransformer.simpleTransformer(prefix, suffix) } | |
| unwrappedPropertyName = unwrappedProperty.name | |
| ownPropertyNames = description.findProperties().mapTo(mutableSetOf()) { it.name } | |
| ownPropertyNames.remove(unwrappedPropertyName) | |
| ownPropertyNames.removeAll(description.ignoredPropertyNames) | |
| unwrappedType = unwrappedProperty.primaryType | |
| val rawBeanDeserializer = ctxt.factory.createBeanDeserializer(ctxt, type, description) | |
| (rawBeanDeserializer as? ResolvableDeserializer)?.resolve(ctxt) | |
| @Suppress("UNCHECKED_CAST") | |
| beanDeserializer = rawBeanDeserializer as JsonDeserializer<T> | |
| } | |
| override fun deserialize(p: JsonParser, ctxt: DeserializationContext): T { | |
| val node = p.readValueAsTree<ObjectNode>() | |
| val ownNode = ObjectNode(ctxt.nodeFactory) | |
| val unwrappedNode = ObjectNode(ctxt.nodeFactory) | |
| node.fields().forEach { (key, value) -> | |
| val transformed: String? = nameTransformer.reverse(key) | |
| if (transformed != null && key !in ownPropertyNames) { | |
| unwrappedNode.replace(transformed, value) | |
| } else { | |
| ownNode.replace(key, value) | |
| } | |
| } | |
| ownNode.replace(unwrappedPropertyName, unwrappedNode) | |
| val syntheticParser = TreeTraversingParser(ownNode) | |
| syntheticParser.nextToken() | |
| return beanDeserializer.deserialize(syntheticParser, ctxt) | |
| } | |
| private class NotUnwrapped( | |
| @Suppress("unused") | |
| @field:JsonUnwrapped(enabled = false) | |
| @JvmField | |
| val dummy: Nothing | |
| ) | |
| companion object { | |
| val notUnwrappedAnnotation: JsonUnwrapped = | |
| NotUnwrapped::class.java.getField("dummy").getAnnotation(JsonUnwrapped::class.java) | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
In case anyone stumbled upon the same issue, we just recently upgraded our project to java 25 and spring boot 4 and this deserializer stopped working because of the jackson 3 version and so on. It was parametrized for a specific class, with one custom statement regarding '@'type field for mapping purposes. Its not perfect but maybe helps someone save time. With the help of claude, here is a working version adjusted to the above-mentioned setup: