Converting a sequence of Scala objects to JSON using the Play Framework

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.

Add new comment

Anonymous format

  • Allowed HTML tags: <em> <strong> <cite> <code> <ul type> <ol start type> <li> <pre>
  • Lines and paragraphs break automatically.