json4s使用指南

浏览: 119 发布日期: 2016-10-27 分类: scala

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

  • 递归类型

  • 只含可序列化字段的类

  • 自定义序列化函数

返回顶部