【问题标题】:How to parse dynamic JSON with Circe如何使用 Circe 解析动态 JSON
【发布时间】:2019-05-08 04:29:37
【问题描述】:

我正在尝试解析 JSON,其中相同的字段可以是数组或对象。同样,特定字段可以是字符串或数字。请考虑以下示例。

  1. 空对象
{
 "technicalData": {}
}
  1. 字段为字符串或数字的集合
{
 "technicalData": [
   { 
      "techValueString": "0.173"
   },
   { 
      "techValueString": 0.173
   }
 ]
}

当数据为 {} 时,如何将 Circe 映射到接受 Nil 的 Scala 类?

case class Response(technicalData: Seq[TechnicalData])

case class TechnicalData(techValueString: String)

谢谢。

【问题讨论】:

  • 如何从案例 1(json 对象)中检索任何 techValueString?你没有非空对象的例子吗?
  • @BenjaminVialatou 这是同一个 JSON,它做得不好(第三方)。如果没有“technicalData”,那么它将显示为 JsObject {}(案例 #1),否则字段将包含在集合中(案例 #2)。基本上它非常定制。

标签: scala circe


【解决方案1】:

这里是应用 Circe 解码器的不太冗长的解决方案

case class Response(technicalData: Seq[TechnicalData])

case class TechnicalData(techValueString: String)

class StringToResponse() extends (String => Response) {

  implicit val responseDecoder: Decoder[Response] = Decoder.instance { c =>
    for {
      technicalData <- c.downField("technicalData").focus match {
        case None => Right(Nil)
        case Some(seq) => seq.asArray match {
          case None => Right(Nil)
          case Some(_) => c.get[Seq[TechnicalData]]("technicalData")
        }
      }
    } yield {
      Response(technicalData)
    }
  }

  implicit val technicalDataDecoder: Decoder[TechnicalData] = (
    Decoder.instance(_.get[String]("techValueString")).or(
      Decoder.instance(_.get[Double]("techValueString").map(_.toString))
    )
  ) mapN TechnicalData

  override def apply(body: String): Response = {
    decode[Response](body) match {
      case Right(response) => response
      case Left(e) => throw new RuntimeException(e)
    }
  }
}

希望这会对遇到类似问题的人有所帮助。

【讨论】:

  • 干得好@Sergey Terentyev!我看到你想要Nil,而没有"technicalData" 键。实际上我并不真正了解instancefocus,所以我可以从中学习。我已经进行了一些搜索,并开始制定另一个解决方案。好吧,我很快就会在我的回答中与你分享。
  • 嗨,本杰明,实际上,如果您在其中使用“Decoder.instance”作为 yield 正文,通常不应该明确调用它。但我发现它非常方便 :) 通过焦点,您可以确定字段是否存在。感谢您的帮助。
【解决方案2】:

这是解决您的问题的一种非常冗长的方法,但我希望它具有让您识别甚至纠正您可能需要的每个极限情况的优势:

import io.circe._
import io.circe.parser.parse

case class Response(technicalData: Seq[TechnicalData])

case class TechnicalData(techValueString: String)

val stringAsJson1 = """{

 "technicalData": {}
}"""

val stringAsJson2 = """{
 "technicalData": [
   { 
      "techValueString": "0.173"
   },
   { 
      "techValueString": 0.173
   }
 ]
}"""


def manageTechnicalDataAsArray(jsonArray: Vector[io.circe.Json]): Response = {
    Response(
      jsonArray.map(cell => {
        val value = cell.asObject
                        .getOrElse(throw new Exception("technicalData as a array should have each cell as an object"))
                        .apply("techValueString")
                        .getOrElse(throw new Exception("techValueString should be a key of any cell under technicalData array"))
        TechnicalData(value.asNumber
                           .map(_.toString)
                           .getOrElse(
                            value.asString
                                 .getOrElse(throw new Exception("techValueString value should be either string or number"))
                           )
                     )
                     }
               )
             )
}

def manageTechnicalDataAsObject(jsonObject: io.circe.JsonObject): Response = {
    jsonObject.toIterable match {
         case empty if empty.isEmpty => Response(Nil)
         case _ => throw new Exception("technicalData when object should be empty")
    }
}

def parseResponse(jsonAsString: String): Response = {
    parse(jsonAsString).getOrElse(Json.Null)
                       .asObject
                       .map(_("technicalData")
                             .getOrElse(throw new Exception("the json should contain a technicalData key"))
                             .arrayOrObject(throw new Exception("technicalData should contain either an objet or array"),
                                            manageTechnicalDataAsArray,
                                            manageTechnicalDataAsObject
                             )
                       ).getOrElse(throw new Exception("the json should contain an object at top"))
}

println(parseResponse(stringAsJson1))
println(parseResponse(stringAsJson2))

我可能很快会提供一个较短的版本,但对限制情况的指示性较差。您可以使用您的好 json 的调整版本来探索它们。

希望对你有帮助。

编辑:这是一个比上面更短、更清洁的解决方案,它是在@Sergey Terentyev 很好地找到一个之后出现的。好吧,它可能以某种方式不太可读,但它倾向于用或多或少的方式来处理限制情况:

  // Structure part
  case class TechnicalData(techValueString: String)
  object TechnicalData {
    def apply[T](input: T) = new TechnicalData(input.toString)
  }

  case class Response(technicalData: Seq[TechnicalData])

  // Decoding part
  import io.circe.{Decoder, parser, JsonObject, JsonNumber}
  import io.circe.Decoder.{decodeString, decodeJsonNumber}

  def tDDGenerator[C : Decoder]: Decoder[TechnicalData] = Decoder.forProduct1("techValueString")(TechnicalData.apply[C])

  implicit val technicalDataDecoder: Decoder[TechnicalData] = tDDGenerator[String].or(tDDGenerator[JsonNumber])

  implicit val responseDecoder: Decoder[Response] = Decoder[JsonObject]
    .emap(_("technicalData").map(o => Right(o.as[Seq[TechnicalData]].fold(_ => Nil, identity)))
      .getOrElse(Right(Nil))
      .map(Response.apply))

  // Test part

  val inputStrings = Seq(
    """{
      | "technicalData": [
      |   {
      |      "techValueString": "0.173"
      |   },
      |   {
      |      "techValueString": 0.173
      |   }
      | ]
      |}
  """.stripMargin,
    """{
      | "technicalData": {}
      |}
  """.stripMargin
  )

  inputStrings.foreach(parser.decode[Response](_).fold(println,println)) 

【讨论】:

  • 谢谢,本杰明。我会尽量适应我的解决方案:)
猜你喜欢
  • 1970-01-01
  • 2019-04-06
  • 2020-03-27
  • 1970-01-01
  • 2016-01-09
  • 1970-01-01
  • 1970-01-01
  • 2014-12-22
  • 2021-12-18
相关资源
最近更新 更多