使用光标
case class Foo(firstName: String, lastName: String, age: Int, stuff: List[Boolean])
import cats.syntax.either._
import io.circe.Decoder
implicit val fooDecoder: Decoder[Foo] = Decoder.instance { c =>
val fnC = c.downArray
for {
fn <- fnC.as[String]
lnC = fnC.deleteGoRight
ln <- lnC.as[String]
ageC = lnC.deleteGoLast
age <- ageC.as[Int]
stuffC = ageC.delete
stuff <- stuffC.as[List[Boolean]]
} yield Foo(fn, ln, age, stuff)
}
这也适用于:
scala> fooDecoder.decodeJson(json"""[ "Foo", "McBar", true, false, 137 ]""")
res0: io.circe.Decoder.Result[Foo] = Right(Foo(Foo,McBar,137,List(true, false)))
但它也为我们指明了错误发生的地方:
scala> fooDecoder.decodeJson(json"""[ "Foo", "McBar", true, false ]""")
res1: io.circe.Decoder.Result[Foo] = Left(DecodingFailure(Int, List(DeleteGoLast, DeleteGoRight, DownArray)))
关键思想是我们交错“读取”操作(即
.as[X]
downArray
还有三个
delete
方法调用)。
c
是一个
HCursor
我们希望它指向一个数组。
c.downArray
将光标移动到数组中的第一个元素。如果输入根本不是数组,或者是空数组,则此操作将失败,我们将得到一条有用的错误消息。如果成功,则
for
中的第二行
对于
删去
方法名称的一部分并不意味着它实际上在变异任何东西circe中的任何东西都不会以用户可以观察到的任何方式变异任何东西,这只是意味着该元素将不可用于对结果游标的任何未来操作。
第三行尝试将原始JSON数组中的第二个元素(现在是新游标中的第一个元素)解码为字符串。完成后,第四行“删除”该元素并移动到数组的末尾,然后第五行尝试将最后一个元素解码为
Int
下一行可能是最有趣的:
stuffC = ageC.delete
这意味着,好的,我们已经到了JSON数组修改视图中的最后一个元素(之前我们删除了前两个元素)。现在我们删除最后一个元素并移动光标
向上的
实际上,你可以用一种更简洁的方式来写:
import cats.syntax.all._
import io.circe.Decoder
implicit val fooDecoder: Decoder[Foo] = (
Decoder[String].prepare(_.downArray),
Decoder[String].prepare(_.downArray.deleteGoRight),
Decoder[Int].prepare(_.downArray.deleteGoLast),
Decoder[List[Boolean]].prepare(_.downArray.deleteGoRight.deleteGoLast.delete)
).map4(Foo)
val bad = """[["Foo"], "McBar", true, "true", false, 13.7 ]"""
val badResult = io.circe.jawn.decodeAccumulating[Foo](bad)
scala> badResult.leftMap(_.map(println))
DecodingFailure(String, List(DownArray))
DecodingFailure(Int, List(DeleteGoLast, DownArray))
DecodingFailure([A]List[A], List(MoveRight, DownArray, DeleteGoParent, DeleteGoLast, DeleteGoRight, DownArray))