JSON basics[翻译]


现代的Web应用经常需要解析并生成JSON格式的数据 (JavaScript 对象符号)。Play通过它的JSON library支持这个功能。

JOSN是一个轻量级的数据交换格式,像下面这样:

{
"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数据的数据结构及这些数据结构和其他数据展现实现之间的实用工具。这个包的一些功能是:

  • 使用最小样板自动转换为Case类和从Case类转换为JSON。如果你想用最小的代码快速的起步和运行,这可能是个开始的地方
  • 解析时自定义验证。
  • 在请求Body中对JSON的自动解析,如果内容不可被解析或提供了不正确的Content-type会自动生成错误。
  • 可以作为独立的可在Play应用以外的地方使用。只需要在你的 build.sbt文件中加上libraryDependencies += "com.typesafe.play" %% "play-json" % playVersion。
  • 高度可自定义。

这个包提供了下列类型:

JsValue

这是表示任何JSON值的特质。JSON库扩展了JsValue的Case类来展示每一个有效的JSON类型: * JsString * JsNumber * JsBoolean * JsObject * JsArray * JsNull

使用多样的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(Seq(
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,JsValue}

val json: JsValue = 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"
)
)
)

使用Writes转换器

ScalaJsValue转换是通过Json.toJson[T](T)(implicit writes: Writes[T])实用方法执行的。这个功能取决于可以吧T转换为JsValueWrites[T]类型的转换器。

Play的JSON API为大多数基本类型提供隐式的Writes,如Int, Double, String, 和 Boolean.它也支持有Writes[T]的任何类型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"))  
To convert your own models to JsValues, you must define implicit Writesconverters and provide them in scope.  
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 = new Writes[Location] {  
def writes(location: Location) = Json.obj(  
"lat" -> location.lat,
"long" -> location.long
)
}

implicit val residentWrites = new Writes[Resident] {  
def writes(resident: Resident) = Json.obj(  
"name" -> resident.name,
"age" -> resident.age,
"role" -> resident.role
)
}

implicit val placeWrites = 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]
)(unlift(Location.unapply))

implicit val residentWrites: Writes[Resident] = (  
(JsPath \ "name").write[String] and
(JsPath \ "age").write[Int] and
(JsPath \ "role").writeNullable[String]
)(unlift(Resident.unapply))

implicit val placeWrites: Writes[Place] = (  
(JsPath \ "name").write[String] and
(JsPath \ "location").write[Location] and
(JsPath \ "residents").write[Seq[Resident]]
)(unlift(Place.unapply))

遍历JsValue结构

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

注意:下面的例子使用了前面例子中创建的JsValue结构。

简单路径\

对JsValue使用\ 操作将返回与字段参数相对应的属性,假如这是一个JsObject。

val lat = (json \ "location" \ "lat").get  
// returns JsNumber(51.235685)

遍历路径 \

使用 \操作将会在当前对象和所有子中的寻找字段。

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

索引查找(适用于JsArrays)

你可以在JsArray中使用带索引的操作取值。

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

从JsValue转换

使用字符串工具

Minified:

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"}]}

Readable:

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.asT: 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被两个类实现了: * JsSuccess: 表示成功验证/转换并封装结果。 * JsError: 表示不成功的验证/转换并包含验证错误的列表。 你可以使用多种模式处理验证结果:

val json = { ... }

val nameResult: JsResult[String] = (json \ "name").validate[String]

// Pattern matching
nameResult match {  
case s: JsSuccess[String] => println("Name: " + s.get)  
case e: JsError => println("Errors: " + JsError.toJson(e).toString())  
}

// 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("field: " + x._1 + ", errors: " + x._2)  
})
None  
},
valid = {  
name => Some(name)  
}
)

JsValue 转换成模型

为了把JsValue 转换为模型,你必须定义隐式的Reads[T],在这里T是你的模型的类型。

注意:曾经实现了Reads并自定义了验证的模式在JSON Reads/Writes/Formats Combinators中有详细介绍。

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)),)

祝青

继续阅读此作者的更多文章