Skip to content
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

Option Deserialization issue with param names that have dashes #438

Closed
pmpinson opened this issue Nov 13, 2019 · 4 comments
Closed

Option Deserialization issue with param names that have dashes #438

pmpinson opened this issue Nov 13, 2019 · 4 comments

Comments

@pmpinson
Copy link

pmpinson commented Nov 13, 2019

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

@pjfanning pjfanning changed the title Option Deserialization Option Deserialization issue with param names that have dahes Dec 29, 2019
@pjfanning pjfanning changed the title Option Deserialization issue with param names that have dahes Option Deserialization issue with param names that have dashes Dec 29, 2019
@pjfanning
Copy link
Member

I added a test case (ParamWithDashNameDeserializerTest) to the 2.10 branch and the issue appears to be that when deserialising, Jackson includes an Integer in the Option instead of a Long - and this leads to an unboxing error when you try to use the Option instance.

[info] - should support param names with dashes *** FAILED ***
[info]   java.lang.ClassCastException: class java.lang.Integer cannot be cast to class java.lang.Long (java.lang.Integer and java.lang.Long are in module java.base of loader 'bootstrap')
[info]   at scala.runtime.BoxesRunTime.unboxToLong(BoxesRunTime.java:107)
[info]   at scala.runtime.java8.JFunction1$mcJJ$sp.apply(JFunction1$mcJJ$sp.java:23)
[info]   at scala.Option.map(Option.scala:230)
[info]   at com.fasterxml.jackson.module.scala.deser.ParamWithDashNameDeserializerTest.useOptionLong(ParamWithDashNameDeserializerTest.scala:22)
[info]   at com.fasterxml.jackson.module.scala.deser.ParamWithDashNameDeserializerTest.$anonfun$new$2(ParamWithDashNameDeserializerTest.scala:47)
[info]   at org.scalatest.OutcomeOf.outcomeOf(OutcomeOf.scala:85)
[info]   at org.scalatest.OutcomeOf.outcomeOf$(OutcomeOf.scala:83)
[info]   at org.scalatest.OutcomeOf$.outcomeOf(OutcomeOf.scala:104)
[info]   at org.scalatest.Transformer.apply(Transformer.scala:22)
[info]   at org.scalatest.Transformer.apply(Transformer.scala:20)
[info]   ...

@PedroCorreiaLuis
Copy link

That might have to do with the Java variables naming, you can't have dashes in Java variables.
Maybe a work around would be to replace the dashes with underscores do the deserialisation and then replace again the underscores with dashes, not sure if that is a "good" solution, or if the correct thing to do is just improve the exception that it gives, by saying something like: " You cannot deserialise scala-like name variables".
That should also happen when the variable is like this: 1Star, Spark as the same problem btw.

@pjfanning
Copy link
Member

This appears to work ok in v2.12 - so closing

@pjfanning
Copy link
Member

there was a remaining issue when Scala 2.11 was used - I have a fix for this in Jackson-Module-Scala 2.12.2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants