Skip to content

Option Deserialization issue with param names that have dashes #438

@pmpinson

Description

@pmpinson

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.
My case class look like this (adapt like the one in the test case https://github.com/FasterXML/jackson-module-scala/blob/master/src/test/scala/com/fasterxml/jackson/module/scala/deser/PrimitiveContainerTest.scala)

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
  }

}

Initial question from google groups https://groups.google.com/forum/?utm_medium=email&utm_source=footer#!msg/jackson-user/NKSx88srl-g/X4Ea2PgfAwAJ

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions