Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jackson mapper with generic class in scala

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).

like image 407
Gal Avatar asked Apr 22 '15 19:04

Gal


People also ask

Should I declare Jackson's ObjectMapper as a static field?

Yes, that is safe and recommended.

What is ObjectMapper class in Jackson?

ObjectMapper is the main actor class of Jackson library. ObjectMapper class ObjectMapper provides functionality for reading and writing JSON, either to and from basic POJOs (Plain Old Java Objects), or to and from a general-purpose JSON Tree Model (JsonNode), as well as related functionality for performing conversions.

Is Fasterxml ObjectMapper thread-safe?

Mapper instances are fully thread-safe provided that ALL configuration of the instance occurs before ANY read or write calls.

What is Jackson's TypeReference?

Class TypeReference<T> This generic abstract class is used for obtaining full generics type information by sub-classing; it must be converted to ResolvedType implementation (implemented by JavaType from "databind" bundle) to be used.


2 Answers

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.

like image 132
Maxim Avatar answered Oct 25 '22 20:10

Maxim


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$

like image 25
prayagupa Avatar answered Oct 25 '22 21:10

prayagupa