diff --git a/release-notes/CREDITS-2.x b/release-notes/CREDITS-2.x index f5cefc91..f4cf3c66 100644 --- a/release-notes/CREDITS-2.x +++ b/release-notes/CREDITS-2.x @@ -36,3 +36,8 @@ Vladimir Petrakovich (frost13it@github) * Contributed fix for #279: 2.10 introduces another binary compatibility issue in `KotlinModule` constructor (2.10.2) + +Drew Stephens (dinomite@github) +* Contributed fix for #281: KotlinObjectSingletonDeserializer fails to deserialize + previously serialized JSON as it doesn't delegate deserializeWithType + (2.11.0) diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index 3ae95b0e..38b99cd9 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -8,6 +8,8 @@ Project: jackson-module-kotlin #284: Use `AnnotationIntrospector.findRenameByField()` to support "is properties" - Add Builder for KotlinModule +#281: Hide singleton deserialization support behind a setting on the module, + `singletonSupport` and enum `SingletonSupport`. Defaults to pre-2.10 behavior. Kotlin updated to 1.3.61 diff --git a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinModule.kt b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinModule.kt index 7c4a917f..1f69f564 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinModule.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinModule.kt @@ -2,6 +2,8 @@ package com.fasterxml.jackson.module.kotlin import com.fasterxml.jackson.databind.MapperFeature import com.fasterxml.jackson.databind.module.SimpleModule +import com.fasterxml.jackson.module.kotlin.SingletonSupport.CANONICALIZE +import com.fasterxml.jackson.module.kotlin.SingletonSupport.DISABLED import kotlin.reflect.KClass private val metadataFqName = "kotlin.Metadata" @@ -14,7 +16,8 @@ class KotlinModule constructor ( val reflectionCacheSize: Int = 512, val nullToEmptyCollection: Boolean = false, val nullToEmptyMap: Boolean = false, - val nullIsSameAsDefault: Boolean = false + val nullIsSameAsDefault: Boolean = false, + val singletonSupport: SingletonSupport = DISABLED ) : SimpleModule(PackageVersion.VERSION) { @Deprecated(level = DeprecationLevel.HIDDEN, message = "For ABI compatibility") constructor( @@ -27,7 +30,8 @@ class KotlinModule constructor ( builder.reflectionCacheSize, builder.nullToEmptyCollection, builder.nullToEmptyMap, - builder.nullIsSameAsDefault + builder.nullIsSameAsDefault, + builder.singletonSupport ) companion object { @@ -47,8 +51,12 @@ class KotlinModule constructor ( context.addValueInstantiators(KotlinInstantiators(cache, nullToEmptyCollection, nullToEmptyMap, nullIsSameAsDefault)) - // [module-kotlin#225]: keep Kotlin singletons as singletons - context.addBeanDeserializerModifier(KotlinBeanDeserializerModifier) + when(singletonSupport) { + DISABLED -> Unit + CANONICALIZE -> { + context.addBeanDeserializerModifier(KotlinBeanDeserializerModifier) + } + } context.insertAnnotationIntrospector(KotlinAnnotationIntrospector(context, cache, nullToEmptyCollection, nullToEmptyMap, nullIsSameAsDefault)) context.appendAnnotationIntrospector(KotlinNamesAnnotationIntrospector(this, cache, ignoredClassesForImplyingJsonCreator)) @@ -80,14 +88,21 @@ class KotlinModule constructor ( var nullIsSameAsDefault: Boolean = false private set + var singletonSupport = DISABLED + private set + fun reflectionCacheSize(reflectionCacheSize: Int) = apply { this.reflectionCacheSize = reflectionCacheSize } - fun nullToEmptyCollection(nullToEmptyCollection: Boolean) = apply { this.nullToEmptyCollection = nullToEmptyCollection } + fun nullToEmptyCollection(nullToEmptyCollection: Boolean) = + apply { this.nullToEmptyCollection = nullToEmptyCollection } fun nullToEmptyMap(nullToEmptyMap: Boolean) = apply { this.nullToEmptyMap = nullToEmptyMap } fun nullIsSameAsDefault(nullIsSameAsDefault: Boolean) = apply { this.nullIsSameAsDefault = nullIsSameAsDefault } + fun singletonSupport(singletonSupport: SingletonSupport) = + apply { this.singletonSupport = singletonSupport } + fun build() = KotlinModule(this) } } diff --git a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/SingletonSupport.kt b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/SingletonSupport.kt new file mode 100644 index 00000000..50e75037 --- /dev/null +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/SingletonSupport.kt @@ -0,0 +1,12 @@ +package com.fasterxml.jackson.module.kotlin + +/** + * Special handling for singletons. + */ +enum class SingletonSupport { + // No special handling of singletons (pre-2.10 behavior) + DISABLED, + // Deserialize then canonicalize (was the default in 2.10) + // [jackson-module-kotlin#225]: keep Kotlin singletons as singletons + CANONICALIZE +} diff --git a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/KotlinModuleTest.kt b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/KotlinModuleTest.kt index daf926ce..0fb8a4d8 100644 --- a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/KotlinModuleTest.kt +++ b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/KotlinModuleTest.kt @@ -1,9 +1,30 @@ package com.fasterxml.jackson.module.kotlin +import com.fasterxml.jackson.module.kotlin.SingletonSupport.CANONICALIZE +import com.fasterxml.jackson.module.kotlin.SingletonSupport.DISABLED import org.junit.Assert.* import org.junit.Test +import kotlin.reflect.full.memberProperties +import kotlin.reflect.full.primaryConstructor class KotlinModuleTest { + /** + * Ensure that the main constructor and the Builder have the same default settings. + */ + @Test + fun constructorAndBuilderCreateSameDefaultObject() { + val constructorModule = KotlinModule() + val builderModule = KotlinModule.Builder().build() + + + assertEquals(KotlinModule::class.primaryConstructor?.parameters?.size, KotlinModule.Builder::class.memberProperties.size) + assertEquals(constructorModule.reflectionCacheSize, builderModule.reflectionCacheSize) + assertEquals(constructorModule.nullToEmptyCollection, builderModule.nullToEmptyCollection) + assertEquals(constructorModule.nullToEmptyMap, builderModule.nullToEmptyMap) + assertEquals(constructorModule.nullIsSameAsDefault, builderModule.nullIsSameAsDefault) + assertEquals(constructorModule.singletonSupport, builderModule.singletonSupport) + } + @Test fun builder_Defaults() { val module = KotlinModule.Builder().build() @@ -12,6 +33,7 @@ class KotlinModuleTest { assertFalse(module.nullToEmptyCollection) assertFalse(module.nullToEmptyMap) assertFalse(module.nullIsSameAsDefault) + assertEquals(DISABLED, module.singletonSupport) } @Test @@ -64,4 +86,17 @@ class KotlinModuleTest { assertFalse(module.nullToEmptyMap) assertTrue(module.nullIsSameAsDefault) } + + @Test + fun builder_EnableCanonicalSingletonSupport() { + val module = KotlinModule.Builder().apply { + singletonSupport(CANONICALIZE) + }.build() + + assertEquals(512, module.reflectionCacheSize) + assertFalse(module.nullToEmptyCollection) + assertFalse(module.nullToEmptyMap) + assertFalse(module.nullIsSameAsDefault) + assertEquals(CANONICALIZE, module.singletonSupport) + } } diff --git a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/ObjectSingletonTest.kt b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/ObjectSingletonTest.kt index 8b0e5f97..e986083f 100644 --- a/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/ObjectSingletonTest.kt +++ b/src/test/kotlin/com/fasterxml/jackson/module/kotlin/test/ObjectSingletonTest.kt @@ -1,18 +1,18 @@ package com.fasterxml.jackson.module.kotlin.test import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.databind.SerializationFeature -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import com.fasterxml.jackson.module.kotlin.KotlinModule +import com.fasterxml.jackson.module.kotlin.SingletonSupport +import com.fasterxml.jackson.module.kotlin.SingletonSupport.CANONICALIZE import com.fasterxml.jackson.module.kotlin.readValue import org.hamcrest.CoreMatchers.equalTo -import org.hamcrest.CoreMatchers.not import org.hamcrest.MatcherAssert.assertThat import org.junit.Test // [module-kotlin#225]: keep Kotlin singletons as singletons class TestObjectSingleton { - - val mapper: ObjectMapper = jacksonObjectMapper() + val mapper: ObjectMapper = ObjectMapper() + .registerModule(KotlinModule(singletonSupport = CANONICALIZE)) object Singleton { var content = 1 // mutable state @@ -49,9 +49,8 @@ class TestObjectSingleton { val newSingleton = mapper.readValue(js) assertThat(newSingleton.content, equalTo(Singleton.content)) - newSingleton.content += 1; + newSingleton.content += 1 assertThat(Singleton.content, equalTo(newSingleton.content)) } - -} \ No newline at end of file +}