JsonFormat
derivation for algebraic data types, inspired by Play Json Derived Codecs.
The derivation built with Scala 2.x is powered by shapeless, whereas the one built with Scala 3 is based on the new Type Class Derivation language API.
The derivation currently supports:
- sum types
- product types
- recursive types
- polymorphic types
This library is built with Sbt 1.5.2 or later, and its master branch is built with Scala 2.13.6 by default but also cross-builds for 3 and 2.12.
NOTE
Scala 2.11 is no longer supported. The latest version available for scala 2.11 is 2.2.2.
If you use sbt add the following dependency to your build file:
libraryDependencies += "io.github.paoloboni" %% "spray-json-derived-codecs" % "<version>"
For automatic derivation, add the following import:
import spray.json.derived.auto._
If you prefer to explicitly define your formats, then you can use semi-auto derivation:
import spray.json.derived.semiauto._
import spray.json._
import spray.json.derived.auto._
import spray.json.DefaultJsonProtocol._
case class Cat(name: String, livesLeft: Int)
object Test extends App {
val oliver: Cat = Cat("Oliver", 7)
val encoded = oliver.toJson
assert(encoded == """{"livesLeft":7,"name":"Oliver"}""".parseJson)
assert(encoded.convertTo[Cat] == oliver)
}
import spray.json._
import spray.json.derived.semiauto._
import spray.json.DefaultJsonProtocol._
case class Cat(name: String, livesLeft: Int)
object Test extends App {
implicit val format: JsonFormat[Cat] = deriveFormat[Cat]
val oliver: Cat = Cat("Oliver", 7)
val encoded = oliver.toJson
assert(encoded == """{"livesLeft":7,"name":"Oliver"}""".parseJson)
assert(encoded.convertTo[Cat] == oliver)
}
Union types are encoded by using a discriminator field, which by default is type
.
import spray.json._
import spray.json.derived.auto._
import spray.json.DefaultJsonProtocol._
sealed trait Pet
case class Cat(name: String, livesLeft: Int) extends Pet
case class Dog(name: String, bonesHidden: Int) extends Pet
object Test extends App {
val oliver: Pet = Cat("Oliver", 7)
val encodedOliver = oliver.toJson
assert(encodedOliver == """{"livesLeft":7,"name":"Oliver","type":"Cat"}""".parseJson)
assert(encodedOliver.convertTo[Pet] == oliver)
val albert: Pet = Dog("Albert", 3)
val encodedAlbert = albert.toJson
assert(encodedAlbert == """{"bonesHidden":3,"name":"Albert","type":"Dog"}""".parseJson)
assert(encodedAlbert.convertTo[Pet] == albert)
}
The discriminator can be customised by annotating the union type with the @Discriminator
annotation:
import spray.json._
import spray.json.derived.auto._
import spray.json.DefaultJsonProtocol._
import spray.json.derived.Discriminator
@Discriminator("petType")
sealed trait Pet
case class Cat(name: String, livesLeft: Int) extends Pet
case class Dog(name: String, bonesHidden: Int) extends Pet
object Test extends App {
val oliver: Pet = Cat("Oliver", 7)
val encodedOliver = oliver.toJson
assert(encodedOliver == """{"livesLeft":7,"name":"Oliver","petType":"Cat"}""".parseJson)
assert(encodedOliver.convertTo[Pet] == oliver)
}
import spray.json._
import spray.json.derived.auto._
import spray.json.DefaultJsonProtocol._
sealed trait Tree
case class Leaf(s: String) extends Tree
case class Node(lhs: Tree, rhs: Tree) extends Tree
object Test extends App {
val obj: Tree = Node(Node(Leaf("1"), Leaf("2")), Leaf("3"))
val encoded = obj.toJson
val expectedJson =
"""{
| "lhs": {
| "lhs": {
| "s": "1",
| "type": "Leaf"
| },
| "rhs": {
| "s": "2",
| "type": "Leaf"
| },
| "type": "Node"
| },
| "rhs": {
| "s": "3",
| "type": "Leaf"
| },
| "type": "Node"
|}
|""".stripMargin
assert(encoded == expectedJson.parseJson)
assert(encoded.convertTo[Tree] == obj)
}
import spray.json._
import spray.json.derived.auto._
import spray.json.DefaultJsonProtocol._
case class Container[T](value: T)
object Test extends App {
val cString: Container[String] = Container("abc")
val cStringEncoded = cString.toJson
assert(cStringEncoded == """{"value":"abc"}""".parseJson)
assert(cStringEncoded.convertTo[Container[String]] == cString)
val cInt: Container[Int] = Container(123)
val cIntEncoded = cInt.toJson
assert(cIntEncoded == """{"value":123}""".parseJson)
assert(cIntEncoded.convertTo[Container[Int]] == cInt)
}
By default, undefined optional members are not rendered:
import spray.json._
import spray.json.derived.auto._
import spray.json.DefaultJsonProtocol._
case class Dog(toy: Option[String])
object Test extends App {
val aDog = Dog(toy = None)
val aDogEncoded = aDog.toJson
assert(aDogEncoded.compactPrint == "{}")
}
It's possible to render undefined optional members as null values by specifying an alternative configuration.
Just specify the alternative configuration as implicit value and enable the renderNullOptions
flag:
import spray.json._
import spray.json.derived.Configuration
import spray.json.derived.auto._
import spray.json.DefaultJsonProtocol._
case class Dog(toy: Option[String])
object Test extends App {
implicit val conf: Configuration = Configuration(renderNullOptions = true)
val aDog = Dog(toy = None)
val aDogEncoded = aDog.toJson
assert(aDogEncoded.compactPrint == """{"toy":null}""")
}
spray-json-derived-codecs is licensed under APL 2.0.