如何让 circe 为生成的 Json 设置一个 either/or 输出场景?

How do I get circe to have an either/or output scenario for the generated Json?

这就是我的意图 - 假设我有一个名为 medical_payments 的字段 - 它可以 "either" 是 limit 如果选择或 豁免

{
  "medical_payments": 
    {
      "limit_value":"one_hundred"
    }
} 

如果它被选为豁免,那么它应该是:

{
  "medical_payments": 
    {
      "waived":true
    }
} 

到目前为止,这是我所拥有的:

sealed trait LimitOrWaiver
case class Limit(limit_key: String) extends LimitOrWaiver
case class Waived(waived: Boolean) extends LimitOrWaiver

case class Selection(medical_payments: LimitOrWaiver)

示例数据:

Selection(medical_payments = Limit("one_hundred")).asJson

输出:

{
  "medical_payments": 
    {
      "Limit": { "limit_value":"one_hundred" } // additional object added
    }
} 

Selection(medical_payments = Waived(true)).asJson 类似,将额外的 Waived:{...} 添加到 Json。

我希望它是 either/or。实现此目标的最佳方法是什么?

我能想到的唯一方法(不是我喜欢的)是使用 forProductN 函数 per the doc 并手动完成所有这些 - 但对于一个大 Json.

您可以几乎使用generic-extras中的配置通过泛型派生来完成此操作:

sealed trait LimitOrWaiver
case class Limit(limitValue: String) extends LimitOrWaiver
case class Waived(waived: Boolean) extends LimitOrWaiver
case class Selection(medicalPayments: LimitOrWaiver)

import io.circe.generic.extras.Configuration, io.circe.generic.extras.auto._
import io.circe.syntax._

implicit val codecConfiguration: Configuration =
  Configuration.default.withDiscriminator("type").withSnakeCaseMemberNames

然后:

scala> Selection(medicalPayments = Limit("one_hundred")).asJson
res0: io.circe.Json =
{
  "medical_payments" : {
    "limit_value" : "one_hundred",
    "type" : "Limit"
  }
}

(请注意,我还将 Scala 大小写 class 成员名称更改为 Scala 惯用的驼峰式大小写,并在配置中处理到蛇形大小写的转换。)

这不是您想要的,因为有那个额外的 type 成员,但是 circe 的泛型推导只支持可往返的编码器/解码器,并且没有某种鉴别器——要么像这样的成员或您在问题中指出的额外对象层 - 不可能通过 JSON.

来回任意 ADT 的值

这可能没问题——您可能不关心对象中额外的 type。如果您愿意,您仍然可以通过一些额外的工作来使用推导:

import io.circe.generic.extras.Configuration, io.circe.generic.extras.auto._
import io.circe.generic.extras.semiauto._
import io.circe.ObjectEncoder, io.circe.syntax._

implicit val codecConfiguration: Configuration =
  Configuration.default.withDiscriminator("type").withSnakeCaseMemberNames

implicit val encodeLimitOrWaiver: ObjectEncoder[LimitOrWaiver] =
  deriveEncoder[LimitOrWaiver].mapJsonObject(_.remove("type"))

并且:

scala> Selection(medicalPayments = Limit("one_hundred")).asJson
res0: io.circe.Json =
{
  "medical_payments" : {
    "limit_value" : "one_hundred"
  }
}

如果您真的想要,您甚至可以将其设为自动,这样 type 将从您派生的任何 ADT 编码器中删除。