假设我有一些像这样的 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]
}
}
Run Code Online (Sandbox Code Playgroud)
我想将其压平一点并转换或删除一些字段,以获得以下结果:
{
"data": {
"ttl": "example input",
"bool": false,
"collection": [6,7,8,9,0],
"innerCollection": [1,2,3,4,5]
}
}
Run Code Online (Sandbox Code Playgroud)
我怎样才能用Circe做到这一点?
(请注意,我将此作为常见问题解答提出,因为Circe Gitter 频道中经常出现类似的问题。这个具体示例来自昨天提出的问题。)
我有时会说 Circe 主要是一个用于编码和解码 JSON 的库,而不是用于转换 JSON 值,一般来说,我建议映射到 Scala 类型,然后定义这些类型之间的关系(正如 Andriy Plokhotnyuk 在此建议的那样),但对于很多情况下,用游标编写转换效果很好,在我看来,这种事情就是其中之一。
以下是我实现此转换的方法:
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)
)
}
Run Code Online (Sandbox Code Playgroud)
进而:
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]
}
}"""
)
Run Code Online (Sandbox Code Playgroud)
和:
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
]
})
Run Code Online (Sandbox Code Playgroud)
如果你以正确的方式看待它,我们的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)
}
Run Code Online (Sandbox Code Playgroud)
在某些情况下,您希望将转换作为需要解码器的管道的一部分来执行,这可能很方便:
scala> io.circe.jawn.decode(myJsonString)(transformData)
res2: Either[io.circe.Error,io.circe.Json] =
Right({
"data" : {
"title" : "example input",
"collection" : [ ...
Run Code Online (Sandbox Code Playgroud)
不过,这也可能会令人困惑,而且我考虑过Transformation向 Circe 添加某种类型,该类型可以封装这样的转换,而无需重新调整Decoder类型类的用途。
该方法和该解码器的一个好处transform是,如果输入数据不具有预期的形状,则生成的错误将包含指向问题的历史记录。