From 35561059ceabdb5e5968cf12b1a32e1c811e7cdb Mon Sep 17 00:00:00 2001 From: "Nelson R. Perez" Date: Mon, 4 Feb 2019 21:00:57 -0500 Subject: [PATCH] This commit finishes with the second part of the equivalent values calculation. In this part of the procedure a query is performed looking for entries in the 'transactions' table that are missing a corresponding entry in the 'equivalent_values' table. Whenere a transaction is detected to not have an equivalent value for a given fiat, a network request is made to the Coingecko's API in order to obtain the BTS market value at the day of that transaction. An equivalent value is thus calculated using the 'bts_value' of the transaction. --- .../activities/ConnectedActivity.kt | 7 ++ .../database/daos/EquivalentValueDao.kt | 1 - .../database/daos/TransferDao.kt | 5 +- .../models/coingecko/HistoricalPrice.kt | 5 ++ .../models/coingecko/MarketData.kt | 3 + .../coingecko/MarketDataDeserializer.kt | 22 +++++ .../network/CoingeckoService.kt | 16 ++++ .../network/ServiceGenerator.java | 16 +++- .../EquivalentValuesRepository.kt | 18 ++++ .../repositories/TransferRepository.kt | 87 +++++++++++++++++-- .../bitsybitshareswallet/utils/Constants.kt | 3 + .../viewmodels/ConnectedActivityViewModel.kt | 18 ++++ .../viewmodels/TransferViewModel.kt | 5 -- .../MarketDataDeserializerTest.kt | 19 ++++ 14 files changed, 209 insertions(+), 16 deletions(-) create mode 100644 app/src/main/java/cy/agorise/bitsybitshareswallet/models/coingecko/HistoricalPrice.kt create mode 100644 app/src/main/java/cy/agorise/bitsybitshareswallet/models/coingecko/MarketData.kt create mode 100644 app/src/main/java/cy/agorise/bitsybitshareswallet/models/coingecko/MarketDataDeserializer.kt create mode 100644 app/src/main/java/cy/agorise/bitsybitshareswallet/network/CoingeckoService.kt create mode 100644 app/src/main/java/cy/agorise/bitsybitshareswallet/repositories/EquivalentValuesRepository.kt create mode 100644 app/src/main/java/cy/agorise/bitsybitshareswallet/viewmodels/ConnectedActivityViewModel.kt create mode 100644 app/src/test/java/cy/agorise/bitsybitshareswallet/MarketDataDeserializerTest.kt diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/activities/ConnectedActivity.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/activities/ConnectedActivity.kt index 5d79aaf..2078a07 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/activities/ConnectedActivity.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/activities/ConnectedActivity.kt @@ -23,6 +23,7 @@ import cy.agorise.bitsybitshareswallet.processors.TransfersLoader import cy.agorise.bitsybitshareswallet.repositories.AssetRepository import cy.agorise.bitsybitshareswallet.utils.Constants import cy.agorise.bitsybitshareswallet.viewmodels.BalanceViewModel +import cy.agorise.bitsybitshareswallet.viewmodels.ConnectedActivityViewModel import cy.agorise.bitsybitshareswallet.viewmodels.TransferViewModel import cy.agorise.bitsybitshareswallet.viewmodels.UserAccountViewModel import cy.agorise.graphenej.Asset @@ -67,6 +68,7 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection { private lateinit var mUserAccountViewModel: UserAccountViewModel private lateinit var mBalanceViewModel: BalanceViewModel private lateinit var mTransferViewModel: TransferViewModel + private lateinit var mConnectedActivityViewModel: ConnectedActivityViewModel private lateinit var mAssetRepository: AssetRepository @@ -115,6 +117,11 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection { mAssetRepository = AssetRepository(this) + // Configure ConnectedActivityViewModel to obtain missing equivalent values + mConnectedActivityViewModel = ViewModelProviders.of(this).get(ConnectedActivityViewModel::class.java) + + mConnectedActivityViewModel.observeMissingEquivalentValuesIn("usd") //TODO: Obtain this from shared preferences? + // Configure UserAccountViewModel to obtain the missing account ids mUserAccountViewModel = ViewModelProviders.of(this).get(UserAccountViewModel::class.java) diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/database/daos/EquivalentValueDao.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/database/daos/EquivalentValueDao.kt index 56ca0bd..314c18a 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/database/daos/EquivalentValueDao.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/database/daos/EquivalentValueDao.kt @@ -5,7 +5,6 @@ import androidx.room.Dao import androidx.room.Insert import androidx.room.Query import cy.agorise.bitsybitshareswallet.database.entities.EquivalentValue -import cy.agorise.bitsybitshareswallet.database.entities.Transfer @Dao interface EquivalentValueDao { diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/database/daos/TransferDao.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/database/daos/TransferDao.kt index 2ef9c7e..306e849 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/database/daos/TransferDao.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/database/daos/TransferDao.kt @@ -3,6 +3,7 @@ package cy.agorise.bitsybitshareswallet.database.daos import androidx.lifecycle.LiveData import androidx.room.* import cy.agorise.bitsybitshareswallet.database.entities.Transfer +import io.reactivex.Observable import io.reactivex.Single @Dao @@ -32,8 +33,8 @@ interface TransferDao { @Query("SELECT * FROM transfers WHERE timestamp != 0 AND bts_value = -1 AND transfer_asset_id != '1.3.0' LIMIT 1") fun getTransfersWithMissingBtsValue(): LiveData - @Query("SELECT * FROM transfers WHERE id NOT IN (SELECT transfer_id FROM equivalent_values WHERE symbol = :symbol)") - fun getTransfersWithMissingValueIn(symbol: String): LiveData> + @Query("SELECT * FROM transfers WHERE id NOT IN (SELECT transfer_id FROM equivalent_values WHERE symbol = :symbol) AND bts_value >= 0 LIMIT 1") + fun getTransfersWithMissingValueIn(symbol: String): Observable @Query("DELETE FROM transfers") fun deleteAll() diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/models/coingecko/HistoricalPrice.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/models/coingecko/HistoricalPrice.kt new file mode 100644 index 0000000..e612543 --- /dev/null +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/models/coingecko/HistoricalPrice.kt @@ -0,0 +1,5 @@ +package cy.agorise.bitsybitshareswallet.models.coingecko + +data class HistoricalPrice(val id: String, + val symbol: String, + val market_data: MarketData) \ No newline at end of file diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/models/coingecko/MarketData.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/models/coingecko/MarketData.kt new file mode 100644 index 0000000..9cad020 --- /dev/null +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/models/coingecko/MarketData.kt @@ -0,0 +1,3 @@ +package cy.agorise.bitsybitshareswallet.models.coingecko + +data class MarketData(var current_price: HashMap) \ No newline at end of file diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/models/coingecko/MarketDataDeserializer.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/models/coingecko/MarketDataDeserializer.kt new file mode 100644 index 0000000..c751cd2 --- /dev/null +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/models/coingecko/MarketDataDeserializer.kt @@ -0,0 +1,22 @@ +package cy.agorise.bitsybitshareswallet.models.coingecko + +import com.google.gson.JsonDeserializationContext +import com.google.gson.JsonDeserializer +import com.google.gson.JsonElement +import junit.framework.Assert +import java.lang.reflect.Type + +class MarketDataDeserializer : JsonDeserializer { + override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): MarketData { + val hashMap = HashMap() + val obj = json?.asJsonObject?.get("current_price")?.asJsonObject + if(obj != null){ + val keySet = obj.asJsonObject.keySet() + for(key in keySet){ + println("$key -> : ${obj[key].asDouble}") + hashMap[key] = obj[key].asDouble + } + } + return MarketData(hashMap) + } +} \ No newline at end of file diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/network/CoingeckoService.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/network/CoingeckoService.kt new file mode 100644 index 0000000..0d951ec --- /dev/null +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/network/CoingeckoService.kt @@ -0,0 +1,16 @@ +package cy.agorise.bitsybitshareswallet.network + +import retrofit2.Call +import cy.agorise.bitsybitshareswallet.models.coingecko.HistoricalPrice +import retrofit2.http.GET +import retrofit2.http.Headers +import retrofit2.http.Query + +interface CoingeckoService { + + @Headers("Content-Type: application/json") + @GET("/api/v3/coins/bitshares/history") + fun getHistoricalValueSync(@Query("id") id: String, + @Query("date") date: String, + @Query("localization") localization: Boolean): Call +} \ No newline at end of file diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/network/ServiceGenerator.java b/app/src/main/java/cy/agorise/bitsybitshareswallet/network/ServiceGenerator.java index 6554677..76279ef 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/network/ServiceGenerator.java +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/network/ServiceGenerator.java @@ -3,6 +3,8 @@ package cy.agorise.bitsybitshareswallet.network; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; +import cy.agorise.bitsybitshareswallet.models.coingecko.MarketData; +import cy.agorise.bitsybitshareswallet.models.coingecko.MarketDataDeserializer; import okhttp3.Interceptor; import okhttp3.OkHttpClient; import okhttp3.logging.HttpLoggingInterceptor; @@ -22,23 +24,26 @@ public class ServiceGenerator{ private static HashMap, Object> Services; - public ServiceGenerator(String apiBaseUrl) { + public ServiceGenerator(String apiBaseUrl, Gson gson) { API_BASE_URL= apiBaseUrl; logging = new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY); httpClient = new OkHttpClient.Builder().addInterceptor(logging); builder = new Retrofit.Builder() .baseUrl(API_BASE_URL) - .addConverterFactory(GsonConverterFactory.create(getGson())) + .addConverterFactory(GsonConverterFactory.create(gson)) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()); Services = new HashMap, Object>(); } + public ServiceGenerator(String apiBaseUrl){ + this(apiBaseUrl, new Gson()); + } + /** * Customizes the Gson instance with specific de-serialization logic */ private Gson getGson(){ GsonBuilder builder = new GsonBuilder(); - return builder.create(); } @@ -76,6 +81,11 @@ public class ServiceGenerator{ httpClient.readTimeout(5, TimeUnit.MINUTES); httpClient.connectTimeout(5, TimeUnit.MINUTES); OkHttpClient client = httpClient.build(); + if(serviceClass == CoingeckoService.class){ + // The MarketData class needs a custom de-serializer + Gson gson = new GsonBuilder().registerTypeAdapter(MarketData.class, new MarketDataDeserializer()).create(); + builder.addConverterFactory(GsonConverterFactory.create(gson)); + } Retrofit retrofit = builder.client(client).build(); return retrofit.create(serviceClass); } diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/repositories/EquivalentValuesRepository.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/repositories/EquivalentValuesRepository.kt new file mode 100644 index 0000000..dbedc1d --- /dev/null +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/repositories/EquivalentValuesRepository.kt @@ -0,0 +1,18 @@ +package cy.agorise.bitsybitshareswallet.repositories + +import android.content.Context +import cy.agorise.bitsybitshareswallet.database.BitsyDatabase +import cy.agorise.bitsybitshareswallet.database.daos.EquivalentValueDao +import cy.agorise.bitsybitshareswallet.database.daos.TransferDao + +class EquivalentValuesRepository(context: Context) { + + private val mEquivalentValuesDao: EquivalentValueDao? + private val mTransfersDao: TransferDao? + + init { + val db = BitsyDatabase.getDatabase(context) + mEquivalentValuesDao = db?.equivalentValueDao() + mTransfersDao = db?.transferDao() + } +} \ No newline at end of file diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/repositories/TransferRepository.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/repositories/TransferRepository.kt index a0918e6..b31e5b3 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/repositories/TransferRepository.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/repositories/TransferRepository.kt @@ -2,19 +2,32 @@ package cy.agorise.bitsybitshareswallet.repositories import android.content.Context import android.os.AsyncTask +import android.util.Log import androidx.lifecycle.LiveData import cy.agorise.bitsybitshareswallet.database.BitsyDatabase +import cy.agorise.bitsybitshareswallet.database.daos.EquivalentValueDao import cy.agorise.bitsybitshareswallet.database.daos.TransferDao +import cy.agorise.bitsybitshareswallet.database.entities.EquivalentValue import cy.agorise.bitsybitshareswallet.database.entities.Transfer +import cy.agorise.bitsybitshareswallet.network.CoingeckoService +import cy.agorise.bitsybitshareswallet.network.ServiceGenerator +import cy.agorise.bitsybitshareswallet.utils.Constants import io.reactivex.Single +import io.reactivex.disposables.CompositeDisposable +import io.reactivex.schedulers.Schedulers +import java.text.SimpleDateFormat +import java.util.* class TransferRepository internal constructor(context: Context) { - + private val TAG = "TransferRepository" private val mTransferDao: TransferDao + private val mEquivalentValuesDao: EquivalentValueDao + private val compositeDisposable = CompositeDisposable() init { val db = BitsyDatabase.getDatabase(context) mTransferDao = db!!.transferDao() + mEquivalentValuesDao = db!!.equivalentValueDao() } fun insertAll(transfers: List) { @@ -41,10 +54,6 @@ class TransferRepository internal constructor(context: Context) { return mTransferDao.getTransferBlockNumberWithMissingTime() } - fun getTransfersWithMissingValueIn(symbol: String): LiveData> { - return mTransferDao.getTransfersWithMissingValueIn(symbol) - } - fun getTransfersWithMissingBtsValue(): LiveData { return mTransferDao.getTransfersWithMissingBtsValue() } @@ -53,6 +62,63 @@ class TransferRepository internal constructor(context: Context) { deleteAllAsyncTask(mTransferDao).execute() } + /** + * Creates a subscription to the transfers table which will listen & process equivalent values. + * + * This function will create a subscription that will listen for missing equivalent values. This will + * automatically trigger a procedure designed to calculate the fiat equivalent value of any entry + * of the 'transactions' table that stil doesn't have a corresponding entry in the 'equivalent_values' + * table for that specific fiat currency. + * + * @param symbol The 3 letters symbol of the desired fiat currency. + */ + fun observeMissingEquivalentValuesIn(symbol: String) { + compositeDisposable.add(mTransferDao.getTransfersWithMissingValueIn(symbol) + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.io()) + .map { transfer -> obtainFiatValue(transfer, symbol) } + .subscribe({ + Log.d(TAG,"Got equivalent value: $it") + mEquivalentValuesDao.insert(it) + },{ + Log.e(TAG,"Error while trying to create a new equivalent value. Msg: ${it.message}") + for(element in it.stackTrace){ + Log.e(TAG,"${element.className}#${element.methodName}:${element.lineNumber}") + } + })) + } + + /** + * Creates an equivalent value for a given transaction. + * + * Function used to perform a request to the Coingecko's price API trying to obtain the + * equivalent value of a specific [Transaction]. + * + * @param transfer The transfer whose equivalent value we want to obtain + * @param symbol The symbol of the fiat that the equivalent value should be calculated in + * @return An instance of the [EquivalentValue] class, ready to be inserted into the database. + */ + fun obtainFiatValue(transfer: Transfer, symbol: String): EquivalentValue { + val sg = ServiceGenerator(Constants.COINGECKO_URL) + val dateFormat = SimpleDateFormat("dd-MM-yyyy", Locale.ROOT) + val date = Date(transfer.timestamp * 1000) + val response = sg.getService(CoingeckoService::class.java) + .getHistoricalValueSync("bitshares", dateFormat.format(date), false) + .execute() + var equivalentFiatValue = -1L + if(response.isSuccessful){ + val price: Double = response.body()?.market_data?.current_price?.get(symbol) ?: -1.0 + // The equivalent value is obtained by: + // 1- Dividing the base value by 100000 (BTS native precision) + // 2- Multiplying that BTS value by the unit price in the chosen fiat + // 3- Multiplying the resulting value by 100 in order to express it in cents + equivalentFiatValue = Math.round(transfer.btsValue?.div(1e5)?.times(price)?.times(100) ?: -1.0) + }else{ + Log.w(TAG,"Request was not successful. code: ${response.code()}") + } + return EquivalentValue(transfer.id, equivalentFiatValue, symbol) + } + private class insertAllAsyncTask internal constructor(private val mAsyncTaskDao: TransferDao) : AsyncTask, Void, Void>() { @@ -79,4 +145,15 @@ class TransferRepository internal constructor(context: Context) { return null } } + + /** + * Called whenever the disposables have to be cleared. + * + * Since this repository manages a subscription it is necessary to clear the disposable after we're done with it. + * The parent ViewModel will let us know when that + */ + fun onCleared() { + if(!compositeDisposable.isDisposed) + compositeDisposable.clear() + } } \ No newline at end of file diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/utils/Constants.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/utils/Constants.kt index 592b280..9e035f7 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/utils/Constants.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/utils/Constants.kt @@ -22,6 +22,9 @@ object Constants { /** Faucet URL used to create new accounts */ const val FAUCET_URL = "https://faucet.palmpay.io" + /** Coingecko's API URL */ + const val COINGECKO_URL = "https://api.coingecko.com" + /** The user selected encrypted PIN */ const val KEY_ENCRYPTED_PIN = "key_encrypted_pin" diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/viewmodels/ConnectedActivityViewModel.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/viewmodels/ConnectedActivityViewModel.kt new file mode 100644 index 0000000..6d3e601 --- /dev/null +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/viewmodels/ConnectedActivityViewModel.kt @@ -0,0 +1,18 @@ +package cy.agorise.bitsybitshareswallet.viewmodels + +import android.app.Application +import androidx.lifecycle.AndroidViewModel +import cy.agorise.bitsybitshareswallet.repositories.TransferRepository + +class ConnectedActivityViewModel(application: Application) : AndroidViewModel(application) { + private var mTransfersRepository = TransferRepository(application) + + fun observeMissingEquivalentValuesIn(symbol: String) { + mTransfersRepository.observeMissingEquivalentValuesIn(symbol) + } + + override fun onCleared() { + super.onCleared() + mTransfersRepository.onCleared() + } +} \ No newline at end of file diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/viewmodels/TransferViewModel.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/viewmodels/TransferViewModel.kt index 07d90f8..3d5ce22 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/viewmodels/TransferViewModel.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/viewmodels/TransferViewModel.kt @@ -1,7 +1,6 @@ package cy.agorise.bitsybitshareswallet.viewmodels import android.app.Application -import android.util.Log import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData import com.google.common.primitives.UnsignedLong @@ -27,10 +26,6 @@ class TransferViewModel(application: Application) : AndroidViewModel(application return mTransferRepository.getTransferBlockNumberWithMissingTime() } - fun getTransfersWithMissingValueIn(symbol: String) : LiveData>{ - return mTransferRepository.getTransfersWithMissingValueIn(symbol) - } - fun getTransfersWithMissingBtsValue() : LiveData { return mTransferRepository.getTransfersWithMissingBtsValue() } diff --git a/app/src/test/java/cy/agorise/bitsybitshareswallet/MarketDataDeserializerTest.kt b/app/src/test/java/cy/agorise/bitsybitshareswallet/MarketDataDeserializerTest.kt new file mode 100644 index 0000000..6c2a422 --- /dev/null +++ b/app/src/test/java/cy/agorise/bitsybitshareswallet/MarketDataDeserializerTest.kt @@ -0,0 +1,19 @@ +package cy.agorise.bitsybitshareswallet + +import com.google.gson.GsonBuilder +import cy.agorise.bitsybitshareswallet.models.coingecko.MarketData +import cy.agorise.bitsybitshareswallet.models.coingecko.MarketDataDeserializer +import org.junit.Assert +import org.junit.Test + +class MarketDataDeserializerTest { + @Test + fun marketDataDeserializationTest(){ + val str = "{\"current_price\": {\"aed\": 0.14139359620401012,\"ars\": 1.476552955052185,\"aud\": 0.05410080634896981,\"bch\": 0.0003021370317928406,\"bdt\": 3.2298217535732276,\"bhd\": 0.01451147244444769,\"bmd\": 0.03849350092032233,\"bnb\": 0.007113127493734956,\"brl\": 0.15000509277539803,\"btc\": 0.00001043269732289735,\"cad\": 0.051866143140042266,\"chf\": 0.03825734329217614,\"clp\": 26.587581916766037,\"cny\": 0.2652895096426772,\"czk\": 0.8706365729081245,\"dkk\": 0.25236393094264586,\"eos\": 0.01566778197589746,\"eth\": 0.0003870069548974383,\"eur\": 0.033804376612212375,\"gbp\": 0.030484350651335475,\"hkd\": 0.3012660745118239,\"huf\": 10.909058160819312,\"idr\": 558.1942568455942,\"ils\": 0.14452962323048843,\"inr\": 2.721290348862006,\"jpy\": 4.327150672205728,\"krw\": 43.47379006939362,\"kwd\": 0.011703102097803801,\"lkr\": 6.939897047172613,\"ltc\": 0.0013225337650442446,\"mmk\": 60.56217136246436,\"mxn\": 0.7738105980956592,\"myr\": 0.1608450935955668,\"nok\": 0.335428517669597,\"nzd\": 0.056803550529088344,\"php\": 2.046274976098886,\"pkr\": 5.3730315641051885,\"pln\": 0.1449376543402434,\"rub\": 2.596498268228413,\"sar\": 0.1444545609036934,\"sek\": 0.3498212376637053,\"sgd\": 0.05281188996415366,\"thb\": 1.2598922851221481,\"try\": 0.20393883733037357,\"twd\": 1.1869880579631216,\"usd\": 0.03849350092032233,\"vef\": 9565.159285292651,\"xag\": 0.002632124388265174,\"xau\": 0.00003094261577979185,\"xdr\": 0.02769368731511483,\"xlm\": 0.3411570542267162,\"xrp\": 0.11074614753363282,\"zar\": 0.5534635980499906}}" + val gson = GsonBuilder().registerTypeAdapter(MarketData::class.java, MarketDataDeserializer()) + .create() + val marketData = gson.fromJson(str, MarketData::class.java) + Assert.assertEquals(0.03849350092032233, marketData.current_price["usd"]) + Assert.assertEquals(0.033804376612212375, marketData.current_price["eur"]) + } +} \ No newline at end of file