代码之家  ›  专栏  ›  技术社区  ›  joesan

Circe编码密封特征失败

  •  0
  • joesan  · 技术社区  · 4 年前

    我有以下案例类:

    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是否存在一个已知的局限性,即它还不适用于我的上述情况?

    0 回复  |  直到 4 年前
        1
  •  3
  •   joesan    4 年前

    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
    

    两者的模式,在任何Scala JSON库中,该库会自动为 case class es,将沿着以下路线

    { "interval": "HH" }
    

    对于 HalfHourInterval

     { "interval": "FH" }
    

    对于 FullHourInterval (即,因为每个字段都有一个名为 interval ,它们实际上是同一类)。事实上,你的模型允许你 FullHourInterval("HH") ,对于至少一种为ADT层次结构生成解码器的方法(文档中使用无形状的方法),这将是解码的结果 { "interval": "HH" } ,因为这基本上需要符合词法顺序的第一个构造函数(即。 全时间隔 ). 如果目的是只允许整小时或半小时的间隔,那么我建议这样表达:

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

    我并不直接熟悉circe的编码方式 case object s、 但是你可以很容易地定义一个编码器和解码器 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")
        }
     }
    

    然后,你会做类似的事情来定义 Encoder / Decoder 对于 PowerUnit