Giter Club home page Giter Club logo

play-json-variants's Introduction

Play JSON Variants

This artifact provides a function Variants.format[A] that takes as parameter a root type hierarchy A and generates a Play Format[A] JSON serializer/deserializer that supports all the subtypes of A.

For instance, consider the following class hierarchy:

sealed trait Foo
case class Bar(x: Int) extends Foo
case class Baz(s: String) extends Foo
case class Bah(s: String) extends Foo

How to write a Reads[Foo] JSON deserializer able to build the right variant of Foo given a JSON value? The naive approach could be to write the following:

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

implicit val fooReads: Reads[Foo] = (__ \ "x").read[Int].map[Foo](Bar) |
                                    (__ \ "s").read[String].map[Foo](Baz) |
                                    (__ \ "s").read[String].map[Foo](Bah)

However this wouldn’t work because the deserializer is unable to distinguish between Baz and Bah values:

val json = Json.obj("s" -> "hello")
val foo = json.validate[Foo] // Is it a `Baz` or a `Bah`?
println(foo) // "Success(Baz(hello))"

Any JSON value containing a String field x is always considered to be a Baz value by the deserializer (though it could be a Bah), just because the Baz and Bah deserializers are tried in order.

In order to differentiate between all the Foo variants, we need to add a field in the JSON representation of Foo values:

val bahJson = Json.obj("s" -> "hello", "$variant" -> "Bah") // This is a `Bah`
val bazJson = Json.obj("s" -> "bye", "$variant" -> "Baz") // This is a `Baz`
val barJson = Json.obj("x" -> "42", "$variant" -> "Bar") // And this is a `Bar`

The deserializer can then be written as follows:

implicit val fooReads: Reads[Foo] = (__ \ "$variant").read[String].flatMap[Foo] {
  case "Bar" => (__ \ "x").read[Int].map(Bar)
  case "Baz" => (__ \ "s").read[String].map(Baz)
  case "Bah" => (__ \ "s").read[String].map(Bah)
}

Usage:

bahJson.validate[Foo] // Success(Bah("hello"))
bazJson.validate[Foo] // Success(Baz("bye"))

The above text introduced a problem and its solution, but this one is very cumbersome: you don’t want to always write by hand the JSON serializer and deserializer of your data type hierarchy.

The purpose of this project is to generate these serializer and deserializer for you. Just write the following and you are done:

import julienrf.variants.Variants

implicit val format: Format[Foo] = Variants.format[Foo]

You can also just generate a Reads or a Writes:

import julienrf.variants.Variants

implicit val reads: Reads[Foo] = Variants.reads[Foo]
implicit val writes: Writes[Foo] = Variants.writes[Foo]

By default the field used to discriminate the target object’s type is named $variant but you can supply another name:

implicit val format: Format[Foo] = Variants.format[Foo]("type")
implicit val reads: Reads[Foo] = Variants.reads[Foo]("type")
implicit val writes: Writes[Foo] = Variants.writes[Foo]("type")

Installation

Add the following dependency to your project:

libraryDependencies += "org.julienrf" %% "play-json-variants" % "1.0.1"

The 1.0.1 version is compatible with Play 2.3.x and with both Scala 2.10 and 2.11.

How Does It Work?

The Variants.format[Foo] is a Scala macro that takes as parameter the root type of a class hierarchy and expands to code equivalent to the hand-written version: it adds a $variant field to the default JSON serializer, containing the name of the variant, and uses it to deserialize values to the correct type.

Known Limitations

  • For now the macro expects its type parameter to be the root sealed trait of a class hierarchy made of case classes or case objects ;
  • Recursive types are not supported ;
  • Polymorphic types are not supported ;
  • Due to initialization order, your class hierarchy must be fully defined before Variants.format is used.

Changelog

  • v1.0.1: Remove unnecessary macro paradise dependency when Scala 2.11 (thanks to Kenji Yoshida)
  • v1.0.0: Support for Reads, Writes and Format

play-json-variants's People

Contributors

julienrf avatar dimitriho avatar jeantil avatar xuwei-k avatar

Watchers

James Cloos avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.