3

Given the following case classes:

case class Mailbox(value: String)
case class Group(objectType: String, mailbox: Mailbox)

I am attempting to find a way to encode a Group object as follows where mailbox is encoded as a string value, rather than an object:

{
  "objectType" : "Group",
  "mailbox" : "mailto:[email protected]"
}

With automatic derivation, encoding/decoding both succeed but I end up with a result like the following as would be expected:

{
  "objectType" : "Group",
  "mailbox" : {
    "value" : "mailto:[email protected]"
  }
}

I can achieve the result that I want by adding a custom encoder like the following:

object Mailbox {
  implicit val encoder: Encoder[Mailbox] = (m: Mailbox) => Json.fromString(m.value)
  implicit val decoder: Decoder[Mailbox] = deriveDecoder[Mailbox]
}

However, then decoding fails with the following:

DecodingFailure(Attempt to decode value on failed cursor, List(DownField(value), DownField(mailbox)))

I've attempted to resolve this by also writing a custom decoder for Mailbox but get the same result. Any guidance on the correct way to approach this situation would be appreciated.

Here is the complete code:

case class Mailbox(value: String)
object Mailbox {
  implicit val encoder: Encoder[Mailbox] = (m: Mailbox) => Json.fromString(m.value)
  implicit val decoder: Decoder[Mailbox] = deriveDecoder[Mailbox]
}

case class Group(objectType: String, mailbox: Mailbox)

object Sandbox {
  def main(args: Array[String]): Unit = {

    val group: Group = Group("Group", Mailbox("mailto:[email protected]"))
    val json: String = group.asJson.spaces2
    println(json)

    parser.decode[Group](json) match {
      case Right(group) => println(group)
      case Left(err) => println(err)
    }
  }
}

Note that this is a derived example, meant only to demonstrate my question.

1
  • 1
    If you make that class a value class and use circe-generic-extras it would work out of the box. But, note that using Value classes may cause performance issues. Commented Mar 28, 2021 at 16:57

2 Answers 2

5

As an alternative for creating custom encoders/decoders, you could also use deriveUnwrappedEncoder/deriveUnwrappedDecoder from circe extras.

Just add following import in you buid.sbt:

libraryDependencies += "io.circe" %% "circe-generic-extras" % "xxx"

and then you will be able to do:

import io.circe.generic.extras.semiauto._

implicit val encoder: Encoder[Mailbox] = deriveUnwrappedEncoder
implicit val decoder: Decoder[Mailbox] = deriveUnwrappedDecoder
Sign up to request clarification or add additional context in comments.

Comments

4

You can use map / contramap instead to map your type to String and back:

object Mailbox {

  implicit val encoder: Encoder[Mailbox] = Encoder.encodeString.contramap[Mailbox](_.value)
  implicit val decoder: Decoder[Mailbox] = Decoder.decodeString.map[Mailbox](Mailbox.apply)
}

A documentation describing almost exactly this scenario is at Custom encoders/decoders

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.