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.
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
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With