Showing posts with label JSON. Show all posts
Showing posts with label JSON. Show all posts

Friday, November 01, 2013

Add/Remove fields to Play's default case class JSON Writes

Play's JSON Macro Inception is great. For most of the time all you need to write for JSON parsing/serialization is something like:
implicit val personFormat = Json.format[Person]
In this example, Person is a case class model in your app.
case class Person(name: String, age: Int)
The implicit personFormat will silently help you whenever you need to either parse a piece of JSON to an instance of Person or serialize an instance of Person to a JSON string.
Now, let's add a boolean val inside the class constructor: isAdult
case class Person(name: String, age: Int) {
  val isAdult: Boolean = age >= 18
}
Since Play!'s JSON Macro Inception only works with the default apply and unapply methods of the case class, it won't include the isAdult in the JSON writes (reads doesn't make much sense here). There isn't much documentation on how to include this new field while still take advantage of the default Writes generated by Play's JSON Macro. Here is how you can achieve it using writes composition.
val personWrites = Json.writes[Person]
val writesWithNewField: Writes[Person] = (personWrites ~ (__ \ "isAdult").write[Boolean])((p: Person) => (p, p.isAdult))
This is not bad, but we can also introduce some generalized helper to minimize the boilerplate. Introducing the OWritesOpts class:
class OWritesOps[A](writes: OWrites[A]) {
  def addField[T: Writes](fieldName: String, field: A => T): OWrites[A] = 
    (writes ~ (__ \ fieldName).write[T])((a: A) => (a, field(a)))


  def removeField(fieldName: String): OWrites[A] = OWrites { a: A =>
    val transformer = (__ \ fieldName).json.prune
    Json.toJson(a)(writes).validate(transformer).get
  }
}

object OWritesOps {
  implicit def from[A](writes: OWrites[A]): OWritesOps[A] = new OWritesOps(writes)
}

With this class, you can add/remove field to default JSON writes like this:
//writes that replaces the age field with the isAdult field in the JSON output. 
val customWrites: Writes[Person] = Json.Writes[Person].
                                     addField("isAdult", _.isAdult).
                                     removeField("age")

That's it, enjoy. I am still new to Play!, if you have a better alternative, please suggest it in the comment. Will appreciate it, that's partly why I wrote this little blog post.

Saturday, May 25, 2013

Case class enumeration in Play 2.1 Application (on top of Salat/MongoDB)

Recently in our Play 2.1(Scala) application I needed to implement a model that's like Java enum. Since scala's Enumeration class doesn't have a fantastic reputation, I decided to go with the case class approach. Our Play 2.1 application is a JSON REST API that sits on top of MongoDB, so I need to make sure that this model can have both JSON and MongoDBObject serialization wired up.
Let's start from Enum base trait
trait Enum[A] {
  trait Value { self: A => }
  val values: List[A]
  def parse(v: String) : Option[A] = values.find(_.toString() == v)
}
We'll see the use of the parse function later.

In our system we have a flag system for content entries. Here is the flag case class enum.
import com.novus.salat.annotations._

@Salat
sealed trait Flag extends Flag.Value

case object DEPRESSING extends Flag
case object VIOLENCE extends Flag
case object NSFW extends Flag

object Flag extends Enum[Flag] with SimpleEnumJson[Flag] {
  val values = List(DEPRESSING, VIOLENCE, NSFW)
}
The @Salat is needed for salat to map abstract class inheritance tree. SimpleEnumJson[T] is the one that provides Play 2.1 JSON API formats which you will need for JSON serialization. Here is its implementation.
import play.api.libs.json._

trait SimpleEnumJson[A] {
  self: Enum[A] =>

  implicit def reads: Reads[A] = new Reads[A] {
    def reads(json: JsValue): JsResult[A] = json match {
      case JsString(v) => parse(v) match {
        case Some(a) => JsSuccess(a)
        case _ => JsError(s"String value ($v) is not a valid enum item ")
      }
      case _ => JsError("String value expected")
    }
  }

  implicit def writes[A]: Writes[A] = new Writes[A] {
    def writes(v: A): JsValue = JsString(v.toString)
  }
}
As the name suggests, SimpleEnumJson uses the simple toString() of the enum item for JSON representation.
Note that we could place the items definitions inside the companion object, but unfortunately salat couldn't map the class that way.
This should be good enough for most cases, a simple case class based enum that can be used in any models.
Now let's make it a little more complex, let's say we need one more attribute in our Flag class: age rating - each flag indicates that the content is only appropriate for users above a certain age.
@Salat
sealed trait Flag extends Flag.Value {
  val ageRating : Int
}
case object DEPRESSING extends Flag { val ageRating = 18 }
case object VIOLENCE extends Flag { val ageRating = 16 }
case object NSFW extends Flag { val ageRating = 21 }
Now we need a bit more work for our JSON reads/writes, the SimpleEnumJSON[T] is no longer sufficient. Let's write a CompositeEnumJSON[T]
trait CompositeEnumJson[A] {
  self: Enum[A] =>
  implicit val reads = (__ \ 'name).read[String].map( parse(_).get )
  val nameWrite = (__ \ 'name).write[String]
  implicit val writes : OWrites[A]
}
Here we map a composite case class enum into a JsObject with a name field that holds the toString() value of the item. When we read from JSON, we only need the "name" field. We need concrete inheriting case class to implement the Json write:
object Flag extends Enum[Flag] with CompositeEnumJson[Flag] {
  val values = List(DEPRESSING, VIOLENCE, NSFW)
  implicit val writes : OWrites[Flag] = (
    nameWrite ~
    (__ \ 'ageRating).write[Int]
  )(unlift(unapply))

  def unapply(flag: Flag) = {
    Some(( flag.toString(), flag.ageRating ))
  }
}
There you have it - a composite case class based enum that can have attributes.
I hope you find this helpful and let me know if you have any suggestion for improvements.