Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala Play framework 2.1 derived classes

I'd like to produce JSON for a List that includes both base classes and derived classes. The code below only produces JSON for the Animal class (I do not get the breed field for the Dog type members). Some help would be appreciated.

import play.api.libs.json._

class Animal (val name:String) {
}

object Animal {
  implicit object animalWrite extends Writes[Animal] {
    def writes(ts: Animal) = JsObject(Seq("name" -> JsString(ts.name)))
  }
}

case class Dog (override val name:String, val breed: String) 
    extends Animal(name)  {
}

object Dog {
    implicit val format = Json.format[Dog]
}

case class Cat (override val name:String, val hairLength: Int) 
    extends Animal(name)  {
}

object Cat {
    implicit val format = Json.format[Cat]
}

object helloWorld extends App {
//  The list below outputs:     [{"name":"Ruff","breed":"labrador"}]
//  val l = List[Dog](Dog("Ruff", "labrador"))

//  The list below outputs:     [{"name":"Ruff"},{"name":"Fluffy"}]
//  I expect to see: [{"name":"Ruff","breed":"labrador"},{"name":"Fluffy","hairLength":3}]
    val l = List[Animal](Dog("Ruff", "labrador"), Cat("Fluffy", 3))
    println(Json.toJson(l))
}

Scala and Play newbie here, please excuse inappropriate use of terminology.

like image 971
ariscris Avatar asked Jul 15 '13 02:07

ariscris


People also ask

What is Play framework in Java?

Play Framework makes it easy to build web applications with Java & Scala. Play is based on a lightweight, stateless, web-friendly architecture. Built on Akka, Play provides predictable and minimal resource consumption (CPU, memory, threads) for highly-scalable applications.

How to implement a new action in a Scala controller?

To implement a new action, we open the Scala file ( HomeController.scala) and add a new method that accepts two parameters, calculates their sum, and passes the result to the view template: Now, let’s open the index.scala.html file, add the sum parameter on top of the file, and use it in the content:

What is the directory structure of a Scala project?

Project Structure Now, it’s time to load the project code into the IDE and look at the directory structure. In our project directory, we see four directories created by the sbt template: app/controllers, app/views, conf, and public. The controllers directory is where we will store our Scala code

What is the best way to inject a class in Scala?

Note that the @Inject annotation must come after the class name but before the constructor parameters, and must have parentheses. Also, Guice does come with several other types of injections, but constructor injection is generally the most clear, concise, and testable in Scala, so we recommend using it.


1 Answers

The json API extensively uses implicit parameters which is a feature of Scala where you can provide an "implicit" parameter list and if you don't specify those parameters the compiler will try to find an object in the current scope that is marked as implicit and matches that signature.

So if you for example would write:

implicit val s = "my implicit string"

def magicPrint(implicit message: String) { println(message) }

// and then call it
magicPrint

The compiler would select s for the parameter message since it is in scope and has the correct type (String), so after the implicit resolution the last line of code would actually look more like this

magicPrint(s)

The Format/Writer is selected by the compiler, at compile time, with an implicit parameter. If you look at the signature of the method, toJson[A](item: A)(implicit writes: Writes[A]), it takes an implicit Writes[A] which in your case is a Writes[List[Animal]] since List[Animal] is the type of your list l. Play contains a default has a writer that takes care of the collection (DefaultWrites.traversableWrites) which in turn takes an implicit Writes[A] - in your case Writes[Animal], so the compiler will select and pass your animalWrites.

That your list contains different types of animals is something that happens at runtime and the compiler has no way of knowing that from the type information available at your Json.toJson(l)

So, as you see you cannot achieve what you want in the way you thought, but you can do it in almost the same way by letting the animal writer know about the subtypes, for example:

implicit object animalWrite extends Writes[Animal] {
  def writes(ts: Animal) = ts match {
    // this will get an implicit Writes[Dog] since d is a Dog
    case d: Dog => Json.toJson(d) 
    // this will get an implicit Writes[Cat] since c is a Cat
    case c: Cat => Json.toJson(c) 
    case x => throw new RuntimeException(s"Unknown animal $x")
  }
}

Hope this helped!

like image 97
johanandren Avatar answered Sep 29 '22 10:09

johanandren