【问题标题】:Circe Encoding Sealed Traits and Co Product Case class FailsCirce 编码密封特征和 Co 产品案例类失败
【发布时间】:2021-03-08 15:52:13
【问题描述】:

我有以下案例类:

case class SmartMeterData(
  dateInterval: SmartMeterDataInterval = HalfHourInterval(),
  powerUnit: PowerUnit = KWH,
  smartMeterId: String,
  timestamp: String,
  value: Double
) {
  def timeIntervalInDuration: FiniteDuration = dateInterval match {
    case HalfHourInterval(_) => FiniteDuration(30, TimeUnit.MINUTES)
    case FullHourInterval(_) => FiniteDuration(60, TimeUnit.MINUTES)
  }
}
object SmartMeterData {
  sealed trait SmartMeterDataInterval { def interval: String }
  case class HalfHourInterval(interval: String = "HH") extends SmartMeterDataInterval
  case class FullHourInterval(interval: String = "FH") extends SmartMeterDataInterval

  sealed trait PowerUnit
  case object WH  extends PowerUnit
  case object KWH extends PowerUnit
  case object MWH extends PowerUnit
}

我刚刚写了一个非常简单的单元测试来看看 Circe 是否适用于我的场景:

"SmartMeterData" should "successfully parse from a valid JSON AST" in {
    val js: String = """
      {
        "dateInterval" : "HH",
        "powerUnit" : "KWH",
        "smartMeterId" : "LCID-001-X-54",
        "timestamp" : "2012-10-12 00:30:00.0000000",
        "value" : 23.0
      }
      """
    val expectedSmartMeterData = SmartMeterData(smartMeterId = "LCID-001-X-54", timestamp = "2012-10-12 00:30:00.0000000", value = 23.0)
    decode[SmartMeterData](js) match {
      case Left(err) => fail(s"Error when parsing valid JSON ${err.toString}")
      case Right(actualSmartMeterData) =>
        assert(actualSmartMeterData equals expectedSmartMeterData)
    }
  }

但它失败并出现以下错误:

Error when parsing valid JSON DecodingFailure(CNil, List(DownField(dateInterval)))

circe 是否存在一个已知限制,它不适用于上述我的情况?

【问题讨论】:

  • 你可以打印expectedSmartMeterData.asJson,看看是{"dateInterval":{"HalfHourInterval":{"interval":"HH"}},"powerUnit":{"KWH":{}},"smartMeterId":"LCID-001-X-54","timestamp":"2012-10-12 00:30:00.0000000","value":23.0}

标签: json scala shapeless circe


【解决方案1】:

Circe 没有理由不工作,但是按照您设计数据模型的方式,任何 Scala JSON 库可以自动生成编码器/解码器的可能性基本上为零,而无需大量手动工作。

例如

sealed trait SmartMeterDataInterval { def interval: String }
case class HalfHourInterval(interval: String = "HH") extends SmartMeterDataInterval
case class FullHourInterval(interval: String = "FH") extends SmartMeterDataInterval

在任何自动为case classes 派生实例的 Scala JSON 库中,两者的架构都将遵循以下原则:

{ "interval": "HH" }

对于HalfHourInterval

 { "interval": "FH" }

对于FullHourInterval(即因为每个都有一个名为interval 的字符串字段,它们实际上是同一个类)。事实上,您的模型允许您拥有FullHourInterval("HH"),这对于为大约 ADT 层次结构生成解码器的至少一种方法(文档中使用 shapeless 的方法)将是解码{ "interval": "HH" } 的结果,因为那本质上采用匹配的词法顺序的第一个构造函数(即FullHourInterval)。如果目的是只允许整小时或半小时的间隔,那么我建议将其表达为:

case object HalfHourInterval extends SmartMeterDataInterval { def interval: String = "HH" } 
case object FullHourInterval extends SmartMeterDataInterval { def interval: String = "FH" }

我并不直接熟悉 circe 如何编码 case objects,但您可以很容易地为 SmartMeterDataInterval 定义编码器和解码器:

object SmartMeterDataInterval {
  implicit val encoder: Encoder[SmartMeterDataInterval] =
    Encoder.encodeString.contramap[SmartMeterDataInterval](_.interval)
  implicit val decoder: Decoder[SmartMeterDataInterval] =
    Decoder.decodeString.emap {
      case "HH" => Right(HalfHourInterval)
      case "FH" => Right(FullHourInterval)
      case _ => Left("not a valid SmartMeterDataInterval")
    }
 }

然后你会做类似的事情来为PowerUnit定义一个Encoder/Decoder

【讨论】:

  • 我遇到了这个错误:必须完全知道匿名函数的参数类型。 (SLS 8.5) 预期类型为:Either[String,com.bigelectrons.models.SmartMeterData.SmartMeterDataInterval] Decoder.decodeString.emap(interval => {
  • @sparkr 你应该稍微修正一下模式匹配。
  • D'oh,这就是我在 SO 答案中进行实时编码所得到的。 :)
猜你喜欢
  • 2018-10-31
  • 2016-08-19
  • 2020-09-10
  • 2016-08-28
  • 2018-09-20
  • 2022-07-21
  • 2019-03-13
  • 2020-03-23
  • 2019-03-15
相关资源
最近更新 更多