From 2e288ba7af09158dab5564686b9a819aec9c211c Mon Sep 17 00:00:00 2001 From: Alejandro Serrano Mena Date: Sun, 13 Apr 2025 09:06:57 +0200 Subject: [PATCH 1/5] Rewrite OptionDeserializer --- .../jackson/module/OptionModule.kt | 89 +++++++++---------- .../jackson/module/OptionModuleTest.kt | 23 +++++ 2 files changed, 63 insertions(+), 49 deletions(-) diff --git a/arrow-libs/integrations/arrow-core-jackson/src/main/kotlin/arrow/integrations/jackson/module/OptionModule.kt b/arrow-libs/integrations/arrow-core-jackson/src/main/kotlin/arrow/integrations/jackson/module/OptionModule.kt index 1473f13a16a..d67e27fff4b 100644 --- a/arrow-libs/integrations/arrow-core-jackson/src/main/kotlin/arrow/integrations/jackson/module/OptionModule.kt +++ b/arrow-libs/integrations/arrow-core-jackson/src/main/kotlin/arrow/integrations/jackson/module/OptionModule.kt @@ -3,18 +3,20 @@ package arrow.integrations.jackson.module import arrow.core.None import arrow.core.Option import arrow.core.Some -import arrow.core.getOrElse -import com.fasterxml.jackson.core.JsonParser import com.fasterxml.jackson.core.json.PackageVersion import com.fasterxml.jackson.databind.BeanDescription import com.fasterxml.jackson.databind.BeanProperty +import com.fasterxml.jackson.databind.DeserializationConfig import com.fasterxml.jackson.databind.DeserializationContext import com.fasterxml.jackson.databind.JavaType import com.fasterxml.jackson.databind.JsonDeserializer import com.fasterxml.jackson.databind.JsonSerializer import com.fasterxml.jackson.databind.MapperFeature import com.fasterxml.jackson.databind.SerializationConfig -import com.fasterxml.jackson.databind.deser.ContextualDeserializer +import com.fasterxml.jackson.databind.deser.Deserializers +import com.fasterxml.jackson.databind.deser.ValueInstantiator +import com.fasterxml.jackson.databind.deser.std.ReferenceTypeDeserializer +import com.fasterxml.jackson.databind.jsontype.TypeDeserializer import com.fasterxml.jackson.databind.jsontype.TypeSerializer import com.fasterxml.jackson.databind.module.SimpleModule import com.fasterxml.jackson.databind.ser.Serializers @@ -23,20 +25,16 @@ import com.fasterxml.jackson.databind.type.ReferenceType import com.fasterxml.jackson.databind.type.TypeBindings import com.fasterxml.jackson.databind.type.TypeFactory import com.fasterxml.jackson.databind.type.TypeModifier -import com.fasterxml.jackson.databind.util.AccessPattern import com.fasterxml.jackson.databind.util.NameTransformer import java.lang.reflect.Type public object OptionModule : SimpleModule(OptionModule::class.java.canonicalName, PackageVersion.VERSION) { - init { - addDeserializer(Option::class.java, OptionDeserializer()) - } - override fun setupModule(context: SetupContext) { super.setupModule(context) context.addSerializers(OptionSerializerResolver) + context.addDeserializers(OptionDeserializerResolver) context.addTypeModifier(OptionTypeModifier) } } @@ -48,12 +46,23 @@ public object OptionSerializerResolver : Serializers.Base() { beanDesc: BeanDescription?, contentTypeSerializer: TypeSerializer?, contentValueSerializer: JsonSerializer?, - ): JsonSerializer<*>? = if (Option::class.java.isAssignableFrom(type.rawClass)) { - val staticTyping = - (contentTypeSerializer == null && config.isEnabled(MapperFeature.USE_STATIC_TYPING)) - OptionSerializer(type, staticTyping, contentTypeSerializer, contentValueSerializer) - } else { - null + ): JsonSerializer<*>? { + if (!Option::class.java.isAssignableFrom(type.rawClass)) return null + val staticTyping = contentTypeSerializer == null && config.isEnabled(MapperFeature.USE_STATIC_TYPING) + return OptionSerializer(type, staticTyping, contentTypeSerializer, contentValueSerializer) + } +} + +public object OptionDeserializerResolver : Deserializers.Base() { + override fun findReferenceDeserializer( + type: ReferenceType, + config: DeserializationConfig, + beanDesc: BeanDescription?, + contentTypeDeserializer: TypeDeserializer?, + contentDeserializer: JsonDeserializer<*>? + ): JsonDeserializer<*>? { + if (!Option::class.java.isAssignableFrom(type.rawClass)) return null + return OptionDeserializer(type, null, contentTypeDeserializer, contentDeserializer) } } @@ -113,41 +122,23 @@ public class OptionSerializer : ReferenceTypeSerializer> { vts: TypeSerializer?, valueSer: JsonSerializer<*>?, unwrapper: NameTransformer?, - ): ReferenceTypeSerializer> = OptionSerializer(this, prop, vts, valueSer, unwrapper, _suppressableValue, _suppressNulls) + ): ReferenceTypeSerializer> = + OptionSerializer(this, prop, vts, valueSer, unwrapper, _suppressableValue, _suppressNulls) } -public class OptionDeserializer : - JsonDeserializer>(), - ContextualDeserializer { - private lateinit var valueType: JavaType - - override fun deserialize(p: JsonParser?, ctxt: DeserializationContext): Option<*> = Option.fromNullable(p).map { ctxt.readValue(it, valueType) } - - override fun createContextual( - ctxt: DeserializationContext, - property: BeanProperty?, - ): JsonDeserializer<*> { - val valueType = - Option.fromNullable(property) - .map { it.type.containedTypeOrUnknown(0) } - .or(Option.fromNullable(ctxt.contextualType?.containedTypeOrUnknown(0))) - .getOrElse { ctxt.constructType(Any::class.java) } - - val deserializer = OptionDeserializer() - deserializer.valueType = valueType - return deserializer - } - - private fun Option.or(other: Option) = when (this) { - is Some -> this - else -> other - } - - override fun getNullValue(ctxt: DeserializationContext): Option<*> = None - - override fun getEmptyValue(ctxt: DeserializationContext?): Option<*> = None - - override fun getNullAccessPattern(): AccessPattern = AccessPattern.CONSTANT - - override fun getEmptyAccessPattern(): AccessPattern = AccessPattern.CONSTANT +public class OptionDeserializer : ReferenceTypeDeserializer> { + public constructor( + fullType: JavaType, + valueInstantiator: ValueInstantiator?, + typeDeserializer: TypeDeserializer?, + jsonDeserializer: JsonDeserializer<*>? + ) : super(fullType, valueInstantiator, typeDeserializer, jsonDeserializer) + + override fun withResolved(typeDeser: TypeDeserializer?, valueDeser: JsonDeserializer<*>?): ReferenceTypeDeserializer> = + OptionDeserializer(valueType, null, typeDeser, valueDeser) + + override fun getNullValue(ctxt: DeserializationContext?): Option<*> = None + override fun referenceValue(contents: Any): Option<*> = Some(contents) + override fun updateReference(reference: Option<*>, contents: Any): Option<*> = Some(contents) + override fun getReferenced(reference: Option<*>): Any? = reference.getOrNull() } diff --git a/arrow-libs/integrations/arrow-core-jackson/src/test/kotlin/arrow/integrations/jackson/module/OptionModuleTest.kt b/arrow-libs/integrations/arrow-core-jackson/src/test/kotlin/arrow/integrations/jackson/module/OptionModuleTest.kt index 3d5078bd7ec..d34d0ebfb35 100644 --- a/arrow-libs/integrations/arrow-core-jackson/src/test/kotlin/arrow/integrations/jackson/module/OptionModuleTest.kt +++ b/arrow-libs/integrations/arrow-core-jackson/src/test/kotlin/arrow/integrations/jackson/module/OptionModuleTest.kt @@ -5,6 +5,7 @@ import arrow.core.some import com.fasterxml.jackson.annotation.JsonInclude import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.fasterxml.jackson.module.kotlin.readValue import com.fasterxml.jackson.module.kotlin.registerKotlinModule import io.kotest.assertions.throwables.shouldNotThrowAny import io.kotest.matchers.shouldBe @@ -75,4 +76,26 @@ class OptionModuleTest { deserialized shouldBe original } } + + data class Foo(val value: Map>) { + enum class Key { BAR, BAZ } + } + + @Test + fun `works with Map, issue #131, part 1`() { + val mapper = ObjectMapper().registerKotlinModule().registerArrowModule() + val original = Foo(mapOf(Foo.Key.BAR to "Hello".some())) + val json = mapper.writeValueAsString(original) + val deserialized = mapper.readValue(json) + deserialized shouldBe original + } + + @Test + fun `works with Map, issue #131, part 2`() { + val mapper = ObjectMapper().registerKotlinModule().registerArrowModule() + val original: Map> = mapOf("foo" to Foo.Key.BAR.some()) + val json = mapper.writeValueAsString(original) + val deserialized = mapper.readValue>>(json) + deserialized shouldBe original + } } From 3f46aee7571a5ed42ec69b227dd264074ef68f3f Mon Sep 17 00:00:00 2001 From: serras <309334+serras@users.noreply.github.com> Date: Sun, 13 Apr 2025 07:10:45 +0000 Subject: [PATCH 2/5] Auto-apply Spotless rules --- .../arrow/integrations/jackson/module/OptionModule.kt | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/arrow-libs/integrations/arrow-core-jackson/src/main/kotlin/arrow/integrations/jackson/module/OptionModule.kt b/arrow-libs/integrations/arrow-core-jackson/src/main/kotlin/arrow/integrations/jackson/module/OptionModule.kt index d67e27fff4b..6d5e8b4e448 100644 --- a/arrow-libs/integrations/arrow-core-jackson/src/main/kotlin/arrow/integrations/jackson/module/OptionModule.kt +++ b/arrow-libs/integrations/arrow-core-jackson/src/main/kotlin/arrow/integrations/jackson/module/OptionModule.kt @@ -59,7 +59,7 @@ public object OptionDeserializerResolver : Deserializers.Base() { config: DeserializationConfig, beanDesc: BeanDescription?, contentTypeDeserializer: TypeDeserializer?, - contentDeserializer: JsonDeserializer<*>? + contentDeserializer: JsonDeserializer<*>?, ): JsonDeserializer<*>? { if (!Option::class.java.isAssignableFrom(type.rawClass)) return null return OptionDeserializer(type, null, contentTypeDeserializer, contentDeserializer) @@ -122,8 +122,7 @@ public class OptionSerializer : ReferenceTypeSerializer> { vts: TypeSerializer?, valueSer: JsonSerializer<*>?, unwrapper: NameTransformer?, - ): ReferenceTypeSerializer> = - OptionSerializer(this, prop, vts, valueSer, unwrapper, _suppressableValue, _suppressNulls) + ): ReferenceTypeSerializer> = OptionSerializer(this, prop, vts, valueSer, unwrapper, _suppressableValue, _suppressNulls) } public class OptionDeserializer : ReferenceTypeDeserializer> { @@ -131,11 +130,10 @@ public class OptionDeserializer : ReferenceTypeDeserializer> { fullType: JavaType, valueInstantiator: ValueInstantiator?, typeDeserializer: TypeDeserializer?, - jsonDeserializer: JsonDeserializer<*>? + jsonDeserializer: JsonDeserializer<*>?, ) : super(fullType, valueInstantiator, typeDeserializer, jsonDeserializer) - override fun withResolved(typeDeser: TypeDeserializer?, valueDeser: JsonDeserializer<*>?): ReferenceTypeDeserializer> = - OptionDeserializer(valueType, null, typeDeser, valueDeser) + override fun withResolved(typeDeser: TypeDeserializer?, valueDeser: JsonDeserializer<*>?): ReferenceTypeDeserializer> = OptionDeserializer(valueType, null, typeDeser, valueDeser) override fun getNullValue(ctxt: DeserializationContext?): Option<*> = None override fun referenceValue(contents: Any): Option<*> = Some(contents) From c9eea9fb85a0e53e36bbdca460f7c067b0f26914 Mon Sep 17 00:00:00 2001 From: serras <309334+serras@users.noreply.github.com> Date: Sun, 13 Apr 2025 07:14:08 +0000 Subject: [PATCH 3/5] Auto-update API files --- .../api/arrow-core-jackson.api | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/arrow-libs/integrations/arrow-core-jackson/api/arrow-core-jackson.api b/arrow-libs/integrations/arrow-core-jackson/api/arrow-core-jackson.api index d004bced0e8..659fe9c3937 100644 --- a/arrow-libs/integrations/arrow-core-jackson/api/arrow-core-jackson.api +++ b/arrow-libs/integrations/arrow-core-jackson/api/arrow-core-jackson.api @@ -86,17 +86,21 @@ public final class arrow/integrations/jackson/module/NonEmptyCollectionsModule : public fun setupModule (Lcom/fasterxml/jackson/databind/Module$SetupContext;)V } -public final class arrow/integrations/jackson/module/OptionDeserializer : com/fasterxml/jackson/databind/JsonDeserializer, com/fasterxml/jackson/databind/deser/ContextualDeserializer { - public fun ()V - public fun createContextual (Lcom/fasterxml/jackson/databind/DeserializationContext;Lcom/fasterxml/jackson/databind/BeanProperty;)Lcom/fasterxml/jackson/databind/JsonDeserializer; - public fun deserialize (Lcom/fasterxml/jackson/core/JsonParser;Lcom/fasterxml/jackson/databind/DeserializationContext;)Larrow/core/Option; - public synthetic fun deserialize (Lcom/fasterxml/jackson/core/JsonParser;Lcom/fasterxml/jackson/databind/DeserializationContext;)Ljava/lang/Object; - public fun getEmptyAccessPattern ()Lcom/fasterxml/jackson/databind/util/AccessPattern; - public fun getEmptyValue (Lcom/fasterxml/jackson/databind/DeserializationContext;)Larrow/core/Option; - public synthetic fun getEmptyValue (Lcom/fasterxml/jackson/databind/DeserializationContext;)Ljava/lang/Object; - public fun getNullAccessPattern ()Lcom/fasterxml/jackson/databind/util/AccessPattern; +public final class arrow/integrations/jackson/module/OptionDeserializer : com/fasterxml/jackson/databind/deser/std/ReferenceTypeDeserializer { + public fun (Lcom/fasterxml/jackson/databind/JavaType;Lcom/fasterxml/jackson/databind/deser/ValueInstantiator;Lcom/fasterxml/jackson/databind/jsontype/TypeDeserializer;Lcom/fasterxml/jackson/databind/JsonDeserializer;)V public fun getNullValue (Lcom/fasterxml/jackson/databind/DeserializationContext;)Larrow/core/Option; public synthetic fun getNullValue (Lcom/fasterxml/jackson/databind/DeserializationContext;)Ljava/lang/Object; + public fun getReferenced (Larrow/core/Option;)Ljava/lang/Object; + public synthetic fun getReferenced (Ljava/lang/Object;)Ljava/lang/Object; + public fun referenceValue (Ljava/lang/Object;)Larrow/core/Option; + public synthetic fun referenceValue (Ljava/lang/Object;)Ljava/lang/Object; + public fun updateReference (Larrow/core/Option;Ljava/lang/Object;)Larrow/core/Option; + public synthetic fun updateReference (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; +} + +public final class arrow/integrations/jackson/module/OptionDeserializerResolver : com/fasterxml/jackson/databind/deser/Deserializers$Base { + public static final field INSTANCE Larrow/integrations/jackson/module/OptionDeserializerResolver; + public fun findReferenceDeserializer (Lcom/fasterxml/jackson/databind/type/ReferenceType;Lcom/fasterxml/jackson/databind/DeserializationConfig;Lcom/fasterxml/jackson/databind/BeanDescription;Lcom/fasterxml/jackson/databind/jsontype/TypeDeserializer;Lcom/fasterxml/jackson/databind/JsonDeserializer;)Lcom/fasterxml/jackson/databind/JsonDeserializer; } public final class arrow/integrations/jackson/module/OptionModule : com/fasterxml/jackson/databind/module/SimpleModule { From c663d1a4ae119bafe5097896dcd008a5d0c866a4 Mon Sep 17 00:00:00 2001 From: Alejandro Serrano Mena Date: Sun, 13 Apr 2025 09:23:53 +0200 Subject: [PATCH 4/5] Trigger build From 249b2fb4d3a48e531e74c2b9ecc13dd64f874945 Mon Sep 17 00:00:00 2001 From: Alejandro Serrano Mena Date: Sun, 13 Apr 2025 20:07:21 +0200 Subject: [PATCH 5/5] Add more tests --- .../jackson/module/DataHelpers.kt | 8 +++++ .../jackson/module/EitherModuleTest.kt | 23 ++++++++++++ .../jackson/module/IorModuleTest.kt | 30 ++++++++++++---- .../jackson/module/NonEmptyListModuleTest.kt | 2 +- .../jackson/module/OptionModuleTest.kt | 35 +++++++++---------- .../integrations/jackson/module/TestHelper.kt | 11 ++++++ 6 files changed, 83 insertions(+), 26 deletions(-) diff --git a/arrow-libs/integrations/arrow-core-jackson/src/test/kotlin/arrow/integrations/jackson/module/DataHelpers.kt b/arrow-libs/integrations/arrow-core-jackson/src/test/kotlin/arrow/integrations/jackson/module/DataHelpers.kt index 956d52505b7..d83be0952e6 100644 --- a/arrow-libs/integrations/arrow-core-jackson/src/test/kotlin/arrow/integrations/jackson/module/DataHelpers.kt +++ b/arrow-libs/integrations/arrow-core-jackson/src/test/kotlin/arrow/integrations/jackson/module/DataHelpers.kt @@ -2,9 +2,17 @@ package arrow.integrations.jackson.module import io.kotest.property.Arb import io.kotest.property.arbitrary.bind +import io.kotest.property.arbitrary.enum import io.kotest.property.arbitrary.int +import io.kotest.property.arbitrary.map import io.kotest.property.arbitrary.string data class SomeObject(val someString: String, val someInt: Int) fun Arb.Companion.someObject(): Arb = Arb.bind(Arb.string(), Arb.int()) { str, int -> SomeObject(str, int) } + +data class MapContainer(val value: Map) { + enum class Key { First, Second } +} + +fun arbMapContainer(arbValue: Arb): Arb> = Arb.map(Arb.enum(), arbValue).map { MapContainer(it) } diff --git a/arrow-libs/integrations/arrow-core-jackson/src/test/kotlin/arrow/integrations/jackson/module/EitherModuleTest.kt b/arrow-libs/integrations/arrow-core-jackson/src/test/kotlin/arrow/integrations/jackson/module/EitherModuleTest.kt index 28c1ef64464..a10d496d15a 100644 --- a/arrow-libs/integrations/arrow-core-jackson/src/test/kotlin/arrow/integrations/jackson/module/EitherModuleTest.kt +++ b/arrow-libs/integrations/arrow-core-jackson/src/test/kotlin/arrow/integrations/jackson/module/EitherModuleTest.kt @@ -6,6 +6,7 @@ import arrow.core.left import arrow.core.right import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef import com.fasterxml.jackson.module.kotlin.readValue import com.fasterxml.jackson.module.kotlin.registerKotlinModule import io.kotest.assertions.throwables.shouldNotThrowAny @@ -164,4 +165,26 @@ class EitherModuleTest { } private val mapper = ObjectMapper().registerKotlinModule().registerArrowModule() + + @Test + fun `works with Map, issue #131, part 1`() = runTest { + checkAll(arbMapContainer(Arb.either(arbFoo, arbBar))) { original -> + val serialized = mapper.writeValueAsString(original) + val deserialized = shouldNotThrowAny { + mapper.readValue(serialized, jacksonTypeRef>>()) + } + deserialized shouldBe original + } + } + + @Test + fun `works with Map, issue #131, part 2`() = runTest { + checkAll(arbMapContainer(Arb.either(arbFoo, arbBar))) { original -> + val serialized = mapper.writeValueAsString(original.value) + val deserialized = shouldNotThrowAny { + mapper.readValue(serialized, jacksonTypeRef>>()) + } + deserialized shouldBe original.value + } + } } diff --git a/arrow-libs/integrations/arrow-core-jackson/src/test/kotlin/arrow/integrations/jackson/module/IorModuleTest.kt b/arrow-libs/integrations/arrow-core-jackson/src/test/kotlin/arrow/integrations/jackson/module/IorModuleTest.kt index 3d9233decec..086594abbdf 100644 --- a/arrow-libs/integrations/arrow-core-jackson/src/test/kotlin/arrow/integrations/jackson/module/IorModuleTest.kt +++ b/arrow-libs/integrations/arrow-core-jackson/src/test/kotlin/arrow/integrations/jackson/module/IorModuleTest.kt @@ -7,6 +7,7 @@ import arrow.core.leftIor import arrow.core.rightIor import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef import com.fasterxml.jackson.module.kotlin.registerKotlinModule import io.kotest.assertions.throwables.shouldNotThrowAny import io.kotest.matchers.nulls.shouldNotBeNull @@ -17,7 +18,6 @@ import io.kotest.property.arbitrary.alphanumeric import io.kotest.property.arbitrary.arbitrary import io.kotest.property.arbitrary.az import io.kotest.property.arbitrary.boolean -import io.kotest.property.arbitrary.choice import io.kotest.property.arbitrary.enum import io.kotest.property.arbitrary.filter import io.kotest.property.arbitrary.int @@ -184,11 +184,27 @@ class IorModuleTest { private val arbTestClass: Arb = arbitrary { TestClass(Arb.ior(arbFoo, arbBar).bind()) } - private fun Arb.Companion.ior(arbL: Arb, arbR: Arb): Arb> = Arb.choice( - arbitrary { arbL.bind().leftIor() }, - arbitrary { arbR.bind().rightIor() }, - arbitrary { (arbL.bind() to arbR.bind()).bothIor() }, - ) - private val mapper = ObjectMapper().registerKotlinModule().registerArrowModule() + + @Test + fun `works with Map, issue #131, part 1`() = runTest { + checkAll(arbMapContainer(Arb.ior(arbFoo, arbBar))) { original -> + val serialized = mapper.writeValueAsString(original) + val deserialized = shouldNotThrowAny { + mapper.readValue(serialized, jacksonTypeRef>>()) + } + deserialized shouldBe original + } + } + + @Test + fun `works with Map, issue #131, part 2`() = runTest { + checkAll(arbMapContainer(Arb.ior(arbFoo, arbBar))) { original -> + val serialized = mapper.writeValueAsString(original.value) + val deserialized = shouldNotThrowAny { + mapper.readValue(serialized, jacksonTypeRef>>()) + } + deserialized shouldBe original.value + } + } } diff --git a/arrow-libs/integrations/arrow-core-jackson/src/test/kotlin/arrow/integrations/jackson/module/NonEmptyListModuleTest.kt b/arrow-libs/integrations/arrow-core-jackson/src/test/kotlin/arrow/integrations/jackson/module/NonEmptyListModuleTest.kt index 44693d3a66a..a2ac28ce18c 100644 --- a/arrow-libs/integrations/arrow-core-jackson/src/test/kotlin/arrow/integrations/jackson/module/NonEmptyListModuleTest.kt +++ b/arrow-libs/integrations/arrow-core-jackson/src/test/kotlin/arrow/integrations/jackson/module/NonEmptyListModuleTest.kt @@ -16,8 +16,8 @@ import io.kotest.property.arbitrary.int import io.kotest.property.arbitrary.string import io.kotest.property.checkAll import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.Test import kotlin.test.Ignore +import kotlin.test.Test class NonEmptyListModuleTest { private val mapper = ObjectMapper().registerKotlinModule().registerArrowModule() diff --git a/arrow-libs/integrations/arrow-core-jackson/src/test/kotlin/arrow/integrations/jackson/module/OptionModuleTest.kt b/arrow-libs/integrations/arrow-core-jackson/src/test/kotlin/arrow/integrations/jackson/module/OptionModuleTest.kt index d34d0ebfb35..40c41ebee45 100644 --- a/arrow-libs/integrations/arrow-core-jackson/src/test/kotlin/arrow/integrations/jackson/module/OptionModuleTest.kt +++ b/arrow-libs/integrations/arrow-core-jackson/src/test/kotlin/arrow/integrations/jackson/module/OptionModuleTest.kt @@ -5,7 +5,6 @@ import arrow.core.some import com.fasterxml.jackson.annotation.JsonInclude import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.module.kotlin.jacksonTypeRef -import com.fasterxml.jackson.module.kotlin.readValue import com.fasterxml.jackson.module.kotlin.registerKotlinModule import io.kotest.assertions.throwables.shouldNotThrowAny import io.kotest.matchers.shouldBe @@ -17,7 +16,7 @@ import io.kotest.property.arbitrary.int import io.kotest.property.arbitrary.string import io.kotest.property.checkAll import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.Test +import kotlin.test.Test class OptionModuleTest { private val mapper = ObjectMapper().registerModule(OptionModule).registerKotlinModule() @@ -77,25 +76,25 @@ class OptionModuleTest { } } - data class Foo(val value: Map>) { - enum class Key { BAR, BAZ } - } - @Test - fun `works with Map, issue #131, part 1`() { - val mapper = ObjectMapper().registerKotlinModule().registerArrowModule() - val original = Foo(mapOf(Foo.Key.BAR to "Hello".some())) - val json = mapper.writeValueAsString(original) - val deserialized = mapper.readValue(json) - deserialized shouldBe original + fun `works with Map, issue #131, part 1`() = runTest { + checkAll(arbMapContainer(Arb.option(Arb.someObject()))) { original -> + val serialized = mapper.writeValueAsString(original) + val deserialized = shouldNotThrowAny { + mapper.readValue(serialized, jacksonTypeRef>>()) + } + deserialized shouldBe original + } } @Test - fun `works with Map, issue #131, part 2`() { - val mapper = ObjectMapper().registerKotlinModule().registerArrowModule() - val original: Map> = mapOf("foo" to Foo.Key.BAR.some()) - val json = mapper.writeValueAsString(original) - val deserialized = mapper.readValue>>(json) - deserialized shouldBe original + fun `works with Map, issue #131, part 2`() = runTest { + checkAll(arbMapContainer(Arb.option(Arb.someObject()))) { original -> + val serialized = mapper.writeValueAsString(original.value) + val deserialized = shouldNotThrowAny { + mapper.readValue(serialized, jacksonTypeRef>>()) + } + deserialized shouldBe original.value + } } } diff --git a/arrow-libs/integrations/arrow-core-jackson/src/test/kotlin/arrow/integrations/jackson/module/TestHelper.kt b/arrow-libs/integrations/arrow-core-jackson/src/test/kotlin/arrow/integrations/jackson/module/TestHelper.kt index 487c5a398e3..58fabdba495 100644 --- a/arrow-libs/integrations/arrow-core-jackson/src/test/kotlin/arrow/integrations/jackson/module/TestHelper.kt +++ b/arrow-libs/integrations/arrow-core-jackson/src/test/kotlin/arrow/integrations/jackson/module/TestHelper.kt @@ -1,9 +1,13 @@ package arrow.integrations.jackson.module import arrow.core.Either +import arrow.core.Ior import arrow.core.NonEmptyList import arrow.core.NonEmptySet import arrow.core.Option +import arrow.core.bothIor +import arrow.core.leftIor +import arrow.core.rightIor import arrow.core.toNonEmptyListOrNull import arrow.core.toNonEmptySetOrNull import arrow.core.toOption @@ -11,6 +15,7 @@ import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.module.kotlin.jacksonTypeRef import io.kotest.matchers.shouldBe import io.kotest.property.Arb +import io.kotest.property.arbitrary.arbitrary import io.kotest.property.arbitrary.choice import io.kotest.property.arbitrary.filter import io.kotest.property.arbitrary.list @@ -36,3 +41,9 @@ fun Arb.Companion.either(left: Arb, right: Arb): Arb> fun Arb.Companion.nonEmptyList(a: Arb): Arb> = list(a).filter(List::isNotEmpty).map { it.toNonEmptyListOrNull()!! } fun Arb.Companion.nonEmptySet(a: Arb): Arb> = list(a).filter(List::isNotEmpty).map { it.toNonEmptySetOrNull()!! } + +fun Arb.Companion.ior(arbL: Arb, arbR: Arb): Arb> = Arb.choice( + arbitrary { arbL.bind().leftIor() }, + arbitrary { arbR.bind().rightIor() }, + arbitrary { (arbL.bind() to arbR.bind()).bothIor() }, +)