§JSON 基础
现代 Web 应用程序通常需要解析和生成 JSON (JavaScript 对象表示法) 格式的数据。Play 通过其 JSON 库 支持此功能。
JSON 是一种轻量级的数据交换格式,看起来像这样
{
"name" : "Watership Down",
"location" : {
"lat" : 51.235685,
"long" : -1.309197
},
"residents" : [ {
"name" : "Fiver",
"age" : 4,
"role" : null
}, {
"name" : "Bigwig",
"age" : 6,
"role" : "Owsla"
} ]
}
要了解有关 JSON 的更多信息,请参阅 json.org.
§Play JSON 库
该 play.api.libs.json
包包含用于表示 JSON 数据的数据结构以及用于在这些数据结构和其他数据表示之间进行转换的实用程序。此包的一些功能包括
- 自动转换 到和来自案例类,只需最少的样板代码。如果您想快速上手,只需编写最少的代码,这可能是您开始的地方。
- 自定义验证 在解析时。
- 自动解析 请求正文中的 JSON,如果内容不可解析或提供不正确的 Content-type 标头,则会自动生成错误。
- 可以在 Play 应用程序之外用作独立库。只需将
libraryDependencies += "org.playframework" %% "play-json" % playVersion
添加到您的build.sbt
文件中。 - 高度可定制。
该包提供以下类型
§JsValue
这是一个表示任何 JSON 值的特征。JSON 库有一个扩展 JsValue
的案例类来表示每个有效的 JSON 类型
使用各种 JsValue
类型,您可以构建任何 JSON 结构的表示。
§Json
Json
对象提供实用程序,主要用于转换为和从 JsValue
结构转换。
§JsPath
表示进入 JsValue
结构的路径,类似于 XML 的 XPath。这用于遍历 JsValue
结构以及隐式转换器模式。
§转换为 JsValue
§使用字符串解析
import play.api.libs.json._
val json: JsValue = Json.parse("""
{
"name" : "Watership Down",
"location" : {
"lat" : 51.235685,
"long" : -1.309197
},
"residents" : [ {
"name" : "Fiver",
"age" : 4,
"role" : null
}, {
"name" : "Bigwig",
"age" : 6,
"role" : "Owsla"
} ]
}
""")
§使用类构造
import play.api.libs.json._
val json: JsValue = JsObject(
Seq(
"name" -> JsString("Watership Down"),
"location" -> JsObject(Seq("lat" -> JsNumber(51.235685), "long" -> JsNumber(-1.309197))),
"residents" -> JsArray(
IndexedSeq(
JsObject(
Seq(
"name" -> JsString("Fiver"),
"age" -> JsNumber(4),
"role" -> JsNull
)
),
JsObject(
Seq(
"name" -> JsString("Bigwig"),
"age" -> JsNumber(6),
"role" -> JsString("Owsla")
)
)
)
)
)
)
Json.obj
和 Json.arr
可以简化构造。请注意,大多数值不需要由 JsValue 类显式包装,工厂方法使用隐式转换(稍后将详细介绍)。
import play.api.libs.json.{JsNull,Json,JsString,JsObject}
val json: JsObject = Json.obj(
"name" -> "Watership Down",
"location" -> Json.obj("lat" -> 51.235685, "long" -> -1.309197),
"residents" -> Json.arr(
Json.obj(
"name" -> "Fiver",
"age" -> 4,
"role" -> JsNull
),
Json.obj(
"name" -> "Bigwig",
"age" -> 6,
"role" -> "Owsla"
)
)
)
您也可以使用 Json.newBuilder
创建 JsObject
import play.api.libs.json.{ JsNull, Json, JsString, JsObject }
def asJson(active: Boolean): JsObject = {
val builder = Json.newBuilder
builder ++= Seq(
"name" -> "Watership Down",
"location" -> Json.obj(
"lat" -> 51.235685D, "long" -> -1.309197D))
if (active) {
builder += "active" -> true
}
builder += "residents" -> Seq(
Json.obj(
"name" -> "Fiver",
"age" -> 4,
"role" -> JsNull
),
Json.obj(
"name" -> "Bigwig",
"age" -> 6,
"role" -> "Owsla"
))
builder.result()
}
§使用 Writes 转换器
Scala 到 JsValue
的转换由实用程序方法 Json.toJson[T](T)(implicit writes: Writes[T])
执行。此功能依赖于类型为 Writes[T]
的转换器,该转换器可以将 T
转换为 JsValue
。
Play JSON API 为大多数基本类型提供隐式 Writes
,例如 Int
、Double
、String
和 Boolean
。它还支持对任何类型 T
的集合的 Writes
,其中存在 Writes[T]
。
import play.api.libs.json._
// basic types
val jsonString = Json.toJson("Fiver")
val jsonNumber = Json.toJson(4)
val jsonBoolean = Json.toJson(false)
// collections of basic types
val jsonArrayOfInts = Json.toJson(Seq(1, 2, 3, 4))
val jsonArrayOfStrings = Json.toJson(List("Fiver", "Bigwig"))
要将您自己的模型转换为 JsValue
,您必须定义隐式 Writes
转换器并在范围内提供它们。
case class Location(lat: Double, long: Double)
case class Resident(name: String, age: Int, role: Option[String])
case class Place(name: String, location: Location, residents: Seq[Resident])
import play.api.libs.json._
implicit val locationWrites: Writes[Location] = new Writes[Location] {
def writes(location: Location) = Json.obj(
"lat" -> location.lat,
"long" -> location.long
)
}
implicit val residentWrites: Writes[Resident] = new Writes[Resident] {
def writes(resident: Resident) = Json.obj(
"name" -> resident.name,
"age" -> resident.age,
"role" -> resident.role
)
}
implicit val placeWrites: Writes[Place] = new Writes[Place] {
def writes(place: Place) = Json.obj(
"name" -> place.name,
"location" -> place.location,
"residents" -> place.residents
)
}
val place = Place(
"Watership Down",
Location(51.235685, -1.309197),
Seq(
Resident("Fiver", 4, None),
Resident("Bigwig", 6, Some("Owsla"))
)
)
val json = Json.toJson(place)
或者,您可以使用组合器模式定义您的Writes
注意:组合器模式在JSON Reads/Writes/Formats Combinators中详细介绍。
import play.api.libs.json._
import play.api.libs.functional.syntax._
implicit val locationWrites: Writes[Location] = (
(JsPath \ "lat").write[Double] and
(JsPath \ "long").write[Double]
)(l => (l.lat, l.long))
implicit val residentWrites: Writes[Resident] = (
(JsPath \ "name").write[String] and
(JsPath \ "age").write[Int] and
(JsPath \ "role").writeNullable[String]
)(r => (r.name, r.age, r.role))
implicit val placeWrites: Writes[Place] = (
(JsPath \ "name").write[String] and
(JsPath \ "location").write[Location] and
(JsPath \ "residents").write[Seq[Resident]]
)(p => (p.name, p.location, p.residents))
§遍历 JsValue 结构
您可以遍历JsValue
结构并提取特定值。语法和功能类似于 Scala XML 处理。
注意:以下示例应用于之前示例中创建的 JsValue 结构。
§简单路径 \
将\
运算符应用于JsValue
将返回JsObject
中与字段参数对应的属性,或JsArray
中该索引处的项
val lat = (json \ "location" \ "lat").toOption
// returns some JsNumber(51.235685)
val bigwig = (json \ "residents" \ 1).toOption
// returns some {"name":"Bigwig","age":6,"role":"Owsla"}
\
运算符返回一个JsLookupResult
,它可以是JsDefined
或JsUndefined
。您可以链接多个\
运算符,如果任何中间值都找不到,结果将是JsUndefined
。在JsLookupResult
上调用get
尝试获取值(如果已定义),如果未定义,则抛出异常。
您还可以使用直接查找apply
方法(如下)来获取对象中的字段或数组中的索引。与get
一样,此方法如果值不存在,将抛出异常。
§递归路径 \\
应用\\
运算符将在当前对象及其所有后代中查找字段。
val names = json \\ "name"
// returns Seq(JsString("Watership Down"), JsString("Fiver"), JsString("Bigwig"))
§直接查找
您可以使用.apply
运算符检索JsArray
或JsObject
中的值,它与简单路径\
运算符相同,只是它直接返回该值(而不是将其包装在JsLookupResult
中),如果索引或键未找到,则抛出异常
val name = json("name")
// returns JsString("Watership Down")
val bigwig2 = json("residents")(1)
// returns {"name":"Bigwig","age":6,"role":"Owsla"}
// (json("residents")(3)
// throws an IndexOutOfBoundsException
// json("bogus")
// throws a NoSuchElementException
如果您正在编写快速且脏的代码并访问您知道存在的某些 JSON 值,例如在一次性脚本或 REPL 中,这将很有用。
§从 JsValue 转换
§使用字符串实用程序
压缩
val minifiedString: String = Json.stringify(json)
{"name":"Watership Down","location":{"lat":51.235685,"long":-1.309197},"residents":[{"name":"Fiver","age":4,"role":null},{"name":"Bigwig","age":6,"role":"Owsla"}]}
可读
val readableString: String = Json.prettyPrint(json)
{
"name" : "Watership Down",
"location" : {
"lat" : 51.235685,
"long" : -1.309197
},
"residents" : [ {
"name" : "Fiver",
"age" : 4,
"role" : null
}, {
"name" : "Bigwig",
"age" : 6,
"role" : "Owsla"
} ]
}
§使用 JsValue.as/asOpt
将JsValue
转换为另一种类型最简单的方法是使用JsValue.as[T](implicit fjs: Reads[T]): T
。这需要一个类型为Reads[T]
的隐式转换器来将JsValue
转换为T
(Writes[T]
的逆运算)。与Writes
一样,JSON API 为基本类型提供Reads
。
val name = (json \ "name").as[String]
// "Watership Down"
val names = (json \\ "name").map(_.as[String])
// Seq("Watership Down", "Fiver", "Bigwig")
如果路径未找到或转换不可行,as
方法将抛出 JsResultException
。更安全的方法是 JsValue.asOpt[T](implicit fjs: Reads[T]): Option[T]
。
val nameOption = (json \ "name").asOpt[String]
// Some("Watership Down")
val bogusOption = (json \ "bogus").asOpt[String]
// None
虽然 asOpt
方法更安全,但任何错误信息都会丢失。
§使用验证
从 JsValue
转换为其他类型首选的方法是使用其 validate
方法(它接受类型为 Reads
的参数)。这会执行验证和转换,返回类型为 JsResult
。JsResult
由两个类实现
您可以应用各种模式来处理验证结果
val json = { ... }
val nameResult: JsResult[String] = (json \ "name").validate[String]
// Pattern matching
nameResult match {
case JsSuccess(name, _) => println(s"Name: $name")
case e: JsError => println(s"Errors: ${JsError.toJson(e)}")
}
// Fallback value
val nameOrFallback = nameResult.getOrElse("Undefined")
// map
val nameUpperResult: JsResult[String] = nameResult.map(_.toUpperCase)
// fold
val nameOption: Option[String] = nameResult.fold(
invalid = { fieldErrors =>
fieldErrors.foreach { x =>
println(s"field: ${x._1}, errors: ${x._2}")
}
Option.empty[String]
},
valid = Some(_)
)
§JsValue 到模型
要从 JsValue 转换为模型,您必须定义隐式 Reads[T]
,其中 T
是您的模型的类型。
注意:用于实现
Reads
和自定义验证的模式在 JSON Reads/Writes/Formats 组合器 中有详细介绍。
case class Location(lat: Double, long: Double)
case class Resident(name: String, age: Int, role: Option[String])
case class Place(name: String, location: Location, residents: Seq[Resident])
import play.api.libs.json._
import play.api.libs.functional.syntax._
implicit val locationReads: Reads[Location] = (
(JsPath \ "lat").read[Double] and
(JsPath \ "long").read[Double]
)(Location.apply _)
implicit val residentReads: Reads[Resident] = (
(JsPath \ "name").read[String] and
(JsPath \ "age").read[Int] and
(JsPath \ "role").readNullable[String]
)(Resident.apply _)
implicit val placeReads: Reads[Place] = (
(JsPath \ "name").read[String] and
(JsPath \ "location").read[Location] and
(JsPath \ "residents").read[Seq[Resident]]
)(Place.apply _)
val json = { ... }
val placeResult: JsResult[Place] = json.validate[Place]
// JsSuccess(Place(...),)
val residentResult: JsResult[Resident] = (json \ "residents")(1).validate[Resident]
// JsSuccess(Resident(Bigwig,6,Some(Owsla)),)
§使用简单元组
简单的 JSON 对象可以读写为简单的元组。
import play.api.libs.json._
val tuple3Reads: Reads[(String, Int, Boolean)] =
Reads.tuple3[String, Int, Boolean]("name", "age", "isStudent")
val tuple3Writes: OWrites[(String, Int, Boolean)] =
OWrites.tuple3[String, Int, Boolean]("name", "age", "isStudent")
val tuple3ExampleJson: JsObject =
Json.obj("name" -> "Bob", "age" -> 30, "isStudent" -> false)
val tuple3Example = Tuple3("Bob", 30, false)
tuple3Writes.writes(tuple3Example) mustEqual tuple3ExampleJson
tuple3Reads.reads(tuple3ExampleJson) mustEqual JsSuccess(tuple3Example)
下一步:JSON 与 HTTP
在本文档中发现错误?此页面的源代码可以在 此处 找到。阅读 文档指南 后,请随时贡献拉取请求。有疑问或建议要分享?前往 我们的社区论坛 与社区开始对话。