json4s是一个基于scala的json解析库。
安装依赖
假设使用SBT进行配置:
val json4sJackson = "org.json4s" %% "json4s-jackson" % "{latestVersion}"
解析JSON字符串
scala> import org.json4s._
scala> import org.json4s.jackson.JsonMethods._
scala> parse(""" { "numbers" : [1, 2, 3, 4] } """)
res0: org.json4s.JsonAST.JValue =
JObject(List((numbers,JArray(List(JInt(1), JInt(2), JInt(3), JInt(4))))))
scala> parse("""{"name":"Toy","price":35.35}""", useBigDecimalForDouble = true)
res1: org.json4s.package.JValue =
JObject(List((name,JString(Toy)), (price,JDecimal(35.35))))
生成JSON
有两种方式生成JSON:
DoubleMode 将浮点型数据转换成
JDouble
,这种方式下DSL使用:
import org.json4s.JsonDSL._
// or
import org.json4s.JsonDSL.WithDouble._
BigDecimalMode 将浮点型数据转换成
JDecimal
,这种方式下DSL使用:
import org.json4s.JsonDSL.WithBigDecimal._
DSL规则
基本数据类型映射到
JSON
的基本数据类型任何
Seq
类型映射到JSON
的数组类型
scala> val json = List(1, 2, 3)
scala> compact(render(json))
res0: String = [1,2,3]
Tuple2[String, Any]
将生成字段
scala> val json = ("name" -> "joe")
scala> compact(render(json))
res1: String = {"name":"joe"}
scala> val json = ("name" -> "joe") ~ ("age" -> 35)
scala> compact(render(json))
res2: String = {"name":"joe","age":35}
所有取值都是可选的,如果值不存在,则直接剔除掉键值对
scala> val json = ("name" -> "joe") ~ ("age" -> Some(35))
scala> compact(render(json))
res3: String = {"name":"joe","age":35}
scala> val json = ("name" -> "joe") ~ ("age" -> (None: Option[Int]))
scala> compact(render(json))
res4: String = {"name":"joe"}
自定义DSL必须实现下面的隐式转换:
type DslConversion = T => JValue
示例
object JsonExample extends App {
import org.json4s._
import org.json4s.JsonDSL._
import org.json4s.jackson.JsonMethods._
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, None)
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)))
}
输出结果为:
scala> JsonExample
{"lotto":{"lotto-id":5,"winning-numbers":[2,45,34,23,7,5,3],"winners":
[{"winner-id":23,"numbers":[2,45,34,23,3,5]},{"winner-id":54,"numbers":[52,3,12,11,18,22]}]}}
使用pretty可以美化输出,注意生成的字符串中没有draw-date字段:
scala> pretty(render(JsonExample.json))
{
"lotto":{
"lotto-id":5,
"winning-numbers":[2,45,34,23,7,5,3],
"winners":[{
"winner-id":23,
"numbers":[2,45,34,23,3,5]
},{
"winner-id":54,
"numbers":[52,3,12,11,18,22]
}]
}
}
合并与比较
两个JSON对象之间还可以进行合并与比较:
scala> import org.json4s._
scala> import org.json4s.jackson.JsonMethods._
scala> val lotto1 = parse("""{
"lotto":{
"lotto-id":5,
"winning-numbers":[2,45,34,23,7,5,3]
"winners":[{
"winner-id":23,
"numbers":[2,45,34,23,3,5]
}]
}
}""")
scala> val lotto2 = parse("""{
"lotto":{
"winners":[{
"winner-id":54,
"numbers":[52,3,12,11,18,22]
}]
}
}""")
scala> val mergedLotto = lotto1 merge lotto2
scala> pretty(render(mergedLotto))
res0: String =
{
"lotto":{
"lotto-id":5,
"winning-numbers":[2,45,34,23,7,5,3],
"winners":[{
"winner-id":23,
"numbers":[2,45,34,23,3,5]
},{
"winner-id":54,
"numbers":[52,3,12,11,18,22]
}]
}
}
scala> val Diff(changed, added, deleted) = mergedLotto diff lotto1
changed: org.json4s.JsonAST.JValue = JNothing
added: org.json4s.JsonAST.JValue = JNothing
deleted: org.json4s.JsonAST.JValue = JObject(List((lotto,JObject(List(JField(winners,
JArray(List(JObject(List((winner-id,JInt(54)), (numbers,JArray(
List(JInt(52), JInt(3), JInt(12), JInt(11), JInt(18), JInt(22))))))))))))))
查询JSON
LINQ(继承语言查询)模式
JSON中的值可以通过for生成表达式来提取。
scala> import org.json4s._
scala> import org.json4s.native.JsonMethods._
scala> val json = parse("""
{ "name": "joe",
"children": [
{
"name": "Mary",
"age": 5
},
{
"name": "Mazy",
"age": 3
}
]
}
""")
scala> for {
JObject(child) <- json
JField("age", JInt(age)) <- child
} yield age
res0: List[BigInt] = List(5, 3)
scala> for {
JObject(child) <- json
JField("name", JString(name)) <- child
JField("age", JInt(age)) <- child
if age > 4
} yield (name, age)
res1: List[(String, BigInt)] = List((Mary,5))
XPATH + HOFs
JSON AST还支持像XPath一样进行查询:
The example json is:
{
"person": {
"name": "Joe",
"age": 35,
"spouse": {
"person": {
"name": "Marilyn"
"age": 33
}
}
}
}
Translated to DSL syntax:
scala> import org.json4s._
scala> import org.json4s.native.JsonMethods._
or
scala> import org.json4s.jackson.JsonMethods._
scala> import org.json4s.JsonDSL._
scala> val json =
("person" ->
("name" -> "Joe") ~
("age" -> 35) ~
("spouse" ->
("person" ->
("name" -> "Marilyn") ~
("age" -> 33)
)
)
)
scala> json \\ "spouse"
res0: org.json4s.JsonAST.JValue = JObject(List(
(person,JObject(List((name,JString(Marilyn)), (age,JInt(33)))))))
scala> compact(render(res0))
res1: String = {"person":{"name":"Marilyn","age":33}}
scala> compact(render(json \\ "name"))
res2: String = {"name":"Joe","name":"Marilyn"}
scala> compact(render((json removeField { _ == JField("name", JString("Marilyn")) }) \\ "name"))
res3: String = {"name":"Joe"}
scala> compact(render(json \ "person" \ "name"))
res4: String = "Joe"
scala> compact(render(json \ "person" \ "spouse" \ "person" \ "name"))
res5: String = "Marilyn"
scala> json findField {
case JField("name", _) => true
case _ => false
}
res6: Option[org.json4s.JsonAST.JValue] = Some((name,JString(Joe)))
scala> json filterField {
case JField("name", _) => true
case _ => false
}
res7: List[org.json4s.JsonAST.JField] = List(JField(name,JString(Joe)), JField(name,JString(Marilyn)))
scala> json transformField {
case JField("name", JString(s)) => ("NAME", JString(s.toUpperCase))
}
res8: org.json4s.JsonAST.JValue = JObject(List((person,JObject(List(
(NAME,JString(JOE)), (age,JInt(35)), (spouse,JObject(List(
(person,JObject(List((NAME,JString(MARILYN)), (age,JInt(33)))))))))))))
scala> json.values
res8: scala.collection.immutable.Map[String,Any] = Map(person -> Map(name -> Joe, age -> 35, spouse -> Map(person -> Map(name -> Marilyn, age -> 33))))
数组元素可以通过索引获取,还可以获取指定类型的元素:
scala> val json = parse("""
{ "name": "joe",
"children": [
{
"name": "Mary",
"age": 5
},
{
"name": "Mazy",
"age": 3
}
]
}
""")
scala> (json \ "children")(0)
res0: org.json4s.JsonAST.JValue = JObject(List((name,JString(Mary)), (age,JInt(5))))
scala> (json \ "children")(1) \ "name"
res1: org.json4s.JsonAST.JValue = JString(Mazy)
scala> json \\ classOf[JInt]
res2: List[org.json4s.JsonAST.JInt#Values] = List(5, 3)
scala> json \ "children" \\ classOf[JString]
res3: List[org.json4s.JsonAST.JString#Values] = List(Mary, Mazy)
提取值
样式类也可以用来从JSON对象中提取值:
scala> import org.json4s._
scala> import org.json4s.jackson.JsonMethods._
scala> implicit val formats = DefaultFormats // Brings in default date formats etc.
scala> case class Child(name: String, age: Int, birthdate: Option[java.util.Date])
scala> case class Address(street: String, city: String)
scala> case class Person(name: String, address: Address, children: List[Child])
scala> val json = parse("""
{ "name": "joe",
"address": {
"street": "Bulevard",
"city": "Helsinki"
},
"children": [
{
"name": "Mary",
"age": 5,
"birthdate": "2004-09-04T18:06:22Z"
},
{
"name": "Mazy",
"age": 3
}
]
}
""")
scala> json.extract[Person]
res0: Person = Person(joe,Address(Bulevard,Helsinki),List(Child(Mary,5,Some(Sat Sep 04 18:06:22 EEST 2004)), Child(Mazy,3,None)))
默认情况下构造器参数必须与json的字段匹配,如果json中的字段名在scala不合法,可以这样解决:
使用反引号
scala> case class Person(`first-name`: String)
使用transform转换函数
scala> case class Person(firstname: String)
scala> json transformField {
case ("first-name", x) => ("firstname", x)
}
如果样式类具有辅助构造函数,提取函数会尽量匹配最佳的构造函数:
scala> case class Bike(make: String, price: Int) {
def this(price: Int) = this("Trek", price)
}
scala> parse(""" {"price":350} """).extract[Bike]
res0: Bike = Bike(Trek,350)
Scala基本类型的值可以直接从JSON中提取:
scala> (json \ "name").extract[String]
res0: String = "joe"
scala> ((json \ "children")(0) \ "birthdate").extract[Date]
res1: java.util.Date = Sat Sep 04 21:06:22 EEST 2004
日期格式可以通过重写DefaultFormats方法实现:
scala> implicit val formats = new DefaultFormats {
override def dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
}
JSON对象也可以转换成Map[String, _],每个字段将转换成键值对:
scala> val json = parse("""
{
"name": "joe",
"addresses": {
"address1": {
"street": "Bulevard",
"city": "Helsinki"
},
"address2": {
"street": "Soho",
"city": "London"
}
}
}""")
scala> case class PersonWithAddresses(name: String, addresses: Map[String, Address])
scala> json.extract[PersonWithAddresses]
res0: PersonWithAddresses("joe", Map("address1" -> Address("Bulevard", "Helsinki"),
"address2" -> Address("Soho", "London")))
序列化
样式类可以序列化和反序列化:
scala> import org.json4s._
scala> import org.json4s.jackson.Serialization
scala> import org.json4s.jackson.Serialization.{read, write}
scala> implicit val formats = Serialization.formats(NoTypeHints)
scala> val ser = write(Child("Mary", 5, None))
scala> read[Child](ser)
res1: Child = Child(Mary,5,None)
序列化支持:
任意深度的样式类
所有的基本类型,包括BigInt与Symbol
List,Seq,Array,Set以及Map
scala.Option
java.util.Date
Polymorphic Lists
递归类型
只含可序列化字段的类
自定义序列化函数