总的来说在scala体系下,对于习惯了java和c#这些常规开发的人来说,无论是akka-http还是play,就处理个json序列化与反序列化真他娘够费劲的。
根据经验,Json处理是比较简单的,但是Play和akka-http的序列化让我烦恼了不少时间,所以我从自己的角度记录一下Play的JSON处理,来理解play的思维方式。
一、官方文档
代码也都是从官方文档复制后重新整理的,下面是参考的官方文档下有几篇JSON的文章
Working with Json
- JSON basics
- JSON with HTTP
- JSON Reads/Writes/Format Combinators
- JSON automated mapping
- JSON Transformers
二、实体序列化例子
1.本例中使用的实体类
package serialize case class Location(lat: Double, long: Double) case class Resident(name: String, age: Int, role: Option[String]) case class Place(name: String, location: Location, residents: Seq[Resident])
2.序列化例子
import play.api.libs.json._ import play.api.libs.functional.syntax._ import serialize.{Location, Place, Resident} object PlayJsonTest{ def main(args:Array[String]):Unit={ implicit val locationReads = Json.reads[Location] implicit val residentReads = Json.reads[Resident] implicit val placeReads = Json.reads[Place] val json: JsValue = Json.parse(""" { "name" : "Watership Down", "location" : { "lat" : 51.235685, "long" : -1.309197 }, "residents" : [ { "name" : "Fiver", "age" : 4, "role" : null }, { "name" : "Bigwig", "age" : 6, "role" : "Owsla" } ] } """) val result: JsResult[Place] = Json.fromJson[Place](json) result match { case JsSuccess(r: Place, path: JsPath) => println("Name: " + r.name) case e: JsError => println("Errors: " + JsError.toJson(e).toString()) } } }
3.解释
一般的json序列化比较简单,有一个类似JsonManager的管理类,提供serialize(object obj)与deserialize<T>(string json)这样方法,一般不需要开发者做太多的工作,比较简单,也是基于这种认识,所以对play的json处理方式就会困惑。
原因是scala和akka系列声称自己是可伸缩的框架,面向大数据领域,他认为传统的方式是比较重的oo方式,而大数据的处理主要是批量处理数据,所以可能只是对批量数据的个别字段做简单的运算即可,不需要沉重的OOM,看他原文的描述:
Are we doomed to convert JSON to OO?
For a few years now, in almost all web frameworks (except recent JavaScript server side stuff maybe in which JSON is the default data structure), we have been used to get JSON from network and convert JSON (or even POST/GET data) into OO structures such as classes (or case classes in Scala). Why?
- For a good reason : OO structures are “language-native” and allows manipulating data with respect to your business logic in a seamless way while ensuring isolation of business logic from web layers.
- For a more questionable reason : ORM frameworks talk to DB only with OO structures and we have (kind of) convinced ourselves that it was impossible to do else… with the well-known good & bad features of ORMs… (not here to criticize those stuff)
Is OO conversion really the default use case?
In many cases, you don’t really need to perform any real business logic with data but validating/transforming before storing or after extracting. Let’s take the CRUD case:
- You just get the data from the network, validate them a bit and insert/update into DB.
- In the other way, you just retrieve data from DB and send them outside.
So, generally, for CRUD ops, you convert JSON into a OO structure just because the frameworks are only able to speak OO.
I don’t say or pretend you shouldn’t use JSON to OO conversion but maybe this is not the most common case and we should keep conversion to OO only when we have real business logic to fulfill.
4.总结
先要知道play的json的关注点,以及他的思维方式,对于这种思维方式的好与坏,其实也无所谓。
三、json4s
json4s是scala下使用OO方式可以很接近的方法,他也提供了一些play.json关注的类xpath查询的这种功能,参见json4s的github
在akka-http下实在是无法忍受从1个到24个类属性序列化的这种方式,所以可以是一个比较好的选择
json4s的sbt引用
"org.json4s" %% "json4s-jackson" % "3.6.2",
本例使用的实体还是上文的实体,对应的序列化代码为:
package org.netsharp.jsonoom
import java.text.SimpleDateFormat
import org.json4s._
import org.json4s.jackson.JsonMethods._
import org.json4s.jackson.Serialization
object Json4sDemo {
def main(args:Array[String]):Unit={
var json:String = """
{
"name" : "Watership Down",
"location" : {
"lat" : 51.235685,
"long" : -1.309197
},
"residents" : [ {
"name" : "Fiver",
"age" : 4,
"role" : null
}, {
"name" : "Bigwig",
"age" : 6,
"role" : "Owsla"
} ]
}
""";
// implicit val formats = Serialization.formats(NoTypeHints)
implicit val formats = new DefaultFormats {
override def dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
}
var place:Place = Serialization.read[Place](json)
json = Serialization.write(place)
println(json)
//另外一种序列化接口
place = parse(json).extract[Place]
}
}
总的来说,json4s和play.json思路类似,他们都是基于jackson的序列化
你说是不是play.json抄袭了json4s?
四、直接用Jackson进行序列化
这篇文章介绍了直接SCALA使用Jackson进行JSON序列化
但是也不是那么完美,集合只能用java的集合,bean不能使用Option[Double]这样的类型