Scala的JsonParser

曾建凯 发布于 2010/09/01 01:18
阅读 6K+
收藏 4

分享一个Scala的JsonParser,虽然Scala官方库内带了,但是不太好用,所以找了很久,刨到这么一个,由于我自己对代码进行了修改,所以附上的源地址

package com.agiers.util

import scala.util.parsing.combinator._
import scala.util.parsing.combinator.syntactical._
import scala.util.parsing.combinator.lexical._
import scala.util.parsing.input.{Reader,StreamReader,CharArrayReader}
import java.io.{InputStream, InputStreamReader}

// 代码出处:http://paste.pocoo.org/raw/106688/
object JsonParser extends StdTokenParsers with ImplicitConversions {
  type Tokens = scala.util.parsing.json.Lexer
  val lexical = new Tokens

  lexical.reserved ++= List("true", "false", "null")
  lexical.delimiters ++= List("{", "}", "[", "]", ":", ",")

  def jsonObj = "{" ~> repsep(objPair, ",") <~ "}" ^^ (JsObject.apply _)
  def jsonArr = "[" ~> repsep(jsonVal, ",") <~ "]" ^^ (JsArray.apply _)
  def objPair  = jsonStr ~ (":" ~> jsonVal) ^^ { case x ~ y => (x, y) }
  def jsonVal: Parser[JsValue] =
    (jsonObj | jsonArr | jsonStr | jsonNum | "true" ^^^ JsTrue | "false" ^^^ JsFalse | "null" ^^^ JsNull)
  def jsonStr = accept("string", { case lexical.StringLit(n) => JsString(n)})
  def jsonNum = accept("number", { case lexical.NumericLit(n) => JsNumber(n) })

  def apply(input: Reader[Char]): JsValue =
    phrase(jsonVal)(new lexical.Scanner(input)) match {
      case Success(result, _) => result
      case _ => throw new Exception("Illegal JSON format")
    }
}

sealed trait JsValue {
  type T
  def self: T
  override def toString = JsValue.toJson(this)

  def get(key: String): JsValue = {
    this match {
      case JsObject(obj) =>
        if (obj.contains(JsString(key)))
          obj(JsString(key))
        else
          JsNull
      case _ =>
        JsNull
    }
  }

  def get(index: Int): JsValue = {
    this match {
      case JsArray(ary) =>
        if (index < ary.length)
          ary(index)
        else
          JsNull
      case _ =>
        JsNull
    }
  }

  def toStr: String = {
    this match {
      case JsString(value) =>
        value
      case JsNumber(value) =>
        value.toString
      case _ =>
        null
    }
  }

  def toFloat: Float = {
    this match {
      case JsString(value) =>
        try {
          value.toFloat
        } catch {
          case _ =>
            0
        }
      case JsNumber(value) =>
        value.toFloat
      case _ =>
        0
    }
  }

  def toInt: Int = {
    this match {
      case JsString(value) =>
        this.toFloat.toInt
      case JsNumber(value) =>
        value.toInt
      case _ =>
        0
    }
  }

  def toBoolean: Boolean = {
    this match {
      case JsTrue =>
        true
      case JsFalse =>
        false
      case JsBoolean(is) =>
        is
      case JsNumber(num) =>
        if (num > 0) true
        else false
      case JsString(str) =>
        if (str != null && str.length > 0) true
        else false
      case _ =>
        false
    }
  }

  def isNull: Boolean = this.toStr == null || this.toStr.isEmpty

  val emptyValue = ""
  
  def wrap: String = {
    this match {
      // JSON
      case JsString(str) => str
      case JsNumber(num) => num.toString
      case JsNull => emptyValue
      case JsTrue => "1"
      case JsFalse => "0"
      case _ => this.toString
      // Java
//      case str: String => str
//      case is: Boolean => if (is) "1" else "0" 
//      case null => emptyValue
//      case _ => v.toString
    }
  }
}

case class JsString(override val self: String) extends JsValue {
  type T = String
}

/**
 * This can also be implemented with as a Double, even though BigDecimal is
 * more loyal to the json spec.
 *  NOTE: Subtle bugs can arise, i.e.
 *    BigDecimal(3.14) != BigDecimal("3.14")
 * such are the perils of floating point arithmetic.
 */
case class JsNumber(override val self: BigDecimal) extends JsValue {
  type T = BigDecimal
}

// This can extend scala.collection.MapProxy to implement Map interface
case class JsObject(override val self: Map[JsString, JsValue]) extends JsValue {
  type T = Map[JsString, JsValue]


}

// This can extend scala.SeqProxy to implement Seq interface
case class JsArray(override val self: List[JsValue]) extends JsValue {
  type T = List[JsValue]
}

sealed abstract case class JsBoolean(b: Boolean) extends JsValue {
  type T = Boolean
  val self = b
}

case object JsTrue extends JsBoolean(true)
case object JsFalse extends JsBoolean(false)
case object JsNull extends JsValue {
  type T = Null
  val self = null
}

object JsObject {
  def apply() = new JsObject(Map())
  def apply(xs: Seq[(JsString, JsValue)]) = new JsObject(Map() ++ xs)
}

object JsNumber {
  def apply(n: Int) = new JsNumber(BigDecimal(n))
  def apply(n: Long) = new JsNumber(BigDecimal(n))
  def apply(n: Float) = new JsNumber(BigDecimal(n))
  def apply(n: Double) = new JsNumber(BigDecimal(n))
  def apply(n: BigInt) = new JsNumber(BigDecimal(n))
  def apply(n: String) = new JsNumber(BigDecimal(n))
}

object JsString {
  def apply(x: Any): JsString = x match {
    case s: Symbol => new JsString(s.name)
    case s: String => new JsString(s)
    // This is a hack needed for JsObject.
    // The other option is to throw an exception here.
    case _ => new JsString(x.toString)
  }
}

object JsValue {
  def apply(x: Any): JsValue = x match {
    case null => JsNull
    case j: JsValue => j
    case true => JsTrue
    case false => JsFalse
    case s @ (_: String | _: Symbol) => JsString(s)
    case n: Int => JsNumber(n)
    case n: Long => JsNumber(n)
    case n: Float => JsNumber(n)
    case n: Double => JsNumber(n)
    case n: BigInt => JsNumber(n)
    case n: BigDecimal => JsNumber(n)
    case xs: List[_] => JsArray(xs.map(JsValue.apply))
    case m: scala.collection.Map[_, _] => JsObject(
      Map.empty ++ (for ((key, value) <- m) yield (JsString(key), JsValue(value)))
    )
    case xs: Seq[_] => JsArray(xs.map(JsValue.apply).toList)
  }

  def fromString(s: String) = JsonParser(new CharArrayReader(s.toCharArray()))
  def fromStream(s: InputStream) = JsonParser(StreamReader(new InputStreamReader(s)))

  def toJson(x: JsValue): String = x match {
    case JsNull => "null"
    case JsBoolean(b) => b.toString
    case JsString(s) => "\"" + s.replaceAll("\\\\", "\\\\\\\\").replaceAll("\\\"", "\\\\\"") + "\""
    case JsNumber(n) => n.toString
    case JsArray(xs) => xs.map(toJson).mkString("[",", ","]")
    case JsObject(m) => m.map{case (key, value) => toJson(key) + " : " + toJson(value)}.mkString("{",", ","}")
  }
}

一般的用法如下:

// 以下内JsObject的测试
val jsonObjStr = """
{
  "title": "这是一个测试",
  "created_at": "2010.09.01",
  "author": "Janpoem"
}
"""  

val jsonObj = JsValue.fromString(jsonObjStr)
val map = HashMap[String, String]()
jsonObj match {
  case JsObject(obj) =>
    // 注意,obj里面是按照JsString("title") -> JsString("这是一个测试")的键值对存放的
    // wrap是我自己加的方法,转换出String
    obj.foreach { kv => map(kv._1.wrap) = kv._2.wrap }
  case _ =>
}
val titleStr = jsonObj.get("title").wrap

// 以下内JsArray的测试
val jsonListStr = """
[ "a", "b", "c", "d" ]
"""
val jsonList = JsValue.fromString(jsonListStr)
val list = ArrayBuffer[String]()
jsonList match {
  case JsArray(ary) =>
    ary.foreach(list + _.wrap)
  case _ =>
}
val aStr = jsonList.get(0).wrap
加载中
0
老盖
老盖

Scala的json解析效率怎么样,好像scala的xml速度很一般

0
xiaowenliang
xiaowenliang

引用来自#2楼“戏水”的帖子

Scala的json解析效率怎么样,好像scala的xml速度很一般

我觉得Scala本身的效率不会太差,本身就是编译型语言,而且编译器的作者也都是java界的顶尖人士了。至于解析json或xml,效率的话也要和使用的解析方式有关系吧。

0
戴威
戴威
无所谓,反正java和scala可以无缝结合,直接用fastjson或者simplejson
S
Solidsnake
那你的意思遇到了scala的类型fastJson 也能解析咯?
0
战士ym
战士ym
Error:(179, 13) case object JsTrue has case ancestor com.cyou.ds.util.JsBoolean, but case-to-case inheritance is prohibited. To overcome this limitation, use extractors to pattern match on non-leaf nodes.
case object JsTrue extends JsBoolean(true)
            ^
0
高傲的码畜
你试过Scala使用fastjson?我使用2.10.4的Scala编译都通不过。会报ambiguous reference to overloaded definition。官网说这是Scala的bug,不过2.11.x就升级了。但是Scala2.11.x后就不向前兼容了。spark使用2.10.x编译的程序,使用2.11.x都不能开发了。正是因为这个问题,我才打算换成Scala原生的json类库。有什么好的解决方案么?
0
胡晓刀
胡晓刀
国内首部系统性介绍Scala语言培训课程
课程观看地址:http://www.xuetuwuyou.com/course/12
返回顶部
顶部