4

本当にひどい JSON をデコードしようとしています。各オブジェクトの型情報は、 などのラベルが付いたフィールド内にエンコードされます。JSON のエンコード/デコードには Circe をtype使用"type": "event"ています。ライブラリは型クラスを利用します。関連する型クラスはです。問題は、どの Decoder もタイプに対して不変であるということです。ここに具体的な例があります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 を使用したいと考えています。Option[A]考えられる解決策の 1 つは、60 または 70 フィールドを持つ「マザー」タイプを作成することですが、私はすでに自分が嫌いで、フィールドに基づいて埋められる70 フィールドのことだけを考えてプログラミングをやめたいと思っていtypeます。

誰でもこれに対する良い解決策を考えることができますか?

4

1 に答える 1

5

そのため、不変A条件を採用し、シェイプレスに頼ることになりました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ました。

于 2015-09-08T17:07:30.473 に答える