我试图解码一些真正可怕的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
}
字段而退出编程。
有人能想到一个很好的解决方案吗?
答案 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
如何做到这一点。