Skip to content

Cannot deserialize a Map whose key is a case class? #251

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

Open
ms-ati opened this issue Apr 12, 2016 · 7 comments
Open

Cannot deserialize a Map whose key is a case class? #251

ms-ati opened this issue Apr 12, 2016 · 7 comments

Comments

@ms-ati
Copy link

ms-ati commented Apr 12, 2016

Are Maps with keys that are simple case classes (of one primitive field) supported?

import com.fasterxml.jackson.databind._
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import com.fasterxml.jackson.module.scala.experimental.ScalaObjectMapper

val mapper = (new ObjectMapper() with ScalaObjectMapper).
        registerModule(DefaultScalaModule).
        configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).
        findAndRegisterModules(). // register joda and java-time modules automatically
        asInstanceOf[ObjectMapper with ScalaObjectMapper]

case class Foo(n: Int)

val m: Map[Foo, String] = Map(Foo(1) -> "bar")

val s = mapper.writeValueAsString(m)
// s: String = {"Foo(1)":"bar"}

mapper.readValue[Map[Foo, String]](s)
// com.fasterxml.jackson.databind.JsonMappingException: Can not find a (Map) Key deserializer for type [simple type, class Foo]
// at [Source: {"Foo(1)":"bar"}; line: 1, column: 1]
// at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:244)
// at com.fasterxml.jackson.databind.deser.DeserializerCache._handleUnknownKeyDeserializer(DeserializerCache.java:587)
// at com.fasterxml.jackson.databind.deser.DeserializerCache.findKeyDeserializer(DeserializerCache.java:168)
// at com.fasterxml.jackson.databind.DeserializationContext.findKeyDeserializer(DeserializationContext.java:500)
// at com.fasterxml.jackson.module.scala.deser.UnsortedMapDeserializer$$anonfun$1.apply(UnsortedMapDeserializerModule.scala:70)
// at com.fasterxml.jackson.module.scala.deser.UnsortedMapDeserializer$$anonfun$1.apply(UnsortedMapDeserializerModule.scala:70)
// at scala.Option.getOrElse(Option.scala:121)
// at com.fasterxml.jackson.module.scala.deser.UnsortedMapDeserializer.createContextual(UnsortedMapDeserializerModule.scala:70)
// at com.fasterxml.jackson.module.scala.deser.UnsortedMapDeserializer.createContextual(UnsortedMapDeserializerModule.scala:39)
// at com.fasterxml.jackson.databind.DeserializationContext.handleSecondaryContextualization(DeserializationContext.java:685)
// at com.fasterxml.jackson.databind.DeserializationContext.findRootValueDeserializer(DeserializationContext.java:482)
// at com.fasterxml.jackson.databind.ObjectMapper._findRootDeserializer(ObjectMapper.java:3890)
// at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3785)
// at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2817)
// at com.fasterxml.jackson.module.scala.experimental.ScalaObjectMapper$class.readValue(ScalaObjectMapper.scala:184)
// at $anon$1.readValue(<console>:17)
// ... 42 elided
@nbauernfeind
Copy link
Member

Unfortunately, json does not support objects as keys. What is happening is that Foo(1) is being stringified as "Foo(1)" (the same as calling .toString on the object) before writing it as the json key. It is not possible to deserialize "Foo(1)" as an instance of Foo of 1.

I can imagine that a value class (i.e. a case class that extends AnyVal) of an integer or a string would be a desirable key, but currently the module does not support this behavior.

Does that make sense?

@ms-tg
Copy link

ms-tg commented Apr 14, 2016

Ah, that does make sense @nbauernfeind, thank you. In this case I guess the best approach is to have an intermediate data structure then, for (de-)serialization, which uses the unwrapped values for keys?

@nbauernfeind
Copy link
Member

It really depends on what you are trying to do, but I find myself doing something like this instead:

val keyMap = Map[String, KeyType]()
val valMap = Map[String, ValType]()

Or if you really are using value classes, then write your own accessors/modifiers that strip out the id from the value class.

@ms-tg
Copy link

ms-tg commented Apr 14, 2016

We are using value classes. But there's enough limitations with Jackson and Scala that I found it easier just to have transformations to/from simplified representations for Jackson.

@nbauernfeind
Copy link
Member

If I'm remembering correctly, it is not possible to tell the difference between value classes and primitives without using scala reflection. However scala 2.10's reflection is not thread safe which makes it a non-starter. I don't think we will be able to support them without carving a 2.10 branch, which won't support the new scala things, or waiting until we stop supporting 2.10.

@cowtowncoder
Copy link
Member

Not sure if this helps, but it is possible to add custom key serializers, deserializers, either for types, or for Map-valued properties. Other custom serializers, deserializers are only used for values; for deserialization there is separate KeyDeserializer, for serialization JsonSerializer is used, but it has to use different write method. So this is why separate ones for keys are needed.

Default set of handlers is quite small (most primitives, enums, Dates), and more extensive for one direction (serialization I think).

@ms-tg
Copy link

ms-tg commented Apr 14, 2016

Yeah I get it. Once 2.10 is not supported, a lot more stuff can be done in this module. As it stands, I contend that transforming to a simplified representation is a lot easier than getting the various custom KeyDeserializer and JsonSerializers all correct...

@nbauernfeind nbauernfeind added this to the 2.8.0 milestone Apr 19, 2016
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