Moshi and Retrofit cannot parse simple JSON - android

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

Related

moshi dynamic polymorphic adapter from string

In Gson I can do something like this:
//mapItem - item of map with info for which class should string be deserialized
BaseReqClass req = (BaseReqClass) gson.fromJson(jsonString, mapItem.getValue());
req.doSomeStuff()
where
class AReq : BaseReqClass()
class BReq : BaseReqClass()
I'm selecting mapItem based on map key, which also clearly define me a subclass which moshi should istantiate (mapItem.getValue()).
What should I do to get the same behavior with Moshi?
I know that it is PolymorphicJsonAdapterFactory but I don’t want to have a special field in my json.
Ok, I found solution.
I know what should I do because of mapItem.
So I've implemented something like this:
val rsp =
"{\"token\":\"ABC\",\"data\":{\"id\":\"1234\",\"hash\":\"9N6PpUW9H8T6tuEc1wcvWu\"},\"locale\":\"EN\"}";
//val rsp = "{\"token\":\"FGH\",\"data\":{\"somefield\":\"someString\",\"otherid\":\"555\"},\"locale\":\"EN\"}"
val factory: JsonAdapter.Factory
if (getServiceName() == "areq") {
factory = ResultJsonAdapterFactory.of(BaseReqClass::class.java)
.withSubtype(AReq::class.java)
} else {
factory = ResultJsonAdapterFactory.of(BaseReqClass::class.java)
.withSubtype(BReq::class.java)
}
val moshi = Moshi.Builder()
.add(factory)
.build()
val jsonAdapter = moshi.adapter(BaseReqClass::class.java)
val req = jsonAdapter.fromJson(rsp)
println("serialized: ${jsonAdapter.toJson(req)}")
I can swap rsp from comment on top and moshi deserialize data to second class object.

Retrofit with custom JsonDeserializer in Kotlin

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

Moshi and Retrofit2: Unable to read service response

I'm trying to read a json response from a webservice, but without success.
This is the json i receive:
{
"rsp": {
"#code": "0",
"#message": ""
},
"listOfStrings":[]
}
And this is relative data class where i parse response
data class Response(
val rsp : Rsp,
val listOfStrings : List<String>
)
data class Rsp(
#Json(name = "#code")
val code : String,
#Json(name = "#message")
val message : String
)
But it seems that moshi for some reason it's not able to parse json into object, because i always get Response object with all null fields.
So what's wrong? May the "#" character of json response fields cause problems?
UPDATE
Now i can parse correctly response by change #Json annotation into #field:Json:
data class Rsp(
#field:Json(name = "#code")
val code : String,
#field:Json(name = "#message")
val message : String
)
But i'm curious to know why it works.
#field:Json is required if you want moshi-kotlin to work with proguard according to the discussion here: https://github.com/square/moshi/issues/315
Try this model and let me know if it works:
#Parcelize
data class Response(
#Json(name = "rsp")
val rsp: Rsp,
#Json(name = "listOfStrings")
val listOfStrings: List<String>
) : Parcelable {
#Parcelize
data class Rsp(
#Json(name = "#code")
val code: String,
#Json(name = "#message")
val message: String
) : Parcelable
}
Edit:
If it didn't work, try to add back-slash behind those field names that have #.
Like: #Json(name = "\#code").
UPDATE AFTER QUESTION GOT UPDATE:
You need to add moshi-kotlin dependency and then using KotlinJsonAdapterFactory
val moshi = Moshi.Builder()
// ... add your own JsonAdapters and factories ...
.add(KotlinJsonAdapterFactory())
.build()
Then moshi couldn't ignore #Json.

onRetrofitFailure() Cannot deserialize instance of `java.util.ArrayList` out of START_OBJECT token

I am given a Json data where an image and some other data is stored. I am trying to fetch that image in an imageview using Retrofit. I created a DTO of the required things as detailed in the code. I am getting and error, on retrofit failure. How can I solve this?
Created Retrofit Instance
Created DTO of JSON data and properties
API service created also
https://s3.ap-south-1.amazonaws.com/zestlife/promotional_banner.json
Link where JSON data is stored.
#Parcelize
#JsonIgnoreProperties(ignoreUnknown = true)
open class MerchantPromotionDTO(
#JsonProperty("image") var image: ImageUrlsDTO? = null,
#JsonProperty("cta") var cta: CTADTO? = null,
#JsonProperty("probability") var probability: Int? = 0,
#JsonProperty("isDismissible") var isDismissible: Boolean? = true,
#JsonProperty("showImmediate") var showImmediate: Boolean? = false
) : BaseResponseDTO()
#Parcelize
#JsonIgnoreProperties(ignoreUnknown = true)
class MerchantpromotionBDTO(
#JsonProperty("promotions") var promotions: ArrayList<MerchantPromotionDTO>?=null
) : BaseResponseDTO()
#GET("https://demo6861386.mockable.io/banner/test")
fun getPromotionalBanner(): Call<ArrayList<MerchantpromotionBDTO>>
fun getPromotionalDetails(): LiveData<ResponseDTO<ArrayList<MerchantpromotionBDTO>>>{
val pBannerDetails=MutableLiveData<ResponseDTO<ArrayList<MerchantpromotionBDTO>>>()
ApiComponent.enqueue({
getPromotionalBanner(
)
},object :OnRequestComplete<ArrayList<MerchantpromotionBDTO>>{
override fun onComplete(responseDTO: ResponseDTO<ArrayList<MerchantpromotionBDTO>>) {
pBannerDetails.value=responseDTO
}
}
)
EDIT:
override fun onStart() {
super.onStart()
populateData()
}
#Synchronized
private fun populateData() {
MerchantpromotionBDTO?.let {
val promImageUrl = it.image?.getImageUrl(CommonUtils.getDisplayDensityLevel(context))
picasso.load(if (promImageUrl.isNullOrEmpty()) null else promImageUrl)
.placeholder(R.drawable.ic_placeholder_minimal)
.into(ivMerchantPromotionBanner)
}
}
I want the response into my logcat and to fetch an image in Imageview
The issue is that the JSON you're getting from the backend is:
{
"promotions": [
...
]
}
Thas is a JSON object, not an array, but you defined getPromotionalBanner method as returning a List<MerchantpromotionBDTO>, so the JSON library cannot perform deserialisation as it expects an array (i.e. something starting with [) but it finds a START_OBJECT token (i.e., {).
You can solve the issue by changing the signature of that method to:
#GET("https://demo6861386.mockable.io/banner/test")
fun getPromotionalBanner(): Call<MerchantpromotionBDTO>

Android App crashes as Json element is empty String ("") and not Object

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)
}

Categories

Resources