Play For Scala 开发指南

Stella981
• 阅读 701

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有两个子类JsSuccessJsError,分别用来处理成功和失败两种情况:

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,可以大大提升运行时处理速度,为开发高性能的响应式系统提供了底层的技术保障。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
java将前端的json数组字符串转换为列表
记录下在前端通过ajax提交了一个json数组的字符串,在后端如何转换为列表。前端数据转化与请求varcontracts{id:'1',name:'yanggb合同1'},{id:'2',name:'yanggb合同2'},{id:'3',name:'yang
待兔 待兔
3个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
Gson
Java对象和Json之间的互转,一般用的比较多的两个类库是Jackson和Gson,下面记录一下Gson的学习使用。基础概念: Serialization:序列化,使Java对象到Json字符串的过程。 Deserialization:反序列化,字符串转换成Java对象使用Maven管理Gson,pom.xml导入gson的依赖
Wesley13 Wesley13
3年前
JavaWeb 之 JSON
一、概述  1、概念JSON:JavaScriptObjectNotation JavaScript对象表示法  2、基本格式varp{"name":"张三","age":23,"sex":"男"};  3、用途和优点(1)json现在多用于存储
Stella981 Stella981
3年前
Ajax和SpringMVC之间JSON交互
Ajax和SpringMVC之间的json数据传输有两种方式:1.直接传输Json对象2.将Json序列化成json字符串1.直接传输Json对象前端Ajax$(document).ready(function(){$("btn_login").click(function(){
Stella981 Stella981
3年前
Play 2.0 用户指南 - 使用JSON库 -- 针对Scala开发者
   概述   使用JSON的推荐方式是使用Play的基于类的JSON库,位于play.api.libs.json下.   该库构建于Jerkson之上,它是一个Scala包装者,基于一个超快的基于Java的JSON库,Jackson.   这种方式的好处是,Java和Scala可以共享同样的库(Jac
Stella981 Stella981
3年前
Gson之实例五
前面四篇博客基本上可以满足我们处理的绝大多数需求,但有时项目中对json有特殊的格式规定.比如下面的json串解析:{"tableName":"students","tableData":{"id":1,"name":"李坤","birthDay":"Jun 22, 2012 9:54:49 PM"},{"id":2,"name":"曹贵生"
Wesley13 Wesley13
3年前
unity将 -u4E00 这种 编码 转汉字 方法
 unity中直接使用 JsonMapper.ToJson(对象),取到的字符串,里面汉字可能是\\u4E00类似这种其实也不用转,服务器会通过类似fastjson发序列化的方式,将json转对象,获取对象的值就是中文但是有时服务器要求将传参中字符串中类似\\u4E00这种转汉字,就需要下面 publ