Skip to content

Tutorials on STTP x UPickle #20

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 15 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions _includes/_markdown/install-upickle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
UPickle is the library for working with JSONs in Scala Toolkit.

{% altDetails install-info-box 'Installing upickle' %}

## Installing upickle

{% tabs upickle-install-methods %}
{% tab 'Scala CLI' %}
In Scala CLI, we can install the entire toolkit in a single line:
```scala
//> using toolkit
```

Alternatively, we can install a specific version of upickle:
```scala
//> using lib "com.lihaoyi::upickle:1.6.0"
```
{% endtab %}
{% tab 'sbt' %}
In our build.sbt file, we add the dependency to the upickle library:
```scala
lazy val example = project.in(file("example"))
.settings(
scalaVersion := "3.2.1",
libraryDependencies += "com.lihaoyi" %% "upickle" % "1.6.0"
)
```
{% endtab %}
{% tab 'Mill' %}
In your build.sc file, we add the artifact to `ivyDeps`:
```scala
object example extends ScalaModule {
def scalaVersion = "3.2.1"
def ivyDeps =
Agg(
ivy"com.lihaoyi::upickle:1.6.0"
)
}
```
{% endtab %}
{% endtabs %}
{% endaltDetails %}
3 changes: 2 additions & 1 deletion _overviews/toolkit/sttp-make-request.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,6 @@ The `basicRequest` is already set up with some [basic headers](https://sttp.soft
Other methods are available for different HTTP methods.

## The `uri` operator before website address

The `uri` operator in the `uri"https://httpbin.org/get"` keeps you safe from making some of the mistakes in the provided addresses.
It can also interpolate values from variables as described in [How to construct URLs from variables](/overviews/toolkit/sttp-variable-urls.html).
It can also interpolate values from variables as described in [How to construct URLs from variables](/overviews/toolkit/sttp-variable-urls.html).
98 changes: 98 additions & 0 deletions _overviews/toolkit/sttp-receive-json-body.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
---
title: How to parse JSON from an HTTP response body?
type: section
description: How to read JSON from an HTTP response body?
num: 19
previous-page: sttp-send-file
next-page: sttp-send-json-body
---

{% include markdown.html path="_markdown/install-sttp.md" %}

To read the JSON body of an HTTP response, you can use the integration between uPickle and sttp.
This tutorial teaches how to do it in two steps:
1. Defining your own data type and its uPickle `ReadWriter`
2. Configuring the response of an HTTP request and sending it using sttp

As an example, we are going to use the [Github REST API](https://docs.github.com/en/rest/users) to get information about the octocat user.

## Defining your own data type and its ReadWriter

In Scala, you can use a `case class` to define your own data type.
For example, you can define a user data type like this:
```scala
case class User(login: String, name: String, location: String)
```

To parse a JSON string to a `User`, you need to provide an instance of `upickle.ReadWriter`.
Luckily, uPickle is able to fully automate that and all you have to write is:
{% tabs 'given' class=tabs-scala-version %}
{% tab 'Scala 2' %}
```scala
implicit val userRw: ReadWriter[UserInfo] = macroRW
```
Having a `implicit` value of type `ReadWriter[UserInfo]` in the current scope informs uPickle and sttp how to write a JSON string from a `UserInfo`.
The second part of this definition, `macroRW`, automates the instantiation of `ReadWriter` so that you don't have to do it by hand.
{% endtab %}
{% tab 'Scala 3' %}
```scala
case class UserInfo(name: String, location: String, bio: String) derives ReadWriter
```
`derives` keyword will automatically provide the `ReadWriter[PetOwner]` in current scope.
{% endtab %}
{% endtabs %}

The `given` keyword may appear strange at first but it is very powerful.
Having a `given` value of type `ReadWriter[User]` in the current scope informs uPickle and sttp how to parse a JSON string to a `User`.
You can learn more about `given` instances in the [Scala 3 book](https://docs.scala-lang.org/scala3/book/ca-given-using-clauses.html).
The second part of this definition, `macroRW`, automates the instanciation of `ReadWriter` so that we don't have to do it by hand.

## Parsing JSON from the response of an HTTP request
Once you have a `given ReadWriter`, it is possible to parse the JSON response of an HTTP request to your data type.

In sttp the description of how to handle the response is part of the request itself.
Thus, to parse a response from JSON to `User`, you can call `response(asJson[User])` on the request.

Here is the complete program that can fetches some information about the octocat user from Github.

{% tabs 'full' class=tabs-scala-version %}
{% tab 'Scala 2' %}
```scala
import sttp.client3._
import sttp.client3.upicklejson._
import upickle.default._

case class User(login: String, name: String, location: String)
implicit val userRw: ReadWriter[UserInfo] = macroRW

val client = SimpleHttpClient() // Create the instance of SimpleHttpClient
val request = basicRequest
.get(uri"https://api.github.com/users/octocat")
.response(asJson[User])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not available from the toolkit without another dependency:

//> using dep "com.softwaremill.sttp.client3::upickle:3.8.13"

I opened scala/toolkit#4 to address this

val response = client.send(request) // Send the request and get the response as a User
println(response.body)
// Prints "Right(User(octocat,The Octocat,San Francisco))"
```
{% endtab %}
{% tab 'Scala 3' %}
```scala
import sttp.client3._
import sttp.client3.upicklejson._
import upickle.default._

case class User(login: String, name: String, location: String) derives ReadWriter

val client = SimpleHttpClient() // Create the instance of SimpleHttpClient
val request = basicRequest
.get(uri"https://api.github.com/users/octocat")
.response(asJson[User])
val response = client.send(request) // Send the request and get the response as a User
println(response.body)
// Prints "Right(User(octocat,The Octocat,San Francisco))"
```
{% endtab %}
{% endtabs %}



When running the program, the `response.body` contains `User("octocat", "The Octocat", "San Francisco")`.
2 changes: 1 addition & 1 deletion _overviews/toolkit/sttp-send-file.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ type: section
description: How to upload a file over HTTP request with Scala Toolkit.
num: 18
previous-page: sttp-request-body-custom
next-page:
next-page: sttp-receive-json-body
---

{% include markdown.html path="_markdown/install-sttp.md" %}
Expand Down
112 changes: 112 additions & 0 deletions _overviews/toolkit/sttp-send-json-body.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
---
title: How to send JSON as body of an HTTP request?
type: section
description: How to construct URLs from variables with Scala Toolkit.
num: 20
previous-page: sttp-receive-json-body
next-page: upickle-intro
---

{% include markdown.html path="_markdown/install-sttp.md" %}

To send an HTTP request with a JSON body, you can use the integration between uPickle and sttp.
This tutorial teaches how to do it in two steps:
1. Defining your own data type and its uPickle `ReadWriter`
2. Configuring the body of an HTTP request and sending it using sttp

As an example, we are going to write a program that can update the bio on your personal Github account using the [Github REST API](https://docs.github.com/en/rest/users/users?apiVersion=2022-11-28).
Beware that you need a secret [Github authentication token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) to run the program.
Do not share this token with anyone and do not paste it online.

### Defining your own data type to
In Scala, you can use a `case class` to define your own data type.
For example, you can describe the information of a Github user like this:
```scala
case class UserInfo(name: String, location: String, bio: String)
```

To create a JSON string from a `UserInfo`, you need to provide an instance of `ReadWriter`
Luckily, `upickle` is able to fully automate that and all you have to write is:
{% tabs 'given' class=tabs-scala-version %}
{% tab 'Scala 2' %}
```scala
implicit val userRw: ReadWriter[UserInfo] = macroRW
```
Having a `implicit` value of type `ReadWriter[UserInfo]` in the current scope informs uPickle and sttp how to write a JSON string from a `UserInfo`.
The second part of this definition, `macroRW`, automates the instantiation of `ReadWriter` so that you don't have to do it by hand.
{% endtab %}
{% tab 'Scala 3' %}
```scala
case class UserInfo(name: String, location: String, bio: String) derives ReadWriter
`derives` keyword will automatically provide the `ReadWriter[PetOwner]` in current scope.
```
{% endtab %}
{% endtabs %}

## Sending an HTTP request with a JSON body of your data type
Once you have a `given ReadWriter`, it is possible to write an instance of your data type as JSON into the body of your HTTP request.

To do so you can call the `body` method and pass it an instance of `UserInfo`.

Here is the complete program that can update the bio on your personnal Github account.

{% tabs 'full' class=tabs-scala-version %}
{% tab 'Scala 2' %}
```scala
import sttp.client3._
import sttp.client3.upicklejson._
import upickle.default._

case class UserInfo(name: String, location: String, bio: String)
implicit val userRw: ReadWriter[UserInfo] = macroRW

// TODO: replace ??? with your own bio and token
val newBio: String = ???
val token: String = ???

// The information to update. Name and location will not be updated.
val userInfo = UserInfo(name = null, location = null, bio = newBio)

val client = SimpleHttpClient() // Create the instance of SimpleHttpClient
val request = basicRequest
.post(uri"https://api.github.com/user")
.auth.bearer(token)
.body(userInfo)
val response = client.send(request) // Send the request and get the response
println(response.body) // Print response body
```
{% endtab %}
{% tab 'Scala 3' %}
```scala
import sttp.client3._
import sttp.client3.upicklejson._
import upickle.default._

case class UserInfo(name: String, location: String, bio: String) derives ReadWriter

// TODO: replace ??? with your own bio and token
val newBio: String = ???
val token: String = ???

// The information to update. Name and location will not be updated.
val userInfo = UserInfo(name = null, location = null, bio = newBio)

val client = SimpleHttpClient() // Create the instance of SimpleHttpClient
val request = basicRequest
.post(uri"https://api.github.com/user")
.auth.bearer(token)
.body(userInfo)
val response = client.send(request) // Send the request and get the response
println(response.body) // Print response body
```
{% endtab %}
{% endtabs %}

To try this program locally:
- In `val newBio: String = ???`, replace `???` with your bio in a string. For instance: `"I am a Scala programmer"`.
Or, if you prefer, you can update your location.
- Generate a [Github authentication token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) with the `user` scope.
- In `val token: String = ???` replace `???` with your token.
Do not share this token with anyone and do not paste it online.

After running the program, you should see your new bio on your Github profile.
11 changes: 11 additions & 0 deletions _overviews/toolkit/upickle-intro.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
title: Working with JSONs
type: chapter
description: How to work with jsons with Scala Toolkit.
num: 21
previous-page: sttp-send-json-body
next-page: upickle-read-json
---

{% include markdown.html path="_markdown/install-upickle.md" %}

67 changes: 67 additions & 0 deletions _overviews/toolkit/upickle-modify-jsons.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
---
title: How to modify JSONs?
type: section
description: How to modify JSONs with Scala Toolkit.
num: 25
previous-page: upickle-write-json
next-page:
---

{% include markdown.html path="_markdown/install-upickle.md" %}

## Modifying jsons
If you want to set, modify or remove fields of a json, you can do it with Scala Toolkit.
First, you have to load the json. You can either do it from a json loaded to a `String`,
or operate on json generated from Scala values.

## Loading a json text and modyfing it
To read the `json` from text, you can use the `ujson.read` function.
This function returns an object representing the json that you can operate on.
Adding new fields and updating their values is done as a simple assignment:
```scala
json("name") = "Peter"
```
The code above would set the `name` field of the object in `json` to `Peter`.
If the field is present, then it will be updated. If not, a new one is created.
Below, you can see an example of all these operations put together.
The `ujson.write` function allowed us to convert back the json object represention to a `String`.
```scala
val json = ujson.read("""{"name":"John","pets":["Toolkitty","Scaniel"]}""")
json("name") = "Peter"
json("surname") = "Scalinsky"
val jsonString: String = ujson.write(json)
println(jsonString)
//prints "{"name":"Peter","pets":["Toolkitty","Scaniel"],"surname":"Scalinisky"}"
```

## Removing fields
To remove fields from json you need to declare whether you are removing them from an `Object` or an `Array`.
- To remove a field from an object, you can use the `.obj.remove("name")` function on json object
- To remove a field from an array, you can use the `.obj.remove(index)` function on json object
Following the previous example, below you can see how to remove some fields from it.
```scala
val json = ujson.read("""{"name":"John","pets":["Toolkitty","Scaniel"]}""")
json.obj.remove("name") // remove "name" field
json("pets").arr.remove(1) // remove pet with index 1 ("Scaniel")
val jsonString: String = ujson.write(json)
println(jsonString) // prints {"pets":["Toolkitty"]}
```
Above, we first removed the field `name` from the top-level object in the json.
Then, we selected the array `pets` and removed the value with index `1` from it.

## Operating on jsons generated from Scala values
If you want to operate on jsons generate from Scala values (like in the [How to write JSONs]({% link _overviews/toolkit/upickle-write-json.md %}) tutorial), then it is possible as well.
Usually, `upickle.write` operation outputs a `String`. But, if you replace it with `upickle.writeJs`, then it returns a json represention from `ujson`.
Then you can operate on it in the same way as you did in the previous code snippets in this tutorial. For example:
```scala
import upickle.default._

case class PetOwner(name: String, pets: List[String])
given ReadWriter[PetOwner] = macroRW
val petOwner = PetOwner("Peter", List("Toolkitty", "Scaniel"))
val json = writeJs[PetOwner](petOwner)
json("surname") = "Scalinsky"
val jsonString = ujson.write(json)
println(jsonString)
//Prints {"name":"Peter","pets":["Toolkitty","Scaniel"],"surname":"Scalinsky"}
```
Loading