簡體   English   中英

在Scala中使用circe解碼結構化JSON數組

[英]Decoding structured JSON arrays with circe in Scala

假設我需要對如下所示的JSON數組進行解碼,其中在開始處有幾個字段,一些任意數量的齊次元素,然后是其他字段:

[ "Foo", "McBar", true, false, false, false, true, 137 ]

我不知道為什么有人會選擇像這樣對他們的數據進行編碼,但是人們做的事情很奇怪,並且在這種情況下,我只需要處理它。

我想將此JSON解碼為如下的case類:

case class Foo(firstName: String, lastName: String, age: Int, stuff: List[Boolean])

我們可以這樣寫:

import cats.syntax.either._
import io.circe.{ Decoder, DecodingFailure, Json }

implicit val fooDecoder: Decoder[Foo] = Decoder.instance { c =>
  c.focus.flatMap(_.asArray) match {
    case Some(fnJ +: lnJ +: rest) =>
      rest.reverse match {
        case ageJ +: stuffJ =>
          for {
            fn    <- fnJ.as[String]
            ln    <- lnJ.as[String]
            age   <- ageJ.as[Int]
            stuff <- Json.fromValues(stuffJ.reverse).as[List[Boolean]]
          } yield Foo(fn, ln, age, stuff)
        case _ => Left(DecodingFailure("Foo", c.history))
      }
    case None => Left(DecodingFailure("Foo", c.history))
  }
}

…有效:

scala> fooDecoder.decodeJson(json"""[ "Foo", "McBar", true, false, 137 ]""")
res3: io.circe.Decoder.Result[Foo] = Right(Foo(Foo,McBar,137,List(true, false)))

但是,那太可怕了。 錯誤消息也完全沒有用:

scala> fooDecoder.decodeJson(json"""[ "Foo", "McBar", true, false ]""")
res4: io.circe.Decoder.Result[Foo] = Left(DecodingFailure(Int, List()))

當然,有一種方法可以做到,不涉及在游標和Json值之間來回切換,不丟棄錯誤消息中的歷史記錄,而只是使人眼花?亂?


一些上下文:關於編寫這樣的自定義JSON數組解碼器的問題大約經常出現(例如, 今天早上 )。 在即將發布的circe版本中,如何執行此操作的具體細節可能會發生變化(盡管API將會類似;有關更多詳細信息,請參見此實驗項目 ),因此,我真的不想花很多時間來添加文檔中有這樣的示例,但是我認為它確實值得進行堆棧溢出問答。

使用游標

有一個更好的方法! 通過直接使用游標,您可以更加簡潔地編寫代碼,同時還可以維護有用的錯誤消息:

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方法調用)進行downArray

當我們開始時, c是我們希望指向數組的HCursor c.downArray將光標移動到數組中的第一個元素。 如果輸入根本不是數組或為空數組,則此操作將失敗,並且我們將收到一條有用的錯誤消息。 如果成功, for -comprehension的第一行將嘗試將第一個元素解碼為字符串,並使光標指向該第一個元素。

for -comprehension中的第二行表示“好的,我們已經完成了第一個元素,因此讓我們忘記它,然后移至第二個。” 方法名稱的delete部分並不意味着它實際上是在改變任何東西-幾乎沒有任何改變以用戶可以觀察到的任何方式-只是意味着該元素將不能再用於結果游標上的任何將來的操作。

第三行嘗試將原始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))

您應該采用這兩種方法中的哪一種取決於您的口味,以及您是否關心錯誤累積—我個人認為第一種方法更具可讀性。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM