如何使用 Circe 的手动解码器反序列化一个非固定的 json 数组?
How can I deserialize an non-fixed array of jsons using Circe's manual decoder?
我有一个 JSON 看起来像:
{
"data": [
{
"id": "1",
"email": "hello@world.com",
"name": "Mr foo",
"roles": [
"Chief Bar Officer"
],
"avatar_url": null,
"phone_number": null
},
{
"id": "2",
"email": "bye@world.com",
"name": "Mr baz",
"roles": [
"Chief Baz Officer"
],
"avatar_url": null,
"phone_number": null
}
]
}
我主要对 parsing/deserializing 数据列表感兴趣,我想手动完成(出于某种神秘原因,我更喜欢手动方式)。
如果这是相关的,我正在使用 sttp 的 circe 库 sttp.client.circe._
,目的是使用 asJson
.[=17= 将来自 get 请求的传入数据直接解析为 Json ]
获取 sttp 请求类似于:
val r1 = basicRequest
.get(uri"https://woooo.woo.wo/v1/users")
.header("accept", "application/json")
.header("Authorization", "topsecret"
.response(asJson[SomeClass])
这是我目前尝试过的方法:
// Define the case class
case class User(
id: String,
email: String,
name: String,
roles: List[String],
avatar_url: Option[String],
phone_number: Option[String]
)
// Define the manual deserializer
case object User {
implicit val userDecoder: Decoder[User] = (hCursor: HCursor) => {
val data = hCursor.downField("data").downArray
for {
id <- data.get[String]("id")
email <- data.get[String]("email")
name <- data.get[String]("name")
roles <- data.get[List[String]]("roles")
avatarUrl <- data.get[Option[String]]("avatarUrl")
phoneNumber <- data.get[Option[String]]("phoneNumber")
} yield User(id, email, name, roles, avatarUrl, phoneNumber)
}
}
我的方法的问题(我认为)是 .downArray
让我只序列化用户数组中的第一个用户。
我的 objective 是为了能够拥有一些用户序列(可能类似于 List[User]
),但目前我最终只反序列化了数组中的一个用户。
值得一提的是,“数据”数组不包含固定数量的用户,每次 api 调用都可能导致不同数量的用户。
感谢 Travis Brown and the circe Gitter community 帮助我解决了这个问题。
我在这里引用特拉维斯的话:
it would be better to build up the instance you need to parse the
top-level JSON object compositionally… i.e. have a Decoder[User] that
only decodes a single user JSON object, and then use
Decoder[List[User]].at("data") or something similar to decode the
top-level JSON object containing the data field with the JSON array.
我最终得到了一个看起来像这样的实现:
case class Users(users: List[User])
case object User {
implicit val usrDecoder: Decoder[User] = (hCursor: HCursor) => {
for {
id <- hCursor.get[String]("id")
email <- hCursor.get[String]("email")
name <- hCursor.get[String]("name")
roles <- hCursor.get[List[String]]("roles")
avatarUrl <- hCursor.get[Option[String]]("avatarUrl")
phoneNumber <- hCursor.get[Option[String]]("phoneNumber")
} yield User(id, email, name, roles, avatarUrl, phoneNumber)
}
implicit val decodeUsers: Decoder[Users] =
Decoder[List[User]].at("data").map(Users)
}
想法是分别组成一个用户的解码器和一个 collection 个用户的解码器。然后通过将 Users
映射到 Decoder,我们将 Decoder 的结果包装到 Users case class.
如果您想解码 List[User]
,您需要为确切类型 List[User]
创建解码器。它可能看起来像:
implicit val userDecoder: Decoder[List[User]] = (hCursor: HCursor) => {
Either.fromOption(
hCursor.downField("data").values,
DecodingFailure("Can't decode data", Nil)
).flatMap { values =>
values.toList.map(_.hcursor).traverse {
userCursor =>
for {
id <- userCursor.get[String]("id")
email <- userCursor.get[String]("email")
name <- userCursor.get[String]("name")
roles <- userCursor.get[List[String]]("roles")
avatarUrl <- userCursor.get[Option[String]]("avatarUrl")
phoneNumber <- userCursor.get[Option[String]]("phoneNumber")
} yield User(id, email, name, roles, avatarUrl, phoneNumber)
}
}
}
然后你可以像这样使用它:
json.as[List[User]]
这可能不是最好的主意,因为 circe 已经可以将 json 数组解码为列表,所以最好只为用户(解码对象)创建解码器:
implicit val userDecoder: Decoder[User] = (hCursor: HCursor) =>
for {
id <- hCursor.get[String]("id")
email <- hCursor.get[String]("email")
name <- hCursor.get[String]("name")
roles <- hCursor.get[List[String]]("roles")
avatarUrl <- hCursor.get[Option[String]]("avatarUrl")
phoneNumber <- hCursor.get[Option[String]]("phoneNumber")
} yield User(id, email, name, roles, avatarUrl, phoneNumber)
在这种情况下,您需要手动进入 data
字段:
json.hcursor.downField("data").as[List[User]]
另一种可能性是为 data
包装器创建 case class:
case class Data[D](data: D)
object Data {
implicit def dataDecoder[D: Decoder]: Decoder[Data[D]] =
(hCursor: HCursor) => hCursor.get[D]("data").map(Data(_))
}
然后你可以这样做:
json.as[Data[List[User]]]
我有一个 JSON 看起来像:
{
"data": [
{
"id": "1",
"email": "hello@world.com",
"name": "Mr foo",
"roles": [
"Chief Bar Officer"
],
"avatar_url": null,
"phone_number": null
},
{
"id": "2",
"email": "bye@world.com",
"name": "Mr baz",
"roles": [
"Chief Baz Officer"
],
"avatar_url": null,
"phone_number": null
}
]
}
我主要对 parsing/deserializing 数据列表感兴趣,我想手动完成(出于某种神秘原因,我更喜欢手动方式)。
如果这是相关的,我正在使用 sttp 的 circe 库 sttp.client.circe._
,目的是使用 asJson
.[=17= 将来自 get 请求的传入数据直接解析为 Json ]
获取 sttp 请求类似于:
val r1 = basicRequest
.get(uri"https://woooo.woo.wo/v1/users")
.header("accept", "application/json")
.header("Authorization", "topsecret"
.response(asJson[SomeClass])
这是我目前尝试过的方法:
// Define the case class
case class User(
id: String,
email: String,
name: String,
roles: List[String],
avatar_url: Option[String],
phone_number: Option[String]
)
// Define the manual deserializer
case object User {
implicit val userDecoder: Decoder[User] = (hCursor: HCursor) => {
val data = hCursor.downField("data").downArray
for {
id <- data.get[String]("id")
email <- data.get[String]("email")
name <- data.get[String]("name")
roles <- data.get[List[String]]("roles")
avatarUrl <- data.get[Option[String]]("avatarUrl")
phoneNumber <- data.get[Option[String]]("phoneNumber")
} yield User(id, email, name, roles, avatarUrl, phoneNumber)
}
}
我的方法的问题(我认为)是 .downArray
让我只序列化用户数组中的第一个用户。
我的 objective 是为了能够拥有一些用户序列(可能类似于 List[User]
),但目前我最终只反序列化了数组中的一个用户。
值得一提的是,“数据”数组不包含固定数量的用户,每次 api 调用都可能导致不同数量的用户。
感谢 Travis Brown and the circe Gitter community 帮助我解决了这个问题。
我在这里引用特拉维斯的话:
it would be better to build up the instance you need to parse the top-level JSON object compositionally… i.e. have a Decoder[User] that only decodes a single user JSON object, and then use Decoder[List[User]].at("data") or something similar to decode the top-level JSON object containing the data field with the JSON array.
我最终得到了一个看起来像这样的实现:
case class Users(users: List[User])
case object User {
implicit val usrDecoder: Decoder[User] = (hCursor: HCursor) => {
for {
id <- hCursor.get[String]("id")
email <- hCursor.get[String]("email")
name <- hCursor.get[String]("name")
roles <- hCursor.get[List[String]]("roles")
avatarUrl <- hCursor.get[Option[String]]("avatarUrl")
phoneNumber <- hCursor.get[Option[String]]("phoneNumber")
} yield User(id, email, name, roles, avatarUrl, phoneNumber)
}
implicit val decodeUsers: Decoder[Users] =
Decoder[List[User]].at("data").map(Users)
}
想法是分别组成一个用户的解码器和一个 collection 个用户的解码器。然后通过将 Users
映射到 Decoder,我们将 Decoder 的结果包装到 Users case class.
如果您想解码 List[User]
,您需要为确切类型 List[User]
创建解码器。它可能看起来像:
implicit val userDecoder: Decoder[List[User]] = (hCursor: HCursor) => {
Either.fromOption(
hCursor.downField("data").values,
DecodingFailure("Can't decode data", Nil)
).flatMap { values =>
values.toList.map(_.hcursor).traverse {
userCursor =>
for {
id <- userCursor.get[String]("id")
email <- userCursor.get[String]("email")
name <- userCursor.get[String]("name")
roles <- userCursor.get[List[String]]("roles")
avatarUrl <- userCursor.get[Option[String]]("avatarUrl")
phoneNumber <- userCursor.get[Option[String]]("phoneNumber")
} yield User(id, email, name, roles, avatarUrl, phoneNumber)
}
}
}
然后你可以像这样使用它:
json.as[List[User]]
这可能不是最好的主意,因为 circe 已经可以将 json 数组解码为列表,所以最好只为用户(解码对象)创建解码器:
implicit val userDecoder: Decoder[User] = (hCursor: HCursor) =>
for {
id <- hCursor.get[String]("id")
email <- hCursor.get[String]("email")
name <- hCursor.get[String]("name")
roles <- hCursor.get[List[String]]("roles")
avatarUrl <- hCursor.get[Option[String]]("avatarUrl")
phoneNumber <- hCursor.get[Option[String]]("phoneNumber")
} yield User(id, email, name, roles, avatarUrl, phoneNumber)
在这种情况下,您需要手动进入 data
字段:
json.hcursor.downField("data").as[List[User]]
另一种可能性是为 data
包装器创建 case class:
case class Data[D](data: D)
object Data {
implicit def dataDecoder[D: Decoder]: Decoder[Data[D]] =
(hCursor: HCursor) => hCursor.get[D]("data").map(Data(_))
}
然后你可以这样做:
json.as[Data[List[User]]]