Sunday, February 19, 2012

Neo4J with Scala Play! 2.0 on Heroku (Part 4) :: Play 2.0/Json

Note

This post is a continuation of this post, which is the thrid part of a blog suite that aims the use of Neo4j and Play2.0 together on Heroku.

Play2.0 - Scala - Json

I'm about to write a quick wrap up, of some Play20's wiki entries and stackoverflow that were all related to Json in Play2.0.
For that, I'll take some usage examples, but also present the underlying libraries (Jerkson) and the used paradigm, SJson.

Scope

Giving that the wiki pages are really clean and self-explaining, I'm not gonna enter deeply in how Json must be used with Play 2.0 albeit I'll give some prerequesites in order to help you understand how I'll use the Neo4J REST API.

play.api.libs.json

This package contains everything you'll need in order to work with Json in Play 2.0.
It defines important structure like JsObject, JsArray and even some like JsUndefined.
They usage is very easy since they are based on classical scala's Map and List of JsValue(parent type).
Here is an example of creating a JsArray and iterating items. To test it in a REPL, I recommend you to enter the Play console by using play in your repo and the console in sbt (to have all libraries loaded).
val ar:JsArray = JsArray(List(JsString("a"), JsString("b"), JsString("c")))
//... or
// val ar:JsArray = JsArray(List("a","b","c").map(JsString(_)))
val listOfStrings = ar.as[List[JsValue]] // this uses the Format (Reads) that I'll talk brievly further
//... or simply
// val listOfStrings = ar.value
//show items
listOfStrings.map { case a:JsString => println(a.value) }
//using Format
listOfStrings.map { println(_.as[String]) }

For arrays, it's quite easy (maybe wrapping could be annoying but a little of pimping can resolve that).
The Jerkson library powerful comes with JsObject usages. It has defined a very clean DSL for querying Json. Here is an example for querying a property or catch a descendant property.
val ar:JsArray = JsArray(List(JsString("a"), JsString("b"), JsString("c")))
//... or
// val ar:JsArray = JsArray(List("a","b","c").map(JsString(_)))
val listOfStrings = ar.as[List[JsValue]] // this uses the Format (Reads) that I'll talk brievly further
//... or simply
// val listOfStrings = ar.value
//show items
listOfStrings.map { case a:JsString => println(a.value) }
//using Format
listOfStrings.map { println(_.as[String]) }

play.api.libs.json.{Format, Reads, Writes}

Until now, you saw that Json is usable with Play. But, I guess that you hope more than that, since this framework is here to ease the work.
And you're right.
Play is coming with a SJson flavor for serialization and deserialization of DSO.
Three traits come in the game.

Reads

Reads defines a simple method reads:
trait Reads[T] {
/**
* Convert the JsValue into a T
*/
def reads(json: JsValue): T
}
view raw Reads.scala hosted with ❤ by GitHub
Having a Reads defined for a type T, we can now extract such instance from Json.
With the object Reads that defines an implicit Reads instance for most common types like Option, String, Short ...

Writes

Writes, like Reads, is very simple and defines a simple method writes:
trait Writes[T] {
/**
* Convert the object into a JsValue
*/
def writes(o: T): JsValue
}
view raw Writes.scala hosted with ❤ by GitHub
Obviously, its purpose is to convert a value of type T into its Json representation and is the inverse function reads.
A Writes object is defined too with conversion to common types.

Format

This trait is there to put the pieces together
trait Format[T] extends Writes[T] with Reads[T]
/**
* Default Json formatters.
*/
object Format extends DefaultFormat
view raw Format.scala hosted with ❤ by GitHub
With the help of this trait, we can now have a serializer of custom domain object from/to Json.
A good practice is to define such Format into the companion object of your DSO, so that it will come in the scope at once when using it.
Here is an example:
class DSO(val prop:String) {}
object DSO {
implicit object DSOFormat extends Format[DSO] {
def reads(json: JsValue): DSO = new DSO(
(json \ "prop").asOpt[String].getOrElse("Not Defined")
)
def writes(d: DSO): JsValue =
JsObject(List(
"prop" -> JsString(d.prop)
))
}
}
view raw example.scala hosted with ❤ by GitHub
Let's see how to use this Format easily to go back and forth from DSO instances.

play.api.libs.json.Json._

This object is an handy one, that defines four convenient methods. Two are here to play with String and JsValue. The other are to play with types and JsValue.
def parse(input: String): JsValue = JerksonJson.parse[JsValue](input)
def stringify(json: JsValue): String = JerksonJson.generate(json)
// and
def toJson[T](o: T)(implicit tjs: Writes[T]): JsValue = tjs.writes(o)
def fromJson[T](json: JsValue)(implicit fjs: Reads[T]): T = fjs.reads(json)
view raw Json.scala hosted with ❤ by GitHub
Back to our simple DSO class, we can now do this:
scala> val d = new DSO("a prop")
d: DSO = DSO@184a5d6
scala> d.prop
res1: String = a prop
scala> toJson(d)
res2: play.api.libs.json.JsValue = {"prop":"a prop"}
scala> fromJson[DSO](res2).prop
res3: String = a prop</pre>
view raw usage.scala hosted with ❤ by GitHub

Enhancement

In order to have more control on effects of your serialization, I would recommend you to consider the Scalaz library's Validation construct.
Indeed, it will help you having more relevant information and all at once if you reads is wrong.
Here is a talk about this (but not in Play).

In the next post, we'll talk about the Dispatch library. See it here

No comments:

Post a Comment