Преобразование JSON с помощью Circe - PullRequest
2 голосов
/ 20 сентября 2019

Предположим, у меня есть некоторые данные 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 Ответ

2 голосов
/ 20 сентября 2019

Я иногда говорил, что Circe - это, прежде всего, библиотека для кодирования и декодирования JSON, а не для преобразования значений JSON, и в целом я бы рекомендовал отображать типы Scala, а затем определять отношения между ними (как предлагает Андрей Плохотнюк здесь ), но во многих случаях написание преобразований с помощью курсоров работает просто отлично, и, на мой взгляд, это один из них.

Вот как я могу реализовать это преобразование:

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" : [ ...

Это также потенциально может сбить с толку, и я подумал о добавлении какого-то типа Transformation в Circe, который бы закрывалПодобные преобразования можно преобразовать без сомнительного изменения класса Decoder.

Одна из приятных особенностей как метода transform, так и этого декодера состоит в том, что если входные данные не имеют ожидаемой формы, то возникает ошибкабудет включать в себя историю, которая указывает на проблему.

...