选择并使用适当的解码器作为新项目的解码器,同时保留错误累积
如果我有一些逻辑需要分歧的程度,因此需要选择一组可能使用的解码器中的1组,则可能会出现此问题。
考虑此代码段:
import cats.implicits.{catsSyntaxTuple3Semigroupal, catsSyntaxTuple2Semigroupal}
import io.circe.{Decoder, HCursor}
sealed trait Example
object Example {
case class OptionOne(
name: String,
time: String,
platform: String
) extends Example
object OptionOne {
implicit val decoder: Decoder[OptionOne] = {
(
Decoder[String].prepare(_.downField("event")),
Decoder[String].prepare(_.downField("time")),
Decoder[String].prepare(_.downField("platform"))
)
.mapN(OptionOne(_, _, _))
}
}
case class OptionTwo(
name: String,
time: String
) extends Example
object OptionTwo {
implicit val decoder: Decoder[OptionTwo] = {
(
Decoder[String].prepare(_.downField("event")),
Decoder[String].prepare(_.downField("time"))
)
.mapN(OptionTwo(_, _))
}
}
implicit val decoder: Decoder[Example] = (a: HCursor) => {
a.get[String]("event").flatMap {
case "some_precise_name" => OptionTwo.decoder(a)
case _ => OptionOne.decoder(a)
}
}
}
如果有条件地基于event
值的值,我想拥有optionOne
或optiontwo
选择的选择的解码器代码>示例确实成功地做到了。此外,如果有问题,它确实会返回解码失败。但是,如果您希望错误积累,这就是问题出现的地方。
如果您考虑此JSON对象进行测试:
Json.obj(
("event", "randomEvent".asJson),
("time", 8.asJson),
("platform", 5.asJson)
)
并将其传递到此基础:示例decoder.decodeaccumulating(InjsonInvalid.hcursor)
,只有第一个错误而不是预期的两个错误。
因此,问题是:如何以保留错误积累的方式完成?
[编辑]为了帮助澄清,这是一个测试套件,我希望通过解决方案来工作:
class ExampleSuite
extends AnyFunSuite
with Matchers
with Inside {
test("Decoder -- OptionOne") {
val inJsonValidApp: Json =
Json.obj(
("event", "something".asJson),
("time", "2021-05-05 20:09:57.448".asJson),
("platform", "app".asJson),
("context", Json.obj(
("app_version", "5.7.9".asJson)
))
)
noException should be thrownBy OptionOne.decoder.decodeJson(inJsonValidApp)
inside(OptionOne.decoder.decodeJson(inJsonValidApp)) {
case Right(OptionOne(name, time, platform)) =>
name should be("something")
time should be("2021-05-05 20:09:57.448")
platform should be("app")
}
val inJsonInvalid =
Json.obj(
("event", "randomEvent".asJson),
("time", 8.asJson),
("platform", 5.asJson)
)
noException should be thrownBy OptionOne.decoder.decodeJson(inJsonInvalid)
inside(OptionOne.decoder.decodeJson(inJsonInvalid)) {
case Left(DecodingFailure(msg, ops)) =>
msg should be("String")
ops should be(List(DownField("time")))
}
noException should be thrownBy OptionOne.decoder.decodeAccumulating(inJsonInvalid.hcursor)
inside(OptionOne.decoder.decodeAccumulating(inJsonInvalid.hcursor)) {
case Invalid(NonEmptyList(head, tail)) =>
inside(head) {
case DecodingFailure(msg, ops) =>
msg should be("String")
ops should be(List(DownField("time")))
}
inside(tail) {
case List(DecodingFailure(msg, ops)) =>
msg should be("String")
ops should be(List(DownField("platform")))
}
}
}
test("Decoder -- OptionTwo") {
val inJsonValidApp: Json =
Json.obj(
("event", "something".asJson),
("time", "2021-05-05 20:09:57.448".asJson),
("platform", "app".asJson)
)
noException should be thrownBy OptionTwo.decoder.decodeJson(inJsonValidApp)
inside(OptionTwo.decoder.decodeJson(inJsonValidApp)) {
case Right(OptionTwo(name, time)) =>
name should be("something")
time should be("2021-05-05 20:09:57.448")
}
val inJsonInvalid =
Json.obj(
("event", "randomEvent".asJson),
("platform", "bee".asJson)
)
noException should be thrownBy OptionTwo.decoder.decodeJson(inJsonInvalid)
inside(OptionTwo.decoder.decodeJson(inJsonInvalid)) {
case Left(DecodingFailure(msg, ops)) =>
msg should be("Attempt to decode value on failed cursor")
ops should be(List(DownField("time")))
}
}
test("Decoder -- Example (valid)") {
val inJsonValidApp: Json =
Json.obj(
("event", "something".asJson),
("time", "2021-05-05 20:09:57.448".asJson),
("platform", "app".asJson)
)
noException should be thrownBy Example.decoder.decodeJson(inJsonValidApp)
inside(Example.decoder.decodeJson(inJsonValidApp)) {
case Right(OptionOne(name, time, platform)) =>
name should be("something")
time should be("2021-05-05 20:09:57.448")
platform should be("app")
}
val inJsonBE: Json =
Json.obj(
("event", "some_precise_name".asJson),
("time", "2021-05-05 20:09:57.448".asJson),
("platform", "app".asJson)
)
noException should be thrownBy Example.decoder.decodeJson(inJsonBE)
inside(Example.decoder.decodeJson(inJsonBE)) {
case Right(OptionTwo(name, time)) =>
name should be("some_precise_name")
time should be("2021-05-05 20:09:57.448")
}
}
test("Decoder -- Example (invalid, single)") {
val inJsonInvalid =
Json.obj(
("event", "randomEvent".asJson),
("time", 5.asJson),
("platform", "bee".asJson)
)
noException should be thrownBy Example.decoder.decodeJson(inJsonInvalid)
inside(Example.decoder.decodeJson(inJsonInvalid)) {
case Left(DecodingFailure(msg, ops)) =>
msg should be("String")
ops should be(List(DownField("time")))
}
}
test("Decoder -- Example (invalid, multiple)") {
val inJsonInvalid =
Json.obj(
("event", "randomEvent".asJson),
("time", 8.asJson),
("platform", 5.asJson)
)
noException should be thrownBy Example.decoder.decodeAccumulating(inJsonInvalid.hcursor)
inside(Example.decoder.decodeAccumulating(inJsonInvalid.hcursor)) {
case Invalid(NonEmptyList(head, tail)) =>
inside(head) {
case DecodingFailure(msg, ops) =>
msg should be("String")
ops should be(List(DownField("time")))
}
inside(tail) {
case List(DecodingFailure(msg, ops)) =>
msg should be("String")
ops should be(List(DownField("platform")))
}
}
}
}
If I have some point where my logic needs to diverge and thus have need to select 1 of some set of possible decoders to use, this issue can arise.
Consider this code snippet:
import cats.implicits.{catsSyntaxTuple3Semigroupal, catsSyntaxTuple2Semigroupal}
import io.circe.{Decoder, HCursor}
sealed trait Example
object Example {
case class OptionOne(
name: String,
time: String,
platform: String
) extends Example
object OptionOne {
implicit val decoder: Decoder[OptionOne] = {
(
Decoder[String].prepare(_.downField("event")),
Decoder[String].prepare(_.downField("time")),
Decoder[String].prepare(_.downField("platform"))
)
.mapN(OptionOne(_, _, _))
}
}
case class OptionTwo(
name: String,
time: String
) extends Example
object OptionTwo {
implicit val decoder: Decoder[OptionTwo] = {
(
Decoder[String].prepare(_.downField("event")),
Decoder[String].prepare(_.downField("time"))
)
.mapN(OptionTwo(_, _))
}
}
implicit val decoder: Decoder[Example] = (a: HCursor) => {
a.get[String]("event").flatMap {
case "some_precise_name" => OptionTwo.decoder(a)
case _ => OptionOne.decoder(a)
}
}
}
If conditionally based on the value of the event
value I want to have either OptionOne
or OptionTwo
selected, the decoder for Example
does successfully do this. Additionally, it does return the decoding failure if there is a problem. However, if you want the errors to accumulate, this is where the problem emerges.
If you consider this JSON object for testing:
Json.obj(
("event", "randomEvent".asJson),
("time", 8.asJson),
("platform", 5.asJson)
)
and pass this in as such: Example.decoder.decodeAccumulating(inJsonInvalid.hcursor)
, only the first error is returned rather than both errors expected.
So the question is this: how can this be done in a way that preserves the accumulation of errors?
[EDIT] to help with clarification, here is a test suite I would expect to pass for the solution to work:
class ExampleSuite
extends AnyFunSuite
with Matchers
with Inside {
test("Decoder -- OptionOne") {
val inJsonValidApp: Json =
Json.obj(
("event", "something".asJson),
("time", "2021-05-05 20:09:57.448".asJson),
("platform", "app".asJson),
("context", Json.obj(
("app_version", "5.7.9".asJson)
))
)
noException should be thrownBy OptionOne.decoder.decodeJson(inJsonValidApp)
inside(OptionOne.decoder.decodeJson(inJsonValidApp)) {
case Right(OptionOne(name, time, platform)) =>
name should be("something")
time should be("2021-05-05 20:09:57.448")
platform should be("app")
}
val inJsonInvalid =
Json.obj(
("event", "randomEvent".asJson),
("time", 8.asJson),
("platform", 5.asJson)
)
noException should be thrownBy OptionOne.decoder.decodeJson(inJsonInvalid)
inside(OptionOne.decoder.decodeJson(inJsonInvalid)) {
case Left(DecodingFailure(msg, ops)) =>
msg should be("String")
ops should be(List(DownField("time")))
}
noException should be thrownBy OptionOne.decoder.decodeAccumulating(inJsonInvalid.hcursor)
inside(OptionOne.decoder.decodeAccumulating(inJsonInvalid.hcursor)) {
case Invalid(NonEmptyList(head, tail)) =>
inside(head) {
case DecodingFailure(msg, ops) =>
msg should be("String")
ops should be(List(DownField("time")))
}
inside(tail) {
case List(DecodingFailure(msg, ops)) =>
msg should be("String")
ops should be(List(DownField("platform")))
}
}
}
test("Decoder -- OptionTwo") {
val inJsonValidApp: Json =
Json.obj(
("event", "something".asJson),
("time", "2021-05-05 20:09:57.448".asJson),
("platform", "app".asJson)
)
noException should be thrownBy OptionTwo.decoder.decodeJson(inJsonValidApp)
inside(OptionTwo.decoder.decodeJson(inJsonValidApp)) {
case Right(OptionTwo(name, time)) =>
name should be("something")
time should be("2021-05-05 20:09:57.448")
}
val inJsonInvalid =
Json.obj(
("event", "randomEvent".asJson),
("platform", "bee".asJson)
)
noException should be thrownBy OptionTwo.decoder.decodeJson(inJsonInvalid)
inside(OptionTwo.decoder.decodeJson(inJsonInvalid)) {
case Left(DecodingFailure(msg, ops)) =>
msg should be("Attempt to decode value on failed cursor")
ops should be(List(DownField("time")))
}
}
test("Decoder -- Example (valid)") {
val inJsonValidApp: Json =
Json.obj(
("event", "something".asJson),
("time", "2021-05-05 20:09:57.448".asJson),
("platform", "app".asJson)
)
noException should be thrownBy Example.decoder.decodeJson(inJsonValidApp)
inside(Example.decoder.decodeJson(inJsonValidApp)) {
case Right(OptionOne(name, time, platform)) =>
name should be("something")
time should be("2021-05-05 20:09:57.448")
platform should be("app")
}
val inJsonBE: Json =
Json.obj(
("event", "some_precise_name".asJson),
("time", "2021-05-05 20:09:57.448".asJson),
("platform", "app".asJson)
)
noException should be thrownBy Example.decoder.decodeJson(inJsonBE)
inside(Example.decoder.decodeJson(inJsonBE)) {
case Right(OptionTwo(name, time)) =>
name should be("some_precise_name")
time should be("2021-05-05 20:09:57.448")
}
}
test("Decoder -- Example (invalid, single)") {
val inJsonInvalid =
Json.obj(
("event", "randomEvent".asJson),
("time", 5.asJson),
("platform", "bee".asJson)
)
noException should be thrownBy Example.decoder.decodeJson(inJsonInvalid)
inside(Example.decoder.decodeJson(inJsonInvalid)) {
case Left(DecodingFailure(msg, ops)) =>
msg should be("String")
ops should be(List(DownField("time")))
}
}
test("Decoder -- Example (invalid, multiple)") {
val inJsonInvalid =
Json.obj(
("event", "randomEvent".asJson),
("time", 8.asJson),
("platform", 5.asJson)
)
noException should be thrownBy Example.decoder.decodeAccumulating(inJsonInvalid.hcursor)
inside(Example.decoder.decodeAccumulating(inJsonInvalid.hcursor)) {
case Invalid(NonEmptyList(head, tail)) =>
inside(head) {
case DecodingFailure(msg, ops) =>
msg should be("String")
ops should be(List(DownField("time")))
}
inside(tail) {
case List(DecodingFailure(msg, ops)) =>
msg should be("String")
ops should be(List(DownField("platform")))
}
}
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
由于它们是解码器,因此您可以将它们结合在一起:
它只会尝试每个
since they are Decoders, you can combine them in a such way:
it will just try each one