I need to parse this json. It works fine by default, but I need to add timestamp, so i use custom deserialize factory.
[
{
"ccy": "USD",
"base_ccy": "UAH",
"buy": "26.60000",
"sale": "26.96000"
},
{
"ccy": "EUR",
"base_ccy": "UAH",
"buy": "28.95000",
"sale": "29.60000"
},
{
"ccy": "RUR",
"base_ccy": "UAH",
"buy": "0.35000",
"sale": "0.38500"
},
{
"ccy": "BTC",
"base_ccy": "USD",
"buy": "8610.8989",
"sale": "9517.3093"
}
]
But json: JsonElement? parametr in MyDeserializer never come with ArrayList, always in single object. How i can read remote json for ArrayList, аnd modify it?
class MyDeserializer : JsonDeserializer<ArrayList<CurrencyItem>> {
override fun deserialize(
json: JsonElement?,
typeOfT: Type?,
context: JsonDeserializationContext?
): ArrayList<CurrencyItem> {
//Clean array save result
var currrencyList = ArrayList<CurrencyItem>()
// Get remote json
val itemsJsonArray = jsonObject.asJsonArray
//Modify remote json to custom object with timestamp
for (item in itemsJsonArray) {
var JsonObject = item.asJsonObject
var ccy = JsonObject.get("ccy").asString
var base_ccy = JsonObject.get("base_ccy").asString
var buy = JsonObject.get("buy").asString
var sale = JsonObject.get("sale").asString
var timestamp = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())
currrencyList.add(CurrencyItem(1, ccy, base_ccy, buy, sale, time))
}
return currrencyList
}
}
Add custom converter to retrofit
//Add converter to retrofit
val retrofit =
Retrofit.Builder().baseUrl("https://api.privatbank.ua/")
//My custom converter
.addConverterFactory(GsonConverterFactory.create(customGson))
.client(okkHttpclient)
.build()
The problem is you're registering your deserializer with the wrong type:
JsonDeserializer<ArrayList<CurrencyItem>>
The type for your deserializer is
ArrayList<CurrentItem>
Yet, you're registering it with
CurrencyItem::class.java
Now, registering generic types is not straightforward as plain types.
In your case you need:
Type currencyItemListType = new TypeToken<ArrayList<CurrencyItem>>() {}.getType();
registerTypeAdapter(currencyItemListType, MyDeserializer())code here
With that, when de retrofit call returns with a success code (2xx) it shoud automatically try to deserialize the json with your deserializer
Related
The data that I want to use has this structure:
{
"1": {
"id": 1,
"name": "Bulbasaur"
},
"2": {
"id": 2,
"name": "Ivysaur"
},
"3": {
"id": 3,
"name": "Venusaur"
}
}
Note:
The number labeling each object matches the id of the Pokémon, not the number of Pokémon
My problem is that when I try to create data classes for this it ends up creating a data class for each object. Not one data class that fits each object. I believe this is due to the number labeling the object(Pokémon) being different for each object.
Is there a way I can format this data in maybe one or two data classes and not over 800?
Ideally I would like the data to be structured like this but it does not work when run.
data class ReleasedPokemonModel(
val id: Int,
val name: String
)
When parsing Json to Object with this special case, you should custom Json Deserializer yourself.
Here I use Gson library to parse Json to Object.
First, create a custom Json Deserializer with Gson. As follows:
PokemonResponse.kt
data class PokemonResponse(
val pokemonMap: List<StringReleasedPokemonModel>
)
data class ReleasedPokemonModel(
val id: Int,
val name: String
)
GsonHelper.kt
object GsonHelper {
fun create(): Gson = GsonBuilder().apply {
registerTypeAdapter(PokemonResponse::class.java, PokemonType())
setLenient()
}.create()
private class PokemonType : JsonDeserializer<PokemonResponse> {
override fun deserialize(
json: JsonElement?,
typeOfT: Type?,
context: JsonDeserializationContext?
): PokemonResponse {
val list = mutableListOf<ReleasedPokemonModel>()
// Get your all key
val keys = json?.asJsonObject?.keySet()
keys?.forEach { key ->
// Get your item with key
val item = Gson().fromJson<ReleasedPokemonModel>(
json.asJsonObject[key],
object : TypeToken<ReleasedPokemonModel>() {}.type
)
list.add(item)
}
return PokemonResponse(list)
}
}
}
Next I will create a GsonConverterFactory so that I can addConvertFactory to Retrofit.
val gsonConverterFactory = GsonConverterFactory.create(GsonHelper.create())
And now I will add retrofit.
val retrofit = Retrofit.Builder()
// Custom your Retrofit
.addConverterFactory(gsonConverterFactory) // Add GsonConverterFactoty
.build()
Finally in ApiService, your response will now return type PokemonResponse.
interface ApiService {
#GET("your_link")
suspend fun getGenres(): PokemonResponse
}
The problem is that there's no JSON array there. it's literally one JSON object with each Pokemon listed as a property. I would recommend that you reformat the JSON beforehand to look like this:
[
{
"id": 1,
"name": "Bulbasaur"
},
{
"id": 2,
"name": "Ivysaur"
},
{
"id": 3,
"name": "Venusaur"
}
]
And then you could model it like this:
data class ReleasedPokemonModel(
val id: Int,
val name: String
)
data class Response(
val items: List<ReleasedPokemonModel>
)
See more here.
And see here for discussion about reformatting the data before handing it to Retrofit.
You can use Map to store the key like the following
data class PokemonResponse(
val pokemonMap:Map<String,ReleasedPokemonModel>
)
data class ReleasedPokemonModel(
val id: Int,
val name: String
)
{
"image_url": [
{
"image_url": "url1"
},
{
"image_url": "url1"
},
{
"image_url": "url1"
}
]
}
I Want To Pass image_url As An Array To API With Multiple Image URL [String] How Can I Pass Like This Using Retrofit [Android - Kotlin]
val jsResult = JSONObject()
val jsArray = JSONArray()
for (i in 0 until imgOnlineList.size) {
val jGroup = JSONObject()
jGroup.put("imageURL", imgOnlineList[i])
jsArray.put(jGroup)
}
jsResult.put("productCategory", jsArray)
Constant.logD(mTAG, "productCategoryOnlineList : ", jsResult.toString())
Since you are using Retrofit with Kotlin, you don't need to write plain Json objects. You can set Gson converter in Retrofit Builder.
You can create two data classes
data class Image(val image_url: String)
data class ImageList(val image_url: List<Image>)
And then pass them to API call as #Body payload or however is required by API provider. e.g.,
#POST("{api_end_point}")
fun sendImages(#Body imageList: ImageList)
I am trying to parse this JSON:
{
"random number1":
{AT=
{av=-54.697, ct=320206.0, mn=-92.47, mx=0.495},
First_UTC=2020-05-17T14:54:38Z
},
"random number2":
{AT=
{av=-54.6437, ct=3204306.0, mn=-92.47, mx=0.495},
First_UTC=2020-05-17T14:54:43Z
}
}
I have made a simple adapter class for this
#FromJson
fun fromJson(json: Map<String, Any>): OuterData {
var count = 0
var moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
var adapter = moshi.adapter<InnerData>(InnerData().javaClass)
json.entries.forEach {
OuterData.innerData[count++] = adapter.fromJson(test)!!
}
}
class InnerData() {
var AT = DataDetail()
var First_UTC = ""
}
It properly iterates over each "random number" entry and the inner objects all look correct but for some reason I always get the Use JsonReader.setLenient(true) to accept malformed JSON at path $. error. It looks like the quotes are getting stripped out of the JSON for some reason and that might be causing an issue. If I use my inner data class directly
I have a POJO model object "apiResponse" that has values from a previous API call.
{
"status":200,
"userModel":{
...SOME VARIABLES...
},
"otherContentRelatedToUserModel":{
..SOME RELATED CONTENT..
}
}
This apiResponse has an "UserModel" as an inner object.
What i want to do is pass this "apiResponse" object to another api call whose response is "UserModel", and have it update only the UserModel object in the APIResponse POJO object.
The objective is to keep a single source of related content, which could change based on the interaction in the application, but might not update the rest of the related content.
Or is it possible to atleast update an already created pojo model as a whole, updating the variable values in the model.?
Reason for this ::
The API's content does not change for a set amount of time in the server, mainly to avoid over traffic to the server. So some amount of logic has to be implemented on the application side. Currently using a DB is not really a viable option.
Basically update only a portion of the already created POJO class object with another api call.
Is this possible in android(kotlin) using retrofit? Or is there any other way this could be achievable?
I think it is not possible to populate the additional fields in the existing UserModel object by using Retrofit, but you can do some magic with GSON:
data class UserModel(
val userId: Int? = null,
val userName: String? = null)
class OtherContentRelatedToUserModel
data class ApiResponsePojo(
val status: Int? = null,
val userModel: UserModel? = null,
val otherContentRelatedToUserModel: OtherContentRelatedToUserModel? = null)
class UserModelInstanceCreator(var userModelToUpdate: UserModel? = null)
: InstanceCreator<UserModel> {
override fun createInstance(type: Type?): UserModel {
return userModelToUpdate ?: UserModel()
}
}
val apiResponseJson =
"""
{
"status":200,
"userModel":{
"userId": 1
},
"otherContentRelatedToUserModel":{
}
}
"""
val userModelResponseJson =
"""
{
"userName": "john wick"
}
"""
val userModelInstanceCreator = UserModelInstanceCreator()
val gson = GsonBuilder()
.registerTypeAdapter(UserModel::class.java, userModelInstanceCreator)
.create()
val apiResponse: ApiResponsePojo = gson.fromJson(apiResponseJson, ApiResponsePojo::class.java)
userModelInstanceCreator.userModelToUpdate = apiResponse.userModel
gson.fromJson(userModelResponseJson, UserModel::class.java)
...
// apiResponse.toString() result
// ApiResponsePojo(status=200, userModel=UserModel(userId=1, userName=john wick)...
I am working on an android project and using RxAndroid, Retrofit to make API call and retrieve json. The json looks something like following :
{
"result": [
{
"parent": "jhasj",
"u_deviation": "skasks",
"caused_by": "ksks",
"u_owner_mi": {
"link": "https://gddhdd.service-now.com/api/now/v1/table/sys_user/ghtytu",
"value": "ghtytu"
},
"impact": "",
}
]
}
I am using gson to parse the Json. The problem is "u_owner_mi" sometimes reruns empty string "" when there is no value assigned to it. I don't have access to change the return type to null. This is making my app crash as I am expecting an object here.
I get the following error :
Expected BEGIN_OBJECT but was STRING
If you can't modify the server, try replacing the offending line in the server response before passing it to the Gson parser. Something like:
String safeResponse = serverResponse.replace("\"u_owner_mi\": \"\"", "\"u_owner_mi\": null");
Your app (client) code is expecting an object according to a contract specified in the class that you pass to GSON. Your app behaves as it should and crashes loudly. You should consider having your server return "u_owner_mi" : null instead of an empty string, assuming you have control over that. The u_owner_mi field on the client side would have to be a nullable type.
If you don't have the ability to fix the api, you could also write a custom deserializer.
Suppose your result class and sub-object are:
data class Result(
val parent: String,
val owner: Any?
)
data class Owner(
val link: String,
val value: String
)
The deserializer could be:
class ResultDeserializer : JsonDeserializer<Result> {
override fun deserialize(json: JsonElement, typeOfT: Type?, context: JsonDeserializationContext?): Result {
val jsonObject = json.asJsonObject
val ownerProperty = jsonObject.get("owner")
return Result(
parent = jsonObject.get("parent").asString,
owner = if (ownerProperty.isJsonObject) context?.deserialize<Owner>(ownerProperty.asJsonObject, Owner::class.java)
else ownerProperty.asString
)
}
}
Finally, to add the deserializer:
#Test
fun deserialization() {
val gson = GsonBuilder().registerTypeAdapter(Result::class.java, ResultDeserializer()).create()
val result1 = gson.fromJson<Result>(jsonWithObject, Result::class.java)
val result2 = gson.fromJson<Result>(jsonWithEmpty, Result::class.java)
}