12

I am using Scala and Circe. I have the following sealed trait.

  sealed trait Mode
  case object Authentication extends Mode
  case object Ocr extends Mode

The output of this case object when called SessionModel.Authentication is the following:

"Authentication":{}

I need to convert this to a string so it outputs "authentication"

Kay
  • 11,044
  • 31
  • 100
  • 173
  • 5
    You can derive a decoder and an encoder using code like [here](https://github.com/plokhotnyuk/jsoniter-scala/blob/130f7e1d87d97cb244147c9d06c2ebe2b4dbc390/jsoniter-scala-benchmark/src/main/scala/com/github/plokhotnyuk/jsoniter_scala/benchmark/CirceEncodersDecoders.scala#L78) after adding [the `circe-generic-extra` dependency](https://github.com/plokhotnyuk/jsoniter-scala/blob/707080c370116fd4d925015b937fa23afd73915a/build.sbt#L140). – Andriy Plokhotnyuk Nov 28 '19 at 10:21
  • 2
    Also, to keep names in a lower case you can use a custom name transformer: implicit val (modeDecoder, modeEncoder) = { implicit val config: Configuration = Configuration.default.withDefaults.copy(transformConstructorNames = _.toLowerCase); (deriveEnumerationDecoder[Mode], deriveEnumerationEncoder[Mode]) } – Andriy Plokhotnyuk Nov 28 '19 at 10:39

1 Answers1

12

As Andriy Plokhotnyuk notes above, you can use circe-generic-extras:

import io.circe.Codec
import io.circe.generic.extras.Configuration
import io.circe.generic.extras.semiauto.deriveEnumerationCodec

sealed trait Mode
case object Authentication extends Mode
case object Ocr extends Mode

object Mode {
  private implicit val config: Configuration =
    Configuration.default.copy(transformConstructorNames = _.toLowerCase)

  implicit val modeCodec: Codec[Mode] = deriveEnumerationCodec[Mode]
}

And then:

scala> import io.circe.syntax._
import io.circe.syntax._

scala> (Authentication: Mode).asJson
res1: io.circe.Json = "authentication"

scala> io.circe.Decoder[Mode].decodeJson(res1)
res2: io.circe.Decoder.Result[Mode] = Right(Authentication)

(Note that Codec is new in 0.12β€”for earlier versions you'll have to write out both instances as in Andriy's comment.)

Unless you have a lot of these to maintain, though, I personally think writing the instances out by hand is often better than using circe-generic-extras, and in this case it's not even much more verbose:

import io.circe.{Decoder, Encoder}

sealed trait Mode
case object Authentication extends Mode
case object Ocr extends Mode

object Mode {
  implicit val decodeMode: Decoder[Mode] = Decoder[String].emap {
    case "authentication" => Right(Authentication)
    case "ocr"            => Right(Ocr)
    case other            => Left(s"Invalid mode: $other")
  }

  implicit val encodeMode: Encoder[Mode] = Encoder[String].contramap {
    case Authentication => "authentication"
    case Ocr            => "ocr"
  }
}

Which works exactly the same as the deriveEnumerationCodec version but doesn't require anything but circe-core, is less magical, compiles much faster, etc. Generic derivation can be great for simple case classes with straightforward mappings, but I think people too often try to stretch it to cover all cases when writing instances manually wouldn't be much of a burden and might even be clearer.

Travis Brown
  • 135,682
  • 12
  • 352
  • 654
  • As of circe 0.12.2, the implicit configuration doesn't seem to be automatically picked up, so only the second 'by hand' approach works for me. If you extend Product and Serializable: `sealed trait Mode extends Product with Serializable`, you'll be able to generate the strings with less boilerplate: `implicit val encodeMode: Encoder[Mode] = Encoder[String].contramap { _.productPrefix.toLowerCase }` – Sotomajor Jun 30 '20 at 06:58