Jackson scala module Option Deserialization Info/Issue

22 views
Skip to first unread message

Pierre-Marie Pinson

unread,
Nov 7, 2019, 1:30:09 PM11/7/19
to jackson-user
Hi,

I am using "com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.10.0"

I am using a deserializer to read json message post into kafka.
At a time we faced a java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.Long.
I dig into this issue and found the related issues in github and the FAQ https://github.com/FasterXML/jackson-module-scala/wiki/FAQ that indicate to annotate offending field with @JsonDeserialize.
I tried this with no success.
case class AnnotatedOptionLongWithDash(@JsonDeserialize(contentAs = classOf[java.lang.Long]) `value-long`: Option[Long])

I try to understand why the test in scala module works but not our code and found that this is because the property contain a dash and we kept the dash in the scala props (Disgusting!).
I finally try to add @JsonProperty annotation to remove the dash and it's working.

I am not sure this issue is related to jackson deserialization and can be fix or it's scala/java that do something when a property have a dash in it.
At least it can help to add a note about this in the workarounds of the FAQ.

Here some tests case to reproduce the issue.

import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import com.fasterxml.jackson.module.scala.{DefaultScalaModule, ScalaObjectMapper}
import org.scalatest.{Matchers, WordSpec}

case class AnnotatedOptionLong(@JsonDeserialize(contentAs = classOf[java.lang.Long]) valueLong: Option[Long])

case class AnnotatedOptionLongWithDash(@JsonDeserialize(contentAs = classOf[java.lang.Long]) `value-long`: Option[Long])

case class AnnotatedOptionLongWithDashButChangeToCamelCase(@JsonProperty("value-long") @JsonDeserialize(contentAs = classOf[java.lang.Long]) valueLong: Option[Long])

class JacksonSerializationIssueTest extends WordSpec with Matchers {

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

 
def deserialize[T](data: Array[Byte])(implicit m: Manifest[T]): T = {
   
try {
     
objectMapper.readValue(data)
   
} catch {
     
case e: Throwable =>
       
throw new RuntimeException("Error deserializing JSON message", e)
   
}
 
}

 
def serialize[T](data: T): Array[Byte] = {
   
try {
     
objectMapper.writeValueAsBytes(data)
   
} catch {
     
case e: Throwable =>
       
throw new RuntimeException("Error serializing JSON message", e)
   
}
 
}

 
def useOptionLong(v: Option[Long]): Long = v.map(_ * 2).getOrElse(0)

 
"same as in test source of jackon library" in {
   
// check deserialization
    val v1 = deserialize[AnnotatedOptionLong]("""{"valueLong":151}""".getBytes)
    v1 shouldBe
AnnotatedOptionLong(Some(151L))
    v1
.valueLong.get shouldBe 151L

    // serialize from case class then deserialize and then apply the method that will fail
    val v2 = JacksonMapper.deserialize[AnnotatedOptionLong](JacksonMapper.serialize(AnnotatedOptionLong(Some(152))))
    v2 shouldBe
AnnotatedOptionLong(Some(152L))
    v2
.valueLong.get shouldBe 152L
    useOptionLong(v2.valueLong) shouldBe 304
  }

 
"failing test because of backtick prop name either if we apply the annotation @JsonDeserialize(contentAs = classOf[java.lang.Long]) " in {
   
// check deserialization
    val v1 = deserialize[AnnotatedOptionLongWithDash]("""{"value-long":251}""".getBytes)
    v1 shouldBe
AnnotatedOptionLongWithDash(Some(251L))
    v1
.`value-long`.get shouldBe 251L

    // serialize from case class then deserialize and then apply the method that will fail
    val v2 = JacksonMapper.deserialize[AnnotatedOptionLongWithDash](JacksonMapper.serialize(AnnotatedOptionLongWithDash(Some(252))))
    v2 shouldBe
AnnotatedOptionLongWithDash(Some(252L))
    v2
.`value-long`.get shouldBe 252L
    useOptionLong(v2.`value-long`) shouldBe 504
  }

 
"working solution because we rename the prop with a dash to a camel case prop" in {
   
// check deserialization
    val v1 = deserialize[AnnotatedOptionLongWithDashButChangeToCamelCase]("""{"value-long":351}""".getBytes)
    v1 shouldBe
AnnotatedOptionLongWithDashButChangeToCamelCase(Some(351L))
    v1
.valueLong.get shouldBe 351L

    // serialize from case class then deserialize and then apply the method that will fail
    val v2 = JacksonMapper.deserialize[AnnotatedOptionLongWithDashButChangeToCamelCase](JacksonMapper.serialize(AnnotatedOptionLongWithDashButChangeToCamelCase(Some(352))))
    v2 shouldBe
AnnotatedOptionLongWithDashButChangeToCamelCase(Some(352L))
    v2
.valueLong.get shouldBe 352L
    useOptionLong(v2.valueLong) shouldBe 704
  }

}


PJ Fanning

unread,
Nov 12, 2019, 5:02:27 PM11/12/19
to jackson-user
Hi Pierre-Marie,

Pierre-Marie Pinson

unread,
Nov 13, 2019, 2:28:20 PM11/13/19
to jackson-user
Reply all
Reply to author
Forward
0 new messages