I am struggling to understand how to convert JSON data with Moshi. I am learning Android and Kotlin and my app is supposed to load and display COVID data. The input JSON format is like this:
[
{
"infected": 109782,
"tested": "NA",
"recovered": 75243,
"deceased": 2926,
"country": "Algeria",
"moreData": "https://api.apify.com/v2/key-value-stores/pp4Wo2slUJ78ZnaAi/records/LATEST?disableRedirect=true",
"historyData": "https://api.apify.com/v2/datasets/hi0DJXpcyzDwtg2Fm/items?format=json&clean=1",
"sourceUrl": "https://www.worldometers.info/coronavirus/",
"lastUpdatedApify": "2021-02-11T12:15:00.000Z"
},
{
"infected": 425561,
"tested": 11205451,
"recovered": 407155,
"deceased": 8138,
"country": "Austria",
"moreData": "https://api.apify.com/v2/key-value-stores/RJtyHLXtCepb4aYxB/records/LATEST?disableRedirect=true",
"historyData": "https://api.apify.com/v2/datasets/EFWZ2Q5JAtC6QDSwV/items?format=json&clean=1",
"sourceUrl": "https://www.sozialministerium.at/Informationen-zum-Coronavirus/Neuartiges-Coronavirus-(2019-nCov).html",
"lastUpdatedApify": "2021-02-11T12:15:00.000Z"
},
...
]
As you can see, numbers can also be represented as strings (as in 'tested'), also some URLs can be missing for some countries. So I followed Moshi documentation and created 2 data classes and a custom adapter.
//desired structure
data class CountryData(
val infected: Int,
val tested: Int,
val recovered: Int,
val deceased: Int,
val country: String,
val moreData: String,
val historyData: String,
val sourceUrl: String,
val lastUpdatedApify: String
)
//actual JSON
data class CountryDataJson(
val infected: String,
val tested: String,
val recovered: String,
val deceased: String,
val country: String,
val moreData: String?,
val historyData: String?,
val sourceUrl: String?,
val lastUpdatedApify: String
)
Custom adapter:
import android.util.Log
import com.example.coronastats.network.CountryData
import com.example.coronastats.network.CountryDataJson
import com.squareup.moshi.FromJson
class CountryJsonAdapter {
@FromJson fun fromJson(countryDataJson: CountryDataJson): CountryData {
val countryData = CountryData(
if (countryDataJson.infected != "NA") countryDataJson.infected.toInt() else -1,
if (countryDataJson.tested != "NA") countryDataJson.tested.toInt() else -1,
if (countryDataJson.recovered != "NA") countryDataJson.recovered.toInt() else -1,
if (countryDataJson.deceased != "NA") countryDataJson.deceased.toInt() else -1,
countryDataJson.country,
countryDataJson.moreData ?: "NA",
countryDataJson.historyData ?: "NA",
countryDataJson.sourceUrl ?: "NA",
countryDataJson.lastUpdatedApify
)
Log.d("adapterLOG", "fromJson triggered")
return countryData
}
}
And my service API:
import com.example.coronastats.CountryJsonAdapter
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import retrofit2.http.GET
private const val BASE_URL = "https://api.apify.com/"
private val moshi = Moshi.Builder()
.add(CountryJsonAdapter())
.build()
private val retrofit = Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create(moshi))
.baseUrl(BASE_URL)
.build()
interface CoronaApiService {
@GET("v2/key-value-stores/tVaYRsPHLjNdNBu7S/records/LATEST?disableRedirect=true")
suspend fun getStatistics() : List<CountryData>
}
object CoronaApi {
val retrofitService: CoronaApiService by lazy {
retrofit.create(CoronaApiService::class.java)
}
}
And I'm getting an empty screen as a result. The Log in the adapter is never triggered, so I assume something is wrong and my adapter is never called.
NB: Without all this converter stuff, the app runs ok with the standard KotlinJsonAdapterFactory() and CountryData class as all strings, but I'd like to know how to get the structure that I have here.