文档

§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 数据的数据结构以及用于在这些数据结构和其他数据表示之间进行转换的实用程序。此包的一些功能包括

该包提供以下类型

§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.objJson.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,例如 IntDoubleStringBoolean。它还支持对任何类型 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,它可以是JsDefinedJsUndefined。您可以链接多个\运算符,如果任何中间值都找不到,结果将是JsUndefined。在JsLookupResult上调用get尝试获取值(如果已定义),如果未定义,则抛出异常。

您还可以使用直接查找apply方法(如下)来获取对象中的字段或数组中的索引。与get一样,此方法如果值不存在,将抛出异常。

§递归路径 \\

应用\\运算符将在当前对象及其所有后代中查找字段。

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

§直接查找

您可以使用.apply运算符检索JsArrayJsObject中的值,它与简单路径\运算符相同,只是它直接返回该值(而不是将其包装在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转换为TWrites[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 的参数)。这会执行验证和转换,返回类型为 JsResultJsResult 由两个类实现

您可以应用各种模式来处理验证结果

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


在本文档中发现错误?此页面的源代码可以在 此处 找到。阅读 文档指南 后,请随时贡献拉取请求。有疑问或建议要分享?前往 我们的社区论坛 与社区开始对话。