如何使用Circe解析动态JSON

时间:2019-05-02 12:58:37

标签: scala circe

我正在尝试解析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)

谢谢。

2 个答案:

答案 0 :(得分:1)

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

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)) 

答案 1 :(得分: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)
    }
  }
}

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