使用Circe转换JSON

4
假设我有这样的一些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频道中经常出现类似的问题。这个具体的例子来自于昨天在那里提出的一个问题。)
1个回答

5
我曾经说过,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)
  )
}

然后:

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类型类怀疑地重新用途。 transform方法和该解码器的一个好处是,如果输入数据的形状与预期不符,结果错误将包括指向问题的历史记录。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接