Documentation

You are viewing the documentation for the 2.4.10 release in the 2.4.x series of releases. The latest stable release series is 3.0.x.

§JSON Macro Inception

Please note this documentation was initially published as an article by Pascal Voitot (@mandubian) on mandubian.com

This feature is still experimental because Scala Macros are still experimental in Scala 2.10. If you prefer not using an experimental feature from Scala, please use hand-written Reads/Writes/Format which are strictly equivalent.

§Writing a default case class Reads/Writes/Format is so boring!

Remember how you write a Reads[T] for a case class.

import play.api.libs.json._
import play.api.libs.functional.syntax._

case class Person(name: String, age: Int, lovesChocolate: Boolean)

implicit val personReads = (
  (__ \ 'name).read[String] and
  (__ \ 'age).read[Int] and
  (__ \ 'lovesChocolate).read[Boolean]
)(Person)

So you write 4 lines for this case class.
You know what?
We have had a few complaints from some people who think it’s not cool to write a Reads[TheirClass] because usually Java JSON frameworks like Jackson or Gson do it behind the curtain without writing anything.
We argued that Play2.1 JSON serializers/deserializers are:

But for some, this didn’t justify the extra lines of code for case classes.

We believe this is a really good approach so we persisted and proposed:

Added power, but nothing changed for the additional 4 lines.

§Let’s be minimalist

As we are perfectionist, now we propose a new way of writing the same code:

import play.api.libs.json._
import play.api.libs.functional.syntax._

case class Person(name: String, age: Int, lovesChocolate: Boolean)

implicit val personReads = Json.reads[Person]

1 line only.
Questions you may ask immediately:

Does it use runtime bytecode enhancement? -> NO

Does it use runtime introspection? -> NO

Does it break type-safety? -> NO

So what?

After creating buzzword JSON coast-to-coast design, let’s call it JSON INCEPTION.



§JSON Inception

§Code Equivalence

As explained just before:

import play.api.libs.json._
// please note we don't import functional.syntax._ as it is managed by the macro itself

implicit val personReads = Json.reads[Person]

// IS STRICTLY EQUIVALENT TO writing

implicit val personReads = (
  (__ \ 'name).read[String] and
  (__ \ 'age).read[Int] and
  (__ \ 'lovesChocolate).read[Boolean]
)(Person)

§Inception equation

Here is the equation describing the windy Inception concept:

(Case Class INSPECTION) + (Code INJECTION) + (COMPILE Time) = INCEPTION

§Case Class Inspection

As you may deduce by yourself, in order to ensure preceding code equivalence, we need :


§INJECTION?

No I stop you immediately…

Code injection is not dependency injection…
No Spring behind inception… No IOC, No DI… No No No ;)

I used this term on purpose because I know that injection is now linked immediately to IOC and Spring. But I’d like to re-establish this word with its real meaning.
Here code injection just means that we inject code at compile-time into the compiled scala AST (Abstract Syntax Tree).

So Json.reads[Person] is compiled and replaced in the compile AST by:

(
  (__ \ 'name).read[String] and
  (__ \ 'age).read[Int] and
  (__ \ 'lovesChocolate).read[Boolean]
)(Person)

Nothing less, nothing more…


§COMPILE-TIME

Yes everything is performed at compile-time.
No runtime bytecode enhancement.
No runtime introspection.

As everything is resolved at compile-time, you will have a compile error if you did not import the required implicits for all the types of the fields.


§Json inception is Scala 2.10 Macros

We needed a Scala feature enabling:

This is enabled by a new experimental feature introduced in Scala 2.10: Scala Macros

Scala macros is a new feature (still experimental) with a huge potential. You can :

Please note that:

As you may discover, writing a macro is not a trivial process since your macro code executes in the compiler runtime (or universe).

So you write macro code 
  that is compiled and executed 
  in a runtime that manipulates your code 
     to be compiled and executed 
     in a future runtime…           

That’s also certainly why I called it Inception ;)

So it requires some mental exercises to follow exactly what you do. The API is also quite complex and not fully documented yet. Therefore, you must persevere when you begin using macros.

I’ll certainly write other articles about Scala macros because there are lots of things to say.
This article is also meant to begin the reflection about the right way to use Scala Macros.
Great power means greater responsability so it’s better to discuss all together and establish a few good manners…


§Writes[T] & Format[T]

Please remark that JSON inception just works for structures having unapply/apply functions with corresponding input/output types.

Naturally, you can also incept Writes[T] and Format[T].

§Writes[T]

import play.api.libs.json._
 
implicit val personWrites = Json.writes[Person]

§Format[T]

import play.api.libs.json._

implicit val personWrites = Json.format[Person]

§Special patterns

import play.api.libs.json._

case class Person(name: String, age: Int)

object Person{
  implicit val personFmt = Json.format[Person]
}
import play.api.libs.json._

case class Person(names: List[String])

object Person{
  implicit val personFmt = Json.format[Person]
}

§Known limitations

Next: Working with XML