Я иногда говорил, что 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
, так и этого декодера состоит в том, что если входные данные не имеют ожидаемой формы, то возникает ошибкабудет включать в себя историю, которая указывает на проблему.