Scala - JSON 对象在字段类型上是多态的
Scala - JSON object is polymorphic on a field type
我正在尝试解码一些非常糟糕的 JSON。每个对象的类型信息都在标记为 type
的字段中编码,即 "type": "event"
等。我使用 Circe 进行 JSON 编码/解码。该库使用类型类,其中相关的类型类是 def apply(c: HCursor): Decoder.Result[A]
。问题是任何解码器都是类型不变的,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
当然,我想要一个 FlightEvent 而不是母事件。一种可能的解决方案是创建一个具有 60 或 70 个字段的 "mother" 类型,但我已经讨厌自己并想退出编程,只是想着 70 个 Option[A]
字段是基于 type
字段。
有谁能想出好的解决办法吗?
所以,我最终接受了一个不变量 A
并依靠 Shapeless 获得 Coproduct
。这导致了一些额外的代码重复,但它极大地简化了我对问题的思考方式以及 Decoder[A]
的处理方式。由于这是一个简单的数据摄取程序的一部分,因此将额外的工作映射到 Coproduct
并为 DB 表示的数据提供更清晰的类型相对容易。这是一个如何组合在一起的玩具示例:
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))
}
}
这是我第一次使用 shapeless,所以很有可能有更简洁的方法来实现它。我确实喜欢正交 Coproduct
制作这个的方式。
我正在尝试解码一些非常糟糕的 JSON。每个对象的类型信息都在标记为 type
的字段中编码,即 "type": "event"
等。我使用 Circe 进行 JSON 编码/解码。该库使用类型类,其中相关的类型类是 def apply(c: HCursor): Decoder.Result[A]
。问题是任何解码器都是类型不变的,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
当然,我想要一个 FlightEvent 而不是母事件。一种可能的解决方案是创建一个具有 60 或 70 个字段的 "mother" 类型,但我已经讨厌自己并想退出编程,只是想着 70 个 Option[A]
字段是基于 type
字段。
有谁能想出好的解决办法吗?
所以,我最终接受了一个不变量 A
并依靠 Shapeless 获得 Coproduct
。这导致了一些额外的代码重复,但它极大地简化了我对问题的思考方式以及 Decoder[A]
的处理方式。由于这是一个简单的数据摄取程序的一部分,因此将额外的工作映射到 Coproduct
并为 DB 表示的数据提供更清晰的类型相对容易。这是一个如何组合在一起的玩具示例:
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))
}
}
这是我第一次使用 shapeless,所以很有可能有更简洁的方法来实现它。我确实喜欢正交 Coproduct
制作这个的方式。