Ошибка кодирования Circe с запечатанными признаками и сопутствующим классом

#json #scala #shapeless #circe

#json #scala #бесформенный #circe

Вопрос:

У меня есть следующий класс case:

 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, когда оно еще не работает для моего случая выше?

Комментарии:

1. Вы можете распечатать expectedSmartMeterData.asJson и увидеть, что это {"dateInterval":{"HalfHourInterval":{"interval":"HH"}},"powerUnit":{"KWH":{}},"smartMeterId":"LCID-001-X-54","timestamp":"2012-10-12 00:30:00.0000000","value":23.0} .

2. circe.github.io/circe/codecs/adt.html

Ответ №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
 

схема для обоих в любой библиотеке Scala JSON, которая автоматически выводит экземпляры для case class es, будет выглядеть следующим образом

 { "interval": "HH" }
 

для HalfHourInterval и

  { "interval": "FH" }
 

для FullHourInterval (т. е. Поскольку у каждого есть одно строковое поле с именем interval , они фактически являются одним и тем же классом). Фактически ваша модель позволяет вам иметь FullHourInterval("HH") , что, по крайней мере, для одного метода генерации декодера для иерархии ADT в circe (того, который в документации использует shapeless) будет результатом декодирования { "interval": "HH" } , поскольку это по существу принимает первый конструктор в лексическом порядке, который соответствует (т.Е. FullHourInterval ). Если цель состоит в том, чтобы разрешить только полные или получасовые интервалы, тогда я бы предложил выразить это как:

 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

Комментарии:

1. Я сталкиваюсь с этой ошибкой: типы аргументов анонимной функции должны быть полностью известны. (SLS 8.5) Ожидаемый тип был: либо [строка, com.bigelectrons.models . SmartMeterData.SmartMeterDataInterval] Decoder.decodeString.emap( интервал => {

2. @sparkr Вам следует немного исправить сопоставление с образцом.

3. О, это то, что я получаю за живое кодирование в ответах SO. 🙂