diff --git a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/annotation_introspector/KotlinFallbackAnnotationIntrospector.kt b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/annotation_introspector/KotlinFallbackAnnotationIntrospector.kt index 106d7dab..1af31049 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/annotation_introspector/KotlinFallbackAnnotationIntrospector.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/annotation_introspector/KotlinFallbackAnnotationIntrospector.kt @@ -23,6 +23,7 @@ import com.fasterxml.jackson.module.kotlin.findPropertyByGetter import com.fasterxml.jackson.module.kotlin.isNullable import com.fasterxml.jackson.module.kotlin.isUnboxableValueClass import com.fasterxml.jackson.module.kotlin.reconstructClassOrNull +import com.fasterxml.jackson.module.kotlin.ser.SequenceToIteratorConverter import com.fasterxml.jackson.module.kotlin.toSignature import kotlinx.metadata.jvm.fieldSignature import kotlinx.metadata.jvm.setterSignature @@ -102,9 +103,15 @@ internal class KotlinFallbackAnnotationIntrospector( } } - // Find a converter to handle the case where the getter returns an unboxed value from the value class. - override fun findSerializationConverter(a: Annotated): Converter<*, *>? = (a as? AnnotatedMethod)?.let { _ -> - cache.findValueClassReturnType(a)?.let { cache.getValueClassBoxConverter(a.rawReturnType, it) } + override fun findSerializationConverter(a: Annotated): Converter<*, *>? = when (a) { + // Find a converter to handle the case where the getter returns an unboxed value from the value class. + is AnnotatedMethod -> cache.findValueClassReturnType(a) + ?.let { cache.getValueClassBoxConverter(a.rawReturnType, it) } + is AnnotatedClass -> + a + .takeIf { Sequence::class.java.isAssignableFrom(it.rawType) } + ?.let { SequenceToIteratorConverter(it.type) } + else -> null } // Determine if the unbox result of value class is nullable diff --git a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/ser/ValueClassBoxConverter.kt b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/ser/Converters.kt similarity index 54% rename from src/main/kotlin/com/fasterxml/jackson/module/kotlin/ser/ValueClassBoxConverter.kt rename to src/main/kotlin/com/fasterxml/jackson/module/kotlin/ser/Converters.kt index f8682428..a980a6c9 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/ser/ValueClassBoxConverter.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/ser/Converters.kt @@ -1,8 +1,21 @@ package com.fasterxml.jackson.module.kotlin.ser +import com.fasterxml.jackson.databind.JavaType import com.fasterxml.jackson.databind.ser.std.StdDelegatingSerializer +import com.fasterxml.jackson.databind.type.TypeFactory import com.fasterxml.jackson.databind.util.StdConverter +internal class SequenceToIteratorConverter(private val input: JavaType) : StdConverter, Iterator<*>>() { + override fun convert(value: Sequence<*>): Iterator<*> = value.iterator() + + override fun getInputType(typeFactory: TypeFactory): JavaType = input + + // element-type may not be obtained, so a null check is required + override fun getOutputType(typeFactory: TypeFactory): JavaType = input.containedType(0) + ?.let { typeFactory.constructCollectionLikeType(Iterator::class.java, it) } + ?: typeFactory.constructType(Iterator::class.java) +} + // S is nullable because value corresponds to a nullable value class // @see KotlinFallbackAnnotationIntrospector.findNullSerializer internal class ValueClassBoxConverter( diff --git a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/ser/serializers/KotlinSerializers.kt b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/ser/serializers/KotlinSerializers.kt index f4722fa8..f039aa9d 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/ser/serializers/KotlinSerializers.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/ser/serializers/KotlinSerializers.kt @@ -14,12 +14,6 @@ import java.lang.reflect.Method import java.lang.reflect.Modifier import java.math.BigInteger -internal object SequenceSerializer : StdSerializer>(Sequence::class.java) { - override fun serialize(value: Sequence<*>, gen: JsonGenerator, provider: SerializerProvider) { - provider.defaultSerializeValue(value.iterator(), gen) - } -} - internal object UByteSerializer : StdSerializer(UByte::class.java) { override fun serialize(value: UByte, gen: JsonGenerator, provider: SerializerProvider) = gen.writeNumber(value.toShort()) @@ -91,7 +85,6 @@ internal class KotlinSerializers : Serializers.Base() { val rawClass = type.rawClass return when { - Sequence::class.java.isAssignableFrom(rawClass) -> SequenceSerializer UByte::class.java.isAssignableFrom(rawClass) -> UByteSerializer UShort::class.java.isAssignableFrom(rawClass) -> UShortSerializer UInt::class.java.isAssignableFrom(rawClass) -> UIntSerializer diff --git a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/_ported/test/SequenceSerdesTests.kt b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/_ported/test/SequenceSerdesTests.kt index 4a99be2c..416927c2 100644 --- a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/_ported/test/SequenceSerdesTests.kt +++ b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/_ported/test/SequenceSerdesTests.kt @@ -1,5 +1,9 @@ package com.fasterxml.jackson.module.kotlin._ported.test +import com.fasterxml.jackson.core.JsonGenerator +import com.fasterxml.jackson.databind.SerializerProvider +import com.fasterxml.jackson.databind.annotation.JsonSerialize +import com.fasterxml.jackson.databind.ser.std.StdSerializer import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.readValue import org.junit.jupiter.api.Assertions.assertEquals @@ -41,4 +45,39 @@ class TestSequenceDeserializer { val result = objectMapper.writeValueAsString(data) assertEquals("{\"value\":[]}", result) } + + class ContentSer : StdSerializer(String::class.java) { + override fun serialize(value: String, gen: JsonGenerator, provider: SerializerProvider) { + provider.defaultSerializeValue("$value-ser", gen) + } + } + + data class ListWrapper( + @JsonSerialize(contentUsing = ContentSer::class) val value: List + ) + + data class SequenceWrapper( + @JsonSerialize(contentUsing = ContentSer::class) + val value: Sequence + ) + + @Test + fun contentUsingTest() { + val mapper = jacksonObjectMapper() + + val listResult = mapper.writeValueAsString(ListWrapper(listOf("foo"))) + val sequenceResult = mapper.writeValueAsString(SequenceWrapper(sequenceOf("foo"))) + + assertEquals("""{"value":["foo-ser"]}""", sequenceResult) + assertEquals(listResult, sequenceResult) + } + + // @see #674 + @Test + fun sequenceOfTest() { + val mapper = jacksonObjectMapper() + val result = mapper.writeValueAsString(sequenceOf("foo")) + + assertEquals("""["foo"]""", result) + } }