Scala marshal to / unmarshal from JSON

Viewed 648

I have some utility code that marshals Scala objects to JSON and vice-versa. It works well, but it has started warning me that "ScalaObjectMapper is deprecated because Manifests are not supported in Scala3". It looks like I should use ClassTagExtensions instead, but it's not clear to me how.

Does anyone know how I can change this utility class to use ClassTagExtensions and achieve the convenient un/marshalling functionality it provides today?

import com.fasterxml.jackson.databind.{DeserializationFeature, ObjectMapper}
import com.fasterxml.jackson.module.scala.ScalaObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule

object JsonUtil {
  val mapper = new ObjectMapper() with ScalaObjectMapper
  // ScalaObjectMapper is deprecated because Manifests are not supported in Scala3
  mapper.registerModule(DefaultScalaModule)
  mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)

  def toJson(value: Map[Symbol, Any]): String = {
    toJson(value map { case (k, v) => k.name -> v })
  }

  def toJson(value: Any): String = {
    mapper.writeValueAsString(value)
  }

  def toMap[V](json: String)(implicit m: Manifest[V]): Map[String, V] = fromJson[Map[String, V]](json)

  def fromJson[T](json: String)(implicit m: Manifest[T]): T = {
    mapper.readValue[T](json)
  }
}

object MarshallableImplicits {

  implicit class Unmarshallable(unMarshallMe: String) {
    def toMap: Map[String,Any] = JsonUtil.toMap(unMarshallMe)
    def toMapOf[V]()(implicit m: Manifest[V]): Map[String,V] = JsonUtil.toMap[V](unMarshallMe)
    def fromJson[T]()(implicit m: Manifest[T]): T =  JsonUtil.fromJson[T](unMarshallMe)
  }

  implicit class Marshallable[T](marshallMe: T) {
    def toJson: String = JsonUtil.toJson(marshallMe)
  }
}

Usage - deserialize

import com.some.package.json.JsonUtil.fromJson
import com.some.package.json.MarshallableImplicits._
def deserialize(json: String): MyCaseClass = fromJson[MyCaseClass](json)

Usage - serialize:

import com.some.package.json.JsonUtil.fromJson
import com.some.package.json.MarshallableImplicits._
def serialize: String = this.toJson
1 Answers

The main idea is that you need to get rid of all implicit Manifest references.

As the implementation of ClassTagExtensions changed in Jackson Scala module 2.13.0, the answer will vary depending on the version you are using.

Jackson Scala module 2.13+:

Use com.fasterxml.jackson.module.scala.JavaTypeable as a replacement for Manifest, this fully supports nested generic types.

object JsonUtil {
  ...

  def toMap[V](json: String)(implicit m: JavaTypeable[V]): Map[String, V] = fromJson[Map[String, V]](json)

  def fromJson[T](json: String)(implicit m: JavaTypeable[T]): T = {
    mapper.readValue[T](json)
  }
}

Jackson Scala module 2.12:

Use ClassTag as a replacement for Manifest, this does not support nested generic types though and you might want to keep using the ScalaObjectMapper with Manifests.

object JsonUtil {
  ...

  def toMap[V](json: String)(implicit m: ClassTag[V]): Map[String, V] = fromJson[Map[String, V]](json)

  def fromJson[T](json: String)(implicit m: ClassTag[T]): T = {
    mapper.readValue[T](json)
  }
}

More details on the original issue: https://github.com/FasterXML/jackson-module-scala/issues/503

Related