simou
simou

Reputation: 2506

Circe and Scala's Enumeration type

I'm trying to wrap my head around Circe.

So, here's the model I've been given:

object Gender extends Enumeration {
     type Gender = Value
     val Male, Female, Unisex, Unknown = Value
}

case class Product(id: String, gender: Gender.Value)

I want to

a) encode the following example as JSON

        val product = Product(id = "1234", gender = Gender.Female)

b) map the resulting JSON back onto the Product case class.


My own attempt didn't get me far:

  object JsonProtocol {
      implicit val productDecoder: Decoder[Product] = deriveDecoder
      implicit val productEncoder: Encoder[Product] = deriveEncoder
  }

result is a compile time error

   Error:(52, 49) could not find Lazy implicit value of type io.circe.generic.decoding.DerivedDecoder[A]
   implicit val productDecoder: Decoder[Product] = deriveDecoder
                                            ^

I've no idea why this exception is thrown and what the solution could look like. Maybe it's the usage of the Enumeration type?

Upvotes: 16

Views: 13777

Answers (4)

pme
pme

Reputation: 14803

For Scala 3 there is now a solution since version 0.14.5 within Circe.

There is a great Blog explaining this: https://scalajobs.com/blog/enum-serialization-in-scala/

The code from the blog:

import io.circe.Codec
import io.circe.derivation.Configuration

given Configuration = Configuration.default
    .withDiscriminator("type")
    .withTransformConstructorNames(_.toUpperCase)​
enum Role {
  case Reader(subscription: Subscription)
  case Editor(profileBio: String, favoriteFont: String)
  case Admin
}
object Role {    ​
  given Codec[Role] = Codec.AsObject.derivedConfigured
}

This creates:

{"type":"TESTER","subscription":"GENESIS"}

If you just have simple Enums you can use the following:

import io.circe.derivation.{Configuration, ConfiguredEnumCodec}

given Configuration = Configuration.default
enum TestOverrideType derives ConfiguredEnumCodec:
  case Exists, NotExists, IsEquals, HasSize

This creates:

"NotExists"

Thanks for the help of Lasering

My earlier answer:

For Scala 3 there is no solution today within Circe.

However there is a library that works nicely: circe-tagged-adt-codec

Here an example that works for me (the rest I do with semiautomatic derivation from Circe):

enum TestOverrideType derives JsonTaggedAdt.PureEncoder:
  case Exists, NotExists, IsEquals, HasSize

Upvotes: 4

pme
pme

Reputation: 14803

The accepted answer is deprecated (circe 0.12.0).

Circe provides now these functions:

Decoder.decodeEnumeration[E <: Enumeration](enum: E)
Encoder.encodeEnumeration[E <: Enumeration](enum: E)

With the example:

implicit val genderDecoder: Decoder[Gender.Value] = Decoder.decodeEnumeration(Gender)
implicit val genderEncoder: Encoder[Gender.Value] = Encoder.encodeEnumeration(Gender)

Upvotes: 15

msilb
msilb

Reputation: 515

Have a look at enumeratum if you want to use enumerations with circe. You could then try something like this:

import enumeratum._

sealed trait Gender extends EnumEntry

case object Gender extends CirceEnum[Gender] with Enum[Gender] {

  case object Male extends Gender
  case object Female extends Gender
  case object Unisex extends Gender
  case object Unknown extends Gender

  val values = findValues
}

Gender.values.foreach { gender =>
    assert(gender.asJson == Json.fromString(gender.entryName))
}

This should work with circe's automatic derivation for use with your case classes.

Upvotes: 11

Howy Perrin
Howy Perrin

Reputation: 171

Try defining your own encoders and decoders for the enum using:

Decoder.enumDecoder[E <: Enumeration](enum: E)
Encoder.enumEncoder[E <: Enumeration](enum: E)

something like:

object JsonProtocol {
  implicit val genderDecoder: Decoder[Gender.Value] = Decoder.enumDecoder(Gender)
  implicit val genderEncoder: Encoder[Gender.Value] = Encoder.enumEncoder(Gender)
  implicit val productDecoder: Decoder[Product] = deriveDecoder
  implicit val productEncoder: Encoder[Product] = deriveEncoder
}

These are needed because the automatic/semiautomatic derivers only work for hierarchies of sealed traits and case classes as far as I know. The reason you see that error is because the derived codecs for Product will implicitly require encoders/decoders for the types of each of it's parameters. An encoder/decoder for String is a standard part of Circe, but you'll probably need to create ones for your own enumerations.

Upvotes: 15

Related Questions