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

Consider supporting map key types that are value-type String wrappers #577

Closed
MrBuddyCasino opened this issue Aug 5, 2022 · 7 comments
Closed

Comments

@MrBuddyCasino
Copy link

MrBuddyCasino commented Aug 5, 2022

It would be nice to have support for stringly-typed values, such as @JvmInline value class SKU(val value: String). Writing custom map key deserializers for every custom Kotlin value type that is really just a String doesn't seem ergonomic.

Currently this throws a java.lang.IllegalArgumentException: Cannot find a (Map) Key deserializer for type [simple type, class ValueTypeTest$SKU], repro case:

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule
import com.fasterxml.jackson.module.kotlin.convertValue
import org.junit.jupiter.api.Test

class ValueTypeTest {

    private val mapper =  ObjectMapper().registerModule(KotlinModule.Builder().build())

    @JvmInline
    value class SKU(val value: String)

    data class FavoritesEntity(
        var favorites: LinkedHashMap<SKU, Int>
    )

    @Test
    fun test() {
        val entity = FavoritesEntity(favorites = linkedMapOf(SKU("milk") to 1))
        val json = mapper.writeValueAsString(entity)
        val deserialized = mapper.convertValue<FavoritesEntity>(json)
        deserialized.favorites
    }

}
@MrBuddyCasino MrBuddyCasino changed the title Consider supporting value types that are String wrappers Consider supporting map key types that are value-type String wrappers Aug 5, 2022
@cowtowncoder
Copy link
Member

So just to confirm I understand this: it'd be good to have special handling for Value types that consists of a single String property? Yes, I can see how that could be useful.
And I suspect same would go for Java 17 record types with a single String property, for what that is worth.

I won't be able to help on Kotlin side (since I don't know Kotlin that well) but I hope someone else might want to tackle this, via added KeyDeserializers implementation (and probably matching functionality for key serializers).

@MrBuddyCasino
Copy link
Author

Yes thats what I meant. Possibly also Data Classes with a single String property, though I expect this to be a much rarer use case.

Value classes can be used as a way to add type safety without overhead. Instead of a String arg, define a value class and now you cannot put the customer number where the order number is supposed to go. On the JVM level this should impose no additional allocations or indirections.

Kotlin also has type aliases, but those do not add type safety as they are assignment-compatible with their aliases, and thus they already work with Jackson.

If you point me in the right direction I might be able to help with the Kotlin side.

@cowtowncoder
Copy link
Member

I hope module maintainers can help here, unfortunately I don't really know the codebase.
But I think there may already be key serializers/deserializers added via KotlinModule. These are separate from "value" serializers and deserializers, either by type (KeyDeserializer) or just functionality (key serializers do implement same class as value type, but need to use different method to write key -- JsonToken.FIELD_NAME vs JsonToken.VALUE_STRING).

@k163377
Copy link
Contributor

k163377 commented Mar 21, 2023

Serialization is already supported with the exception of a few features (#536).
Deserialization is covered in #650.

Therefore, this issue is closed as a duplicate.

@k163377 k163377 closed this as completed Mar 21, 2023
@geirsagberg
Copy link

Deserializing a map where the key is an value class does not work in 2.17 unfortunately:

import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper

@JvmInline
value class Id(val value: Int)

data class Foo(val valuesById: Map<Id, String>)

val objectMapper = jacksonObjectMapper()

val serialized = objectMapper.writeValueAsString(Foo(mapOf(Id(1) to "one", Id(2) to "two")))

val deserialized = objectMapper.readValue(serialized, Foo::class.java)

Throws:

Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot find a (Map) Key deserializer for type...

@cowtowncoder
Copy link
Member

@geirsagberg If there are issues remaining, please file a new issue outlining specific problem. It may (and probably should) reference this issue as background, but we do not typically re-open old issues unless it's part of on-going development of feature for branch from which there is no release yet.

@geirsagberg
Copy link

geirsagberg commented Mar 21, 2024

Got it, I have opened issue #777.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants