By Alvin Alexander. Last updated: June 29, 2024
As a brief note today, I was starting to look at a free JSON REST web service that to get stock information, and their JSON for a single stock looks like this:
{
"Global Quote": {
"01. symbol": "IBM",
"02. open": "182.4300",
"03. high": "182.8000",
"04. low": "180.5700",
"05. price": "181.5800",
"06. volume": "3037990",
"07. latest trading day": "2024-04-19",
"08. previous close": "181.4700",
"09. change": "0.1100",
"10. change percent": "0.0606%"
}
}
I wanted to figure out how to decode that JSON into a Scala class/object using ZIO JSON, and long story short, someone on Discord helped me find a solution. Their solution wasn’t 100% correct, but it was close enough that I could figure out the rest. Another useful URL is the ZIO JSON Configuration page.
Here’s the ZIO JSON solution:
package test4s
////> using scala "3"
////> using lib "dev.zio::zio::2.0.22"
////> using lib "dev.zio::zio-http::3.0.0-RC4"
////> using lib "dev.zio::zio-json::0.6.2"
import zio.*
import zio.Console.*
import zio.http.{Client, Response, URL}
import zio.json.*
import java.io.IOException
sealed trait Data
/**
* This solution comes from:
* https://discord.com/channels/629491597070827530/733728086637412422/1231821749226832032
* and:
* https://zio.dev/zio-json/configuration
*/
@jsonHint("Global Quote")
case class GlobalQuote(
@jsonField("01. symbol") symbol: String,
@jsonField("02. open") open: String,
@jsonField("03. high") high: String,
@jsonField("04. low") low: String,
@jsonField("05. price") price: String,
@jsonField("06. volume") volume: String,
@jsonField("07. latest trading day") latestTradingDay: String,
@jsonField("08. previous close") previousClose: String,
@jsonField("09. change") change: String,
@jsonField("10. change percent") changePercent: String
) extends Data
object GlobalQuote {
implicit val decoder: JsonDecoder[Data] = DeriveJsonDecoder.gen[Data]
}
import GlobalQuote.*
object HttpsJson104Stocks extends ZIOAppDefault:
val apiKey = "MYKEYHERE"
val symbol = "AAPL"
val function = "GLOBAL_QUOTE"
val maybeUrl: Either[Exception, URL] =
URL.decode(s"https://www.alphavantage.co/query?function=${function}&symbol=${symbol}&apikey=${apiKey}")
// TODO: handle this better
val url: URL = maybeUrl.toOption.get
// TODO: fix the `Throwable | String` part.
val program: ZIO[Client & Scope, Throwable | String, Data] = for
client: Client <- ZIO.service[Client]
res: Response <- client.url(url).get("/")
jsonBody: String <- res.body.asString
data: Data <- ZIO.fromEither(jsonBody.fromJson[Data])
yield
data
val run =
program.provide(
Client.default,
Scope.default
).foldZIO(
failure => Console.printLineError(s"failure = $failure"),
success => Console.printLine(s"success = $success")
)
this post is sponsored by my books: | |||
#1 New Release |
FP Best Seller |
Learn Scala 3 |
Learn FP Fast |
The important parts about that solution are:
- The
@jsonHint("Global Quote")
annotation - The
@jsonField("01. symbol")
annotations, that map the JSON strings to mycase class
fields - Having the
GlobalQuote
extend thesealed trait Data
, which helps get past the initial `"Global Quote" string inside the JSON
I also started using foldZIO
at the end of the application so I can see both the success and failure information from the app.