@@ -4,6 +4,7 @@ import tools.jackson.core.JacksonException
44import tools.jackson.core.JsonGenerator
55import tools.jackson.core.JsonParser
66import tools.jackson.core.JsonToken
7+ import tools.jackson.core.util.JsonGeneratorDelegate
78import tools.jackson.databind.DeserializationContext
89import tools.jackson.databind.DeserializationFeature
910import tools.jackson.databind.JacksonSerializable
@@ -14,6 +15,7 @@ import tools.jackson.databind.SerializationFeature
1415import tools.jackson.databind.deser.DeserializationProblemHandler
1516import tools.jackson.databind.deser.ValueInstantiator
1617import tools.jackson.databind.jsontype.TypeSerializer
18+ import tools.jackson.databind.ser.bean.BeanSerializerBase
1719import tools.jackson.databind.util.TokenBuffer
1820import java.lang.reflect.Type
1921
@@ -73,16 +75,17 @@ internal class ArgumentConverter(private val mapper: ObjectMapper, type: Type) {
7375 }
7476
7577 private fun convertsToOwnClass (value : Any ): Boolean = try {
76- mapper.readValue(buffer (value), value.javaClass)
78+ mapper.readValue(bufferFailingOnEmptyBeans (value), value.javaClass)
7779 true
7880 } catch (e: JacksonException ) {
7981 false
8082 }
8183
82- private fun read (value : Any? ): Any? = reader.readValue(buffer (value))
84+ private fun read (value : Any? ): Any? = reader.readValue(bufferFailingOnEmptyBeans (value))
8385
84- private fun buffer (value : Any? ): TokenBuffer =
85- writer.writeValueIntoBuffer(value).forceUseOfBigDecimal(useBigDecimalForFloats)
86+ private fun bufferFailingOnEmptyBeans (value : Any? ): TokenBuffer = buffer(FailOnEmptyBeans (value))
87+
88+ private fun buffer (value : Any? ): TokenBuffer = writer.writeValueIntoBuffer(value).forceUseOfBigDecimal(useBigDecimalForFloats)
8689
8790 private val embeddedValueHandler = object : DeserializationProblemHandler () {
8891 // called for an interface or abstract class, e.g. Part, since no deserializer of it returns embedded values
@@ -93,13 +96,21 @@ internal class ArgumentConverter(private val mapper: ObjectMapper, type: Type) {
9396 p : JsonParser ,
9497 msg : String?
9598 ): Any? {
96- val value = if (p.hasToken( JsonToken . VALUE_EMBEDDED_OBJECT )) p.embeddedObject else null
99+ val value = p.embeddedValue()
97100 return if (instClass.isInstance(value)) value else NOT_HANDLED
98101 }
99102
100103 // called for an embedded value read as a bean of another class, which the value is converted to on its own
101104 override fun handleWeirdNativeValue (context : DeserializationContext , targetType : JavaType , valueToConvert : Any , p : JsonParser ): Any? =
102- mapper.readValue(buffer(valueToConvert), targetType)
105+ convertOnItsOwn(valueToConvert, targetType)
106+
107+ // called for an embedded value read as a Map or JsonNode, e.g. a scalar without properties, which converts to {}
108+ override fun handleUnexpectedToken (context : DeserializationContext , targetType : JavaType , t : JsonToken ? , p : JsonParser , failureMsg : String? ): Any? =
109+ p.embeddedValue()?.let { convertOnItsOwn(it, targetType) } ? : NOT_HANDLED
110+
111+ private fun JsonParser.embeddedValue (): Any? = if (hasToken(JsonToken .VALUE_EMBEDDED_OBJECT )) embeddedObject else null
112+
113+ private fun convertOnItsOwn (value : Any , targetType : JavaType ): Any? = mapper.readValue(buffer(value), targetType)
103114 }
104115
105116 // FAIL_ON_EMPTY_BEANS whatever the mapper says: a scalar class with no visible properties would otherwise be read back as
@@ -114,6 +125,32 @@ internal class ArgumentConverter(private val mapper: ObjectMapper, type: Type) {
114125
115126 private val nullToJavaDefaultReader by lazy { reader.without(DeserializationFeature .FAIL_ON_NULL_FOR_PRIMITIVES ) }
116127
128+ // FAIL_ON_EMPTY_BEANS skips a class with annotations, which Jackson writes as an empty object even if it has no properties,
129+ // e.g. any Kotlin class, since they all have @Metadata
130+ private class FailOnEmptyBeans (private val value : Any? ) : JacksonSerializable {
131+ override fun serialize (gen : JsonGenerator , serializers : SerializationContext ) {
132+ serializers.writeValue(EmptyBeanCheck (gen, serializers), value)
133+ }
134+
135+ override fun serializeWithType (gen : JsonGenerator , serializers : SerializationContext , typeSer : TypeSerializer ) {
136+ serialize(gen, serializers)
137+ }
138+ }
139+
140+ private class EmptyBeanCheck (gen : JsonGenerator , private val serializers : SerializationContext ) : JsonGeneratorDelegate(gen, false ) {
141+ override fun writeEndObject (): JsonGenerator {
142+ val context = streamWriteContext()
143+ val bean = context.currentValue()
144+ if (context.entryCount == 0 && bean != null && hasNoProperties(bean.javaClass)) {
145+ serializers.reportBadDefinition<Unit >(bean.javaClass, " No properties discovered to write ${bean.javaClass.name} " )
146+ }
147+ return super .writeEndObject()
148+ }
149+
150+ private fun hasNoProperties (beanClass : Class <* >) =
151+ (serializers.findValueSerializer(beanClass) as ? BeanSerializerBase )?.properties()?.hasNext() == false
152+ }
153+
117154 private class EmbeddedValue (private val value : Any ) : JacksonSerializable {
118155 override fun serialize (gen : JsonGenerator , serializers : SerializationContext ) {
119156 gen.writeEmbeddedObject(value)
0 commit comments