jsonscalajacksonjackson2

Jackson scala module: deserialize case object enumeration


I am using Jackson scala module and I need to serialize/deserialize case object enumerations. The serialization works fine, but when deserializing back the values I get a com.fasterxml.jackson.databind.exc.InvalidDefinitionException. There is a way to make it work? I would like to avoid using "classic" scala enumerations, since I would lose the ability to define enumeration hierarchies.

Code used for the tests:

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


sealed trait Operations { @JsonValue def jsonValue: String }

case object Foo extends Operations { override def jsonValue: String = "Foo" }

case object Bar extends Operations { override def jsonValue: String = "Bar" }


sealed trait RichOperations extends Operations

case object Baz extends RichOperations { override def jsonValue: String = "Baz" }


object JsonUtils extends App {

  val jsonMapper = new ObjectMapper() with ScalaObjectMapper
  jsonMapper.registerModule(DefaultScalaModule)
    .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)

  def toJson(value: Any): String = jsonMapper.writeValueAsString(value)

  def fromJson[T: Manifest](json: String): T = jsonMapper.readValue[T](json)


  println(toJson(Foo)) // "Foo"
  println(toJson(Bar)) // "Bar"
  println(toJson(Baz)) // "Baz"
  println(fromJson[Operations](toJson(Foo))) // throws InvalidDefinitionException
  println(fromJson[Operations](toJson(Bar)))
  println(fromJson[RichOperations](toJson(Baz)))
}

Solution

  • I solved the problem defining custom deserializers.

    import com.fasterxml.jackson.core.JsonParser
    import com.fasterxml.jackson.databind.{DeserializationContext, JsonDeserializer}
    
    
    class OperationsDeserializer extends JsonDeserializer[Operations] {
    
      override def deserialize(p: JsonParser, ctxt: DeserializationContext): Operations = {
        p.getValueAsString match {
          case Foo.jsonValue => Foo
          case Bar.jsonValue => Bar
          case value => throw new IllegalArgumentException(s"Undefined deserializer for value: $value")
        }
      }
    }
    
    
    class RichOperationsDeserializer extends JsonDeserializer[Operations] {
    
      override def deserialize(p: JsonParser, ctxt: DeserializationContext): Operations = {
        p.getValueAsString match {
          case Foo.jsonValue => Foo
          case Bar.jsonValue => Bar
          case Baz.jsonValue => Baz
          case value => throw new IllegalArgumentException(s"Undefined deserializer for value: $value")
        }
      }
    }
    

    Moreover, since stable identifiers are required in match/case, I changed jsonValue from def to val: this unfortunately requires the declaration of @JsonValue annotation in each case object enumeration.

    import com.fasterxml.jackson.databind.annotation.JsonDeserialize
    
    
    @JsonDeserialize(using = classOf[OperationsDeserializer])
    sealed trait Operations { val jsonValue: String }
    
    case object Foo extends Operations { @JsonValue override val jsonValue: String = "Foo" }
    
    case object Bar extends Operations { @JsonValue override val jsonValue: String = "Bar" }
    
    
    @JsonDeserialize(using = classOf[RichOperationsDeserializer])
    sealed trait RichOperations extends Operations
    
    case object Baz extends RichOperations { @JsonValue override val jsonValue: String = "Baz" }