Scala Circe 推断 class
Scala Circe infer class
我正在使用来自网络套接字的消息。它们采用以下两种格式之一:
{"typ": "subscription", "channel": "BTC-USD"}
或
{"typ": "ticker", "price": 10213.42}
所以我有三种情况 类 与 Circe 的解码器一起使用:
case class GenericMessage(typ: String)
case class SubscriptionMessage(channel: String)
case class TickerMessage(price: Double)
我怎样才能让 Circe 找出消息的类型,而不嵌套大量的解码尝试?
val message: String = ??? // From websocket
decode[GenericMessage](message) match {
case Left(e) => ??? // Handle error
case Right(decoded) => decoded.typ match {
case "subscription" => decode[SubscriptionMessage](message) match {
case Left(e) => ??? // Handle error
case Right(subscriptionMessage) => ??? // Handle subscription message
}
case "ticker" => decode[TickerMessage](message) match {
case Left(e) => ??? // Handle error
case Right(tickerMessage) => ??? // Handle ticker message
}
case other => ??? // Handle unrecognized message
}
}
你可以试试我的 Circe ADT 扩展:
https://github.com/abdolence/circe-tagged-adt-codec
因此,在您的情况下它将是:
sealed trait Message
object Message {
@JsonAdt("ticker")
case class Ticker(price : BigDecimal) extends Message
@JsonAdt("subscription")
case class Subscription(channel: Channel) extends Message
implicit val encoder : Encoder[Message] = JsonTaggedAdtCodec.createEncoder[Message]("typ")
implicit val decoder : Decoder[Message] = JsonTaggedAdtCodec.createDecoder[Message]("typ")
}
PS。由于浮点舍入问题,我不建议对 money/price 类型使用 Double
。
感谢@LuisMiguelMejiaSuarez。
我使用 circe-generic-extras:
做了以下解决方案
sealed trait WebSocketMessage
final case class Subscriptions(typ: String, channel: String) extends WebSocketMessage
final case class Ticker(typ: String, price: Double) extends WebSocketMessage
implicit val config: Configuration = Configuration.default
.withDiscriminator("type") // Tell circe that the 'type' attribute specifies which case class to decode to
.copy(transformConstructorNames = _.toLowerCase) // Map 'ticker' string to 'Ticker' class name. Could also pass a more complex mapping function here to use arbitrary class names
.copy(transformMemberNames = {
case "typ" => "type" // The word 'type' is a reserved Scala, so class members are renamed to'typ' instead
case other => Configuration.snakeCaseTransformation(other) // Tell circe to map product_ids field to productIds class member, for example.
})
我正在使用来自网络套接字的消息。它们采用以下两种格式之一:
{"typ": "subscription", "channel": "BTC-USD"}
或
{"typ": "ticker", "price": 10213.42}
所以我有三种情况 类 与 Circe 的解码器一起使用:
case class GenericMessage(typ: String)
case class SubscriptionMessage(channel: String)
case class TickerMessage(price: Double)
我怎样才能让 Circe 找出消息的类型,而不嵌套大量的解码尝试?
val message: String = ??? // From websocket
decode[GenericMessage](message) match {
case Left(e) => ??? // Handle error
case Right(decoded) => decoded.typ match {
case "subscription" => decode[SubscriptionMessage](message) match {
case Left(e) => ??? // Handle error
case Right(subscriptionMessage) => ??? // Handle subscription message
}
case "ticker" => decode[TickerMessage](message) match {
case Left(e) => ??? // Handle error
case Right(tickerMessage) => ??? // Handle ticker message
}
case other => ??? // Handle unrecognized message
}
}
你可以试试我的 Circe ADT 扩展: https://github.com/abdolence/circe-tagged-adt-codec
因此,在您的情况下它将是:
sealed trait Message
object Message {
@JsonAdt("ticker")
case class Ticker(price : BigDecimal) extends Message
@JsonAdt("subscription")
case class Subscription(channel: Channel) extends Message
implicit val encoder : Encoder[Message] = JsonTaggedAdtCodec.createEncoder[Message]("typ")
implicit val decoder : Decoder[Message] = JsonTaggedAdtCodec.createDecoder[Message]("typ")
}
PS。由于浮点舍入问题,我不建议对 money/price 类型使用 Double
。
感谢@LuisMiguelMejiaSuarez。
我使用 circe-generic-extras:
sealed trait WebSocketMessage
final case class Subscriptions(typ: String, channel: String) extends WebSocketMessage
final case class Ticker(typ: String, price: Double) extends WebSocketMessage
implicit val config: Configuration = Configuration.default
.withDiscriminator("type") // Tell circe that the 'type' attribute specifies which case class to decode to
.copy(transformConstructorNames = _.toLowerCase) // Map 'ticker' string to 'Ticker' class name. Could also pass a more complex mapping function here to use arbitrary class names
.copy(transformMemberNames = {
case "typ" => "type" // The word 'type' is a reserved Scala, so class members are renamed to'typ' instead
case other => Configuration.snakeCaseTransformation(other) // Tell circe to map product_ids field to productIds class member, for example.
})