Added wrapper parameter to toJson/toPrice.
This commit is contained in:
parent
6aaef8e089
commit
fc861276ef
4 changed files with 151 additions and 52 deletions
59
README.md
59
README.md
|
@ -35,30 +35,69 @@ spotPrice(
|
||||||
```
|
```
|
||||||
|
|
||||||
Parameters | Description
|
Parameters | Description
|
||||||
:---------- |:-------------------------------------------------------------
|
:---------- |:------------------------------------------------------------
|
||||||
`base` | The cryptocurrency ticker symbol (`BTC`, `ETH`, `ETH2`, etc.)
|
`base` | The cryptocurrency ticker symbol (`BTC`, `ETH`, `LTC`, etc.)
|
||||||
`currency` | The fiat currency ISO 4217 code. (`USD`, `GBP`, `EUR`, etc.)
|
`currency` | The fiat currency ISO 4217 code. (`USD`, `GBP`, `EUR`, etc.)
|
||||||
`date` | The `LocalDate` for historical price data.
|
`date` | The `LocalDate` for historical price data.
|
||||||
|
|
||||||
A `CryptoPrice` is returned defined as follows:
|
A `CryptoPrice` object is returned defined as follows:
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
CryptoPrice(val base: String, val currency: String, val amount: BigDecimal)
|
CryptoPrice(val base: String, val currency: String, val amount: BigDecimal)
|
||||||
```
|
```
|
||||||
The parameter names match the [Coinbase API](https://developers.coinbase.com/api/v2#get-spot-price).
|
The parameter names match the [Coinbase API](https://developers.coinbase.com/api/v2#get-spot-price).
|
||||||
|
|
||||||
To display the amount as a fomatted currency use the `toCurrency` function:
|
#### Format
|
||||||
|
|
||||||
|
To display the amount as a formatted currency, use the `toCurrency` function:
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
val euro = CryptoPrice("BTC", "EUR", 12345.67.toBigDecimal())
|
val euro = CryptoPrice("BTC", "EUR", 23456.78.toBigDecimal())
|
||||||
println(euro.toCurrency()) // will print: €12,345.67
|
println(euro.toCurrency()) // €23,456.78
|
||||||
|
|
||||||
val krone = CryptoPrice("BTC", "DKK", 12345.67.toBigDecimal())
|
val krone = CryptoPrice("BTC", "DKK", 123456.78.toBigDecimal())
|
||||||
println(krone.toCurrency(Locale("da", "DK"))) // will print: 12.345,67 kr.
|
println(krone.toCurrency(Locale("da", "DK"))) // 123.456,78 kr.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
##### JSON
|
||||||
|
|
||||||
|
To convert a `CryptoPrice` object back to JSON, use the `toJson` function:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
val price = CryptoPrice("BTC", "USD", 34567.89.toBigDecimal())
|
||||||
|
println(price.toJson())
|
||||||
|
```
|
||||||
|
|
||||||
|
*output:*
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"data":{"base":"BTC","currency":"USD","amount":"34567.89"}}
|
||||||
|
```
|
||||||
|
|
||||||
|
The format matches the [Coinbase API](https://developers.coinbase.com/api/v2#get-spot-price). To specify a different (or no) object wrapper, use:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
println(price.toJson("bitcoin"))
|
||||||
|
println(price.toJson("")) // or price.toString()
|
||||||
|
```
|
||||||
|
|
||||||
|
*output:*
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"bitcoin":{"base":"BTC","currency":"USD","amount":"34567.89"}}
|
||||||
|
{"base":"BTC","currency":"USD","amount":"34567.89"}
|
||||||
|
```
|
||||||
|
|
||||||
|
Similarly, to create a `CryptoPrice` object from JSON, use the `toPrice` function:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
val btc = """{"data":{"base":"BTC","currency":"USD","amount":"34567.89"}}""".toPrice()
|
||||||
|
val eth = """{"ether":{"base":"ETH","currency":"USD","amount":"2345.67"}}""".toPrice("ether")
|
||||||
|
```
|
||||||
|
|
||||||
### Extending
|
### Extending
|
||||||
|
|
||||||
A generic `apiCall()` function is available to access other [API data endpoints](https://developers.coinbase.com/api/v2#data-endpoints). For example to retried the current [buy price](https://developers.coinbase.com/api/v2#get-buy-price) of a cryptocurrency:
|
A generic `apiCall()` function is available to access other [API data endpoints](https://developers.coinbase.com/api/v2#data-endpoints). For example to retrieve the current [buy price](https://developers.coinbase.com/api/v2#get-buy-price) of a cryptocurrency:
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
apiCall(paths = listOf("prices", "BTC-USD", "buy"), params = emptyMap())
|
apiCall(paths = listOf("prices", "BTC-USD", "buy"), params = emptyMap())
|
||||||
|
@ -66,7 +105,7 @@ apiCall(paths = listOf("prices", "BTC-USD", "buy"), params = emptyMap())
|
||||||
will return something like:
|
will return something like:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{"data":{"base":"BTC","currency":"USD","amount":"58977.17"}}
|
{"data":{"base":"BTC","currency":"USD","amount":"34554.32"}}
|
||||||
```
|
```
|
||||||
|
|
||||||
See the [examples](https://github.com/ethauvin/cryptoprice/blob/master/examples/) for more details.
|
See the [examples](https://github.com/ethauvin/cryptoprice/blob/master/examples/) for more details.
|
||||||
|
|
|
@ -3,6 +3,5 @@
|
||||||
<ManuallySuppressedIssues/>
|
<ManuallySuppressedIssues/>
|
||||||
<CurrentIssues>
|
<CurrentIssues>
|
||||||
<ID>ThrowsCount:CryptoPrice.kt$CryptoPrice.Companion$ @JvmStatic @JvmOverloads @Throws(CryptoException::class, IOException::class) fun apiCall(paths: List<String>, params: Map<String, String> = emptyMap()): String</ID>
|
<ID>ThrowsCount:CryptoPrice.kt$CryptoPrice.Companion$ @JvmStatic @JvmOverloads @Throws(CryptoException::class, IOException::class) fun apiCall(paths: List<String>, params: Map<String, String> = emptyMap()): String</ID>
|
||||||
<ID>ThrowsCount:CryptoPrice.kt$CryptoPrice.Companion$ @JvmStatic @Throws(CryptoException::class) fun String.toPrice(): CryptoPrice</ID>
|
|
||||||
</CurrentIssues>
|
</CurrentIssues>
|
||||||
</SmellBaseline>
|
</SmellBaseline>
|
||||||
|
|
|
@ -61,18 +61,17 @@ open class CryptoPrice(val base: String, val currency: String, val amount: BigDe
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts JSON data object to [CryptoPrice].
|
* Converts JSON data object to [CryptoPrice].
|
||||||
|
*
|
||||||
|
* @param wrapper Specifies the JSON object the price data is wrapped in.
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
|
@JvmOverloads
|
||||||
@Throws(CryptoException::class)
|
@Throws(CryptoException::class)
|
||||||
fun String.toPrice(): CryptoPrice {
|
fun String.toPrice(wrapper: String = "data"): CryptoPrice {
|
||||||
try {
|
try {
|
||||||
val json = JSONObject(this)
|
val json = if (wrapper.isNotBlank()) JSONObject(this).getJSONObject(wrapper) else JSONObject(this)
|
||||||
if (json.has("data")) {
|
with(json) {
|
||||||
with(json.getJSONObject("data")) {
|
return CryptoPrice(getString("base"), getString("currency"), getString("amount").toBigDecimal())
|
||||||
return CryptoPrice(getString("base"), getString("currency"), getString("amount").toBigDecimal())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw CryptoException(message = "Missing price data.")
|
|
||||||
}
|
}
|
||||||
} catch (e: NumberFormatException) {
|
} catch (e: NumberFormatException) {
|
||||||
throw CryptoException(message = "Could not convert amount to number.", cause = e)
|
throw CryptoException(message = "Could not convert amount to number.", cause = e)
|
||||||
|
@ -83,6 +82,9 @@ open class CryptoPrice(val base: String, val currency: String, val amount: BigDe
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Makes an API call.
|
* Makes an API call.
|
||||||
|
*
|
||||||
|
* @param paths The list of path segments for the API URL. For example: `["prices", "BTC-USD", "spot"]`.
|
||||||
|
* @param params The map of query parameters.
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
|
@ -147,35 +149,83 @@ open class CryptoPrice(val base: String, val currency: String, val amount: BigDe
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the [amount] as a currency formatted string, such as `$1,203.33`.
|
* Returns the [amount] as a currency formatted string.
|
||||||
|
*
|
||||||
|
* For example: `$1,203.33`.
|
||||||
|
*
|
||||||
|
* @param locale The desired locale.
|
||||||
|
* @param minFractionDigits The minimum number of digits allowed in the fraction portion of the currency.
|
||||||
*/
|
*/
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
@Throws(IllegalArgumentException::class)
|
@Throws(IllegalArgumentException::class)
|
||||||
fun toCurrency(locale: Locale = Locale.getDefault(Locale.Category.FORMAT), minFractionDigits: Int = 2): String {
|
fun toCurrency(locale: Locale = Locale.getDefault(Locale.Category.FORMAT), minFractionDigits: Int = 2): String {
|
||||||
return NumberFormat.getCurrencyInstance(locale).let {
|
return NumberFormat.getCurrencyInstance(locale).let {
|
||||||
it.setCurrency(Currency.getInstance(currency))
|
it.currency = Currency.getInstance(currency)
|
||||||
it.setMinimumFractionDigits(minFractionDigits)
|
it.minimumFractionDigits = minFractionDigits
|
||||||
it.format(amount)
|
it.format(amount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a JSON representation of the [CryptoPrice].
|
* Indicates whether some other object is _equal to_ this one.
|
||||||
*/
|
*/
|
||||||
fun toJson(): String {
|
override fun equals(other: Any?): Boolean {
|
||||||
return JSONStringer()
|
if (this === other) return true
|
||||||
.`object`().key("data")
|
if (javaClass != other?.javaClass) return false
|
||||||
.`object`()
|
|
||||||
|
other as CryptoPrice
|
||||||
|
|
||||||
|
if (base != other.base) return false
|
||||||
|
if (currency != other.currency) return false
|
||||||
|
if (amount != other.amount) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a hash code value for the object.
|
||||||
|
*/
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = base.hashCode()
|
||||||
|
result = 31 * result + currency.hashCode()
|
||||||
|
result = 31 * result + amount.hashCode()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a JSON representation of the [CryptoPrice].
|
||||||
|
*
|
||||||
|
* For example, with the default `data` wrapper:
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* {"data":{"base":"BTC","currency":"USD","amount":"58977.17"}}
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param wrapper Specifies a JSON object to wrap the price data in.
|
||||||
|
*/
|
||||||
|
@JvmOverloads
|
||||||
|
fun toJson(wrapper: String = "data"): String {
|
||||||
|
val json = JSONStringer()
|
||||||
|
if (wrapper.isNotBlank()) json.`object`().key(wrapper)
|
||||||
|
json.`object`()
|
||||||
.key("base").value(base)
|
.key("base").value(base)
|
||||||
.key("currency").value(currency)
|
.key("currency").value(currency)
|
||||||
.key("amount").value(amount.toString())
|
.key("amount").value(amount.toString())
|
||||||
.endObject()
|
.endObject()
|
||||||
.endObject()
|
if (wrapper.isNotBlank()) json.endObject()
|
||||||
.toString()
|
return json.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a JSON respresentation of the [CryptoPrice].
|
* Returns a JSON respresentation of the [CryptoPrice].
|
||||||
|
*
|
||||||
|
* For example:
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* {"base":"BTC","currency":"USD","amount":"58977.17"}
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @see [toJson]
|
||||||
*/
|
*/
|
||||||
override fun toString(): String = toJson()
|
override fun toString(): String = toJson("")
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@ package net.thauvin.erik.crypto
|
||||||
import net.thauvin.erik.crypto.CryptoPrice.Companion.apiCall
|
import net.thauvin.erik.crypto.CryptoPrice.Companion.apiCall
|
||||||
import net.thauvin.erik.crypto.CryptoPrice.Companion.spotPrice
|
import net.thauvin.erik.crypto.CryptoPrice.Companion.spotPrice
|
||||||
import net.thauvin.erik.crypto.CryptoPrice.Companion.toPrice
|
import net.thauvin.erik.crypto.CryptoPrice.Companion.toPrice
|
||||||
import java.math.BigDecimal
|
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
|
@ -15,11 +14,12 @@ import kotlin.test.assertTrue
|
||||||
* [CryptoPrice] Tests
|
* [CryptoPrice] Tests
|
||||||
*/
|
*/
|
||||||
class CryptoPriceTest {
|
class CryptoPriceTest {
|
||||||
val jsonData = "{\"data\":{\"base\":\"BTC\",\"currency\":\"USD\",\"amount\":\"%s\"}}"
|
private val jsonPrice = "{\"base\":\"BTC\",\"currency\":\"USD\",\"amount\":\"%s\"}"
|
||||||
|
private val jsonData = "{\"data\":$jsonPrice}"
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Throws(CryptoException::class)
|
@Throws(CryptoException::class)
|
||||||
fun testBTCPrice() {
|
fun testBitcoinPrice() {
|
||||||
val price = spotPrice("BTC")
|
val price = spotPrice("BTC")
|
||||||
assertEquals("BTC", price.base, "BTC")
|
assertEquals("BTC", price.base, "BTC")
|
||||||
assertEquals("USD", price.currency, "is USD")
|
assertEquals("USD", price.currency, "is USD")
|
||||||
|
@ -28,7 +28,7 @@ class CryptoPriceTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Throws(CryptoException::class)
|
@Throws(CryptoException::class)
|
||||||
fun testETHPrice() {
|
fun testEtherPrice() {
|
||||||
val price = spotPrice("ETH", "EUR")
|
val price = spotPrice("ETH", "EUR")
|
||||||
assertEquals("ETH", price.base, "ETH")
|
assertEquals("ETH", price.base, "ETH")
|
||||||
assertEquals("EUR", price.currency, "is EUR")
|
assertEquals("EUR", price.currency, "is EUR")
|
||||||
|
@ -37,16 +37,16 @@ class CryptoPriceTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Throws(CryptoException::class)
|
@Throws(CryptoException::class)
|
||||||
fun testETH2Price() {
|
fun testLitecoinPrice() {
|
||||||
val price = spotPrice("ETH2", "GBP")
|
val price = spotPrice("LTC", "GBP")
|
||||||
assertEquals("ETH2", price.base, "ETH2")
|
assertEquals("LTC", price.base, "LTC")
|
||||||
assertEquals("GBP", price.currency, "is GBP")
|
assertEquals("GBP", price.currency, "is GBP")
|
||||||
assertTrue(price.amount.signum() > 0, "GBP > 0")
|
assertTrue(price.amount.signum() > 0, "LTC > 0")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Throws(CryptoException::class)
|
@Throws(CryptoException::class)
|
||||||
fun testBCHPrice() {
|
fun testBitcoinCashPrice() {
|
||||||
val price = spotPrice("BCH", "GBP", LocalDate.now().minusDays(1))
|
val price = spotPrice("BCH", "GBP", LocalDate.now().minusDays(1))
|
||||||
assertEquals("BCH", price.base, "BCH")
|
assertEquals("BCH", price.base, "BCH")
|
||||||
assertEquals("GBP", price.currency, "is GBP")
|
assertEquals("GBP", price.currency, "is GBP")
|
||||||
|
@ -89,7 +89,7 @@ class CryptoPriceTest {
|
||||||
fun testToCurrency() {
|
fun testToCurrency() {
|
||||||
val d = 12345.60.toBigDecimal()
|
val d = 12345.60.toBigDecimal()
|
||||||
val usd = CryptoPrice("BTC", "USD", d)
|
val usd = CryptoPrice("BTC", "USD", d)
|
||||||
assertEquals("$12,345.60", usd.toCurrency(), "EUR format")
|
assertEquals("$12,345.60", usd.toCurrency(), "USD format")
|
||||||
|
|
||||||
val eur = CryptoPrice("BTC", "EUR", d)
|
val eur = CryptoPrice("BTC", "EUR", d)
|
||||||
assertEquals("€12,345.60", eur.toCurrency(), "EUR format")
|
assertEquals("€12,345.60", eur.toCurrency(), "EUR format")
|
||||||
|
@ -97,10 +97,10 @@ class CryptoPriceTest {
|
||||||
val gbp = CryptoPrice("ETH", "GBP", d)
|
val gbp = CryptoPrice("ETH", "GBP", d)
|
||||||
assertEquals("£12,345.60", gbp.toCurrency(), "GBP format")
|
assertEquals("£12,345.60", gbp.toCurrency(), "GBP format")
|
||||||
|
|
||||||
val aud = CryptoPrice("BTC", "AUD", d)
|
val aud = CryptoPrice("LTC", "AUD", d)
|
||||||
assertEquals("A$12,345.60", aud.toCurrency(), "AUD format")
|
assertEquals("A$12,345.60", aud.toCurrency(), "AUD format")
|
||||||
|
|
||||||
val dk = CryptoPrice("BTC", "DKK", d)
|
val dk = CryptoPrice("BCH", "DKK", d)
|
||||||
assertEquals("12.345,60 kr.", dk.toCurrency(Locale("da", "DK")), "EUR-DKK format")
|
assertEquals("12.345,60 kr.", dk.toCurrency(Locale("da", "DK")), "EUR-DKK format")
|
||||||
|
|
||||||
val jp = CryptoPrice("BTC", "JPY", d)
|
val jp = CryptoPrice("BTC", "JPY", d)
|
||||||
|
@ -112,11 +112,11 @@ class CryptoPriceTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testToJson() {
|
fun testToJson() {
|
||||||
listOf("1234.5", "1234.56", "1234.567").forEach {
|
listOf("1234.5", "1234.56", "1234.567").forEachIndexed { i, d ->
|
||||||
val json = jsonData.format(it)
|
val data = jsonData.format(d)
|
||||||
with(json.toPrice()) {
|
with(data.toPrice()) {
|
||||||
assertEquals(json, toJson(), "toJson($it)")
|
assertEquals(data, toJson(), "toJson($d)")
|
||||||
assertEquals(json, toString(), "toString($it)")
|
assertEquals(data.replace("data","price$i"), toJson("price$i"), "toJson(price$i)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -126,16 +126,19 @@ class CryptoPriceTest {
|
||||||
@Throws(CryptoException::class)
|
@Throws(CryptoException::class)
|
||||||
fun testToPrice() {
|
fun testToPrice() {
|
||||||
val d = "57515.60"
|
val d = "57515.60"
|
||||||
val json = jsonData.format(d)
|
val data = jsonData.format(d)
|
||||||
val price = json.toPrice()
|
val price = data.toPrice()
|
||||||
assertEquals("BTC", price.base, "base is BTC")
|
assertEquals("BTC", price.base, "base is BTC")
|
||||||
assertEquals("USD", price.currency, "currency is USD")
|
assertEquals("USD", price.currency, "currency is USD")
|
||||||
assertEquals(d, price.amount.toString(), "amount is $d")
|
assertEquals(d, price.amount.toString(), "amount is $d")
|
||||||
|
|
||||||
|
assertEquals(price, price.toString().toPrice(""), "toPrice('')")
|
||||||
|
assertEquals(price, price.toJson("test").toPrice("test"), "toPrice(test)")
|
||||||
|
|
||||||
assertFailsWith(
|
assertFailsWith(
|
||||||
message = "double conversion did not fail",
|
message = "amount conversion did not fail",
|
||||||
exceptionClass = CryptoException::class,
|
exceptionClass = CryptoException::class,
|
||||||
block = { json.replace("5", "a").toPrice() }
|
block = { data.replace("5", "a").toPrice() }
|
||||||
)
|
)
|
||||||
|
|
||||||
assertFailsWith(
|
assertFailsWith(
|
||||||
|
@ -147,7 +150,15 @@ class CryptoPriceTest {
|
||||||
assertFailsWith(
|
assertFailsWith(
|
||||||
message = "no base did not fail",
|
message = "no base did not fail",
|
||||||
exceptionClass = CryptoException::class,
|
exceptionClass = CryptoException::class,
|
||||||
block = { json.replace("base", "foo").toPrice() }
|
block = { data.replace("base", "foo").toPrice() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testToString() {
|
||||||
|
val json = jsonPrice.format("1234.5")
|
||||||
|
val price = json.toPrice("")
|
||||||
|
assertEquals(json, price.toString(), "toString()")
|
||||||
|
assertEquals(price.toString(), price.toJson(""), "toString() = toJson('')")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue