Я имею дело с API который ожидает объект JSON, где одно из значений (большой двоичный объект) является строковым объектом JSON:
{
"credential": {
"blob": "{\"access\":\"181920\",\"secret\":\"secretKey\"}",
"project_id": "731fc6f265cd486d900f16e84c5cb594",
"type": "ec2",
"user_id": "bb5476fd12884539b41d5a88f838d773"
}
}
Мой класс домена:
case class Credential(access: String, secret: String, projectId: String, userId: String)
Кодировать доменный класс легко:
implicit val encoder: Encoder[Credential] = (a: Credential) => Json.obj(
"type" -> "ec2".asJson,
"blob" -> Map("access" -> a.access, "secret" -> a.secret).asJson.noSpaces.asJson,
"project_id" -> a.projectId.asJson,
"user_id" -> a.userId.asJson
)
Однако декодирование намного сложнее:
implicit val decoder: Decoder[Credential] = (c: HCursor) => for {
blobJsonString <- c.get[String]("blob")
blob <- decode[Json](blobJsonString).left.map(e => DecodingFailure(e.getMessage, c.downField("blob").history))
access <- blob.hcursor.get[String]("access")
secret <- blob.hcursor.get[String]("secret")
projectId <- c.get[String]("project_id")
userId <- c.get[String]("user_id")
} yield Credential(access, secret, projectId, userId)
Мне не нравится эта реализация, потому что она заставляет меня зависеть от circe-parser и ломать уровень абстракции, предоставляемый кодировщиками/декодерами.
Есть ли способ реализовать декодер, который обычно выполняет двойное декодирование?
Ответ 1 · рейтинг 1 · Ivan Kurchenko
Что ж, поскольку описанный JSON не совсем типичный случай, я не уверен, что можно полностью избежать ручного разбора, но если вы измените класс случая, представляющий эту структуру, вы можете использовать некоторые преимущества, которые предлагает circe. Пожалуйста, найдите пример кода ниже:
import io.circe._
import io.circe.generic.semiauto._
import io.circe.generic.auto._
object CredentialsParseApp {
case class CredentialsBlob(access: String, secret: String)
object CredentialsBlob {
implicit val encoder: Encoder[CredentialsBlob] = {
val derivedEncoder: Encoder[CredentialsBlob] = deriveEncoder[CredentialsBlob]
Encoder[String].contramap(blob => derivedEncoder(blob).noSpaces)
}
implicit val decoder: Decoder[CredentialsBlob] = {
val derivedDecoder: Decoder[CredentialsBlob] = deriveDecoder[CredentialsBlob]
Decoder[String].emap { value =>
for {
json <- parser.parse(value).left.map(_.message)
blob <- json.as(derivedDecoder).left.map(_.message)
} yield blob
}
}
}
case class Credentials(blob: CredentialsBlob, project_id: String, `type`: String = "ec2", user_id: String)
case class Response(credential: Credentials)
def main(args: Array[String]): Unit = {
val jsonString =
"""{
| "credential": {
| "blob": "{\"access\": \"181920\", \"secret\": \"secretKey\" }",
| "project_id": "731fc6f265cd486d900f16e84c5cb594",
| "type": "ec2",
| "user_id": "bb5476fd12884539b41d5a88f838d773"
| }
|}""".stripMargin
println(parser.parse(jsonString).flatMap(_.as[Response]))
}
}
что в моем случае дало следующий результат:
Right(Response(Credentials(CredentialsBlob(181920,secretKey),731fc6f265cd486d900f16e84c5cb594,ec2,bb5476fd12884539b41d5a88f838d773)))
Для этого примера я использовал версию circe "0.12.3". Надеюсь это поможет!