The code in this blog post shows how to convert a Seq
of Scala objects to their equivalent JSON representation using the Play Framework v2.3. Specifically, I’m working on an application to display Twitter data, and I want to convert a Seq[Tweet]
to its JSON.
The goal
My goal in the following code is to return some JSON that looks like this:
[ {"username":"John", "tweet":"Scala rules!", "date":"Mon Sep 23 07:38:13 MDT 2013"}, {"username":"Jane", "tweet":"Play is awesome!", "date":"Mon Sep 23 07:38:15 MDT 2013"}, {"username":"Fred", "tweet":"FP rocks!", "date":"Mon Sep 23 07:38:17 MDT 2013"}, ]
I get the actual data by querying Twitter, but that isn’t important. As far as the data source goes, the only important thing is that I call the TwitterDao.getTweetsInListAsSeq
method, which returns an Option[Seq[Tweet]]
, and I create the JSON from that Seq
.
The Play Framework Controller
My Play Framework Controller
code looks like this:
package controllers import play.api.mvc._ import play.api.data._ import play.api.libs.json.Json import play.api.libs.json._ import models._ object Twitter extends Controller { // returns a list of tweets from a twitter list named "Scala Peeps" def peeps = Action { val tweets = TwitterDao.getTweetsInListAsSeq("Scala Peeps") // Option[Seq[Tweet]] tweets match { case Some(seqOfTweet) => Ok(convertTweetsToJson(seqOfTweet)) case None => Ok("Bummer, technical error") } } // this works because reads/writes are defined in Format[Tweet] def convertTweetsToJson(tweets: Seq[Tweet]): JsValue = Json.toJson(tweets) // more code ... }
The peeps
method in that code is a normal Play Action
method. I need to use Async
as well, but that’s not important here. (I also have an entry for it in the routes file, connecting it to a URI and request method, but that isn’t important for this example.)
The important code is the convertTweetsToJson
method. It used to be much longer, but since I’m now using a Format
object, the body of the method is only this:
Json.toJson(tweets)
Because of this, the convertTweetsToJson
method isn’t really needed, but I’ve kept it in so you can compare it to a similar method below.
Creating an implicit Format object
To get that code to work I had to define this other code in a file named Tweet.scala:
import play.api.libs.json.Json import play.api.libs.json._ case class Tweet( username: String, tweet: String, date: String ) object Tweet { implicit object TweetFormat extends Format[Tweet] { // convert from Tweet object to JSON (serializing to JSON) def writes(tweet: Tweet): JsValue = { // tweetSeq == Seq[(String, play.api.libs.json.JsString)] val tweetSeq = Seq( "username" -> JsString(tweet.username), "tweet" -> JsString(tweet.tweet), "date" -> JsString(tweet.date) ) JsObject(tweetSeq) } // convert from JSON string to a Tweet object (de-serializing from JSON) // (i don't need this method; just here to satisfy the api) def reads(json: JsValue): JsResult[Tweet] = { JsSuccess(Tweet("", "", "")) } } }
The important part of this code is the writes
method in the implicit TweetFormat
object; it’s used to serialize the Scala object Tweet
to its JSON representation. The code shown is a fairly standard implementation of this method.
Given all of that code, my peeps
method returns the desired JSON.
Another approach
Before I thought to use the approach of creating an implicit Format
object, my convertTweetsToJson
method looked like this:
def convertTweetsToJsonOrig(tweets: Seq[Tweet]): JsValue = { Json.toJson( tweets.map { t => Map("username" -> t.username, "tweet" -> t.tweet, "date" -> t.date) } ) }
This code returns the same JSON as the previous approach, and it’s also simpler. I also think it’s a great use of the map
method. (I found an example like this in the Play docs.) Frankly, for my purposes, I’ll probably go back to using it, but I wanted to show the other approach because it may come in handy in other situations.