文章目录
  1. 1. 介绍
  2. 2. 下载JSON4S
  3. 3. 需导入的核心包介绍
  4. 4. JSON字符串转对象
  5. 5. 对象转JSON字符串
  6. 6. 合并和差异化对比
  7. 7. XML的支持
  8. 8. 总结
  9. 9. 参考

介绍

Scala环境下已经至少有6种Json解析的类库很用了很相似的抽象语法树(AST),而JSON4S这个项目的目标就是提供一个单一的AST树供其他Scala类库来使用。

JSON4S工作原理

JSON4S的features:

  • 快速的JSON解析
  • LINQ 风格的查询
  • 可以紧密结合样例类
  • 差别比较和合并
  • 使用DSL来生成有效格式的JSON
  • 支持XPATH
  • 优雅的打印
  • 支持XML的转换
  • 序列化

下载JSON4S

作者使用的是json4s-native_*.jar(与lift-json相同的实现)。下载的时候注意将版本号进行替换。

需导入的核心包介绍

  • import org.json4s._
    不用多说,解析都是通过它来完成的。
  • import org.json4s.native.JsonMethods._
    Scala对象与外部JSON资源(字符串,流,文件等)相互转换的开放接口,代码不多,这里可以贴一下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    trait JsonMethods extends org.json4s.JsonMethods[Document] {

    //外部JSON资源转Scala对象
    def parse(in: JsonInput, useBigDecimalForDouble: Boolean = false): JValue = in match {
    case StringInput(s) => JsonParser.parse(s, useBigDecimalForDouble)
    case ReaderInput(rdr) => JsonParser.parse(rdr, useBigDecimalForDouble)
    case StreamInput(stream) => JsonParser.parse(Source.fromInputStream(stream).bufferedReader(), useBigDecimalForDouble)
    case FileInput(file) => JsonParser.parse(Source.fromFile(file).bufferedReader(), useBigDecimalForDouble)
    }
    //外部JSON资源转Scala对象(返回值可选)
    def parseOpt(in: JsonInput, useBigDecimalForDouble: Boolean = false): Option[JValue] = in match {
    case StringInput(s) => JsonParser.parseOpt(s, useBigDecimalForDouble)
    case ReaderInput(rdr) => JsonParser.parseOpt(rdr, useBigDecimalForDouble)
    case StreamInput(stream) => JsonParser.parseOpt(Source.fromInputStream(stream).bufferedReader(), useBigDecimalForDouble)
    case FileInput(file) => JsonParser.parseOpt(Source.fromFile(file).bufferedReader(), useBigDecimalForDouble)
    }

    /** Renders JSON.
    * Scala对象转外部JSON<资源
    * @see Printer#compact
    * @see Printer#pretty
    */

    def render(value: JValue): Document = value match {
    case null => text("null")
    case JBool(true) => text("true")
    case JBool(false) => text("false")
    case JDouble(n) => text(n.toString)
    case JDecimal(n) => text(n.toString)
    case JInt(n) => text(n.toString)
    case JNull => text("null")
    case JNothing => sys.error("can't render 'nothing'")
    case JString(null) => text("null")
    case JString(s) => text("\""+JsonAST.quote(s)+"\"")
    case JArray(arr) => text("[") :: series(trimArr(arr).map(render)) :: text("]")
    case JObject(obj) =>
    val nested = break :: fields(trimObj(obj).map({case (n,v) => text("\""+JsonAST.quote(n)+"\":") :: render(v)}))
    text("{") :: nest(2, nested) :: break :: text("}")
    }

    private def trimArr(xs: List[JValue]) = xs.filter(_ != JNothing)
    private def trimObj(xs: List[JField]) = xs.filter(_._2 != JNothing)
    private def series(docs: List[Document]) = punctuate(text(","), docs)
    private def fields(docs: List[Document]) = punctuate(text(",") :: break, docs)

    private def punctuate(p: Document, docs: List[Document]): Document =
    if (docs.length == 0) empty
    else docs.reduceLeft((d1, d2) => d1 :: p :: d2)

    //紧凑和漂亮的打印
    def compact(d: Document): String = Printer.compact(d)
    def pretty(d: Document): String = Printer.pretty(d)


    }

    object JsonMethods extends native.JsonMethods

    可以发现json4s提供了parserender两大方法来进行JSON的转换。

  • import org.json4s.JsonDSL._
    render方法需要传入的是JValue类型的值,它并不是Scala的原生对象,所以需要通过转换而为之。JsonDSL这个特质就是提供了这些隐式转换:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
      trait JsonDSL extends java.lang.Object with org.json4s.Implicits {
    implicit def seq2jvalue[A](s : scala.Traversable[A])(implicit evidence$1 : scala.Function1[A, org.json4s.JsonAST.JValue]) : org.json4s.JsonAST.JArray = { /* compiled code */ }
    implicit def map2jvalue[A](m : scala.Predef.Map[scala.Predef.String, A])(implicit evidence$2 : scala.Function1[A, org.json4s.JsonAST.JValue]) : org.json4s.JsonAST.JObject = { /* compiled code */ }
    implicit def option2jvalue[A](opt : scala.Option[A])(implicit evidence$3 : scala.Function1[A, org.json4s.JsonAST.JValue]) : org.json4s.JsonAST.JValue = { /* compiled code */ }
    implicit def symbol2jvalue(x : scala.Symbol) : org.json4s.JsonAST.JString = { /* compiled code */ }
    implicit def pair2jvalue[A](t : scala.Tuple2[scala.Predef.String, A])(implicit evidence$4 : scala.Function1[A, org.json4s.JsonAST.JValue]) : org.json4s.JsonAST.JObject = { /* compiled code */ }
    implicit def list2jvalue(l : scala.List[org.json4s.JsonAST.JField]) : org.json4s.JsonAST.JObject = { /* compiled code */ }
    implicit def jobject2assoc(o : org.json4s.JsonAST.JObject) : JsonDSL.this.JsonListAssoc = { /* compiled code */ }
    class JsonListAssoc(left : scala.List[org.json4s.JsonAST.JField]) extends java.lang.Object {
    def ~(right : scala.Tuple2[scala.Predef.String, org.json4s.JsonAST.JValue]) : org.json4s.JsonAST.JObject = { /* compiled code */ }
    def ~(right : org.json4s.JsonAST.JObject) : org.json4s.JsonAST.JObject = { /* compiled code */ }
    }
    implicit def pair2Assoc[A](t : scala.Tuple2[scala.Predef.String, A])(implicit evidence$5 : scala.Function1[A, org.json4s.JsonAST.JValue]) : JsonDSL.this.JsonAssoc[A] = { /* compiled code */ }
    class JsonAssoc[A](left : scala.Tuple2[scala.Predef.String, A])(implicit evidence$6 : scala.Function1[A, org.json4s.JsonAST.JValue]) extends java.lang.Object {
    def ~[B](right : scala.Tuple2[scala.Predef.String, B])(implicit evidence$7 : scala.Function1[B, org.json4s.JsonAST.JValue]) : org.json4s.JsonAST.JObject = { /* compiled code */ }
    def ~(right : org.json4s.JsonAST.JObject) : org.json4s.JsonAST.JObject = { /* compiled code */ }
    }
    }

JSON字符串转对象

可以使用parse方法将JSON字符串进行转换

1
2
val t=parse(""" { "name" : "tom","age":23,"number":[1,2,3,4] } """)
println(t)

最终打印的结果为:

JObject(List((name,JString(tom)), (age,JInt(23)), (number,JArray(List(JInt(1), JInt(2), JInt(3), JInt(4))))))

根据打印结果可以发现转换之后的并不是Scala的原生对象,那么如何取得它的原生对象呢?它其实是一个Map[String,_]类型的转换

1
2
3
4
for((k,v)<-t.values.asInstanceOf[Map[String,_]])
{
println(k,"->",v,v.getClass)
}

Map遍历输出之后

(name,->,tom,class java.lang.String)
(age,->,23,class scala.math.BigInt)
(number,->,List(1, 2, 3, 4),class scala.collection.immutable.$colon$colon)

可以发现parse之后仍旧保留着语言来字符串中可识别的类型,那么还有其他方式取值吗?其实它还支持使用反斜杠(\)的方式进行在顶级目录下取值:

1
2
3
println("name",(t \ "name").values)
println("age",(t \ "age").values)
println("number",(t \ "number").values)

取到得结果为

(name,tom)
(age,23)
(number,List(1, 2, 3, 4))

这个功能是不是略显惊讶,特别方法,恩,的确很方便。

同时该对象还可以直接转换为样例类:

1
2
3
4
case class Student(name:String,age:Int)//注意这个样例类不要放方法里面,放在外部可访问的地方即可

implicit val formats = DefaultFormats//这个日期转换的隐式导入不要忘了,如果有特殊格式还需要自己写
println(t.extract[Student])//这里即可取的对应的样例类数据

输出为:

Student(tom,23)

这个转换功能特别方法,果然,Scala下得JSON转换类库还是有很多自己的特色的^_^。

对象转JSON字符串

注意在打印的时候一定要添加compact()方法,这样才能将字符串紧凑的表示,不然都是Doc对象。

  • 集合类的序列化

    1
    2
    3
    4
    println(compact(render(Array(1,2,3,4))))
    //结果:[1,2,3,4]
    println(compact(render(Map("name"->"tom","age"->"23"))))
    //结果:{"name":"tom","age":"23"}

    只支持SeqList

  • 元组的序列化

    1
    2
    3
    val tuple=("name","tom")
    println(compact(render(tuple)))
    //结果:{"name":"tom"}

    注意:这里只支持Tuple2[String,A]这种格式的元组,将会序列化成键值对

  • 可以使用~将两个字段进行连接操作

    1
    2
    3
    4
    5
    6
    val tuple=("name","tom")~("age",23)
    println(tuple)
    println(compact(render(tuple)))
    //结果:
    //JObject(List((name,JString(tom)), (age,JInt(23))))
    //{"name":"tom","age":23}
  • 任何键的值可以为可选的,为None时将不会被序列化

    1
    2
    3
    4
    5
    6
    val tuple=("name","tom")~("age",None:Option[Int])
    println(tuple)
    println(compact(render(tuple)))
    //结果:
    //JObject(List((name,JString(tom)), (age,JNothing)))
    //{"name":"tom"}
  • 一个复杂一点的列子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    case class Winner(id: Long, numbers: List[Int])
    case class Lotto(id: Long, winningNumbers: List[Int], winners: List[Winner], drawDate: Option[java.util.Date])

    val winners = List(Winner(23, List(2, 45, 34, 23, 3, 5)), Winner(54, List(52, 3, 12, 11, 18, 22)))
    val lotto = Lotto(5, List(2, 45, 34, 23, 7, 5, 3), winners, Option(new java.util.Date()))
    val json =
    ("lotto" ->
    ("lotto-id" -> lotto.id) ~
    ("winning-numbers" -> lotto.winningNumbers) ~
    ("draw-date" -> lotto.drawDate.map(_.toString)) ~
    ("winners" ->
    lotto.winners.map { w =>
    (("winner-id" -> w.id) ~
    ("numbers" -> w.numbers))
    }))

    println(compact(render(json)))

    打印的结果为:

    {"lotto":{"lotto-id":5,"winning-numbers":[2,45,34,23,7,5,3],"draw-date":"Sun May 24 15:41:29 CST 2015","winners":[{"winner-id":23,"numbers":[2,45,34,23,3,5]},{"winner-id":54,"numbers":[52,3,12,11,18,22]}]}}
    

    如果你使用pretty()方法来组织JSON:

    1
    println(pretty(render(json)))

    就可以得到:

    {
      "lotto":{
        "lotto-id":5,
        "winning-numbers":[2,45,34,23,7,5,3],
        "draw-date":"Sun May 24 15:43:01 CST 2015",
        "winners":[{
          "winner-id":23,
          "numbers":[2,45,34,23,3,5]
        },{
          "winner-id":54,
          "numbers":[52,3,12,11,18,22]
        }]
      }
    }
    

    这样可读性明显提高多了。

合并和差异化对比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
val t1=parse(
"""
{"name":"tom",
"age":23,
"class":["xiaoerban"]
}
""")

val t2=parse(
"""
{"name":"tom",
"age":23,
"class":["xiaosanban"]
}
""")

val t3=t1 merge t2
println(pretty(render(t3)))

合并之后的结果为

{
  "name":"tom",
  "age":23,
  "class":["xiaoerban","xiaosanban"]
}

接下来咱们对比一下合并之后的差异化:

1
2
3
4
val Diff(changed,added,deleted)=t3 diff t1
println("changed",changed)
println("added",added)
println("deleted",deleted)

输出的差异化为:

(changed,JNothing)
(added,JNothing)
(deleted,JObject(List((class,JArray(List(JString(xiaosanban)))))))

XML的支持

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import org.json4s.Xml.{toJson, toXml}
val xml =
<users>
<user>
<id>1</id>
<name>Harry</name>
</user>
<user>
<id>2</id>
<name>David</name>
</user>
</users>

val json = toJson(xml)
println(pretty(render(json)))

输出的结果为:

{
  "users":{
    "user":[{
      "id":"1",
      "name":"Harry"
    },{
      "id":"2",
      "name":"David"
    }]
  }
}

总结

JSON4S很好很强大,其他功能请参考1。

虽然Scala可以无缝的直接访问java版本的JSON解析类库,但是由于Scala有它自己的语法简洁性,所以在Scala环境下强烈推荐使用JSON4S.^_^

参考

  1. https://github.com/json4s/json4s

本作品采用[知识共享署名-非商业性使用-相同方式共享 2.5]中国大陆许可协议进行许可,我的博客欢迎复制共享,但在同时,希望保留我的署名权kubiCode,并且,不得用于商业用途。如您有任何疑问或者授权方面的协商,请给

文章目录
  1. 1. 介绍
  2. 2. 下载JSON4S
  3. 3. 需导入的核心包介绍
  4. 4. JSON字符串转对象
  5. 5. 对象转JSON字符串
  6. 6. 合并和差异化对比
  7. 7. XML的支持
  8. 8. 总结
  9. 9. 参考