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

Cannot use value class as map key in 2.17 #777

Open
3 tasks done
geirsagberg opened this issue Mar 18, 2024 · 8 comments
Open
3 tasks done

Cannot use value class as map key in 2.17 #777

geirsagberg opened this issue Mar 18, 2024 · 8 comments

Comments

@geirsagberg
Copy link

Search before asking

  • I searched in the issues and found nothing similar.
  • I searched in the issues of databind and other modules used and found nothing similar.
  • I have confirmed that the problem only occurs when using Kotlin.

Describe the bug

Jackson 2.17 introduced support for deserializing value classes. However, using a value class as a key in a map still fails on deserialization.

To Reproduce

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...

Expected behavior

When deserializing a map Map<MyValueClass, Any> where MyValueClass wraps a supported primitive, the map should deserialize without throwing an error.

Versions

Kotlin: 1.9.22
Jackson-module-kotlin: 2.17.0
Jackson-databind: 2.17.0

Additional context

No response

@k163377
Copy link
Contributor

k163377 commented Mar 20, 2024

I am positively considering adding this feature.
I have made a prototype at kogera for policy consideration.
ProjectMapK/jackson-module-kogera#224

It will be basically the same usability as deserialization of value class.
The difference is that the wrapped type is also deserialized by the context-accessible KeyDeserializer.

However, I am busy and sick, so it may take some time until I can merge it into kotlin-module.
I hope to complete it by May at the latest.


@cowtowncoder
I have two questions about Jackson functionality.

I don't believe there is a JsonCreator like feature used to deserialize keys, is this correct?

Also, are there any caveats about searching for another KeyDeserializer from a KeyDeserializer?
https://github.com/ProjectMapK/jackson-module-kogera/pull/224/files#diff-b03a6f91081da3aa22c53b161b0a2f6f1a0cf4ba318329907046b5f5335b17c8R72-R73

I am assuming that this is a very limited edge case, but I am assuming that there are nested types that require a special deserialization method.
https://github.com/ProjectMapK/jackson-module-kogera/pull/224/files#diff-11c6e0b61e487e3914690655846ffd6100b92c022082ea610bb8d086f7be18dcR87-R116

@junkdog
Copy link

junkdog commented Apr 8, 2024

Just to clarify, previous versions of jackson don't exhibit errors when using value classes as keys in maps. As such, I think this should be labeled as a bug and not an enhancement.

@k163377
Copy link
Contributor

k163377 commented Apr 8, 2024

@junkdog
A generic key deserializer for the value class is not currently provided by jackosn-module-kotlin.
This means that Cannot find a (Map) Key deserializer should be thrown as for other classes.

It is still possible to deserialize a value class as a Map key by registering the appropriate key deserializer.

For these reasons, I interpret this as a feature request.

@cowtowncoder
Copy link
Member

Maybe the difference here is wrt serialization (where "value.toString()" is used for "unknown" types) vs deserialization (where exception will be thrown)?
Handling differs quite a bit b/w reading (deser) and writing (ser).

@cowtowncoder
Copy link
Member

@cowtowncoder I have two questions about Jackson functionality.

I don't believe there is a JsonCreator like feature used to deserialize keys, is this correct?

No I don't think so; except for @JsonKey annotation... use of which might work via custom AnnotationIntrospector.

Also, are there any caveats about searching for another KeyDeserializer from a KeyDeserializer? https://github.com/ProjectMapK/jackson-module-kogera/pull/224/files#diff-b03a6f91081da3aa22c53b161b0a2f6f1a0cf4ba318329907046b5f5335b17c8R72-R73

Not sure; in general ideally would be resolved earlier and not during reading (in fact, it really should be done during construction/resolution for deserialization; sometimes not doable for serialization). But other than that I suspect it should be fine.

@k163377
Copy link
Contributor

k163377 commented Apr 27, 2024

except for @jsonkey annotation... use of which might work via custom AnnotationIntrospector.

Is this not a function for @JsonValue?
I wanted to make sure there is no such thing as @JsonKeyCreator.

@k163377
Copy link
Contributor

k163377 commented Apr 27, 2024

I will not implement this feature in kotlin-module until the following issues are resolved, as it will override custom implementations defined by the user.
FasterXML/jackson-databind#4444

I am unable to work on a fix as my busy state has become rather worse.
If anyone has a strong desire for this feature, I would appreciate it if you could fix it.

@cowtowncoder
Copy link
Member

Is this not a function for @JsonValue?
I wanted to make sure there is no such thing as @JsonKeyCreator

No, as name implies, it is for JSON values -- keys are specifically separate thing (from Jackson perspective), key names for java.util.Maps bound from JSON Object property names (vs property values). Part of the problem is that Property names are exposed as JsonToken.FIELD_NAME so regular value JsonDeserializer cannot be used; and thereby there is separate KeyDeserializer.
At any rate, handling is quite separate and as such I don't see how @JsonValue could be used outside limited cases (like with maybe Enum type)

But there are also other relevant annotations: @JsonDeserialize(keyUsing = <KeyDeserializer class> and @JsonSerialize(keyUsing = <key serializer class> that can be used on class to mark handler(s).

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