JSON 基础

现代Web应用程序经常需要解析和生成JSON(JavaScrpit Object Notation)格式的数据。Play框架通过JSON库的支持可以完成上述任务。

JSON是一种轻量级的数据交换格式,请看下面一个例子:

  1. {
  2. "name" : "Watership Down",
  3. "location" : {
  4. "lat" : 51.235685,
  5. "long" : -1.309197
  6. },
  7. "residents" : [ {
  8. "name" : "Fiver",
  9. "age" : 4,
  10. "role" : null
  11. }, {
  12. "name" : "Bigwig",
  13. "age" : 6,
  14. "role" : "Owsla"
  15. } ]
  16. }

如果想了解跟多关于JSON的知识,请访问json.org

Play框架的JSON库

play.api.libs.json包中包含表示JSON数据的数据结构和用于将这些数据结构与其他数据格式互相转换的实用工具。

JsValue

这是一个特质(trait),可以表示任何JSON值。JSON库中通过一系列对JsVlaue进行扩展的case类来表示各种有效的JSON类型。

  • JsString
  • JsNumber
  • JsBoolean
  • JsObject
  • JsArray
  • JsNull

你可以利用这些多种多样的JsValue类型来构造任何JSON结构。

Json

Json对象提供一些工具,这些工具用于将数据格式转换为JsValue结构或者逆向转换。

JsPath

JsPath用于表示JsValue内部结构的路径,类似于XPath对XML的意义。可以利用JsPath析取JsValue结构,或者对隐式转换进行模式匹配。

构造JsValue实例

字符串解析

  1. import play.api.libs.json._
  2. val json: JsValue = Json.parse("""
  3. {
  4. "name" : "Watership Down",
  5. "location" : {
  6. "lat" : 51.235685,
  7. "long" : -1.309197
  8. },
  9. "residents" : [ {
  10. "name" : "Fiver",
  11. "age" : 4,
  12. "role" : null
  13. }, {
  14. "name" : "Bigwig",
  15. "age" : 6,
  16. "role" : "Owsla"
  17. } ]
  18. }
  19. """)

类构造器

  1. import play.api.libs.json._
  2. val json: JsValue = JsObject(Seq(
  3. "name" -> JsString("Watership Down"),
  4. "location" -> JsObject(Seq("lat" -> JsNumber(51.235685), "long" -> JsNumber(-1.309197))),
  5. "residents" -> JsArray(Seq(
  6. JsObject(Seq(
  7. "name" -> JsString("Fiver"),
  8. "age" -> JsNumber(4),
  9. "role" -> JsNull
  10. )),
  11. JsObject(Seq(
  12. "name" -> JsString("Bigwig"),
  13. "age" -> JsNumber(6),
  14. "role" -> JsString("Owsla")
  15. ))
  16. ))
  17. ))

通过Json.objJson.arr构造可能更简单些。注意大部分值不需要显式得用JsValue类封装,工厂方法会执行隐式转换(接下来是一个例子)。

  1. import play.api.libs.json.{JsNull,Json,JsString,JsValue}
  2. val json: JsValue = Json.obj(
  3. "name" -> "Watership Down",
  4. "location" -> Json.obj("lat" -> 51.235685, "long" -> -1.309197),
  5. "residents" -> Json.arr(
  6. Json.obj(
  7. "name" -> "Fiver",
  8. "age" -> 4,
  9. "role" -> JsNull
  10. ),
  11. Json.obj(
  12. "name" -> "Bigwig",
  13. "age" -> 6,
  14. "role" -> "Owsla"
  15. )
  16. )
  17. )

Writers转换器

Scala中通过工具方法Json.toJson[T](T)(implicit writes:Writes[T])。这个功能通过类型转换器Writes[T]将T类型的数据转换为JsValue。

Play框架的JSON库API接口提供了大部分基础类型的隐式Writes,例如IntDoubleStringBoolean。当然,该JSON库也有针对包含上述基本类型元素的集合的Writes转换器。

  1. import play.api.libs.json._
  2. // basic types
  3. val jsonString = Json.toJson("Fiver")
  4. val jsonNumber = Json.toJson(4)
  5. val jsonBoolean = Json.toJson(false)
  6. // collections of basic types
  7. val jsonArrayOfInts = Json.toJson(Seq(1, 2, 3, 4))
  8. val jsonArrayOfStrings = Json.toJson(List("Fiver", "Bigwig"))

如果想把自己定义的模型转换成JsValues,你需要定义隐式的Writes转换器,并将它们引入执行环境。

  1. import play.api.libs.json._
  2. // basic types
  3. val jsonString = Json.toJson("Fiver")
  4. val jsonNumber = Json.toJson(4)
  5. val jsonBoolean = Json.toJson(false)
  6. // collections of basic types
  7. val jsonArrayOfInts = Json.toJson(Seq(1, 2, 3, 4))
  8. val jsonArrayOfStrings = Json.toJson(List("Fiver", "Bigwig"))
  9. To convert your own models to JsValues, you must define implicit Writes converters and provide them in scope.
  10. case class Location(lat: Double, long: Double)
  11. case class Resident(name: String, age: Int, role: Option[String])
  12. case class Place(name: String, location: Location, residents: Seq[Resident])
  13. import play.api.libs.json._
  14. implicit val locationWrites = new Writes[Location] {
  15. def writes(location: Location) = Json.obj(
  16. "lat" -> location.lat,
  17. "long" -> location.long
  18. )
  19. }
  20. implicit val residentWrites = new Writes[Resident] {
  21. def writes(resident: Resident) = Json.obj(
  22. "name" -> resident.name,
  23. "age" -> resident.age,
  24. "role" -> resident.role
  25. )
  26. }
  27. implicit val placeWrites = new Writes[Place] {
  28. def writes(place: Place) = Json.obj(
  29. "name" -> place.name,
  30. "location" -> place.location,
  31. "residents" -> place.residents)
  32. }
  33. val place = Place(
  34. "Watership Down",
  35. Location(51.235685, -1.309197),
  36. Seq(
  37. Resident("Fiver", 4, None),
  38. Resident("Bigwig", 6, Some("Owsla"))
  39. )
  40. )
  41. val json = Json.toJson(place)

作为备选,你可以通过配合模式(combinator pattern)来定义自己的Writes转换器。

注意:关于配合模式(combinator pattern)在JSON Reads/Writes/Formats 选择器一节中有详细介绍。

  1. import play.api.libs.json._
  2. import play.api.libs.functional.syntax._
  3. implicit val locationWrites: Writes[Location] = (
  4. (JsPath \ "lat").write[Double] and
  5. (JsPath \ "long").write[Double]
  6. )(unlift(Location.unapply))
  7. implicit val residentWrites: Writes[Resident] = (
  8. (JsPath \ "name").write[String] and
  9. (JsPath \ "age").write[Int] and
  10. (JsPath \ "role").writeNullable[String]
  11. )(unlift(Resident.unapply))
  12. implicit val placeWrites: Writes[Place] = (
  13. (JsPath \ "name").write[String] and
  14. (JsPath \ "location").write[Location] and
  15. (JsPath \ "residents").write[Seq[Resident]]
  16. )(unlift(Place.unapply))

析取JsValue结构

你可以析取JsValue结构并读取特定的值。语法和功能类似于Scala处理XML的方法。

  1. 注意:下面的例子应用在前面创建的JsValue结构上

Simple path\

\操作符应用于一个JsValue可以返回跟相关域对应的属性。下面假设有一个JsObject:

  1. val lat = json \ "location" \ "lat"
  2. // 返回JsNumber(51.235685)

Recursive path\\

使用\\操作符将会递归查找当前对象中以及所有依赖对象中的所有对应域。

  1. val names = json \\ "name"
  2. // returns Seq(JsString("Watership Down"), JsString("Fiver"), JsString("Bigwig"))

Index lookup(for JsArrays)

可以通过索引值从JsArray中获取值。

  1. val bigwig = (json \ "residents")(1)
  2. // returns {"name":"Bigwig","age":6,"role":"Owsla"}

解析JsValue结构

字符串工具

  • 微型
  1. val minifiedString: String = Json.stringify(json)
  2. {"name":"Watership Down","location":{"lat":51.235685,"long":-1.309197},"residents":[{"name":"Fiver","age":4,"role":nul
  • 可读的
  1. val readableString: String = Json.prettyPrint(json)
  2. {
  3. "name" : "Watership Down",
  4. "location" : {
  5. "lat" : 51.235685,
  6. "long" : -1.309197
  7. },
  8. "residents" : [ {
  9. "name" : "Fiver",
  10. "age" : 4,
  11. "role" : null
  12. }, {
  13. "name" : "Bigwig",
  14. "age" : 6,
  15. "role" : "Owsla"
  16. } ]
  17. }

JsValue.as或者Jsvalue.asOpt

将JsValue对象转换成其他类型的最简单的方法是使用JsValue.as[T](implicit fjs:Reads[T]):T 需要自定义一个类型转换器Reads[T]来讲JsValue转换成T类型的数据(和Writes[T]相反)。 跟Writes一样,JSON库提供了Reads转换器需要的基本类型。

  1. val name = (json \ "name").as[String]
  2. // "Watership Down"
  3. val names = (json \\ "name").map(_.as[String])
  4. // Seq("Watership Down", "Fiver", "Bigwig")

如果路径(path)不存在或者转换失败,as方法会抛出JsResultException异常。更安全的方法是使用JsValue.asOpt[T](implicit fjs:Reads[T]):Option[T]

  1. val nameOption = (json \ "name").asOpt[String]
  2. // Some("Watership Down")
  3. val bogusOption = (json \ "bogus").asOpt[String]
  4. // None

尽管asOpt方法更安全,但是如果有错误也不能捕捉到。

使用有效性(validation)

推荐使用validate方法将JsValue转换为其他类型(这个方法含有一个Read类型的参数)。这个方法同时执行有效性验证和类型转换操作,返回的结果类型是JsResultJsResult通过两个类实现:

  • JsSuccess——表示验证/转换成功并封装结果。
  • JsError——表示验证/转换不成功,并包含有错误列表。

可以使用多种模式来处理验证结果:

  1. val json = { ... }
  2. val nameResult: JsResult[String] = (json \ "name").validate[String]
  3. // Pattern matching
  4. nameResult match {
  5. case s: JsSuccess[String] => println("Name: " + s.get)
  6. case e: JsError => println("Errors: " + JsError.toFlatJson(e).toString())
  7. }
  8. // Fallback value
  9. val nameOrFallback = nameResult.getOrElse("Undefined")
  10. // map
  11. val nameUpperResult: JsResult[String] = nameResult.map(_.toUpperCase())
  12. // fold
  13. val nameOption: Option[String] = nameResult.fold(
  14. invalid = {
  15. fieldErrors => fieldErrors.foreach(x => {
  16. println("field: " + x._1 + ", errors: " + x._2)
  17. })
  18. None
  19. },
  20. valid = {
  21. name => Some(name)
  22. }
  23. )

由JsValue转换为模型(model)

如果要将JsValue转换为模型,你要定义隐式Reads[T]转换器,其中T是模型的类型。

注意:此处实现Reads所用的模式和自定义有效性的技术细节在JSON Reads/Writes/Formats 选择器一节中有详细介绍。

  1. case class Location(lat: Double, long: Double)
  2. case class Resident(name: String, age: Int, role: Option[String])
  3. case class Place(name: String, location: Location, residents: Seq[Resident])
  4. import play.api.libs.json._
  5. import play.api.libs.functional.syntax._
  6. implicit val locationReads: Reads[Location] = (
  7. (JsPath \ "lat").read[Double] and
  8. (JsPath \ "long").read[Double]
  9. )(Location.apply _)
  10. implicit val residentReads: Reads[Resident] = (
  11. (JsPath \ "name").read[String] and
  12. (JsPath \ "age").read[Int] and
  13. (JsPath \ "role").readNullable[String]
  14. )(Resident.apply _)
  15. implicit val placeReads: Reads[Place] = (
  16. (JsPath \ "name").read[String] and
  17. (JsPath \ "location").read[Location] and
  18. (JsPath \ "residents").read[Seq[Resident]]
  19. )(Place.apply _)
  20. val json = { ... }
  21. val placeResult: JsResult[Place] = json.validate[Place]
  22. // JsSuccess(Place(...),)
  23. val residentResult: JsResult[Resident] = (json \ "residents")(1).validate[Resident]
  24. // JsSuccess(Resident(Bigwig,6,Some(Owsla)),)

下一节: JSON with HTTP