【问题标题】:Can't decode subclasses using Circe in Scala无法在 Scala 中使用 Circe 解码子类
【发布时间】:2020-07-06 14:32:20
【问题描述】:

我有一个 Scala 项目,我在其中使用 Circe 来处理 json。 我无法从 JSON 解码到层次结构的子类。

我遇到问题的代码是以下测试:

  test("FailingResponse - Conversion between case object and Json works") {
    val caseObject = FailingResponse("Some Error", StatusCodes.INTERNAL_ERROR)
    val jsonString = caseObject
      .asJson
      .printWith(Printer.noSpaces)

    decode[ValuationResponse](jsonString) must be(Right(caseObject))
  }

我希望能够解码为 ValuationResponse 的任何子类,因为在解码时我无法确定响应是失败响应还是成功响应。我希望解码器能够推断出它是什么类型的 ValuationReponse,对其进行解码并使其作为“通用”ValuationResponse 可用。然后我可以使用匹配案例或类似的东西来操作它以获得实际的特定类型。

相反,我在该测试中得到的是 DecodingFailure 错误。我做错了什么?

这是层次结构的代码:

sealed trait ValuationResponse {
  def statusCode: StatusCode
}

case class SuccessfulResponse(values: List[StockValuation], symbol: String, function: TimeSeriesType, statusCode: StatusCode) extends ValuationResponse

case class FailingResponse(reason: String, statusCode: StatusCode) extends ValuationResponse

case class ValuationRequest(function: TimeSeriesType = TIME_SERIES_INTRADAY, symbol: String, interval: IntraDayInterval = IntraDayIntervals.MIN_5)

object derivation {

  implicit val encodeResponse: Encoder[ValuationResponse] = Encoder.instance {
    case response@SuccessfulResponse(_, _, _, _) => response.asJson
    case response@FailingResponse(_, _) => response.asJson
  }
  implicit val decodeResponse: Decoder[ValuationResponse] =
    List[Decoder[ValuationResponse]](
      Decoder[SuccessfulResponse].widen,
      Decoder[FailingResponse].widen
    ).reduceLeft(_ or _)

  implicit val encodeRequest: Encoder[ValuationRequest] = Encoder.instance {
    case response@ValuationRequest(_, _, _) => response.asJson
  }
  implicit val decodeRequest: Decoder[ValuationRequest] =
    List[Decoder[ValuationRequest]](
      Decoder[ValuationRequest].widen
    ).reduceLeft(_ or _)
}

这些是它使用的枚举(是的,我知道有一个状态码的枚举是愚蠢的啊):

sealed abstract class TimeSeriesType(val text: String) extends EnumEntry {}

sealed abstract class IntraDayInterval(val text: String) extends EnumEntry {}

object TimeSeriesFunctions extends Enum[TimeSeriesType] with CirceEnum[TimeSeriesType] {
  val values: immutable.IndexedSeq[TimeSeriesType] = findValues

  case object TIME_SERIES_INTRADAY extends TimeSeriesType("TIME_SERIES_INTRADAY")

  case object TIME_SERIES_DAILY extends TimeSeriesType("TIME_SERIES_DAILY")

  case object TIME_SERIES_WEEKLY extends TimeSeriesType("TIME_SERIES_WEEKLY")

  case object TIME_SERIES_MONTHLY extends TimeSeriesType("TIME_SERIES_MONTHLY")

}

object IntraDayIntervals extends Enum[IntraDayInterval] with CirceEnum[IntraDayInterval] {
  val values: immutable.IndexedSeq[IntraDayInterval] = findValues

  case object MIN_1 extends IntraDayInterval("1min")

  case object MIN_5 extends IntraDayInterval("5min")

  case object MIN_15 extends IntraDayInterval("15min")

  case object MIN_30 extends IntraDayInterval("30min")

  case object MIN_60 extends IntraDayInterval("60min")

}

object StatusCodes extends Enum[StatusCode] with CirceEnum[StatusCode] {
  val values: immutable.IndexedSeq[StatusCode] = findValues

  case object SUCCESS extends StatusCode(200)

  case object INTERNAL_ERROR extends StatusCode(500)

  case object REQUESTER_ERROR extends StatusCode(400)

}


【问题讨论】:

    标签: json scala enums traits circe


    【解决方案1】:

    当我对您的代码进行了一些修改测试时(删除了一些东西以使代码编译更容易):

      type StatusCode = Int
      sealed trait ValuationResponse {
        def statusCode: StatusCode
      }
    
      case class SuccessfulResponse(succ: String, statusCode: StatusCode) extends ValuationResponse
    
      case class FailingResponse(reason: String, statusCode: StatusCode) extends ValuationResponse
    
      case class ValuationRequest(test: String)
    
      object derivation {
    
        implicit val encodeResponse: Encoder[ValuationResponse] = Encoder.instance {
          case response@SuccessfulResponse(_, _) => response.asJson
          case response@FailingResponse(_, _) => response.asJson
        }
        implicit val decodeResponse: Decoder[ValuationResponse] =
          List[Decoder[ValuationResponse]](
            Decoder[SuccessfulResponse].widen,
            Decoder[FailingResponse].widen
          ).reduceLeft(_ or _)
    
        implicit val encodeRequest: Encoder[ValuationRequest] = Encoder.instance {
          case response@ValuationRequest(_) => response.asJson
        }
        implicit val decodeRequest: Decoder[ValuationRequest] =
          List[Decoder[ValuationRequest]](
            Decoder[ValuationRequest].widen
          ).reduceLeft(_ or _)
      }
    
      val caseObject = FailingResponse("Some Error", 200)
      val jsonString = caseObject
            .asJson
            .printWith(Printer.noSpaces)
    

    我明白了

    @ decode[ValuationResponse](jsonString)
    res21: Either[Error, ValuationResponse] = Left(DecodingFailure(CNil, List()))
    

    但是,当我从对象导入隐式时

    @ import derivation._
    import derivation._
    
    @ decode[ValuationResponse](jsonString)
    res23: Either[Error, ValuationResponse] = Right(FailingResponse("Some Error", 200))
    

    事情是,默认情况下,Circe 使用区分字段来区分 sum 类型的成员。如果您不导入 derivation 对象,您可以看到您的值被编码成什么:

    @ {
      val jsonString = (caseObject : ValuationResponse)
            .asJson
            .printWith(Printer.noSpaces)
      }
    jsonString: String = "{\"FailingResponse\":{\"reason\":\"Some Error\",\"statusCode\":200}}"
    

    因此,您在解码案例类时使用了自动派生的编解码器 - 如果您删除了 import io.circe.generic.auto._,当您尝试解码而不导入您自己编写的代码 (import derivation._) 时,您的编译将失败。

    为避免将来出现此类情况:

    • 不要在编解码器使用站点上的生产中导入io.circe.generic.auto._ - 它可以为应该使用您的手写/手动派生编解码器的案例类派生新编解码器(这会导致像这样的错误)
    • 更喜欢io.circe.generic.semiauto._ 将派生编解码器召唤到您需要它们的位置(而不是Decoder[A]deriveDecoder[A]
    • 将半自动派生的编解码器和手写编解码器放在派生编解码器的类型的伴生对象中(如果可能的话) - 这样就无需在每次需要时手动导入它们
      import io.circe.generic.semiauto._
    
      sealed trait ValuationResponse ...
      object ValuationResponse {
        implicit val decodeResponse: Decoder[ValuationResponse] =
          List[Decoder[ValuationResponse]](
            deriveDecoder[SuccessfulResponse].widen,
            deriveDecoder[FailingResponse].widen
          ).reduceLeft(_ or _)
      }
    

    顺便说一句。使用 semiauto 还可以帮助您避免其他错误,例如您的代码中的 cyclic-dependency on initialization of your implicit

    @ derivation.decodeRequest.decodeJson("test".asJson)
    java.lang.NullPointerException
      ammonite.$sess.cmd7$.<clinit>(cmd7.sc:1)
    

    但如果它使用deriveDecoder:

    implicit val decodeRequest: Decoder[ValuationRequest] =
        List[Decoder[ValuationRequest]](
          deriveDecoder[ValuationRequest].widen
        ).reduceLeft(_ or _)
    

    你会得到:

    @ val decodeRequests: Decoder[ValuationRequest] =
          List[Decoder[ValuationRequest]](
            io.circe.generic.semiauto.deriveDecoder[ValuationRequest].widen
          ).reduceLeft(_ or _)
    decodeRequests: Decoder[ValuationRequest] = io.circe.generic.decoding.DerivedDecoder$$anon$1@30570f04
    
    @ decodeRequests.decodeJson("test".asJson)
    res9: Decoder.Result[ValuationRequest] = Left(DecodingFailure(Attempt to decode value on failed cursor, List(DownField(test))))
    

    【讨论】:

    • 好吧,在做了你建议的那些改变之后,解码工作了。但是在编码时我得到:异常或错误导致运行中止。 java.lang.StackOverflowError at io.circe.syntax.package$EncoderOps$.asJson$extension(package.scala:10) at eventbus.ValuationRequest$.$anonfun$encodeRequest$1(eventBusCases.scala:36) 第 36 行是:case response@ValuationRequest(_, _, _) => response.asJson
    • 问题现已解决。我不得不将评估请求更改为: object ValuationRequest {implicit val encodeRequest: Encoder[ValuationRequest] = derivedEncoder[ValuationRequest] implicit val decodeRequest: Decoder[ValuationRequest] = derivedDecoder[ValuationRequest] } asJson 的递归性显然给了麻烦。
    猜你喜欢
    • 2021-09-21
    • 2021-04-24
    • 2019-03-15
    • 2017-10-03
    • 2020-05-31
    • 2019-03-13
    • 2017-05-24
    • 2017-07-06
    • 2021-11-11
    相关资源
    最近更新 更多