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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4170949/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-03 17:39:23  来源:igfitidea点击:

How to parse JSON in Scala using standard Scala classes?

jsonscala

提问by Phil

I am using the build in JSON class in Scala 2.8 to parse JSON code. I don't want to use the Liftweb one or any other due to minimizing dependencies.

我在 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]
}

回答by huynhjl

This is a solution based on extractors which will do the class cast:

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

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

At the start of the for loop I artificially wrap the result in a list so that it yields a list at the end. Then in the rest of the for loop I use the fact that generators (using <-) and value definitions (using =) will make use of the unapply methods.

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

(Older answer edited away - check edit history if you're curious)

(旧答案已被删除 - 如果您好奇,请检查编辑历史记录)

回答by Matthias Braun

This is the way I do the pattern match:

这是我进行模式匹配的方式:

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

回答by murrayju

I like @huynhjl's answer, it led me down the right path. However, it isn't great at handling error conditions. If the desired node does not exist, you get a cast exception. I've adapted this slightly to make use of Optionto better handle this.

我喜欢@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)
}

Of course, this doesn't handle errors so much as avoid them. This will yield an empty list if any of the json nodes are missing. You can use a matchto check for the presence of a node before acting...

当然,这并不会处理错误,而是要避免它们。如果缺少任何 json 节点,这将产生一个空列表。您可以在操作之前使用 amatch来检查节点是否存在...

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"
  }
}

回答by Don Mackenzie

I tried a few things, favouring pattern matching as a way of avoiding casting but ran into trouble with type erasure on the collection types.

我尝试了一些事情,支持模式匹配作为避免强制转换的一种方式,但遇到了集合类型的类型擦除问题。

The main problem seems to be that the complete type of the parse result mirrors the structure of the JSON data and is either cumbersome or impossible to fully state. I guess that is why Anyis used to truncate the type definitions. Using Anyleads to the need for casting.

主要问题似乎是解析结果的完整类型反映了 JSON 数据的结构,并且要么很麻烦,要么无法完全说明。我想这就是Any用于截断类型定义的原因。使用Any导致需要铸造。

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.

我已经破解了下面的一些内容,这些内容简洁但非常特定于问题中的代码所隐含的 JSON 数据。更一般的东西会更令人满意,但我不确定它是否会非常优雅。

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}

回答by Yuriy Tumakha

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

回答by Echo Zeng

You can do like this! Very easy to parse JSON code :P

你可以这样做!非常容易解析 JSON 代码:P

package org.sqkb.service.common.bean

import java.text.SimpleDateFormat

import org.json4s
import org.json4s.JValue
import org.json4s.Hymanson.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)
  }

}

回答by hmehdi

This is the way I do the Scala Parser Combinator Library:

这是我做 Scala Parser Combinator Library 的方式:

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)

  }
}