Play For Scala 开发指南 - 第9章 Json 开发
Play Json 简介
Play 内置了一套JSON库,以帮助开发者简化JSON操作。目前Play的JSON库包含以下功能:
-
Json对象与字符串之间互转
-
Json对象和Case Class之间互转
-
Json数据校验
-
Json格式之间互转
Play的JSON库并不依赖于Play环境,可以单独使用,通过如下方式可以将它引入到自己的项目:
libraryDependencies += "com.typesafe.play" %% "play-json" % playVersion
基本JSON类型
所有的基本JSON类型都继承自JsValue
trait。Play JSON 库提供的基本类型如下:
-
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,可以大大提升运行时处理速度,为开发高性能的响应式系统提供了底层的技术保障。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
-
上一篇
Hystrix Semaphore timeout
When to use semaphore For Thread isolation. there is a thread context switch cost. but this is almost can be ignored in most application. see Thread pool For circuits that wrap very low-latency requests (such as those that primarily hit in-memory caches) the overhead can be too high and in those cases you can use another method such as tryable semaphores which, while they do not allow for timeouts, provide most of the resilience benefits without the overhead. The overhead in general, however, is...
-
下一篇
Uber 业务预测系统实践
Forecasting is ubiquitous 如何利用预测来构建更好的产品和服务 定量预测方法可分为:基于模型(model-based)或因果关系,统计方法(statistical methods)和机器学习方法(machine learning approaches) Forecasting at Uber: An Introduction | September 6, 2018 | By Franziska Bell, Slawek Smyl 近年来,机器学习,深度学习和概率编程在精准预测方面显示出巨大潜力。除普通的统计算法外,Uber 还使用这三种技术构建预测解决方案。下面将讨论 Uber 预测系统使用的关键组件,流行方法,回溯测试和预测区间。 预测无处不在。除了战略预测之外(例如预测收入,生产和支出),产业界还需要能够针对短期战术行为作出准确预测(例如订货数量和招聘需求),以跟上企业的成长步伐。Uber 常见的预测场景包括: 市场预测(Marketplace forecasting):作为 Uber 平台的一个关键要素,市场预测能够基于精准时间和空间的方式预测用户供需,以...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- Dcoker安装(在线仓库),最新的服务器搭配容器使用
- MySQL数据库在高并发下的优化方案
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- Windows10,CentOS7,CentOS8安装Nodejs环境
- CentOS8编译安装MySQL8.0.19
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- CentOS7,8上快速安装Gitea,搭建Git服务器
- SpringBoot2整合Thymeleaf,官方推荐html解决方案