【问题标题】:Decode json inside json string在 json 字符串中解码 json
【发布时间】:2021-02-02 17:52:45
【问题描述】:

我正在处理an API,它需要一个 JSON 对象,其中一个值(blob)是一个字符串化的 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,并破坏了编码器/解码器提供的抽象层。

有没有办法实现一个以一般方式进行双重解码的解码器?

【问题讨论】:

    标签: json scala circe


    【解决方案1】:

    好吧,因为描述的 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”。希望这可以帮助!

    【讨论】:

    • 这是一个很好的改进,但基本问题仍然存在。
    • @SimãoMartins 是的,我同意这仍然需要手动工作才能将此字符串解析为有意义的结构,但从我的角度来看,这并不是真正的问题,因为它是极端情况,@ 987654324@ 基础设施已建成。另一种选择是更改 JSON 架构,但 AFAIK 它是不受您控制的外部 API。
    • 你是说只使用解码器是不可能的吗?或者换句话说,不使用解析器模块?
    • @SimãoMartins 恐怕是这样,因为这是极端情况,开箱即用的库不支持。但是,我不认为这真的是一个问题,因为如果您认为通常的 JSON 不应该作为字符串出现在字段中,那么 JSON 解析库通常不支持这种情况,而是会处理这种情况,就像 circe 中的自定义编解码器一样。作为证明,我将指向 circe doc 中的 Instant 示例 - circe.github.io/circe/codecs/custom-codecs.html
    • 如果您发布的答案说如果不使用解析器模块就无法做到,我会将其标记为已接受/正确(我不记得术语)
    猜你喜欢
    • 2019-04-03
    • 2013-05-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-11
    • 2016-05-31
    相关资源
    最近更新 更多