Skip to content

Parameterized type violated when deserializing an Option #243

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
salokanto opened this issue Mar 18, 2016 · 7 comments
Open

Parameterized type violated when deserializing an Option #243

salokanto opened this issue Mar 18, 2016 · 7 comments

Comments

@salokanto
Copy link

I can deserialize just about anything into an Option:

case class Foo(integer: Option[Int])

object OptionFail {
  val mapper = new ObjectMapper() with ScalaObjectMapper
  mapper.registerModule(DefaultScalaModule)

  def main(args: Array[String]) {
    val json = """{"integer" : "a cat"}"""
    println(mapper.readValue[Foo](json))
    // prints: Foo(Some(a cat))
  }
}

Using:

libraryDependencies ++= Seq(
  "com.fasterxml.jackson.core" % "jackson-databind" % "2.7.3",
  "com.fasterxml.jackson.module" % "jackson-module-scala_2.11" % "2.7.2"
)
@chasebradford
Copy link

  1. ObjectMapper is a java class with a generic readValue call. It doesn't have any runtime type information, so you have to give it some by passing the class. mapper.readValue(json, classOf[Foo])
  2. Passing classOf or a TypeReference is usually good enough to get deep generic type info. Unfortunately, generics with primitives suffer from the way scala encodes those types. See: https://github.com/FasterXML/jackson-module-scala/wiki/FAQ#deserializing-optionint-and-other-primitive-challenges

@kralikba
Copy link

Unfortunately this latter is not a solution for "deeper" types, e.g. Option[Seq[Int]]. Jackson believes that it is not a compatible type (that being a collection-like type of a collection-like type of a simple type). I've not yet had time to dig deeper, but I suspect that it is a nontrivial fix.

@chasebradford
Copy link

I doubt there is a fix for the nested container types. The JVM sees that as Option[Seq[Object]]. Scala just doesn't provide enough type information to figure that out, and the JsonDeserialize option only works for one layer down.

@nbauernfeind
Copy link
Member

@kralikba I'm not sure I understand what you mean by "deeper" types. I would expect Option[Seq[T]] to deserialize (and serialize) correctly. Int as T might have issues as @chasebradford points out that generic types using primitives suffer from how scala encodes them (due to boxing on your behalf). Can you provide an example of what you want to do that jackson is having trouble with?

@kralikba
Copy link

@nbauernfeind E.g. the following example:

  val mapper = new ObjectMapper with ScalaObjectMapper
  mapper.registerModule(DefaultScalaModule)

  case class Y(os: Option[Seq[Int]])
  println(mapper.readValue[Y]("{}"))

  case class X(@JsonDeserialize(contentAs = classOf[java.lang.Integer]) os: Option[Seq[Int]])
  println(mapper.readValue[X]("{}"))

Outputs Y(None), then fails with the following exception:

Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: 
Failed to narrow value type of [collection-like type; class scala.Option, contains [collection-like type; class scala.collection.Seq, contains [simple type, class java.lang.Object]]] 
with concrete-type annotation (value java.lang.Integer), from '': 
Class java.lang.Integer not subtype of [collection-like type; class scala.collection.Seq, contains [simple type, class java.lang.Object]]

@nbauernfeind
Copy link
Member

Thanks for your example. Class X is an invalid use of contentAs since you're declaring the content of the Option to be an Int, not the content of the Seq to be an int. (I'm sure you already know this as you're trying to say that it is not a work around.)

I've verified, as @chasebradford says, that the JVM/Jackson is only able to detect the Option as an Option[Seq[Object]] (instead of Option[Seq[Int]]). Similarly I tried with just Option[Int] or Seq[Int] with the same results. The byte codes makes this look like it is a java.lang.Object.

The code does indeed throw the expected exception if you directly use a TypeReference via mapper.readValue[Option[Int]]("\"a cat\"").

I believe that this can be improved by using Scala reflection in scala 2.11+, and I would like to do this during/for Jackson 2.8.x. Until then, however, I do not think I can make this any better. I will keep it at the top of my mind though. Maybe @cowtowncoder has some ideas that we're not yet taking advantage of.

Your [suboptimal] work arounds:

  • You may wish to use Option[Seq[java.lang.Integer]] in the meantime.
  • You can create a case class SeqInt(@JsonDeserialize(contentAs = classOf[java.lang.Integer] value: Seq[Int]) and use it case class Y(os: Option[SeqInt]) but unfortunately the json has one extraneous level of indirection.

@pjfanning
Copy link
Member

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

5 participants