-
-
Notifications
You must be signed in to change notification settings - Fork 99
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
3d8ca0f
commit 160b312
Showing
2 changed files
with
334 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,204 @@ | ||
Goal: show the power of auto-derivation | ||
# Getting started | ||
Let's start our adventure with jsoniter-scala from parsing and serialization of some complex data structure of nested | ||
collections and case classes. | ||
|
||
Example: | ||
1. | ||
Nested case classes with a couple of collections (`Seq` and `Map`) | ||
## Prerequisites | ||
In all tutorials we will use [scala-cli](https://scala-cli.virtuslab.org) scripts, so you'll need to install it upfront. | ||
|
||
Challenge: find concrete (non-abstract) collection from standard Scala library that is not supported yet by auto-derivation | ||
You can use any text editor to copy and paste provided code snippets. Please, also, check Scala CLI Cookbooks | ||
documentation if you use [VSCode](https://scala-cli.virtuslab.org/docs/cookbooks/ide/vscode), | ||
[Intellij IDEA](https://scala-cli.virtuslab.org/docs/cookbooks/ide/intellij), or | ||
[emacs](https://scala-cli.virtuslab.org/docs/cookbooks/ide/emacs) editors. | ||
|
||
As an example, if you use [IntelliJ IDEA](https://www.jetbrains.com/idea/) with the latest version of Scala plugin then | ||
you can try its [improved support for Scala CLI projects](https://blog.jetbrains.com/scala/2024/11/13/intellij-scala-plugin-2024-3-is-out/#scala-cli). | ||
|
||
Latest versions of jsoniter-scala libraries require JDK 11 or above. You can use any of its distributions. | ||
|
||
In all tutorials we will use Scala 3 and start from a file with the following dependencies and imports: | ||
|
||
```scala | ||
//> using scala 3 | ||
//> using dep "com.github.plokhotnyuk.jsoniter-scala::jsoniter-scala-core::2.31.3" | ||
//> using compileOnly.dep "com.github.plokhotnyuk.jsoniter-scala::jsoniter-scala-macros::2.31.3" | ||
|
||
import com.github.plokhotnyuk.jsoniter_scala.core._ | ||
import com.github.plokhotnyuk.jsoniter_scala.macros._ | ||
``` | ||
|
||
Please just copy and paste code snippets from the subsequent steps and run by pressing some `Run` button/keystroke in | ||
your IDE or run it using `scala-cli <file_name.scala>` command from the terminal. | ||
|
||
Make sure to preserve indentation when copying and pasting. | ||
|
||
## Definition of a complex data structure | ||
|
||
Let's imagine that we need to generate a JSON representation of some report for some US shop. | ||
|
||
Below is a definition of its data structures and instantiation of some sample data: | ||
|
||
```scala | ||
enum Category extends Enum[Category]: | ||
case Electronics, Fashion, HomeGoods | ||
|
||
case class Product(id: Long, name: String, category: Category, price: BigDecimal, description: String) | ||
|
||
enum OrderStatus extends Enum[OrderStatus]: | ||
case Pending, Shipped, Delivered, Cancelled | ||
|
||
case class OrderItem(product: Product, quantity: Int) | ||
|
||
case class Order(id: Long, customer: Customer, items: List[OrderItem], status: OrderStatus) | ||
|
||
case class Customer(id: Long, name: String, email: String, address: Address) | ||
|
||
case class Address(street: String, city: String, state: String, zip: String) | ||
|
||
enum PaymentType extends Enum[PaymentType]: | ||
case CreditCard, PayPal | ||
|
||
case class PaymentMethod(`type`: PaymentType, details: Map[String, String]/*e.g. card number, expiration date*/) | ||
|
||
case class Payment(method: PaymentMethod, amount: BigDecimal, timestamp: java.time.Instant) | ||
|
||
case class OrderPayment(order: Order, payment: Payment) | ||
|
||
val product1 = Product( | ||
id = 1L, | ||
name = "Apple iPhone 16", | ||
category = Category.Electronics, | ||
price = BigDecimal(999.99), | ||
description = "A high-end smartphone with advanced camera and AI capabilities" | ||
) | ||
val product2 = Product( | ||
id = 2L, | ||
name = "Nike Air Max 270", | ||
category = Category.Fashion, | ||
price = BigDecimal(129.99), | ||
description = "A stylish and comfortable sneaker with a full-length air unit" | ||
) | ||
val product3 = Product( | ||
id = 3L, | ||
name = "KitchenAid Stand Mixer", | ||
category = Category.HomeGoods, | ||
price = BigDecimal(299.99), | ||
description = "A versatile and powerful stand mixer for baking and cooking" | ||
) | ||
val customer1 = Customer( | ||
id = 1L, | ||
name = "John Doe", | ||
email = "john.doe@example.com", | ||
address = Address( | ||
street = "123 Main St", | ||
city = "Anytown", | ||
state = "CA", | ||
zip = "12345" | ||
) | ||
) | ||
val customer2 = Customer( | ||
id = 2L, | ||
name = "Jane Smith", | ||
email = "jane.smith@example.com", | ||
address = Address( | ||
street = "456 Elm St", | ||
city = "Othertown", | ||
state = "NY", | ||
zip = "67890" | ||
) | ||
) | ||
val order1 = Order( | ||
id = 1L, | ||
customer = customer1, | ||
items = List( | ||
OrderItem(product1, 1), | ||
OrderItem(product2, 2) | ||
), | ||
status = OrderStatus.Pending | ||
) | ||
val order2 = Order( | ||
id = 2L, | ||
customer = customer2, | ||
items = List( | ||
OrderItem(product3, 1) | ||
), | ||
status = OrderStatus.Shipped | ||
) | ||
val paymentMethod1 = PaymentMethod( | ||
`type` = PaymentType.CreditCard, | ||
details = Map( | ||
"card_number" -> "1234-5678-9012-3456", | ||
"expiration_date" -> "12/2026" | ||
) | ||
) | ||
val paymentMethod2 = PaymentMethod( | ||
`type` = PaymentType.PayPal, | ||
details = Map( | ||
"paypal_id" -> "jane.smith@example.com" | ||
) | ||
) | ||
val payment1 = Payment( | ||
method = paymentMethod1, | ||
amount = BigDecimal(1259.97), | ||
timestamp = java.time.Instant.parse("2025-01-03T12:30:45Z") | ||
) | ||
val payment2 = Payment( | ||
method = paymentMethod2, | ||
amount = BigDecimal(299.99), | ||
timestamp = java.time.Instant.parse("2025-01-15T19:10:55Z") | ||
) | ||
val orderPayment1 = OrderPayment( | ||
order = order1, | ||
payment = payment1 | ||
) | ||
val orderPayment2 = OrderPayment( | ||
order = order2, | ||
payment = payment2 | ||
) | ||
val report = List(orderPayment1, orderPayment2) | ||
``` | ||
|
||
## Defining the codec | ||
|
||
To derive a codec we will use the following line: | ||
```scala | ||
given JsonValueCodec[List[OrderPayment]] = JsonCodecMaker.make | ||
``` | ||
|
||
## Serialization | ||
|
||
Now we are ready to serialize list the report. Just need to define some entry point method and call `writeToString`. | ||
We will also print resulting JSON to the system output to see it as a script running result on the screen: | ||
|
||
```scala | ||
@main def gettingStarted: Unit = | ||
val json = writeToString(report) | ||
println(json) | ||
``` | ||
|
||
From this moment you can run the script and see a long JSON string on the screen. | ||
|
||
## Parsing | ||
|
||
Having the JSON string in memory you can parse it using following lines that should be pasted to the end of the | ||
`gettingStarted` method: | ||
```scala | ||
val parsedReport = readFromString(json) | ||
println(parsedReport) | ||
``` | ||
|
||
Now you can rerun the script and get additionally printed `toString` representation of a report parsed form JSON string. | ||
|
||
If something gone wrong you can pick the final version of [a script for this tutorial](1-getting-started.scala) and | ||
then run it in the terminal. | ||
|
||
## Challenge | ||
Find any concrete (non-abstract) collection from standard Scala library that is not supported by auto-derivation | ||
of `JsonCodecMaker.make` macros yet. | ||
|
||
## Recap | ||
In this tutorial we learned basics for parsing and serialization of complex nested data structures using `scala-cli`. | ||
Having definition of data structures and their instances in memory we need to: | ||
1. Add dependency usage and package imports of `core` and `macros` modules of jsoniter-scala | ||
2. Define `given` type-class instance of `JsonValueCodec` for top-level data structure | ||
3. Call `writeToString` to serialize an instance of complex data structure to JSON representation | ||
4. Call `readFromString` to parse a complex data structure from JSON representation |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
//> using scala 3 | ||
//> using dep "com.github.plokhotnyuk.jsoniter-scala::jsoniter-scala-core::2.31.3" | ||
//> using compileOnly.dep "com.github.plokhotnyuk.jsoniter-scala::jsoniter-scala-macros::2.31.3" | ||
|
||
import com.github.plokhotnyuk.jsoniter_scala.core._ | ||
import com.github.plokhotnyuk.jsoniter_scala.macros._ | ||
|
||
enum Category extends Enum[Category]: | ||
case Electronics, Fashion, HomeGoods | ||
|
||
case class Product(id: Long, name: String, category: Category, price: BigDecimal, description: String) | ||
|
||
enum OrderStatus extends Enum[OrderStatus]: | ||
case Pending, Shipped, Delivered, Cancelled | ||
|
||
case class OrderItem(product: Product, quantity: Int) | ||
|
||
case class Order(id: Long, customer: Customer, items: List[OrderItem], status: OrderStatus) | ||
|
||
case class Customer(id: Long, name: String, email: String, address: Address) | ||
|
||
case class Address(street: String, city: String, state: String, zip: String) | ||
|
||
enum PaymentType extends Enum[PaymentType]: | ||
case CreditCard, PayPal | ||
|
||
case class PaymentMethod(`type`: PaymentType, details: Map[String, String]/*e.g. card number, expiration date*/) | ||
|
||
case class Payment(method: PaymentMethod, amount: BigDecimal, timestamp: java.time.Instant) | ||
|
||
case class OrderPayment(order: Order, payment: Payment) | ||
|
||
val product1 = Product( | ||
id = 1L, | ||
name = "Apple iPhone 16", | ||
category = Category.Electronics, | ||
price = BigDecimal(999.99), | ||
description = "A high-end smartphone with advanced camera and AI capabilities" | ||
) | ||
val product2 = Product( | ||
id = 2L, | ||
name = "Nike Air Max 270", | ||
category = Category.Fashion, | ||
price = BigDecimal(129.99), | ||
description = "A stylish and comfortable sneaker with a full-length air unit" | ||
) | ||
val product3 = Product( | ||
id = 3L, | ||
name = "KitchenAid Stand Mixer", | ||
category = Category.HomeGoods, | ||
price = BigDecimal(299.99), | ||
description = "A versatile and powerful stand mixer for baking and cooking" | ||
) | ||
val customer1 = Customer( | ||
id = 1L, | ||
name = "John Doe", | ||
email = "john.doe@example.com", | ||
address = Address( | ||
street = "123 Main St", | ||
city = "Anytown", | ||
state = "CA", | ||
zip = "12345" | ||
) | ||
) | ||
val customer2 = Customer( | ||
id = 2L, | ||
name = "Jane Smith", | ||
email = "jane.smith@example.com", | ||
address = Address( | ||
street = "456 Elm St", | ||
city = "Othertown", | ||
state = "NY", | ||
zip = "67890" | ||
) | ||
) | ||
val order1 = Order( | ||
id = 1L, | ||
customer = customer1, | ||
items = List( | ||
OrderItem(product1, 1), | ||
OrderItem(product2, 2) | ||
), | ||
status = OrderStatus.Pending | ||
) | ||
val order2 = Order( | ||
id = 2L, | ||
customer = customer2, | ||
items = List( | ||
OrderItem(product3, 1) | ||
), | ||
status = OrderStatus.Shipped | ||
) | ||
val paymentMethod1 = PaymentMethod( | ||
`type` = PaymentType.CreditCard, | ||
details = Map( | ||
"card_number" -> "1234-5678-9012-3456", | ||
"expiration_date" -> "12/2026" | ||
) | ||
) | ||
val paymentMethod2 = PaymentMethod( | ||
`type` = PaymentType.PayPal, | ||
details = Map( | ||
"paypal_id" -> "jane.smith@example.com" | ||
) | ||
) | ||
val payment1 = Payment( | ||
method = paymentMethod1, | ||
amount = BigDecimal(1259.97), | ||
timestamp = java.time.Instant.parse("2025-01-03T12:30:45Z") | ||
) | ||
val payment2 = Payment( | ||
method = paymentMethod2, | ||
amount = BigDecimal(299.99), | ||
timestamp = java.time.Instant.parse("2025-01-15T19:10:55Z") | ||
) | ||
val orderPayment1 = OrderPayment( | ||
order = order1, | ||
payment = payment1 | ||
) | ||
val orderPayment2 = OrderPayment( | ||
order = order2, | ||
payment = payment2 | ||
) | ||
val report = List(orderPayment1, orderPayment2) | ||
|
||
given JsonValueCodec[List[OrderPayment]] = JsonCodecMaker.make | ||
|
||
@main def gettingStarted: Unit = | ||
val json = writeToString(report) | ||
println(json) | ||
val parsedReport = readFromString(json) | ||
println(parsedReport) |