Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hide singleton support #307

Merged
merged 12 commits into from
Mar 13, 2020
5 changes: 5 additions & 0 deletions release-notes/CREDITS-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -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)
2 changes: 2 additions & 0 deletions release-notes/VERSION-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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(
Expand All @@ -27,7 +30,8 @@ class KotlinModule constructor (
builder.reflectionCacheSize,
builder.nullToEmptyCollection,
builder.nullToEmptyMap,
builder.nullIsSameAsDefault
builder.nullIsSameAsDefault,
builder.singletonSupport
)

companion object {
Expand All @@ -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))
Expand Down Expand Up @@ -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)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
@@ -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()
Expand All @@ -12,6 +33,7 @@ class KotlinModuleTest {
assertFalse(module.nullToEmptyCollection)
assertFalse(module.nullToEmptyMap)
assertFalse(module.nullIsSameAsDefault)
assertEquals(DISABLED, module.singletonSupport)
}

@Test
Expand Down Expand Up @@ -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)
}
}
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -49,9 +49,8 @@ class TestObjectSingleton {
val newSingleton = mapper.readValue<Singleton>(js)
assertThat(newSingleton.content, equalTo(Singleton.content))

newSingleton.content += 1;
newSingleton.content += 1

assertThat(Singleton.content, equalTo(newSingleton.content))
}

}
}