0

I have an Enumeration in Scala

object Status extends Enumeration {
  type Status = Value
  val Success = Value
  val Error = Value
}

This is used in the below -

case class Response(
    status: Status,
    errorMessage: String
)

I want to store Response in a file. So, I am using Jackson object mapper (com.fasterxml.jackson.databind.ObjectMapper ) to serialize it.

writeOutputToFile(filePath: Path , objectMapper.writeValueAsString(response)) 

However, object mapper writes an empty json to the file. I know object mapper requires a getter method to serialize. Is that why this is failing? Would I need a custom object mapper?

maddie
  • 629
  • 10
  • 29
  • Does https://stackoverflow.com/questions/28270621/using-jackson-to-de-serialize-a-scala-case-class answer your question? – Mateusz Kubuszok May 18 '20 at 20:58
  • 1
    Just a part of it. My main concern was about the Enumeration class. ObjectMapper prints it as `{"status":{"scala$Enumeration$Val$$i":0},"message":null}` – maddie May 18 '20 at 21:26
  • 1
    Jackson is Java library and it doesn't understand how Scala defines things. Which is why wrapper might help. Though the easy way is wrapper and no-`Enumeration` – Mateusz Kubuszok May 18 '20 at 21:38
  • You might prefer a sealed trait with case objects – user May 18 '20 at 21:56

1 Answers1

0

You can define generic serializer and deserializer for all enums and then for each of them register corresponding pairs of instances:

class EnumSerializer[T <: scala.Enumeration](e: T) extends JsonSerializer[T#Value] {
  override def serialize(x: T#Value, jg: JsonGenerator, spro: SerializerProvider): Unit = 
    jg.writeString(x.toString)
}

class EnumDeserializer[T <: scala.Enumeration](e: T) extends JsonDeserializer[T#Value] {
  private[this] val ec = new ConcurrentHashMap[String, T#Value]

  override def deserialize(jp: JsonParser, ctxt: DeserializationContext): T#Value = Try {
    val s = jp.getValueAsString
    var x = ec.get(s)
    if (x eq null) {
      x = e.values.iterator.find(_.toString == s).get
      ec.put(s, x)
    }
    x
  }.getOrElse(ctxt.handleUnexpectedToken(classOf[T#Value], jp).asInstanceOf[T#Value])
}

val objectMapper: ObjectMapper with ScalaObjectMapper = {
  val jsonFactory = new JsonFactoryBuilder()
    .configure(...)
    .build()
  new ObjectMapper(jsonFactory) with ScalaObjectMapper {
    registerModule(DefaultScalaModule)
    registerModule(new SimpleModule()
      .addSerializer(classOf[Status], new EnumSerializer(Status))
      .addDeserializer(classOf[Status] new EnumDeserializer(Status))
      ...
    )
  }
}

The proposed solution is much safe and more efficient in runtime than just using of Status.withName(s) for each deserialization.

It will work even for the dynamic definition of enum values, like:

object Status extends Enumeration {
  type Status = Value
  val Success = Value
  val Error = Value

  def extra(name: String): Status = Value(nextId, name)
}

Status.extra("Unknown")

Full sources are here.

Andriy Plokhotnyuk
  • 7,883
  • 2
  • 44
  • 68