diff --git a/docs/tutorials/1-getting-started.md b/docs/tutorials/1-getting-started.md index e119069c6..5917e9bcb 100644 --- a/docs/tutorials/1-getting-started.md +++ b/docs/tutorials/1-getting-started.md @@ -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 \ No newline at end of file +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 ` 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 diff --git a/docs/tutorials/1-getting-started.scala b/docs/tutorials/1-getting-started.scala new file mode 100644 index 000000000..192f3c7a1 --- /dev/null +++ b/docs/tutorials/1-getting-started.scala @@ -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)