FasterXML / jackson-module-scala

Add-on module for Jackson (https://github.com/FasterXML/jackson) to support Scala-specific datatypes
Apache License 2.0
502 stars 142 forks source link

Case class deserialized as Map2 for unusual path-dependent types #366

Open OndrejSpanel opened 6 years ago

OndrejSpanel commented 6 years ago

Deserialization fails in an unexpected way in following code. No exception is thrown, however from field of Container is deserialized as Map instead of XY, the program outputs:

Container(Map(x -> 0.0, y -> 0.0))

package my.jackpack

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import com.fasterxml.jackson.module.scala.experimental.ScalaObjectMapper

case class XY(x: Double = 0, y: Double = 0)

trait Abstract {
  type Vector2f

  trait ConstructVector2f {
    def apply(x: Double, y: Double): Vector2f
  }
  implicit val Vector2f: ConstructVector2f
}

object Concrete extends Abstract {
  type Vector2f = XY
  object Vector2f extends ConstructVector2f {
    def apply(x: Double, y: Double) = XY(x, y)
  }
}

object Abstract {
  val Link: Abstract = Concrete
}

import Abstract.Link.Vector2f

case class Container(from: Vector2f)

object Main extends App {

  val mapper = new ObjectMapper with ScalaObjectMapper

  mapper.registerModule(DefaultScalaModule)

  val input = Container(Vector2f(0,0))

  val out = mapper.writeValueAsString(input)

  val loaded = mapper.readValue[Container](out)

  val xy: Vector2f = loaded.from

  println(loaded) // prints "Container(Map(x -> 0.0, y -> 0.0))" instead of "Container(XY(0.0,0.0))"
  assert(xy.isInstanceOf[XY]) // fails
}

This happens only if the Vector2f is used as import Abstract.Link.Vector2f. Once you change it to Concrete.Vector2f it works fine.

nbauernfeind commented 6 years ago

I'm not quite sure how this is supposed to work.

For example, the compiler doesn't even know what type the imported Vector2f is:

    println(manifest[Vector2f])

Yields the errors:

Error:(41, 21) No Manifest available for Abstract.Link.Vector2f.
    println(manifest[Vector2f])

It knows what the type is supposed to be if your link doesn't cast to the super:

object Abstract {
  val Link = Concrete
}

Or

object Abstract {
  val Link: Concrete.type = Concrete
}

The only way that the compiler is figuring out how to construct an XY in val input = Container(Vector2f(0,0)) is because of the companion object that you've fabricated to get around the fact that the type is unknown in the abstract class.

I think you're going to want to approach this problem from another angle. Is this something you're still trying to work on?