Creating a simple command line project in Kotlin that fetches data from CoinbaseAPI

relatively new to Kotlin and wanted to get some practice in. I am trying to create a simple command-line project that consumes the CoinbaseAPI. I want to be able to input a Currency Code, i.e. USD, EUR and once a user inputs the currency, I want to query the Coinbase public API using that currency.

Here’s what I have so far

main.kt

@Serializable
@OptIn(kotlinx.serialization.ExperimentalSerializationApi::class)
data class ExchangeRates(val data: Map<String, Double>)

@Serializable
@OptIn(kotlinx.serialization.ExperimentalSerializationApi::class)
data class BinanceTicker(val symbol: String, val priceChangePercent: String)

@OptIn(kotlinx.serialization.ExperimentalSerializationApi::class)
fun main() {
    val scanner = Scanner(System.`in`)
    print("Enter an ISO-4217 currency code (e.g., USD, EUR): ")
    val currencyCode = scanner.next()

    val client = OkHttpClient()

    // Query Coinbase Public API
    val coinbaseApiUrl = "https://api.coinbase.com/v2/exchange-rates?currency=$currencyCode"
    val coinbaseRequest = Request.Builder().url(coinbaseApiUrl).build()
    val coinbaseResponse: Response = client.newCall(coinbaseRequest).execute()
    val coinbaseResponseBody = coinbaseResponse.body?.string()

    val exchangeRates = Json.decodeFromString<ExchangeRates>(coinbaseResponseBody.orEmpty())
    val cryptoCurrencies = filterCryptoCurrencies(exchangeRates.data.keys)

    // Query Binance 24-Hour Ticker API
    val binanceApiUrl = "https://api.binance.us/api/v3/ticker/24hr"
    val binanceRequest = Request.Builder().url(binanceApiUrl).build()
    val binanceResponse: Response = client.newCall(binanceRequest).execute()
    val binanceResponseBody = binanceResponse.body?.string()

    val binanceTickers = Json.decodeFromString<List<BinanceTicker>>(binanceResponseBody.orEmpty())
    val results = calculateAndOutputResults(cryptoCurrencies, binanceTickers, exchangeRates)

    results.forEach { (cryptoCode, value, percentChange) ->
        println("Crypto: $cryptoCode, Value: $value, Percent Change: $percentChange%")
    }
}

fun filterCryptoCurrencies(allCurrencies: Set<String>): List<String> {
    // Filtering out non-cryptocurrencies based on a provided list
    val cryptoCurrenciesList = listOf("BTC", "ETH", "ADA", "SOL") // Example list
    return allCurrencies.filter { it in cryptoCurrenciesList }
}

fun calculateAndOutputResults(
    cryptoCurrencies: List<String>,
    binanceTickers: List<BinanceTicker>,
    exchangeRates: ExchangeRates
): List<Triple<String, Double, String>> {
    return cryptoCurrencies.mapNotNull { cryptoCode ->
        binanceTickers.find { it.symbol.startsWith("${cryptoCode}BTC") }?.let { binanceTicker ->
            val percentChange = binanceTicker.priceChangePercent
            val value = exchangeRates.data[cryptoCode]

            if (value != null) {
                Triple(cryptoCode, value, percentChange)
            } else {
                null
            }
        }
    }
}

When I run this program, I am able to input a Currency Code but once I hit enter, I get this error

Exception in thread "main" kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 25: Failed to parse type 'double' for input 'USD'
JSON input: {"data":{"currency":"USD","rates":{"00":"14.69507714915.....
	at kotlinx.serialization.json.internal.JsonExceptionsKt.JsonDecodingException(JsonExceptions.kt:24)
	at kotlinx.serialization.json.internal.JsonExceptionsKt.JsonDecodingException(JsonExceptions.kt:32)
	at kotlinx.serialization.json.internal.JsonLexer.fail(JsonLexer.kt:493)
	at kotlinx.serialization.json.internal.JsonLexer.fail$default(JsonLexer.kt:492)
	at kotlinx.serialization.json.internal.StreamingJsonDecoder.decodeDouble(StreamingJsonDecoder.kt:299)
	at kotlinx.serialization.internal.DoubleSerializer.deserialize(Primitives.kt:128)
	at kotlinx.serialization.internal.DoubleSerializer.deserialize(Primitives.kt:124)
	at kotlinx.serialization.json.internal.PolymorphicKt.decodeSerializableValuePolymorphic(Polymorphic.kt:63)
	at kotlinx.serialization.json.internal.StreamingJsonDecoder.decodeSerializableValue(StreamingJsonDecoder.kt:32)
	at kotlinx.serialization.encoding.AbstractDecoder.decodeSerializableValue(AbstractDecoder.kt:43)
	at kotlinx.serialization.encoding.AbstractDecoder.decodeSerializableElement(AbstractDecoder.kt:70)
	at kotlinx.serialization.encoding.CompositeDecoder$DefaultImpls.decodeSerializableElement$default(Decoding.kt:535)
	at kotlinx.serialization.internal.MapLikeSerializer.readElement(CollectionSerializers.kt:111)
	at kotlinx.serialization.internal.MapLikeSerializer.readElement(CollectionSerializers.kt:84)
	at kotlinx.serialization.internal.AbstractCollectionSerializer.readElement$default(CollectionSerializers.kt:51)
	at kotlinx.serialization.internal.AbstractCollectionSerializer.merge(CollectionSerializers.kt:36)
	at kotlinx.serialization.internal.AbstractCollectionSerializer.deserialize(CollectionSerializers.kt:43)
	at kotlinx.serialization.json.internal.PolymorphicKt.decodeSerializableValuePolymorphic(Polymorphic.kt:63)
	at kotlinx.serialization.json.internal.StreamingJsonDecoder.decodeSerializableValue(StreamingJsonDecoder.kt:32)
	at kotlinx.serialization.encoding.AbstractDecoder.decodeSerializableValue(AbstractDecoder.kt:43)
	at kotlinx.serialization.encoding.AbstractDecoder.decodeSerializableElement(AbstractDecoder.kt:70)
	at ExchangeRates$$serializer.deserialize(Main.kt:9)
	at ExchangeRates$$serializer.deserialize(Main.kt:9)
	at kotlinx.serialization.json.internal.PolymorphicKt.decodeSerializableValuePolymorphic(Polymorphic.kt:63)
	at kotlinx.serialization.json.internal.StreamingJsonDecoder.decodeSerializableValue(StreamingJsonDecoder.kt:32)
	at kotlinx.serialization.json.Json.decodeFromString(Json.kt:100)
	at MainKt.main(Main.kt:73)
	at MainKt.main(Main.kt)

I’m not sure how to proceed, any help is much appreciated.

I don’t know Coinbase API, but your data objects doesn’t seem to match the data from the API. You specified data as Map<String, Double>, but in reality it is an object with fields: currency, rates, etc.