Scala - JSON object is polymorphic in field type

I am trying to decode some really terrible JSON. Type information for each object is encoded in a field with a label type, i.e., "type": "event"etc. I use Circe for JSON encoding / decoding. The library uses type classes, where the corresponding class type def apply(c: HCursor): Decoder.Result[A]. The problem is that any decoder is invariant to the type A. Here is a concrete example

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

Decoding works fine, but MotherEventalways returns

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 , , 70 Option[A] , type.

- ?

+4
1

, 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.

+4

Source: https://habr.com/ru/post/1606006/


All Articles