Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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 @@ -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
Expand Down Expand Up @@ -75,4 +76,26 @@ class OptionModuleTest {
deserialized shouldBe original
}
}

data class Foo(val value: Map<Key, Option<String>>) {
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()))
Comment thread
serras marked this conversation as resolved.
Outdated
val json = mapper.writeValueAsString(original)
val deserialized = mapper.readValue<Foo>(json)
deserialized shouldBe original
}

@Test
fun `works with Map, issue #131, part 2`() {
val mapper = ObjectMapper().registerKotlinModule().registerArrowModule()
val original: Map<String, Option<Foo.Key>> = mapOf("foo" to Foo.Key.BAR.some())
val json = mapper.writeValueAsString(original)
val deserialized = mapper.readValue<Map<String, Option<Foo.Key>>>(json)
deserialized shouldBe original
}
}