Richard Searle

home

Writing non trivial Scala domain model as Json using Play framework

16 Aug 2015

Given the following domain model, implement the required Writers to convert instances to Json.

sealed trait Mode
case object Automatic extends Mode
case object Manual extends Mode

sealed trait Configure 
case object Stop extends Configure
case class Power(on: Boolean) extends Configure
case class Motor(mode:Mode) extends Configure

The standard writer incantation implicit val mode = Json.writes[Mode] fails when applied to the Mode trait because there is no unapply function.

This can be resolved by placing the following function in scope and deriving Mode from ObjectMarker. The need to modify the domain model is suboptimal, but fortunately has no real consequence. The object instance is represented by its class name, excluding the package(s) prefix and the trailing $.

trait ObjectMarker

implicit def markerWrites[T <: ObjectMarker] = new Writes[T] {
    def writes(m: T) = JsString(m.getClass.getSimpleName.dropRight(1))
}  

The class instances of Configure are written as a Json object, tagged by the class name (excluding package(s) prefix). The object instances of Configure follow the same representation used for the enum as trait+object pattern above.

trait CustomWrites[T] extends Writes[T] {
  def output(m: T)( value: JsValue) = Json.obj(m.getClass.getSimpleName -> value)
  def obj(v:T) = JsString(v.getClass.getSimpleName.dropRight(1))
}

implicit val configureWrites = new CustomWrites[Configure] {
   implicit val w1 = Json.writes[Power]
   implicit val w2 = Json.writes[Motor]

   def writes(m: Configure) = {
     def out = output(m)_
      m match {
        case p: Power => out(Json.toJson(p))
        case p: Motor => out(Json.toJson(p))
        case e @ Stop => obj(e)
     }
   }
}

The above code can probably be generated by a macro, but that effort is not yet justified.