如何使用 Circe 创建 Option 类型的自定义编码?

How to create custom encoding of Option types with Circe?

有可能 class 看起来像这样:

case class Amount(value: Int)
case class Data(insurance: Option[Amount], itemPrice: Amount)

如果 insurance = None 它应该得到默认值 waived: true

例如:

Data(Some(123),100).asJson

// output
{
  "insurance": {
    "value": 123
  },
  "price": 100
}

And when no Insurance is opted for:

Data(None,100).asJson

// output
{
  "insurance": {
    "waived: true
  },
  "price": 100
}

如何实现这种细粒度的控制?我尝试了 forProduct2mapJsonObject 的各种技巧,但无法使其正常运行:

implicit val testEncoder = deriveEncoder[Option[Amount]].mapJsonObject(j => {

    val x = j("Some") match {
      case Some(s) => // need to convert to [amount -> "value"]
      case None => JsonObject.apply(("waived",Json.fromBoolean(true)))
    }

    x
  })

这很容易让我得到 waived:true 的部分,但不知道如何处理 Some(s) 的情况。

如果 {"waived": true} 是任何 Option[Amount] 的预期行为,如果它是 None,那么如果您为 Option[Amount] 编写自定义编码器,则可以依赖半自动派生编码器

这是一个例子

import io.circe.{Encoder, Json}
import io.circe.syntax._
import io.circe.generic.semiauto._

case class Amount(value: Int)
case class Data(insurance: Option[Amount], itemPrice: Amount)

object Amount {
  implicit val encoder: Encoder[Amount] = deriveEncoder
}

object Data {
  implicit val encoderOptionalAmount: Encoder[Option[Amount]] = (optA: Option[Amount]) =>
      optA match {
        case Some(amount) => amount.asJson
        case None => Json.obj("waived" -> true.asJson)
      }

  implicit val encoder: Encoder[Data] = deriveEncoder[Data]
}

println(Data(insurance = None, itemPrice = Amount(10)).asJson)

/*
{
  "insurance" : {
    "waived" : true
  },
  "itemPrice" : {
    "value" : 10
  }
}
*/

工作原理:deriveEncoder[Data] 将为 itemPrice(Amount 类型)和 Option[Amount] 类型的保险调用隐式编码器。

Option[T] 的默认编码器只是跳过 None 的值,但由于我们在最近的范围(数据对象伴侣)中为 Option[T] 定义了另一个隐式编码器,它不会'不要在全局范围内寻找隐式编码器,为您提供您想要的东西。