使用 argonaut/circe 解码 json 数组中对象的单个字段
Decode single field of object in a json array with argonaut/circe
假设我有这样的json
{
"sha": "some sha",
"parents": [{
"url": "some url",
"sha": "some parent sha"
}]
}
还有这种情况class
case class Commit(sha: String, parentShas: List[String])
在游戏中-json我可以这样写:
val commitReads: Reads[Commit] = (
(JsPath \ "sha").read[String] and
(JsPath \ "parents" \ "sha").read[List[String]]
)(Commit.apply _)
我正在寻找一种只解码 argonaut/circe 中 "parent" 的 "sha" 的等效方法,但我还没有找到任何方法。 "HCursor/ACursor" 有 downArray 但从那以后我不知道该怎么做。非常感谢您!
circe 和 Argonaut 都不会跟踪在 JSON 对象中读取了哪些字段,因此您可以忽略额外的 "url"
字段(就像在 Play 中一样)。比较棘手的部分是找到 Play 的 \
的等价物,虽然你已经说服我我们需要添加它,但 circe 目前还没有。
首先,如果你有一个单独的 SHA 类型,这就相对容易了:
import io.circe.Decoder
val doc = """
{
"sha": "some sha",
"parents": [{
"url": "some url",
"sha": "some parent sha"
}]
}
"""
case class Sha(value: String)
object Sha {
implicit val decodeSha: Decoder[Sha] = Decoder.instance(_.get[String]("sha")).map(Sha(_))
}
case class Commit(sha: Sha, parentShas: List[Sha])
object Commit {
implicit val decodeCommit: Decoder[Commit] = for {
sha <- Decoder[Sha]
parents <- Decoder.instance(_.get[List[Sha]]("parents"))
} yield Commit(sha, parents)
}
或者,使用 Cats 的应用语法:
import cats.syntax.cartesian._
implicit val decodeCommit: Decoder[Commit] =
(Decoder[Sha] |@| Decoder.instance(_.get[List[Sha]]("parents"))).map(Commit(_, _))
然后:
scala> import io.circe.jawn._
import io.circe.jawn._
scala> decode[Commit](doc)
res0: cats.data.Xor[io.circe.Error,Commit] = Right(Commit(Sha(some sha),List(Sha(some parent sha))))
但这并不是真正的答案,因为我不会要求您更改模型。 :) 实际答案没那么有趣:
case class Commit(sha: String, parentShas: List[String])
object Commit {
val extractSha: Decoder[String] = Decoder.instance(_.get[String]("sha"))
implicit val decodeCommit: Decoder[Commit] = for {
sha <- extractSha
parents <- Decoder.instance(c =>
c.get("parents")(Decoder.decodeCanBuildFrom[String, List](extractSha, implicitly))
)
} yield Commit(sha, parents)
}
这很糟糕,我很惭愧这是必要的,但它确实有效。我刚刚提交 an issue 以确保在未来的 circe 版本中这会变得更好。
假设我有这样的json
{
"sha": "some sha",
"parents": [{
"url": "some url",
"sha": "some parent sha"
}]
}
还有这种情况class
case class Commit(sha: String, parentShas: List[String])
在游戏中-json我可以这样写:
val commitReads: Reads[Commit] = (
(JsPath \ "sha").read[String] and
(JsPath \ "parents" \ "sha").read[List[String]]
)(Commit.apply _)
我正在寻找一种只解码 argonaut/circe 中 "parent" 的 "sha" 的等效方法,但我还没有找到任何方法。 "HCursor/ACursor" 有 downArray 但从那以后我不知道该怎么做。非常感谢您!
circe 和 Argonaut 都不会跟踪在 JSON 对象中读取了哪些字段,因此您可以忽略额外的 "url"
字段(就像在 Play 中一样)。比较棘手的部分是找到 Play 的 \
的等价物,虽然你已经说服我我们需要添加它,但 circe 目前还没有。
首先,如果你有一个单独的 SHA 类型,这就相对容易了:
import io.circe.Decoder
val doc = """
{
"sha": "some sha",
"parents": [{
"url": "some url",
"sha": "some parent sha"
}]
}
"""
case class Sha(value: String)
object Sha {
implicit val decodeSha: Decoder[Sha] = Decoder.instance(_.get[String]("sha")).map(Sha(_))
}
case class Commit(sha: Sha, parentShas: List[Sha])
object Commit {
implicit val decodeCommit: Decoder[Commit] = for {
sha <- Decoder[Sha]
parents <- Decoder.instance(_.get[List[Sha]]("parents"))
} yield Commit(sha, parents)
}
或者,使用 Cats 的应用语法:
import cats.syntax.cartesian._
implicit val decodeCommit: Decoder[Commit] =
(Decoder[Sha] |@| Decoder.instance(_.get[List[Sha]]("parents"))).map(Commit(_, _))
然后:
scala> import io.circe.jawn._
import io.circe.jawn._
scala> decode[Commit](doc)
res0: cats.data.Xor[io.circe.Error,Commit] = Right(Commit(Sha(some sha),List(Sha(some parent sha))))
但这并不是真正的答案,因为我不会要求您更改模型。 :) 实际答案没那么有趣:
case class Commit(sha: String, parentShas: List[String])
object Commit {
val extractSha: Decoder[String] = Decoder.instance(_.get[String]("sha"))
implicit val decodeCommit: Decoder[Commit] = for {
sha <- extractSha
parents <- Decoder.instance(c =>
c.get("parents")(Decoder.decodeCanBuildFrom[String, List](extractSha, implicitly))
)
} yield Commit(sha, parents)
}
这很糟糕,我很惭愧这是必要的,但它确实有效。我刚刚提交 an issue 以确保在未来的 circe 版本中这会变得更好。