forked from scala/docs.scala-lang
-
Notifications
You must be signed in to change notification settings - Fork 4
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
Closed
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
b654979
Init upicle tutorials
szymon-rd fac5ab2
Further link fixes
szymon-rd 663ff0e
Reading jsons lesson
szymon-rd 91d59bf
Fixes to the tutorial
szymon-rd a976c98
More work on reading JSONs tutorial
szymon-rd 9064793
3 tutorials in total for upickle
szymon-rd a17198b
More tutorials on upickle
szymon-rd b0f48d7
Update links to prev/next
szymon-rd b33910e
Fixes in the upickle tutorials
szymon-rd 08bdc26
Merge branch 'toolkit-sttp' into toolkit-upickle
szymon-rd 47e5593
Fix links
szymon-rd f17c139
Merge branch 'toolkit-upickle' of https://github.com/scalacenter/docs…
szymon-rd c3caf4d
Tutorials on STTP x UPickle
szymon-rd da697be
Use real examples from the Github REST API
adpi2 00b26a0
Add snippets for different versions of Scala
szymon-rd File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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,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 %} |
This file contains hidden or 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
This file contains hidden or 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,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]) | ||
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")`. |
This file contains hidden or 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
This file contains hidden or 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,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. |
This file contains hidden or 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,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" %} | ||
|
This file contains hidden or 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,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"} | ||
``` |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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