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

Collection of case classes deserialized as Collection of Map2 #650

Open
jameskyle opened this issue Oct 9, 2023 · 4 comments
Open

Collection of case classes deserialized as Collection of Map2 #650

jameskyle opened this issue Oct 9, 2023 · 4 comments

Comments

@jameskyle
Copy link

import com.fasterxml.jackson.databind.{ObjectMapper, SerializationFeature}
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import com.fasterxml.jackson.databind.json.{JsonMapper}

case class B(a: String, b: String)

val mapper = JsonMapper.builder().addModule(DefaultScalaModule).build()

mapper.writeValueAsString(B("a", "b"))
// "{\"a\":\"a\",\"b\":\"b\"}"

val s = mapper.writeValueAsString(List(B("a", "b"), B("c", "d")))
// "[{\"a\":\"a\",\"b\":\"b\"},{\"a\":\"c\",\"b\":\"d\"}]"

mapper.readValue(s, classOf[List[B]])
// List(Map("a" -> "a", "b" -> "b"), Map("a" -> "c", "b" -> "d"))

Any attempt to use the values throws a cast error. Unable to cast Map2 to B.

@pjfanning
Copy link
Member

pjfanning commented Oct 9, 2023

Try this:

    val mapper = JsonMapper.builder().addModule(DefaultScalaModule).build() :: ClassTagExtensions
    val data = List(B("a", "b"), B("c", "d"))
    val s = mapper.writeValueAsString(data)
    mapper.readValue[List[B]](s) shouldEqual data

Jackson is a Java library based on Java Reflection. This approach is problematic due to Type Erasure.

ClassTagExtensions uses Scala ClassTags to preserve more of the type information.

@pjfanning
Copy link
Member

This also seems to work:

    val mapper = JsonMapper.builder().addModule(DefaultScalaModule).build()
    val data = List(B("a", "b"), B("c", "d"))
    val s = mapper.writeValueAsString(data)
    mapper.readValue(s, new TypeReference[List[B]] {}) shouldEqual data

The TypeReference is another way of preserving type information that would otherwise by lost to Type Erasure.

@jameskyle
Copy link
Author

Great! I assumed it was a type erasure issue.

I had some issues trying ClassYagExtension that were unrelated to the immediate problem (probably build environment related).

I'll give the Type Reference approach a try.

@jameskyle
Copy link
Author

Try this:

    val mapper = JsonMapper.builder().addModule(DefaultScalaModule).build() :: ClassTagExtensions
    val data = List(B("a", "b"), B("c", "d"))
    val s = mapper.writeValueAsString(data)
    mapper.readValue[List[B]](s) shouldEqual data

Jackson is a Java library based on Java Reflection. This approach is problematic due to Type Erasure.

ClassTagExtensions uses Scala ClassTags to preserve more of the type information.

Just reporting back, this doesn't actually pass

  it should "encode and decode consistently using class tag extension" in {
    case class B(a: String, b: String)
    val mapper = JsonMapper.builder().addModule(DefaultScalaModule).build() :: ClassTagExtensions
    val data = List(B("a", "b"), B("c", "d"))
    val s = mapper.writeValueAsString(data)
    mapper.readValue[List[B]](s) shouldEqual data
  }

Produces the following failed test

List(Map("a" -> "a", "b" -> "b"), Map("a" -> "c", "b" -> "d")) did not equal List(B(a,b), B(c,d))
ScalaTestFailureLocation: com.safegraph.galaxy.maps.elastic_fusion.entities.self_serve.SelfServeDataGenerationConfigTest at (SelfServeDataGenerationConfigTest.scala:24)
Expected :List(B(a,b), B(c,d))
Actual   :List(Map("a" -> "a", "b" -> "b"), Map("a" -> "c", "b" -> "d"))

But this one works..

This also seems to work:

val mapper = JsonMapper.builder().addModule(DefaultScalaModule).build()
val data = List(B("a", "b"), B("c", "d"))
val s = mapper.writeValueAsString(data)
mapper.readValue(s, new TypeReference[List[B]] {}) shouldEqual data
The TypeReference is another way of preserving type information that would > otherwise by lost to Type Erasure.

Any idea why the first doesn't? I ran the example in a repl and it did seem to work. But not as a unit test.

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

2 participants