Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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 <init> ()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 <init> (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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
}
}
Expand All @@ -48,12 +46,23 @@ public object OptionSerializerResolver : Serializers.Base() {
beanDesc: BeanDescription?,
contentTypeSerializer: TypeSerializer?,
contentValueSerializer: JsonSerializer<Any>?,
): 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)
}
}

Expand Down Expand Up @@ -116,38 +125,18 @@ public class OptionSerializer : ReferenceTypeSerializer<Option<*>> {
): ReferenceTypeSerializer<Option<*>> = OptionSerializer(this, prop, vts, valueSer, unwrapper, _suppressableValue, _suppressNulls)
}

public class OptionDeserializer :
JsonDeserializer<Option<*>>(),
ContextualDeserializer {
private lateinit var valueType: JavaType

override fun deserialize(p: JsonParser?, ctxt: DeserializationContext): Option<*> = Option.fromNullable(p).map { ctxt.readValue<Any>(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 <A> Option<A>.or(other: Option<A>) = when (this) {
is Some<A> -> 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<Option<*>> {
Comment thread
serras marked this conversation as resolved.
public constructor(
fullType: JavaType,
valueInstantiator: ValueInstantiator?,
typeDeserializer: TypeDeserializer?,
jsonDeserializer: JsonDeserializer<*>?,
) : super(fullType, valueInstantiator, typeDeserializer, jsonDeserializer)

override fun withResolved(typeDeser: TypeDeserializer?, valueDeser: JsonDeserializer<*>?): ReferenceTypeDeserializer<Option<*>> = 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()
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<SomeObject> = Arb.bind(Arb.string(), Arb.int()) { str, int -> SomeObject(str, int) }

data class MapContainer<V>(val value: Map<Key, V>) {
enum class Key { First, Second }
}

fun <V> arbMapContainer(arbValue: Arb<V>): Arb<MapContainer<V>> = Arb.map(Arb.enum<MapContainer.Key>(), arbValue).map { MapContainer<V>(it) }
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<MapContainer<Either<Foo, Bar>>>())
}
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<Map<MapContainer.Key, Either<Foo, Bar>>>())
}
deserialized shouldBe original.value
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -184,11 +184,27 @@ class IorModuleTest {

private val arbTestClass: Arb<TestClass> = arbitrary { TestClass(Arb.ior(arbFoo, arbBar).bind()) }

private fun <L, R> Arb.Companion.ior(arbL: Arb<L>, arbR: Arb<R>): Arb<Ior<L, R>> = 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<MapContainer<Ior<Foo, Bar>>>())
}
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<Map<MapContainer.Key, Ior<Foo, Bar>>>())
}
deserialized shouldBe original.value
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,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()
Expand Down Expand Up @@ -75,4 +75,26 @@ class OptionModuleTest {
deserialized shouldBe original
}
}

@Test
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<MapContainer<Option<SomeObject>>>())
}
deserialized shouldBe original
}
}

@Test
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<Map<MapContainer.Key, Option<SomeObject>>>())
}
deserialized shouldBe original.value
}
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
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
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
Expand All @@ -36,3 +41,9 @@ fun <A, B> Arb.Companion.either(left: Arb<A>, right: Arb<B>): Arb<Either<A, B>>
fun <A> Arb.Companion.nonEmptyList(a: Arb<A>): Arb<NonEmptyList<A>> = list(a).filter(List<A>::isNotEmpty).map { it.toNonEmptyListOrNull()!! }

fun <A> Arb.Companion.nonEmptySet(a: Arb<A>): Arb<NonEmptySet<A>> = list(a).filter(List<A>::isNotEmpty).map { it.toNonEmptySetOrNull()!! }

fun <L, R> Arb.Companion.ior(arbL: Arb<L>, arbR: Arb<R>): Arb<Ior<L, R>> = Arb.choice(
arbitrary { arbL.bind().leftIor() },
arbitrary { arbR.bind().rightIor() },
arbitrary { (arbL.bind() to arbR.bind()).bothIor() },
)