diff --git a/README.md b/README.md index 171044685..a36948f88 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ The goal of this project is to create the best all-round JSON library for Scala: In order to use this library, we need to add the following line in our `build.sbt` file: ```scala -libraryDependencies += "dev.zio" %% "zio-json" % "0.6.2" +libraryDependencies += "dev.zio" %% "zio-json" % "0.7.3" ``` ## Example @@ -50,15 +50,21 @@ into a Scala `case class` case class Banana(curvature: Double) ``` -To do this, we create an *instance* of the `JsonDecoder` typeclass for `Banana` using the `zio-json` code generator. It is best practice to put it on the companion of `Banana`, like so +To do this, we derive an *instance* of the `JsonDecoder` typeclass for `Banana`. ```scala -object Banana { - implicit val decoder: JsonDecoder[Banana] = DeriveJsonDecoder.gen[Banana] -} +case class Banana(curvature: Double) derives JsonDecoder ``` -_Note: If you’re using Scala 3 and your case class is defining default parameters, `-Yretain-trees` needs to be added to `scalacOptions`._ +> [!NOTE] +> +> In scala 2, we need to use the `zio-json` code generator. It is best practice to put it on the companion of `Banana`, like so +> +> ```scala +> object Banana { +> implicit val decoder: JsonDecoder[Banana] = DeriveJsonDecoder.gen[Banana] +> } +> ``` Now we can parse JSON into our object @@ -67,13 +73,10 @@ scala> """{"curvature":0.5}""".fromJson[Banana] val res: Either[String, Banana] = Right(Banana(0.5)) ``` -Likewise, to produce JSON from our data we define a `JsonEncoder` +Likewise, to produce JSON from our data we derive a `JsonEncoder` ```scala -object Banana { - ... - implicit val encoder: JsonEncoder[Banana] = DeriveJsonEncoder.gen[Banana] -} +case class Banana(curvature: Double) derives JsonEncoder scala> Banana(0.5).toJson val res: String = {"curvature":0.5} @@ -85,6 +88,16 @@ val res: String = } ``` +> [!NOTE] +> +> In scala 2: +> ```scala +> object Banana { +> ... +> implicit val encoder: JsonEncoder[Banana] = DeriveJsonEncoder.gen[Banana] +> } +> ``` + And bad JSON will produce an error in `jq` syntax with an additional piece of contextual information (in parentheses) ``` @@ -95,20 +108,49 @@ val res: Either[String, Banana] = Left(.curvature(expected a number, got w)) Say we extend our data model to include more data types ```scala -sealed trait Fruit -case class Banana(curvature: Double) extends Fruit -case class Apple (poison: Boolean) extends Fruit +enum Fruit: + case Banana(curvature: Double) extends Fruit + case Apple(poison: Boolean) extends Fruit ``` -we can generate the encoder and decoder for the entire `sealed` family +we can generate the encoder and decoder for the entire `sealed` family using `JsonCodec` ```scala -object Fruit { - implicit val decoder: JsonDecoder[Fruit] = DeriveJsonDecoder.gen[Fruit] - implicit val encoder: JsonEncoder[Fruit] = DeriveJsonEncoder.gen[Fruit] -} +enum Fruit derives JsonCodec: + case Banana(curvature: Double) + case Apple(poison: Boolean) ``` +> [!NOTE] +> +> In scala 2: +> +> ```scala mdoc:compile-only +> import zio.json._ +> +> sealed trait Fruit extends Product with Serializable +> case class Banana(curvature: Double) extends Fruit +> case class Apple(poison: Boolean) extends Fruit +> +> object Fruit { +> implicit val decoder: JsonDecoder[Fruit] = +> DeriveJsonDecoder.gen[Fruit] +> +> implicit val encoder: JsonEncoder[Fruit] = +> DeriveJsonEncoder.gen[Fruit] +> } +> +> val json1 = """{ "Banana":{ "curvature":0.5 }}""" +> val json2 = """{ "Apple": { "poison": false }}""" +> val malformedJson = """{ "Banana":{ "curvature": true }}""" +> +> json1.fromJson[Fruit] +> json2.fromJson[Fruit] +> malformedJson.fromJson[Fruit] +> +> List(Apple(false), Banana(0.4)).toJsonPretty +> ``` + allowing us to load the fruit based on a single field type tag in the JSON ``` @@ -122,19 +164,13 @@ val res: Either[String, Fruit] = Right(Apple(false)) Almost all of the standard library data types are supported as fields on the case class, and it is easy to add support if one is missing. ```scala -import zio.json._ +import zio.json.* -sealed trait Fruit extends Product with Serializable -case class Banana(curvature: Double) extends Fruit -case class Apple(poison: Boolean) extends Fruit +enum Fruit extends Product, Serializable derives JsonCodec: + case Banana(curvature: Double) extends Fruit + case Apple(poison: Boolean) extends Fruit -object Fruit { - implicit val decoder: JsonDecoder[Fruit] = - DeriveJsonDecoder.gen[Fruit] - - implicit val encoder: JsonEncoder[Fruit] = - DeriveJsonEncoder.gen[Fruit] -} +export Fruit.* val json1 = """{ "Banana":{ "curvature":0.5 }}""" val json2 = """{ "Apple": { "poison": false }}""" diff --git a/docs/index.md b/docs/index.mdx similarity index 71% rename from docs/index.md rename to docs/index.mdx index 205c70050..9e465423a 100644 --- a/docs/index.md +++ b/docs/index.mdx @@ -4,6 +4,9 @@ title: "Getting Started with ZIO Json" sidebar_label: "Getting Started" --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + [ZIO Json](https://github.com/zio/zio-json) is a fast and secure JSON library with tight ZIO integration. @PROJECT_BADGES@ @@ -34,10 +37,23 @@ Let's try a simple example of encoding and decoding JSON using ZIO JSON. All the following code snippets assume that the following imports have been declared + + + ```scala import zio.json._ ``` + + + +```scala +import zio.json.* +``` + + + + Say we want to be able to read some JSON like ```json @@ -50,6 +66,9 @@ into a Scala `case class` case class Banana(curvature: Double) ``` + + + To do this, we create an *instance* of the `JsonDecoder` typeclass for `Banana` using the `zio-json` code generator. It is best practice to put it on the companion of `Banana`, like so ```scala @@ -58,7 +77,19 @@ object Banana { } ``` -_Note: If you’re using Scala 3 and your case class is defining default parameters, `-Yretain-trees` needs to be added to `scalacOptions`._ + + + +To do this, we derive an *instance* of the `JsonDecoder` typeclass for `Banana`. + +```scala +case class Banana(curvature: Double) derives JsonDecoder +``` + +Note: If your case class is defining default parameters, -Yretain-trees needs to be added to scalacOptions. + + + Now we can parse JSON into our object @@ -67,14 +98,29 @@ scala> """{"curvature":0.5}""".fromJson[Banana] val res: Either[String, Banana] = Right(Banana(0.5)) ``` -Likewise, to produce JSON from our data we define a `JsonEncoder` +Likewise, to produce JSON from our data we derive a `JsonEncoder` + + + ```scala object Banana { ... implicit val encoder: JsonEncoder[Banana] = DeriveJsonEncoder.gen[Banana] } +``` + + + + +```scala +case class Banana(curvature: Double) derives JsonEncoder +``` + + + +``` scala> Banana(0.5).toJson val res: String = {"curvature":0.5} @@ -94,14 +140,32 @@ val res: Either[String, Banana] = Left(.curvature(expected a number, got w)) Say we extend our data model to include more data types + + + ```scala sealed trait Fruit case class Banana(curvature: Double) extends Fruit case class Apple (poison: Boolean) extends Fruit ``` + + + +```scala +enum Fruit: + case Banana(curvature: Double) extends Fruit + case Apple (poison: Boolean) extends Fruit +``` + + + + we can generate the encoder and decoder for the entire `sealed` family + + + ```scala object Fruit { implicit val decoder: JsonDecoder[Fruit] = DeriveJsonDecoder.gen[Fruit] @@ -109,6 +173,18 @@ object Fruit { } ``` + + + +```scala +enum Fruit derives JsonCodec: + case Banana(curvature: Double) + case Apple (poison: Boolean) +``` + + + + allowing us to load the fruit based on a single field type tag in the JSON ``` @@ -121,6 +197,9 @@ val res: Either[String, Fruit] = Right(Apple(false)) Almost all of the standard library data types are supported as fields on the case class, and it is easy to add support if one is missing. + + + ```scala mdoc:compile-only import zio.json._ @@ -147,6 +226,32 @@ malformedJson.fromJson[Fruit] List(Apple(false), Banana(0.4)).toJsonPretty ``` + + + +```scala +import zio.json.* + +enum Fruit extends Product, Serializable derives JsonCodec: + case Banana(curvature: Double) extends Fruit + case Apple(poison: Boolean) extends Fruit + +export Fruit.* + +val json1 = """{ "Banana":{ "curvature":0.5 }}""" +val json2 = """{ "Apple": { "poison": false }}""" +val malformedJson = """{ "Banana":{ "curvature": true }}""" + +json1.fromJson[Fruit] +json2.fromJson[Fruit] +malformedJson.fromJson[Fruit] + +List(Apple(false), Banana(0.4)).toJsonPretty +``` + + + + # How Extreme **performance** is achieved by decoding JSON directly from the input source into business objects (inspired by [plokhotnyuk](https://github.com/plokhotnyuk/jsoniter-scala)). Although not a requirement, the latest advances in [Java Loom](https://wiki.openjdk.java.net/display/loom/Main) can be used to support arbitrarily large payloads with near-zero overhead.