我有一个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的任何子类,因为在解码时,我不确定该响应是FailingResponse还是SuccessResponse。我希望解码器能够推断出它是什么类型的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)
}
当我通过一些修改测试您的代码时(删除了一些东西以使代码编译更容易:]
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使用区分字段来区分和类型成员。如果不导入
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 _) }
顺便说一句。使用半自动还可以帮助您避免其他错误,例如您的代码中的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))))