3
votes

I'm trying to create an encoder and decoder for a case class I have:

case class Road(id: String, light: RoadLight, names: Map[String, String])

RoadLight is a java class, with enum.

public enum RoadLight {
red,yellow,green
}

I have tried to do a semi-auto encode&decode: making a implicit encoders and decoders.
I've started with the Map[String,String] type:

implicit val namesDecoder: Decoder[Map[String, String]] = deriveDecoder[Map[String, String]]
implicit val namesEncoder: Encoder[Map[String, String]] = deriveEncoder[Map[String, String]]

But I did get an error for both of them!

1: could not find Lazy implicit value of type io.circe.generic.decoding.DerivedDecoder[A]

2: Error: not enough arguments for method deriveDecoder: (implicit decode: shapeless.Lazy[io.circe.generic.decoding.DerivedDecoder[A]])io.circe.Decoder[A]. Unspecified value parameter decode. implicit val namesDecoder: Decoder[Map[String,String]]= deriveDecoder

I've done everything by the book, can't understand what's wrong. I'm not even trying to parse the case class, only the map, and even that doesn't work.

Any ideas? Thanks!

2

2 Answers

4
votes

Scaladoc says

/**
 * Semi-automatic codec derivation.
 *
 * This object provides helpers for creating [[io.circe.Decoder]] and [[io.circe.ObjectEncoder]]
 * instances for case classes, "incomplete" case classes, sealed trait hierarchies, etc.

Map is not a case class or element of sealed trait hierarchy.

https://github.com/circe/circe/issues/216

Encode Map[String, MyCaseClass] into Seq[String, String] using circe

Circe and Scala's Enumeration type

4
votes

circe-generic does not create codecs for java enums, only for scala product and sum types. But rolling your own for RoadLight is not hard. And once you have that, you get the map.

The code below works:

object RoadLightCodecs {
  implicit val decRl: Decoder[RoadLight] = Decoder.decodeString.emap {
    case "red" => Right(RoadLight.Red)
    case "yellow" => Right(RoadLight.Yellow)
    case "green" => Right(RoadLight.Green)
    case s => Left(s"Unrecognised traffic light $s")
  }

  implicit val encRl: Encoder[RoadLight] = Encoder.encodeString.contramap(_.toString)


  implicit val decodeMap = Decoder.decodeMap[String, RoadLight]
  implicit val encodeMap = Encoder.encodeMap[String, RoadLight]
}

So what we have done is made codecs for the basic types and then use them to build the bigger map codec.

Now as far as I am aware, there aren't any libraries that do this automatically for java enums, although it should theoretically be possible to write one. But using combinators on basic codecs to build up more complex ones works great and scales well.

EDIT: I had a play at auto-deriving java enum codecs and you can almost do it:

  def decodeEnum[E <: Enum[E]](values: Array[E]): Decoder[E] = Decoder.decodeString.emap { str =>
    values.find(_.toString.toLowerCase == str)
      .fold[Either[String, E]](Left(s"Value $str does not map correctly"))(Right(_))
  }

  def encodeEnum[E <: Enum[E]]: Encoder[E] =
    Encoder.encodeString.contramap(_.toString.toLowerCase)

  implicit val roadLightDecoder = decodeEnum[RoadLight](RoadLight.values())
  implicit val roadLightEncoder = encodeEnum[RoadLight]

So encodeEnum could be automatic (you could make it implicit instead of the val at the end) but the decoder needs to be given the values (which I see no way of getting automatically from the type), so you need to pass those when creating the codec.