Scala Play 支援自己實作的 play.api.libs.json JSON Lirary
類別
JsValue: trait 代表任意一種 JSON value,以下這些是 extend JsValue 的 case classes
- JsString
- JsNumber
- JsBoolean
- JsObject
- JsArray
JsNull
Json 提供 utilties,轉換 JsValue
JsPath 代表 JsValue 資料結構中的 path,類似 XML 的 XPath,可使用 pattern 在 JsValue 中進行 data traversal
Converting to a JsValue
- 以 json String 直接 parsing 並轉換
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"
} ]
}
""")
直接將上面的 code ,在 activator console 中進行測試,就可以得到測試結果
scala> json
res0: play.api.libs.json.JsValue = {"name":"Watership Down","location":{"lat":51.235685,"long":-1.309197},"residents":[{"name":"Fiver","age":4,"role":null},{"name":"Bigwig","age":6,"role":"Owsla"}]}
- 以 class 的方式建立 JsValue
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")
))
))
))
使用 Writes converters
scala converting to a JsValue 是以 Json.toJsonT(implicit writes: Writes[T]) 進行轉換的,這需要 Writes 物件的幫忙。Play JSON API 提供了一個 implicit Writes 物件,可以轉換 Int, Double, String, Boolean ...,同時也支援 collections
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"))
如果要轉換自己的資料物件類別,就須要提供一個 implicit Writes converter
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)
也可以使用 combinator pattern
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))
Traversing a JsValue structure
可以 traverse JsValue 並取得特定的資料欄位,這就類似 Scala XML processing
- Simple Path \
val lat = (json \ "location" \ "lat").get
// returns JsNumber(51.235685)
- Recursive path \
val names = json \\ "name"
// returns Seq(JsString("Watership Down"), JsString("Fiver"), JsString("Bigwig"))
- Index lookup (針對 JsArrays)
val bigwig = (json \ "residents")(1)
// returns {"name":"Bigwig","age":6,"role":"Owsla"}
Converting from a JsValue
- 使用 String utilities
val minifiedString: String = Json.stringify(json)
適合閱讀的格式
val readableString: String = Json.prettyPrint(json)
- 使用 JsValue.as/asOpt
將 JsValue 轉換為其他資料類別的方式,最簡單的方式就是用 JsValue.asT: T ,但這個方式需要用到一個 implicit converter: Reads[T]
val name = (json \ "name").as[String]
// "Watership Down"
val names = (json \\ "name").map(_.as[String])
// Seq("Watership Down", "Fiver", "Bigwig")
但是 as 可能會產生 JsResultException,所以比較好的方式,應該是改用 JsValue.asOptT: Option[T]
val nameOption = (json \ "name").asOpt[String]
// Some("Watership Down")
val bogusOption = (json \ "bogus").asOpt[String]
// None
- 使用 validation
會產生兩種 JsResult
- JsSuccess: validation/convertion 成功
- JsError: validation/conversion 失敗
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 轉換成 data model
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)),)
沒有留言:
張貼留言