• Stars
    star
    192
  • Rank 202,019 (Top 4 %)
  • Language
    Scala
  • License
    MIT License
  • Created almost 11 years ago
  • Updated 7 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

(Note: this project has been renamed from play-json-variants to play-json-derived-codecs)

Play JSON Derived Codecs

Reads, OWrites and OFormat derivation for algebraic data types (sealed traits and case classes, possibly recursive), powered by shapeless.

Compared to the built-in macros, this project brings support for:

  • sealed traits ;
  • recursive types ;
  • polymorphic types.

The artifacts are built for Scala and Scala.js 2.12, and 2.13, Play 2.9 and Shapeless 2.3.

For Play 2.8 compatibility see version 7.0.0.

Usage

import julienrf.json.derived

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

object User {
  implicit val reads: Reads[User] = derived.reads()
}

The API is simple: the object julienrf.json.derived has just three methods.

  • reads[A](), derives a Reads[A] ;
  • owrites[A](), derives a OWrites[A] ;
  • oformat[A](), derives a OFormat[A].

Representation of Sum Types

By default, sum types (types extending a sealed trait) are represented by a JSON object containing one field whose name is the name of the concrete type and whose value is the JSON object containing the value of the given type.

For instance, consider the following data type:

sealed trait Foo
case class Bar(s: String, i: Int) extends Foo
case object Baz extends Foo

The default JSON representation of Bar("quux", 42) is the following JSON object:

{
  "Bar": {
    "s": "quux",
    "i": 42
  }
}

Configuring the Derivation Process

Three aspects of the derivation process can be configured:

  • the representation of sum types,
  • the way case class field names are mapped to JSON property names,
  • the type name used to discriminate sum types.

Custom Representation of Sum Types

The default representation of sum types may not fit all use cases. For instance, it is not very practical for enumerations.

For instance, you might want to represent the Bar("quux", 42) value as the following JSON object:

{
  "type": "Bar",
  "s": "quux",
  "i": 42
}

Here, the type information is flattened with the Bar members.

You can do so by using the methods in the derived.flat object:

implicit val fooOWrites: OWrites[Foo] =
  derived.flat.owrites((__ \ "type").write[String])

In case you need even more control, you can implement your own TypeTagOWrites and TypeTagReads.

Custom Field Names Mapping

By default, case class fields are mapped to JSON object properties having the same name.

You can transform this mapping by supplying a different NameAdapter parameter. For instance, to use “snake case” in JSON:

implicit val userFormat: OFormat[User] = derived.oformat(adapter = NameAdapter.snakeCase)

Custom Type Names

By default, case class names are used as discriminators (type tags) for sum types.

You can configure the type tags to use by using the derived.withTypeTag object:

implicit val fooFormat: OFormat[Foo] =
  derived.withTypeTag.oformat(TypeTagSetting.FullClassName)

The library provides the following TypeTagSetting values out of the box:

  • TypeTagSetting.ShortClassName: use the class name (as it is defined in Scala)
  • TypeTagSetting.FullClassName: use the fully qualified name
  • TypeTagSetting.UserDefinedName: require the presence of an implicit CustomTypeTag[A] for all type A of the sum type, providing the type tag to use

Custom format for certain types in hierarchy

Sometimes, you might want to represent one type differently than default format would. This can be done by creating an implicit instance of DerivedReads or DerivedWrites for said type. Below is an example of implementing both custom reads and writes for a single class in a hierarchy:

sealed trait Hierarchy
case class First(x: Integer)
case class Second(y: Integer)

implicit val SecondReads: DerivedReads[Second] = new DerivedReads[Second] {
  def reads(tagReads: TypeTagReads, adapter: NameAdapter) = tagReads.reads("Second", (__ \ "foo").read[Integer].map(foo => Second(foo)))
}

implicit val SecondWrites: DerivedOWrites[Second] = new DerivedOWrites[Second] {
  override def owrites(tagOwrites: TypeTagOWrites, adapter: NameAdapter): OWrites[Second] =
    tagOwrites.owrites[Second](
      "Second",
      OWrites[Second](s => JsObject(("foo", Json.toJson(s.y)) :: Nil))
    )
}

val defaultTypeFormat = (__ \ "type").format[String]
implicit val HierarchyFormat = derived.flat.oformat[Hierarchy](defaultTypeFormat)

This will cause Second to be read with SecondReads, and read with SecondWrites.

Avoiding redundant derivation

By default, the auto-derivation mechanism will be applied to the whole sealed hierarchy. This might be costly in terms of compile-time (as Shapeless is being used under the hood). To avoid this, it is possible to define an Format for the different cases, thus only using auto-derivation for the branching in the sealed trait and nothing else.

sealed trait Hierarchy

case class First(a: Int, b: Int, c: Int) extends Hierarchy
case class Second(x: Int, y: Int, c: Int) extends Hierarchy

object First {
  implicit val format: OFormat[First] = Json.format
}

object Second {
  implicit val format: OFormat[Second] = Json.format
}

implicit val HierarchyFormat = derived.oformat[Hierarchy]()

Important note: in case derived.flat is being used, it's recommended that the provided Formats actually produce JsObjects. If that's not the case, a synthetic wrapper around the user-provided result will be generated on-the-fly. For this reason, Json.valueFormat and the like are not compatible with derived.flat, and it is best to avoid using them together.

Here is what will happen if they are used together:

sealed trait Foo
case class Bar(x: Int) extends Foo

object Bar {
  implicit val format: Format[Bar] = Json.valueFormat
}

implicit val fooFormat = derived.flat.oformat[Foo]((__ \ "type").format[String])

Json.toJson(Bar(42)) // { "type": "Bar", "__syntheticWrap__": 42 }

Without the provided Formats the derivation mechanism will traverse all the fields in the hierarchy (in this case 6 in total), which may be costly for larger case classes.

Providing the implicits this way can also be used for customization without having to deal with supplying your own type-tags.

Contributors

See here.

Changelog

See here.

More Repositories

1

play-jsmessages

Library to compute localized messages of your Play application on client side
Scala
124
star
2

scalm

Elm-inspired Scala library for writing web user interfaces
Scala
119
star
3

enum

Scala
68
star
4

effects-without-monads-but-with-dotty

Scala
38
star
5

chooze

Scala
19
star
6

pfe-samples

Code samples of the book Play Framework Essentials
Java
18
star
7

faithful

Lightweight Scala.js promise implementation that does not violate parametricity
Scala
18
star
8

lms-tutorial

Scala
12
star
9

play-jsonp-filter

JSONP filter for Play framework
Scala
11
star
10

mergical

Merge your iCal feeds
JavaScript
10
star
11

composite-ui-play2

Scala
8
star
12

twitterstream

Sample application inspired from the twitterstream Play 2.0 sample
Scala
7
star
13

arrows-typelevel-summit-2017

JavaScript
7
star
14

glitter

Embedded Scala DSL for HTML templates
Scala
7
star
15

play-table

Table module for Play! framework
Java
7
star
16

reactivemongo-derived-codecs

Scala
7
star
17

scala-lessons

Scala
6
star
18

play-products

Scala
6
star
19

di-with-play2

Dependency injection example using Play2
Scala
6
star
20

closely

Find useful amenities (drinking water, waste bin, pharmacy, etc.) close to you
JavaScript
5
star
21

play-macro-templates

Using Scala macros to dynamically call Play templates
Scala
5
star
22

play-custom-pathbinder

Scala
4
star
23

endpoints-problemdetailserrors

Scala
3
star
24

play-forms

Scala
2
star
25

happy-numbers

My solution to the Tony Morris “Happy Numbers” exercise
Scala
2
star
26

dotty-endpoints

Scala
2
star
27

etoffe

Lightweight markup language processor written in Scala
Scala
2
star
28

Alaska

Scala vs Haskell
Haskell
2
star
29

EquiDist

2
star
30

play-table-sample

Sample application for the Play! table module
Java
2
star
31

julienrf.github.com

HTML
2
star
32

jQuery-rant

JavaScript
2
star
33

play-endpoints

Scala
2
star
34

play-modules

Scala
1
star
35

pac-js

JavaScript
1
star
36

play-glitter-sample

Sample application for the Play! glitter module
Scala
1
star
37

blueprintr

Scala
1
star
38

resilience

TeX
1
star
39

play-plovr

Plovr integration into Play! framework 1.x
Java
1
star
40

play-glitter

Glitter module for Play! framework
Scala
1
star
41

pac-gwt

A PAC framework on top of GWT
Java
1
star
42

selector3

JavaScript
1
star
43

tp-scala

Scala
1
star
44

Debate-it

Debate platform
Python
1
star
45

tyco

Reusable components to build web sites
Scala
1
star
46

el

Reduce the boilerplate to build DOM fragments
JavaScript
1
star