1

I'm trying to write a method that will allow Jackson ObjectMapper readValue on a json string to a parameterized object type. Something like this

case class MyObj(field1: String, field2: String)

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

def fromJson[T](jsonString: String, objTyp: T): T = {

    objectMapper.readValue(jsonString, classOf[T])

}

val x = fromJson("""{"field1": "something", "field2": "something"}""", MyObj)

This of course returns an error of

class type required but T found

i've looked at this issue Scala classOf for type parameter but it doesn't seem to help. It seems like this is possible to do somehow. Looking for any help

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
J.Hammond
  • 251
  • 3
  • 17
  • Jackson Scala module already provides such helper methods, it's not clear why you need to reimplement them. – Gaël J Feb 15 '23 at 17:09

2 Answers2

1

You have to give it the actual runtime class to parse into, not just a type parameter.

One way to do it is passing the class directly:

def fromJson[T](json: String, clazz: Class[T]) = objectMapper.readValue[T](json, clazz)

val x = fromJson("""...""", classOf[MyObj])

Alternatively, you can use ClassTag, which looks a bit messier in implementation, but kinda prettier at call site:

def fromJson[T : ClassTag](json: String): T = objectMapper.readValue[T](
  json, 
  implicitly[ClassTag[T]].runtimeClass.asInstanceOf[Class[T]]
)

val x = fromJson[MyObj]("""{"field1": "something", "field2": "something"}""")
Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
Dima
  • 39,570
  • 6
  • 44
  • 70
1

i've looked at this issue Scala classOf for type parameter but it doesn't seem to help.

In the very first answer there it's written classTag[T].runtimeClass as a replacement of classOf[T]. This should help.

Regarding the signature

def fromJson[T](jsonString: String, objTyp: T): T

You should notice that MyObj has type MyObj.type (companion-object type), not MyObj (case-class type).

Class companion object vs. case class itself

So if you call fromJson("""...""", MyObj) then the types in these two places

def fromJson[...](jsonString: String, objTyp: ???): ???
                                              ^^^   ^^^  <--- HERE

can't be the same.

If it's enough for you to call

fromJson("""...""", classOf[MyObj])

or

fromJson[MyObj]("""...""")

(normally it should be enough) then please see @Dima's answer, you should prefer those options, they're easier.

Just in case, if you really want to call like fromJson("""...""", MyObj) then for example you can use the type class ToCompanion (this is more complicated) from

Invoke construcotr based on passed parameter

Get companion object of class by given generic type Scala (answer)

// ToCompanion should be defined in a different subproject

def fromJson[C, T](jsonString: String, objTyp: C)(implicit
  toCompanion: ToCompanion.Aux[C, T],
  classTag: ClassTag[T]
): T =
  objectMapper.readValue(jsonString, classTag.runtimeClass.asInstanceOf[Class[T]])

val x = fromJson("""{"field1": "something", "field2": "something"}""", MyObj)
// MyObj(something,something)
Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
  • Thank you for this answer, I checked @Dima's as that is what I actually implemented but your answer was really helpful in understanding the issue. – J.Hammond Feb 15 '23 at 18:29