Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Strange deserializing problems with generic types using Scala and Jackson and java.lang.Integer or scala.Int

We all know that generic types are subject to type erasure under Java and Scala. But we ran into a strange problem in Scala using Jackson and the Scala Jackson Module.

I created a small test to show the problem.

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule

object GenericTest {

  case class TestWithInt(id: Option[Int])
  case class TestWithInteger(id: Option[Integer])

  def main(args: Array[String]) {

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

    // Test with scala's Int
    val test = mapper.readValue[TestWithInt]("""{ "id" : 5 }""", classOf[TestWithInt])
    print("Test 1: ")
    println(test.id.get +  1)

    val test2 = mapper.readValue[TestWithInt]("""{ "id" : "5" }""", classOf[TestWithInt])
    print("Test 2: ")
    try {
      println(test2.id.get + 1)
    } catch {
      case e: ClassCastException => println(e.getMessage)
    }

    // Test with java.lang.Integer
    val test3 = mapper.readValue[TestWithInteger]("""{ "id" : 5 }""", classOf[TestWithInteger])
    print("Test 3: ")
    println(test3.id.get +  1)

    val test4 = mapper.readValue[TestWithInteger]("""{ "id" : "5" }""", classOf[TestWithInteger])
    print("Test 4: ")
    println(test4.id.get + 1)
  }
}

The output of the above is:

Test 1: 6
Test 2: java.lang.String cannot be cast to java.lang.Integer
Test 3: 6
Test 4: 6

Where does this different kind of behaviour come from? Generic Type Erasure, Jackson, Jackson Scala Module?

like image 630
longliveenduro Avatar asked Oct 15 '13 11:10

longliveenduro


1 Answers

This is becoming such a common question that I wrote a FAQ for it:

[A]ll primitive type parameters are represented as Object to the JVM. ... The Scala module has informed Jackson that Option is effectively a container type, but it relies on Java reflection to determine the contained type, and comes up with Object.

The current workaround for this use case is to add the @JsonDeserialize annotation to the member being targeted. Specifically, this annotation has a set of parameters that can be used for different situations:

  • contentAs for collections or map values (supported)
  • keyAs for Map keys (currently unsupported)

Examples of how to use this annotation can be found in the tests directory.

There's a lot more detail in the FAQ for the curious.

like image 101
Christopher Currie Avatar answered Sep 20 '22 12:09

Christopher Currie