用 Circe 改造 JSON
Transforming JSON with Circe
假设我有一些这样的 JSON 数据:
{
"data": {
"title": "example input",
"someBoolean": false,
"innerData": {
"innerString": "input inner string",
"innerBoolean": true,
"innerCollection": [1,2,3,4,5]
},
"collection": [6,7,8,9,0]
}
}
我想将它展平一点并转换或删除一些字段,以获得以下结果:
{
"data": {
"ttl": "example input",
"bool": false,
"collection": [6,7,8,9,0],
"innerCollection": [1,2,3,4,5]
}
}
如何使用 Circe 执行此操作?
(请注意,我将此作为常见问题解答提问,因为昨天 Circe Gitter channel. This specific example is from a question 中经常出现类似的问题。)
我有时会说 Circe 主要是一个用于编码和解码 JSON 的库,而不是用于转换 JSON 值的库,通常我建议映射到 Scala 类型然后定义它们之间的关系(正如 Andriy Plokhotnyuk 所建议的 here),但在许多情况下,使用游标编写转换工作得很好,在我看来,这种事情就是其中之一。
以下是我将如何实施此转换:
import io.circe.{DecodingFailure, Json, JsonObject}
import io.circe.syntax._
def transform(in: Json): Either[DecodingFailure, Json] = {
val someBoolean = in.hcursor.downField("data").downField("someBoolean")
val innerData = someBoolean.delete.downField("innerData")
for {
boolean <- someBoolean.as[Json]
collection <- innerData.get[Json]("innerCollection")
obj <- innerData.delete.up.as[JsonObject]
} yield Json.fromJsonObject(
obj.add("boolean", boolean).add("collection", collection)
)
}
然后:
val Right(json) = io.circe.jawn.parse(
"""{
"data": {
"title": "example input",
"someBoolean": false,
"innerData": {
"innerString": "input inner string",
"innerBoolean": true,
"innerCollection": [1,2,3]
},
"collection": [6,7,8]
}
}"""
)
并且:
scala> transform(json)
res1: Either[io.circe.DecodingFailure,io.circe.Json] =
Right({
"data" : {
"title" : "example input",
"collection" : [
6,
7,
8
]
},
"boolean" : false,
"collection" : [
1,
2,
3
]
})
如果你以正确的方式看待它,我们的 transform
方法有点像解码器,我们实际上可以将它写成一个解码器(尽管我绝对建议不要将其隐式化):
import io.circe.{Decoder, Json, JsonObject}
import io.circe.syntax._
val transformData: Decoder[Json] = { c =>
val someBoolean = c.downField("data").downField("someBoolean")
val innerData = someBoolean.delete.downField("innerData")
(
innerData.delete.up.as[JsonObject],
someBoolean.as[Json],
innerData.get[Json]("innerCollection")
).mapN(_.add("boolean", _).add("collection", _)).map(Json.fromJsonObject)
}
在某些情况下,如果您想将转换作为需要解码器的管道的一部分来执行,这会很方便:
scala> io.circe.jawn.decode(myJsonString)(transformData)
res2: Either[io.circe.Error,io.circe.Json] =
Right({
"data" : {
"title" : "example input",
"collection" : [ ...
不过,这也可能令人困惑,我考虑过向 Circe 添加某种 Transformation
类型,它可以封装这样的转换,而不会怀疑地重新利用 Decoder
类型 class.
transform
方法和此解码器的一个好处是,如果输入数据不具有预期的形状,则产生的错误将包含指向问题的历史记录。
假设我有一些这样的 JSON 数据:
{
"data": {
"title": "example input",
"someBoolean": false,
"innerData": {
"innerString": "input inner string",
"innerBoolean": true,
"innerCollection": [1,2,3,4,5]
},
"collection": [6,7,8,9,0]
}
}
我想将它展平一点并转换或删除一些字段,以获得以下结果:
{
"data": {
"ttl": "example input",
"bool": false,
"collection": [6,7,8,9,0],
"innerCollection": [1,2,3,4,5]
}
}
如何使用 Circe 执行此操作?
(请注意,我将此作为常见问题解答提问,因为昨天 Circe Gitter channel. This specific example is from a question 中经常出现类似的问题。)
我有时会说 Circe 主要是一个用于编码和解码 JSON 的库,而不是用于转换 JSON 值的库,通常我建议映射到 Scala 类型然后定义它们之间的关系(正如 Andriy Plokhotnyuk 所建议的 here),但在许多情况下,使用游标编写转换工作得很好,在我看来,这种事情就是其中之一。
以下是我将如何实施此转换:
import io.circe.{DecodingFailure, Json, JsonObject}
import io.circe.syntax._
def transform(in: Json): Either[DecodingFailure, Json] = {
val someBoolean = in.hcursor.downField("data").downField("someBoolean")
val innerData = someBoolean.delete.downField("innerData")
for {
boolean <- someBoolean.as[Json]
collection <- innerData.get[Json]("innerCollection")
obj <- innerData.delete.up.as[JsonObject]
} yield Json.fromJsonObject(
obj.add("boolean", boolean).add("collection", collection)
)
}
然后:
val Right(json) = io.circe.jawn.parse(
"""{
"data": {
"title": "example input",
"someBoolean": false,
"innerData": {
"innerString": "input inner string",
"innerBoolean": true,
"innerCollection": [1,2,3]
},
"collection": [6,7,8]
}
}"""
)
并且:
scala> transform(json)
res1: Either[io.circe.DecodingFailure,io.circe.Json] =
Right({
"data" : {
"title" : "example input",
"collection" : [
6,
7,
8
]
},
"boolean" : false,
"collection" : [
1,
2,
3
]
})
如果你以正确的方式看待它,我们的 transform
方法有点像解码器,我们实际上可以将它写成一个解码器(尽管我绝对建议不要将其隐式化):
import io.circe.{Decoder, Json, JsonObject}
import io.circe.syntax._
val transformData: Decoder[Json] = { c =>
val someBoolean = c.downField("data").downField("someBoolean")
val innerData = someBoolean.delete.downField("innerData")
(
innerData.delete.up.as[JsonObject],
someBoolean.as[Json],
innerData.get[Json]("innerCollection")
).mapN(_.add("boolean", _).add("collection", _)).map(Json.fromJsonObject)
}
在某些情况下,如果您想将转换作为需要解码器的管道的一部分来执行,这会很方便:
scala> io.circe.jawn.decode(myJsonString)(transformData)
res2: Either[io.circe.Error,io.circe.Json] =
Right({
"data" : {
"title" : "example input",
"collection" : [ ...
不过,这也可能令人困惑,我考虑过向 Circe 添加某种 Transformation
类型,它可以封装这样的转换,而不会怀疑地重新利用 Decoder
类型 class.
transform
方法和此解码器的一个好处是,如果输入数据不具有预期的形状,则产生的错误将包含指向问题的历史记录。