Scala Circe推断类

问题描述

我正在使用来自websocket的消息。它们有以下两种格式之一:

{"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。由于浮点舍入问题,我不建议将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.
  })

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...