jackson-module-scala
jackson-module-scala copied to clipboard
Collection of case classes deserialized as Collection of Map2
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.
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.
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.
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.
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.