在 Circe 执行 "at least one of two fields should be present" 规则

Enforce a "at least one of two fields should be present" rule in Circe

我正在编写一个 Circe 解析器,其中的架构要求至少设置两个字段之一。这是非常具体的,它似乎不是用 Circe API 轻松完成的方法。

让我们称我们的字段为 texthtml

我已经尝试创建一个 class,我们称它为 Content,将它作为单个参数添加到通用模型中,如果它的两个字段(文本和html) 是 None。问题是如何定义解码器,因为如果我做这样的事情

implicit val decodeContent: Decoder[ItemContent] =
    Decoder.forProduct2("text", "html")(Content.apply)

它要求两个字段都存在。

我想要一个解码器,如果缺少该字段,则将 None 传递给 Content.apply,但我认为这不是预期的行为。

否则应该有一个完全不同的解决方案,但我想不出一个。

谢谢

您可以使用 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")))
}

Runnable version


为避免指定其他字段,可以使用半自动派生作为基础:

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)
    }
}

Runnable version