Jackson mapper with generic class in scala

2019-06-22 15:29发布

问题:

I am trying to serialise GeneralResponse:

case class GeneralResponse[T](succeeded: Boolean, payload: Option[T])

and the payload is GroupsForUserResult:

case class GroupsForUserResult(groups: Seq[UUID]).

I am using mapper.readValue(response.body, classOf[GeneralResponse[GroupsForUserResult]]) but unfortunately the payload is serialised as a Map and not as the desired case class (GroupForUserResult).

回答1:

Because of Java Erasure - Jackson can't know at runtime about the generic type T from the line -

mapper.readValue(response.body, classOf[GeneralResponse[GroupsForUserResult]])

A solution to this problem will be

import com.fasterxml.jackson.core.`type`.TypeReference

mapper.readValue(json, new TypeReference[GeneralResponse[GroupsForUserResult]] {})

This way you provide an instance of TypeReference with all the needed Type information.



回答2:

The accepted answer is close enough but you also have to provide the Type Parameter to .readValue method,

Working example with test,

import com.fasterxml.jackson.core.`type`.TypeReference
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import org.scalatest.{FunSuite, Matchers}

case class Customer[T](name: String, address: String, metadata: T)

case class Privileged(desc: String)

class ObjectMapperSpecs extends FunSuite with Matchers {

  test("deserialises to case class") {

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

    val value1 = new TypeReference[Customer[Privileged]] {}

    val response = objectMapper.readValue[Customer[Privileged]](
      """{
           "name": "prayagupd",
           "address": "myaddress",
           "metadata": { "desc" : "some description" }
         }
      """.stripMargin, new TypeReference[Customer[Privileged]] {})

    response.metadata.getClass shouldBe classOf[Privileged]
    response.metadata.desc shouldBe "some description"
  }

}

The signature of com.fasterxml.jackson.databind.ObjectMapper#readValue,

public <T> T readValue(String content, TypeReference valueTypeRef)
    throws IOException, JsonParseException, JsonMappingException
{
    return (T) _readMapAndClose(_jsonFactory.createParser(content), _typeFactory.constructType(valueTypeRef));
} 

If you don't provide the type parameter, it will blow up with error Customer cannot be cast to scala.runtime.Nothing$