简介
本文将介绍Json格式的打包和解包。
Json扩展库
在Scala中又大量的Json库可供选择。很大程度上取决于项目框架的选择和项目配置。本文不打算对现有工具包中的最佳选择进行辩论。
下面的文章将主要介绍Argonaut,但是大概的方法也同样适用于其他Json库。我选择Argonaut是因为它很适合我的功能发展,同时和Scala还有Scalatra都配合的很好。
其他Json库还有Json4s、Spray-Json、Lift-Json等,其中Json4s支持Native和Jackson。
转换
在我们讨论转换之前首先需要定义数据模型,这里使用slick sample structuree:
case class Person (id: Int,name: String,age: Int,addressId:Int)
case class Address (id: Int, street: String,city: String)
// no direct reference, to fit with slick database models
case class PersonWithAddress(person: Person, address: Address)
现在可以实例化一个带有address的person:
val person = Person(0, "John Rambo" , 67, 0)
val address = Address(0, "101 W Main St", "Madison, Kentucky")
val pa = PersonWithAddress(person, address)
如果是在开发REST服务的话,需要适用json格式来接受或响应数据。如果需要手动构造每一个路由的数据结构转换将会非常恐怖,因此我们需要一些类似下面的功能:
// convert the person to json
val json = pa.asJson
Scala内部提供了隐式类型转换的功能。我们的情况使用这种功能会非常方便,并配合Argonaut的编解码器。对于我们的情况则有如下定义:
// implicit conversion with argonaut
implicit def PersonAddressEncodeJson: EncodeJson[PersonWithAddress] =
EncodeJson((p: PersonWithAddress) =>
("id" := p.person.id) ->:
("name" := p.person.name) ->:
("age" := p.person.age) ->:
("address" := Json (
("id" := p.address.id),
("street" := p.address.street),
("city" := p.address.city)
)
) ->: jEmptyObject)
现在我们把所有的步骤拼接起来后就可以完成从scala数据类型到Json的格式转化:
{
"id": 0,
"name": "John Rambo",
"age": 67,
"address": {
"id": 0,
"street": "101 W Main St",
"city": "Madison, Kentucky"
}
}
要解析Json数据:
implicit def PersonAddressDecodeJson: DecodeJson[PersonWithAddress] =
DecodeJson(c => for {
id <- (c --\ "id").as[Int]
name <- (c --\ "name").as[String]
age <- (c --\ "age").as[Int]
address <- (c --\ "address").as[Json]
// extract data from address
addressid <- (address.acursor --\ "id").as[Int]
street <- (address.acursor --\ "street").as[String]
city <- (address.acursor --\ "city").as[String]
} yield PersonWithAddress(Person(id, name, age, addressid), Address(addressid, street, city)))
解析的结果得到我们期望的scala数据结构:
PersonWithAddress(Person(0,John Rambo,67,0),Address(0,101 W Main St,Madison, Kentucky))
相对于Node.js,这种转换也是一种验证,如果一个参数错误,则转换失败。如果想要使部分参数可选,则需要进行调整,使用Option[Int]替换Int:
age <- (c --\ "age").as[Option[Int]]
一个复杂的实例
import argonaut._, Argonaut._
object ImplicitConversion {
// data model based on http://slick.typesafe.com/doc/2.1.0/orm-to-slick.html
case class Person (id: Int,name: String,age: Int,addressId:Int)
case class Address (id: Int, street: String,city: String)
// no direct reference, to fit with slick database models
case class PersonWithAddress(person: Person, address: Address)
// implicit conversion with argonaut
implicit def PersonAddressEncodeJson: EncodeJson[PersonWithAddress] =
EncodeJson((p: PersonWithAddress) =>
("id" := p.person.id) ->:
("name" := p.person.name) ->:
("age" := p.person.age) ->:
("address" := Json (
("id" := p.address.id),
("street" := p.address.street),
("city" := p.address.city)
)
) ->: jEmptyObject)
implicit def PersonAddressDecodeJson: DecodeJson[PersonWithAddress] =
DecodeJson(c => for {
id <- (c --\ "id").as[Int]
name <- (c --\ "name").as[String]
age <- (c --\ "age").as[Int]
address <- (c --\ "address").as[Json]
// extract data from address
addressid <- (address.acursor --\ "id").as[Int]
street <- (address.acursor --\ "street").as[String]
city <- (address.acursor --\ "city").as[String]
} yield PersonWithAddress(Person(id, name, age, addressid), Address(addressid, street, city)))
def main(args: Array[String]) {
// running a sample
val person = Person(0, "John Rambo" , 67, 0)
val address = Address(0, "101 W Main St", "Madison, Kentucky")
val pa = PersonWithAddress(person, address)
// convert the person to json
val json = pa.asJson
var content = json.toString()
println (content)
// we should get a person instance here
var padecoded : PersonWithAddress = content.decodeOption[PersonWithAddress].get
println (padecoded)
}
}
结论
简单的几行代码,我们就封装了Json的打包和拆包,真的是很容易。可能不像在Node中那么直观,但是带有附加的类型检查,如果是在Node中则会很复杂。