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. 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.

Post new comment

The content of this field is kept private and will not be shown publicly.