karelcemus / play-json

Context-aware Writes materializer to convert classes into JSON in Play framework 2

GitHub

Context-aware JSON Format materialization macro
for Play framework

Note: This version supports Play framework 2.6.x with JDK 8.
For previous versions see older releases.

All frameworks including Play's macro inception consider only direct 1:1 mapping between classes and JSON. That means it accepts an instance and serializes it into JSON with all fields, with these names, in this format and type. However, there are many cases when we want some slight modification of the model before its serialization or after its reading. For example, we might want to rename a field, ignore a field, transform the value, e.g., a string to upper case or parse int to string, or consider the value only when a condition is satisfied. Furthermore, some of these, e.g., conditions, might depend on the current execution context including user's identity, application state, and the processed instance. For example, let's image that we write an instance of User. His password should be ignored, and his email should be always lower-cased and visible only to him or an administrator. Now imagine how would you do this with common tools and frameworks, which usually do not support such complex behavior. You would have to either write your own Writes instance or create alternatives of User fitting desired situations.

This library fills this gap. It provides set of annotations and two complex macros reading them and generating context-aware Reads and Writes seamlessly working with the Play framework. You have plenty of options how to customize your data during reading and writing without writing any boilerplate code. This gives you real power as everything is done during compilation, keeps the JSON protocol up-to-date with your models, and gives you powerful customization tools into your toolkit.

What is context-aware, why we want it

Both Reads and Writes instances provided by Play are stateless. They do not accept anything but the instance/JSON they process. Unfortunately, this is very limiting as sometimes we have to make a decision based on current context. For example, an email in Users instance is visible only to him or admins, but we are unable to implement such Reads or Writes as they do not consider the context. Furthermore, the context might contains other data such as current application state, user's preferences, geographical location, etc.

To overcome this gap the library silently brings ContextualReads and ContextualWrites accepting besides the instance/JSON also a Context. The Context is an abstract type and it is up to you to implement it. That means it may contain anything you need available during processing. Having both ContextualReads/ContextualWrites and the Context instance, it provides one-time-use Reads and Writes instance matching the current context. The library simply makes instances of them using closures, there is not hidden magic. The significant benefit lies in silent introduction of a context and seamless cooperation with Play's API and inner implementation.

In conclusion, the library is context-aware as it considers current execution context and enables you to use it during your JSON reading/writing to modify the output.

Macro inception

Just as well as Play framework, this library uses macro inception instead of runtime inspection, reflection and complex output construction. Instead, it uses compile-time code generation (materialization macro) and it is fully type-safe, as it produces regular Scala code. There is no bytecode enhancement.

How to add the module into the project

Add the following lines into your SBT settings (build.sbt):

// context-aware json materializers
libraryDependencies += "com.github.karelcemus" %% "play-json" % "1.1.0"

// macro annotations require macro paradise in both the library and the project using it
addCompilerPlugin( "org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full )

Requirements

This library requires JDK 8 or newer, Scala 2.11 or newer, and is designed for Play 2.6. However, it should work also with older releases of the Play framework as the JSON Reads and Writes API has not changed for several releases. Furthermore, it uses advanced Scala macros, so it depends on Scala Macro Paradise. Add it into your project.

How to use this module

The module is automatically enabled by adding the macro paradise dependency into your project. The module is annotation-driven, i.e., to generate the Reads/Writes just annotate classes.

JSON Protocol as ContextualJson

Core object of the module is the Protocol. This object is implemented by the user as it declares type of Context and optionally adds additional configuration.

@protocol.in, @protocol.out, @protocol.io

class, case class, materialization macro

@protocol.strict

@protocol.removeNulls

Field-specific settings

@json.in, @json.out, @json.io

@json.when

@json.name

@json.transform

@json.default