Play Json 简介html
Play 内置了一套JSON库,以帮助开发者简化JSON操做。目前Play的JSON库包含如下功能:ajax
Json对象与字符串之间互转数据库
Json对象和Case Class之间互转json
Json数据校验api
Json格式之间互转数组
Play的JSON库并不依赖于Play环境,能够单独使用,经过以下方式能够将它引入到本身的项目:安全
libraryDependencies += "com.typesafe.play" %% "play-json" % playVersion
基本JSON类型服务器
全部的基本JSON类型都继承自JsValue
trait。Play JSON 库提供的基本类型以下:app
JsString微服务
JsNumber
JsBoolean
JsObject
JsArray
JsNull
在日程开发中,咱们不多跟这些JSON基本类型打交道。由于在Play中对于基本类型T(例如 String, Int, ...)以及Seq[T]已经提供了默认的隐式转换, 能够自动将其转换成对应的JSON类型,例如:
//基本类型值 Json.obj("name" -> JsString("joymufeng")) //能够简写成: Json.obj("name" -> "joymufeng") //序列类型值 Json.obj("emails" -> JsArray(Seq(JsString("a"), JsString("b")))) //能够简写成: Json.obj("emails" -> Seq("a", "b"))
在Play的JSON库里,整形和浮点型都使用JsNumber表示,这是一个略为糟糕的设计,由于会致使JSON数据没法在多语言环境下共享。例如经过Java代码向MongoDB写入了一个整形数值,可是通过Play的JSON库修改后变成了浮点型,Java代码再次读取时便会报错。
基本的JSON操做
构建一个JsObject对象
//直接构建 val json = Json.obj( "name" -> "joymufeng", "emails" -> Json.arr("joymufeng@163.com"), "address" -> Json.obj( "province" -> "JiangSu", "city" -> "NanJing" ) ) //从JSON字符串构建 val json = Json.parse("""{ "name": "joymufeng", "emails": ["joymufeng@163.com"], "address": { "province": "JiangSu", "city" -> "NanJing" } }""")
读写操做
//读取指定路径值 val city = (json \ "address" \ "city").as[String] //若是指定路径不存在则返回None val cityOpt = (json \ "address" \ "city").asOpt[String] //读取数组内容 val emails = (json \ "emails").as[List[String]] //读取数组的第1个元素 val email = (json \ "emails")(0) //更新指定路径值 var obj = Json.obj("a" -> 1) obj ++= Json.obj("b" -> 2) //obj: {"a":1,"b":2}
格式化输出
//格式化输出 val prettyStr = Json.prettyPrint(obj)
JsObject 与 Case Class 互转
Json Format 宏
Play虽然为基本类型T以及Seq[T]提供了默认的隐式转换,可是对于用户自定义的 Case Class,因为没法事先知晓,须要须要用户本身声明隐式转换对象。Play 为开发者提供了 Format 宏,只须要一行代码即可以完成声明操做。假设咱们有以下两个 Case Class:
case class Address(province: String, city: String) case class Person(name: String, emails: List[String], address: Address)
咱们只须要声明以下两个隐式的Format对象就能够了,在运行时,隐式的 Format 对象会自动完成编解码操做:
import play.api.libs.json.Json implicit val addressFormat = Json.format[Address] implicit val personFormat = Json.format[Person]
Format 宏的展开是在编译期执行的,一方面提高了类型的安全性,另外一方,区别于 Java 的反射机制,Format 宏是在编译器生成编解码器,在运行期有更高的执行效率。关于 Scala 宏的更多内容请参考官方文档。
常见互转操做
将上面两个隐式 Format 对象导入到当前做用域,咱们即可以自由地在 JsObject 和 Case Class 之间进行互转:
val person = Person("joymufeng", List("joymufeng@163.com"), Address("JiangSu", "NanJing")) //将 Case Class 转换成 Json val json = Json.toJson[Person](person) //将 Json 转换成 Case Class val p1 = Json.fromJson[Person](json).get //或者 val p2 = json.as[Person] val p3 = json.asOpt[Person].get
咱们发现Json.fromJson[Person](json)
返回的类型并非Person
而是JsResult[Person]
,这是由于从 Json 到Case Class的转换可能会发生错误,JsResult
有两个子类JsSuccess
和JsError
,分别用来处理成功和失败两种状况:
Json.fromJson[Person](json) match { //转换成功 case p: JsSuccess[Person] => println(p) //转换失败 case e: JsError => println(e.errors) }
开发技巧
上面的代码在转换时须要将隐式的 Format 对象显式地导入到当前的做用域,使用起来有些不便。咱们能够把隐式 Format 对象定义在伴生对象中,这样的话就能够在任意位置执行转换而无需导入隐式对象:
import play.api.libs.json.Json case class Address(province: String, city: String) case object Address { implicit val addressFormat = Json.format[Address] } case class Person(name: String, emails: List[String], address: Address) case object Person { implicit val personFormat = Json.format[Person] }
编译器会自动到目标类型和源类型的伴生对象中获取隐式的 Format 对象,无需手动导入。
上面的方法须要针对每一个 Case Class 建立一个伴生对象,编写起来比较繁琐。咱们也能够在包对象(package object)中建立隐式的 Format 对象,假设 Address 和 Person 都定义在 models 包下,则咱们能够为 models 包建立一个包对象,并在其中建立隐式的 Format 对象:
package object models { import play.api.libs.json.Json implicit val addressFormat = Json.format[Address] implicit val personFormat = Json.format[Person] }
因为编译器会自动从源类型或目标类型的父对象中查找隐式转换,因此定义在包对象中的隐式 Format 对象会被自动加载,而无需显示导入。
更多的隐式转换来源请参考官方的总结的隐式转换规则。
Json 请求与 Json 响应
Json
是目前使用最为普遍的数据交换格式,利用 Play 的 Json 库,咱们能够开发很是健壮的 RESTful 应用。
构建 Json 请求
借助jQuery
能够很容易构建一个请求体为 Json
的 Post
请求:
$.ajax({ type: 'post', dataType: 'json', contentType: 'application/json; charset=utf-8', data: {...}, url: '/post', success: function(res){ //请求成功处理 }, error: function(e){ //请求失败处理 } });
须要注意,客户端在执行 Post
请求时必须明确指定Content-Type
请求头,不然服务器端没法正确识别。
处理 Json 请求
在服务器端,咱们能够经过以下方式接收 Json 请求:
def doReciveJson = Action { implicit request => request.body.asJson match { case Some(obj) => obj.validate[Person] match { case JsSuccess(person, _) => Ok(Json.obj("status" -> 0)) case JsError(_) => Ok(Json.obj("status" -> 1, "msg" -> "Json数据校验失败!")) } case None => Ok(Json.obj("status" -> 1, "msg" -> "请求格式有误!")) } }
再次提醒,客户端 Post 请求必须携带Content-Type
请求头,不然服务器端在执行request.body.asJson
代码时将没法正确解析出 Json 数据。经过request.body.as*
方法,咱们能够将请求体转换成不一样的数据格式,前提是请求的Content-Type
内容必须与目标数据格式一致。下面列举常见的as*方法所要求的Content-Type类型,供你们开发时参阅:
asJson
:text/json 或 application/json
asText
:text/plain
asFormUrlEncoded
:application/x-www-form-urlencoded
asXml
:application/xml
asMultipartFormData
:multipart/form-data
asRaw
:其它类型
在服务器端,咱们能够构建一个 Json 对象,而且直接做为响应写回客户端,Play 会自动添加合适的响应头:
Ok(Json.obj("status" -> 0))
在生成 Json 响应时,咱们并无明确指定字符编码格式,这是因为按照 RFC 7159 规范,Play 使用默认的 UTF-8 对 Json 内容进行编码,客户端能够经过检测 Json 内容的前4个字节自动检测出 UTF-8 字符编码,继而能够正确解码 Json 内容。
RFC 7159规定在为 Json 指定 Content-Type 时无需指定编码格式,而且指定编码格式是非法操做。客户端能够根据 Json 内容的前4个字节自动检测出正确的编码格式。
小结
随着NoSQL数据库和微服务的不断普及,JSON数据在Web开发中显得愈来愈重要。借助 MongoDB 等 BSON数据库,咱们能够实现全栈式 Json 开发,大大简化了数据的处理流程。例如对于复杂的业务数据,如绘图工具导出的 Json 数据,咱们能够直接入库,省去中间的 Case Class 相互转换过程。在 Json 处理领域,Play 和 Scala 有着自然的优点,一方面经过 Scala 的优雅语法以及 Play 的 Json DSL,咱们能够轻松地构建和处理 Json;另外一方面,相比于 Java 的反射机制,利用 Scala 语言提供的编译器期 Macro,能够大大提高运行时处理速度,为开发高性能的响应式系统提供了底层的技术保障。