I'm writing a Circe parser where the schema requires that at least one of two fields is set. This is quite specific and it doesn't seem to be a way to do it easily with Circe API.
Let's call our fields text
and html
.
I tried already to create a class, let's call it Content
, add it to the general model as a single parameter and raise an exception in the constructor if both its fields (text and html) are None. The problem is how to define the decoder, because if I do something like this
implicit val decodeContent: Decoder[ItemContent] =
Decoder.forProduct2("text", "html")(Content.apply)
it requires both fields to be present anyway.
What I would like would be to have a decoder that, if the field is missing, pass a None to the Content.apply
but I don't think this is the expected behaviour.
Otherwise there should be a totally different solution but I cannot think of one.
Thank you
You can use Decoder#emap
:
import io.circe._, parser._
case class ItemContent(text: Option[String], html: Option[String])
object ItemContent {
implicit val decoder =
Decoder.forProduct2("text", "html")(ItemContent.apply).emap {
case ItemContent(None, None) => Left("Neither text nor html is present")
case x => Right(x)
}
}
assert {
decode[ItemContent]("{}").isLeft &&
decode[ItemContent]("""{"html": "foo"}""") == Right(
ItemContent(None, Some("foo"))) &&
decode[ItemContent]("""{"text": "bar"}""") == Right(
ItemContent(Some("bar"), None)) &&
decode[ItemContent]("""{"html": "foo", "text": "bar"}""") == Right(
ItemContent(Some("bar"), Some("foo")))
}
To avoid specifying other fields it is possible to use semi-automatic derivation as a base:
import io.circe._, parser._, io.circe.generic.semiauto._
case class ItemContent(text: Option[String],
html: Option[String],
other: Int,
fields: String)
object ItemContent {
implicit val decoder =
deriveDecoder[ItemContent].emap { ic =>
if (ic.text.isEmpty && ic.html.isEmpty)
Left("Both `text` and `html` are missing")
else Right(ic)
}
}