Scala - JSON对象在字段类型上是多态的

时间:2015-09-04 21:05:15

标签: scala shapeless scala-cats

我试图解码一些真正可怕的JSON。每个对象的类型信息在标记为"type": "event"的字段内编码,即def apply(c: HCursor): Decoder.Result[A]等。我使用Circe进行JSON编码/解码。该库使用类型类,其中相关的类型类为A。问题是任何解码器对于sealed trait MotherEvent { val id: UUID val timestamp: DateTime } implicit val decodeJson: Decoder[MotherEvent] = new Decoder[MotherEvent] { def apply(c: HCursor) = { c.downField("type").focus match { case Some(x) => x.asString match { case Some(string) if string == "flight" => FlightEvent.decodeJson(c) case Some(string) if string == "hotel" => // etc // like a bunch of these case None => Xor.Left(DecodingFailure("type is not a string", c.history)) } case None => Xor.Left(DecodingFailure("not type found", c.history)) } } sealed trait FlightEvents(id: UUID, timestamp: DateTime, flightId: Int) case class Arrival(id: UUID, timestamp: DateTime, flightId: Int) extends Event // a metric ton of additional fields case class Departure(id: UUID, timestamp: DateTime, flightId: Int) extends Event // samsies as Arrival 类型都是不变的。这是一个具体的例子

MotherEvent

解码工作正常,但始终返回val jsonString = // from wherevs, where the json string is flightevent val x = decode[MotherEvent](jsonString) println(x) // prints (cats.data.Xor[io.circe.Error, MotherEvent] = Right(FlightEvent) println(x.flightId) // ERROR- flightId is not a member of MotherEvent

Option[A]

当然,我希望有一个FlightEvent而不是母亲活动。一种可能的解决方案是创建一个母亲"有60或70个字段的类型,但我已经讨厌自己,只想考虑基于type字段填充的70个public class TestClass : IDisposable { #region IDisposable Support private bool disposedValue = false; // To detect redundant calls protected virtual void Dispose(bool disposing) { if (!disposedValue) { if (disposing) { // TODO: dispose managed state (managed objects). } // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. // TODO: set large fields to null. disposedValue = true; } } // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources. // ~DisposeTest() { // // Do not change this code. Put cleanup code in Dispose(bool disposing) above. // Dispose(false); // } // This code added to correctly implement the disposable pattern. public void Dispose() { // Do not change this code. Put cleanup code in Dispose(bool disposing) above. Dispose(true); // TODO: uncomment the following line if the finalizer is overridden above. // GC.SuppressFinalize(this); } #endregion } 字段而退出编程。

有人能想到一个很好的解决方案吗?

1 个答案:

答案 0 :(得分:4)

所以,我最终拥抱了一个不变的A并依靠Shapeless获得Coproduct。这会导致一些额外的代码重复,但它大大简化了我对问题的思考以及Decoder[A]的处理方式。由于这是简单数据摄取程序的一部分,因此在Coproduct上映射其他工作相对容易,并且数据库表示的数据具有更清晰的类型。这是一个如何结合在一起的玩具示例:

import shapeless._
import io.circe._, io.circe.Decoder.instance 

case class FlightEvent(id: UUID, departureTime: DateTime, arrivalTime: DateTime)
case class HotelEvent(id: UUID, city: String)
case class CarEvent(id: UUID, carrier: String)
// assume valid Decoder typeclasses in each companion object

type FHC = FlightEvent :+: HotelEvent :+: CarEvent :+: CNil
type FHCs = List[FHC]

implicit val decodeFHC: Decoder[FHC] = instance { c => 
  c.downField("type".focus match {
    case Some(t) => t.asString match {
      case Some(string) if string == "flight" => FlightEvent.decodeJson(c) map { Coproduct[FHC](_) }
      case Some(string) if string == "hotel"  => HotelEvent.decodeJson(c) map { Coproduct[FHC](_) }
      case Some(string) if string == "car"    => CarEvent.decodeJson(c) map { Coproduct[FHC](_) }
      case Some(string) => Xor.Left(DecodingFailure(s"unkown type $string", c.history))
      case None =>  Xor.Left(DecodingFailure("json field \"type\", string expected", c.history))
    }
    case None => Xor.Left(DecodingFailure("json field \"type\" not found", c.history))
  }
}

这是我第一次使用无形状,所以很有可能有更清洁的方法来实现这一点。我确实喜欢正交Coproduct如何做到这一点。