版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/yiifaa/article/details/82054393
在《从JAVA到Scala(三):implicit的三种用法》中,详细地说明了“implicit”关键字的用法,但在实际中,它们可能结合使用为一种模式,比如play框架的JSON用法。
// 在JSON化实例之前,必须定义隐式类型Writes[Location]
implicit val locationWrites = new Writes[Location] {
def writes(location: Location) = Json.obj(
"lat" -> location.lat,
"long" -> location.long
)
}
// 有代码省略
// 得到JsValue对象就可以进行JSON串行化了
val json = Json.toJson(place)
首先吐槽一下,用惯了Spring MVC与Jackson ObjectMapper,初次很不习惯,转个JSON还需要这么多工作?谁说JAVA的代码就一定比Scala多?(PS:其实是我们被Spring框架惯坏了。)
分析其代码,我们发现,我们要串行化对象,必须首先要将其转换为JsValue对象,源码如下:
def stringify(json: JsValue): String =
StaticBinding.generateFromJsValue(json, false)
怎样得到JsValue对象呢,当然靠Json.toJson方法,源码如下:
def toJson[T](o: T)(implicit tjs: Writes[T]): JsValue = tjs.writes(o)
这是一个高阶隐式类型约束函数(请看implicit的第一种用法),意思是参数o的类型为T,且在调用toJson(o)方法时,必须有隐式值Writes[T]存在,具体到第一段代码,后端调用的完整方法如下:
Json.toJson(place)(
new Writes[Location] {
def writes(location: Location) = Json.obj(
"lat" -> location.lat,
"long" -> location.long
)
}
)
Json.toJson方法还有另外一种写法(参考自《Scala In Depth》),如下:
// 少一个参数,无法写出实现
// 请高手指点
def toJson[T: Writes](o: T)
自定义隐式类型约束
第一步,定义特质,如下:
trait ToInt[T] {
// 自定义签名
def toInt(entity: T): Int
}
第二步,定义方法,包含对特质的引用,如下:
def toInt[A](x: A)(implicit y: ToInt[A]) = y.toInt(x)
第三步,创建隐式对象,如下:
implicit val tp = new ToInt[Person] {
override def toInt(entity: Person): Int = entity.age
}
最后,调用即可,如下:
val p = new Person("yiifaa", 32)
// 直接打印
println(toInt(p))
整合
如果按照上述的写法,在每次调用的时候都需要声明一个隐式类型变量,不仅导致了大量的重复,而且也太丑了,这时候就可以利用伴生对象来改进代码质量,如下:
package domain
import play.api.libs.json.{JsValue, Json, Writes}
case class Person(username:String, age:Int)
// 声明伴生对象
object Person {
// 不要省略implicit关键字
implicit val PersonToJson = new Writes[Person] {
override def writes(o: Person): JsValue = Json.obj(
"username" -> o.username,
"age" -> o.age
)
}
}
现在只需要引入“Person”即可自行转换,如下:
def show(id: Long) = Action {
import domain.Person
val out = Json.toJson(new Person("yiifaa", 32))
Ok(out).as("application/json")
}