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

Cannot deserialize abstract class with type parameter #435

Open
alexblickenstaff opened this issue Oct 22, 2019 · 1 comment
Open

Cannot deserialize abstract class with type parameter #435

alexblickenstaff opened this issue Oct 22, 2019 · 1 comment

Comments

@alexblickenstaff
Copy link

I'm trying to serialize and deserialize an abstract class that has a type parameter, but the deserialization isn't working. Instead of deserializing the JSON into an object of the type parameter's class, Jackson deserializes it into a Map, so I'm unable to read the object's properties.

Here is the code:

import com.fasterxml.jackson.annotation.{JsonSubTypes, JsonTypeInfo}
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule

object GithubExample {

  @JsonTypeInfo(
    use = JsonTypeInfo.Id.NAME,
    include = JsonTypeInfo.As.PROPERTY,
    property = "@type")
  @JsonSubTypes(Array(
    new JsonSubTypes.Type(value = classOf[ResultWrapperSuccess[_]]),
    new JsonSubTypes.Type(value = classOf[ResultWrapperFailure[_]])
  ))
  trait ResultWrapperInterface[T] {
    protected def obj: T
  }

  case class ResultWrapperSuccess[T](result: T) extends ResultWrapperInterface[T] {
    override protected def obj: T = result
  }

  case class ResultWrapperFailure[F](failure: F) extends ResultWrapperInterface[F] {
    override protected def obj: F = failure
  }

  case class User(name: String, age: Option[Int])

  def main(args: Array[String]): Unit = {

    val mapper = new ObjectMapper()
    mapper.registerModule(DefaultScalaModule)

    val user = User("John Smith", Some(39))
    val serializedUser = mapper.writeValueAsString(user)
    println(s"(1)        serializedUser: $serializedUser")

    val deserializedUser = mapper.readValue(serializedUser, classOf[User])
    println(s"(2)      deserializedUser: $deserializedUser")
    println(s"(3) deserializedUser.name: ${deserializedUser.name}")


    val wrapperSuccess = ResultWrapperSuccess[User](user)
    val serializedSuccess = mapper.writeValueAsString(wrapperSuccess)
    println(s"(4)     serializedSuccess: $serializedSuccess")

    val deserializedSuccess = mapper.readValue(serializedSuccess, classOf[ResultWrapperInterface[User]])
    deserializedSuccess match {
      case _: ResultWrapperFailure[_] =>
      case success: ResultWrapperSuccess[User] =>
        println(s"(5)               success: $success")
        println(s"(6)        success.result: ${success.result}")
        println(s"(7)   success.result.name: ${success.result.name}")
    }

  }

}

The first part when we serialize and deserialize the User object works just fine. The code breaks on (7) when it tries to access success.result.name because success.result is somehow a Map instead of a User.

Here is the output:

(1)        serializedUser: {"name":"John Smith","age":39}
(2)      deserializedUser: User(John Smith,Some(39))
(3) deserializedUser.name: John Smith
(4)     serializedSuccess: {"@type":"GithubExample$ResultWrapperSuccess","result":{"name":"John Smith","age":39}}
(5)               success: ResultWrapperSuccess(Map(name -> John Smith, age -> 39))
(6)        success.result: Map(name -> John Smith, age -> 39)
Exception in thread "main" java.lang.ClassCastException: scala.collection.immutable.Map$Map2 cannot be cast to GithubExample$User
    at GithubExample$.main(GithubExample.scala:55)
    at GithubExample.main(GithubExample.scala)

As evidenced by the logs, the serialization seems to be working just fine. Is there something I need to change to get the deserialization working?

@cowtowncoder
Copy link
Member

cowtowncoder commented Oct 22, 2019

Combination of generic type and polymorphic type handling is not supported by jackson-databind (both are supported separately, just not in this combination), so you would need to refactor things to achieve polymorphism separately for wrapper (to get success/fail subtypes), and then for enclosed value type (using @JsonTypeInfo on result / failure property).

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