如何使用标准的 Scala 类解析 Scala 中的 JSON?

我使用 Scala 2.8中 JSON 类中的构建来解析 JSON 代码。由于最小化依赖性,我不想使用 Liftweb。

The way I am doing it seems too imperative, is there a better way to do it?

import scala.util.parsing.json._
...
val json:Option[Any] = JSON.parseFull(jsonString)
val map:Map[String,Any] = json.get.asInstanceOf[Map[String, Any]]
val languages:List[Any] = map.get("languages").get.asInstanceOf[List[Any]]
languages.foreach( langMap => {
val language:Map[String,Any] = langMap.asInstanceOf[Map[String,Any]]
val name:String = language.get("name").get.asInstanceOf[String]
val isActive:Boolean = language.get("is_active").get.asInstanceOf[Boolean]
val completeness:Double = language.get("completeness").get.asInstanceOf[Double]
}
162526 次浏览

这是一个基于提取器的解决方案,它将进行类强制转换:

class CC[T] { def unapply(a:Any):Option[T] = Some(a.asInstanceOf[T]) }


object M extends CC[Map[String, Any]]
object L extends CC[List[Any]]
object S extends CC[String]
object D extends CC[Double]
object B extends CC[Boolean]


val jsonString =
"""
{
"languages": [{
"name": "English",
"is_active": true,
"completeness": 2.5
}, {
"name": "Latin",
"is_active": false,
"completeness": 0.9
}]
}
""".stripMargin


val result = for {
Some(M(map)) <- List(JSON.parseFull(jsonString))
L(languages) = map("languages")
M(language) <- languages
S(name) = language("name")
B(active) = language("is_active")
D(completeness) = language("completeness")
} yield {
(name, active, completeness)
}


assert( result == List(("English",true,2.5), ("Latin",false,0.9)))

在 for 循环的开始,我人为地将结果包装在一个列表中,以便在结束时生成一个列表。然后,在 for 循环的其余部分中,我将使用这样一个事实: 生成器(使用 <-)和值定义(使用 =)将使用取消应用的方法。

(旧的答案编辑掉了——如果你好奇,可以查看编辑历史记录)

我尝试了一些方法,把模式匹配作为避免铸造的一种方法,但是遇到了在收藏类型上进行类型擦除的麻烦。

主要的问题似乎是解析结果的完整类型反映了 JSON 数据的结构,并且要么很麻烦,要么不可能完全状态。我想这就是为什么使用 任何来截断类型定义的原因。使用 任何导致需要铸造。

I've hacked something below which is concise but is extremely specific to the JSON data implied by the code in the question. Something more general would be more satisfactory but I'm not sure if it would be very elegant.

implicit def any2string(a: Any)  = a.toString
implicit def any2boolean(a: Any) = a.asInstanceOf[Boolean]
implicit def any2double(a: Any)  = a.asInstanceOf[Double]


case class Language(name: String, isActive: Boolean, completeness: Double)


val languages = JSON.parseFull(jstr) match {
case Some(x) => {
val m = x.asInstanceOf[Map[String, List[Map[String, Any]]]]


m("languages") map {l => Language(l("name"), l("isActive"), l("completeness"))}
}
case None => Nil
}


languages foreach {println}

我是这样做模式匹配的:

val result = JSON.parseFull(jsonStr)
result match {
// Matches if jsonStr is valid JSON and represents a Map of Strings to Any
case Some(map: Map[String, Any]) => println(map)
case None => println("Parsing failed")
case other => println("Unknown data structure: " + other)
}

我喜欢@huynhjl 的回答,它引导我走上了正确的道路。然而,它并不擅长处理错误条件。如果所需的节点不存在,则会得到强制转换异常。为了更好地处理这个问题,我稍微调整了一下 Option

class CC[T] {
def unapply(a:Option[Any]):Option[T] = if (a.isEmpty) {
None
} else {
Some(a.get.asInstanceOf[T])
}
}


object M extends CC[Map[String, Any]]
object L extends CC[List[Any]]
object S extends CC[String]
object D extends CC[Double]
object B extends CC[Boolean]


for {
M(map) <- List(JSON.parseFull(jsonString))
L(languages) = map.get("languages")
language <- languages
M(lang) = Some(language)
S(name) = lang.get("name")
B(active) = lang.get("is_active")
D(completeness) = lang.get("completeness")
} yield {
(name, active, completeness)
}

当然,与其说这是处理错误,不如说是避免错误。如果缺少任何 json 节点,这将产生一个空列表。您可以使用 match在执行操作之前检查节点的存在..。

for {
M(map) <- Some(JSON.parseFull(jsonString))
} yield {
map.get("languages") match {
case L(languages) => {
for {
language <- languages
M(lang) = Some(language)
S(name) = lang.get("name")
B(active) = lang.get("is_active")
D(completeness) = lang.get("completeness")
} yield {
(name, active, completeness)
}
}
case None => "bad json"
}
}
val jsonString =
"""
|{
| "languages": [{
|     "name": "English",
|     "is_active": true,
|     "completeness": 2.5
| }, {
|     "name": "Latin",
|     "is_active": false,
|     "completeness": 0.9
| }]
|}
""".stripMargin


val result = JSON.parseFull(jsonString).map {
case json: Map[String, List[Map[String, Any]]] =>
json("languages").map(l => (l("name"), l("is_active"), l("completeness")))
}.get


println(result)


assert( result == List(("English", true, 2.5), ("Latin", false, 0.9)) )

您可以这样做! 很容易解析 JSON 代码: P

package org.sqkb.service.common.bean


import java.text.SimpleDateFormat


import org.json4s
import org.json4s.JValue
import org.json4s.jackson.JsonMethods._
//import org.sqkb.service.common.kit.{IsvCode}


import scala.util.Try


/**
*
*/
case class Order(log: String) {


implicit lazy val formats = org.json4s.DefaultFormats


lazy val json: json4s.JValue = parse(log)


lazy val create_time: String = (json \ "create_time").extractOrElse("1970-01-01 00:00:00")
lazy val site_id: String = (json \ "site_id").extractOrElse("")
lazy val alipay_total_price: Double = (json \ "alipay_total_price").extractOpt[String].filter(_.nonEmpty).getOrElse("0").toDouble
lazy val gmv: Double = alipay_total_price
lazy val pub_share_pre_fee: Double = (json \ "pub_share_pre_fee").extractOpt[String].filter(_.nonEmpty).getOrElse("0").toDouble
lazy val profit: Double = pub_share_pre_fee


lazy val trade_id: String = (json \ "trade_id").extractOrElse("")
lazy val unid: Long = Try((json \ "unid").extractOpt[String].filter(_.nonEmpty).get.toLong).getOrElse(0L)
lazy val cate_id1: Int = (json \ "cate_id").extractOrElse(0)
lazy val cate_id2: Int = (json \ "subcate_id").extractOrElse(0)
lazy val cate_id3: Int = (json \ "cate_id3").extractOrElse(0)
lazy val cate_id4: Int = (json \ "cate_id4").extractOrElse(0)
lazy val coupon_id: Long = (json \ "coupon_id").extractOrElse(0)


lazy val platform: Option[String] = Order.siteMap.get(site_id)




def time_fmt(fmt: String = "yyyy-MM-dd HH:mm:ss"): String = {
val dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
val date = dateFormat.parse(this.create_time)
new SimpleDateFormat(fmt).format(date)
}


}

这就是我使用 Scala 解析器组合子库的方法:

import scala.util.parsing.combinator._
class ImprovedJsonParser extends JavaTokenParsers {


def obj: Parser[Map[String, Any]] =
"{" ~> repsep(member, ",") <~ "}" ^^ (Map() ++ _)


def array: Parser[List[Any]] =
"[" ~> repsep(value, ",") <~ "]"


def member: Parser[(String, Any)] =
stringLiteral ~ ":" ~ value ^^ { case name ~ ":" ~ value => (name, value) }


def value: Parser[Any] = (
obj
| array
| stringLiteral
| floatingPointNumber ^^ (_.toDouble)
|"true"
|"false"
)


}
object ImprovedJsonParserTest extends ImprovedJsonParser {
def main(args: Array[String]) {
val jsonString =
"""
{
"languages": [{
"name": "English",
"is_active": true,
"completeness": 2.5
}, {
"name": "Latin",
"is_active": false,
"completeness": 0.9
}]
}
""".stripMargin




val result = parseAll(value, jsonString)
println(result)


}
}

不推荐使用 scala.util.parsing.json.JSON

下面是另一种使用 circe.FYI 文档的方法: https://circe.github.io/circe/cursors.html

build.sbt中添加依赖项,我使用了 scala 2.13.4,注意 scala 版本必须与库版本对齐。

val circeVersion = "0.14.0-M2"


libraryDependencies ++= Seq(
"io.circe"  %% "circe-core"     % circeVersion,
"io.circe"  %% "circe-generic"  % circeVersion,
"io.circe"  %% "circe-parser"   % circeVersion
)

例子一:

case class Person(name: String, age: Int)


object Main {
def main(args: Array[String]): Unit = {
val input =
"""
|{
|  "kind": "Listing",
|  "data": [
|    {
|      "name": "Frodo",
|      "age": 51
|    },
|    {
|      "name": "Bilbo",
|      "age": 60
|    }
|  ]
|}
|""".stripMargin


implicit val decoderPerson: Decoder[Person] = deriveDecoder[Person] // decoder required to parse to custom object


val parseResult: Json = circe.parser.parse(input).getOrElse(Json.Null)
val data: ACursor = parseResult.hcursor.downField("data") // get the data field
val personList: List[Person] = data.as[List[Person]].getOrElse(null) // parse the dataField to a list of Person
for {
person <- personList
} println(person.name + " is " + person.age)
}
}

Example 2, json has an object within an object:

case class Person(name: String, age: Int, position: Position)
case class Position(x: Int, y: Int)


object Main {
def main(args: Array[String]): Unit = {
val input =
"""
|{
|  "kind": "Listing",
|  "data": [
|    {
|      "name": "Frodo",
|      "age": 51,
|      "position": {
|        "x": 10,
|        "y": 20
|      }
|    },
|    {
|      "name": "Bilbo",
|      "age": 60,
|      "position": {
|        "x": 75,
|        "y": 85
|      }
|    }
|  ]
|}
|""".stripMargin


implicit val decoderPosition: Decoder[Position] = deriveDecoder[Position] // must be defined before the Person decoder
implicit val decoderPerson: Decoder[Person] = deriveDecoder[Person]


val parseResult = circe.parser.parse(input).getOrElse(Json.Null)
val data = parseResult.hcursor.downField("data")
val personList = data.as[List[Person]].getOrElse(null)
for {
person <- personList
} println(person.name + " is " + person.age + " at " + person.position)
}
}