I have the following case class:
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
}
I just wrote a very simple unit test to see if Circe works for my scenario:
"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)
}
}
But it fails with the following error:
Error when parsing valid JSON DecodingFailure(CNil, List(DownField(dateInterval)))
Is there a known limitation with circe where it does not yet work for my case above?
There's no reason that Circe wouldn't work, but the way you've designed the data model, there's basically zero chance of any Scala JSON library that can automatically generate an encoder/decoder working without a lot of manual work.
For example
sealed trait SmartMeterDataInterval { def interval: String }
case class HalfHourInterval(interval: String = "HH") extends SmartMeterDataInterval
case class FullHourInterval(interval: String = "FH") extends SmartMeterDataInterval
the schema for both, in any Scala JSON library which automatically derives instances for case class
es, would be along the lines of
{ "interval": "HH" }
for HalfHourInterval
and
{ "interval": "FH" }
for FullHourInterval
(i.e. because each has a single string field named interval
, they're effectively the same class). In fact your model allows you to have FullHourInterval("HH")
, which for at least one method of generating a decoder for an ADT hierarchy in circe (the one in the documentation which uses shapeless) would be the result of decoding { "interval": "HH" }
, since that essentially takes the first constructor in lexical order which matches (i.e. FullHourInterval
). If the intent is to only allow full- or half-hour intervals, then I'd suggest expressing that as:
case object HalfHourInterval extends SmartMeterDataInterval { def interval: String = "HH" }
case object FullHourInterval extends SmartMeterDataInterval { def interval: String = "FH" }
I'm not directly familiar with how circe encodes case object
s, but you can pretty easily define an encoder and decoder for 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")
}
}
You would then do something similar to define an Encoder
/Decoder
for PowerUnit