I'm new in Kotlin and I'm trying to parse a simple JSON, but I'm getting an
" Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull, parameter vouchers"
result.products is always null, but I can see in the logs that retrofit is getting correctly the json with a 200 ok request. So I suppose that could be a problem when I'm trying to parse the json
How can I solve this?
I have add my code below
disposable = ApiServe.getVouchers()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ result -> processVouchers(result.products) },
{ error -> error(error.message)}
)
fun processVouchers(vouchers : List<Product>){
mCallback?.onResponseVouchers(vouchers)
}
GET VOUCHERES in ApiServe class
#GET(Api.ENDPOINT.VOUCHER_ENDPOIN)
fun getVoucher(): Observable<Response<Vouchers>>
MODEL
data class Voucher(val products: List<Product>)
data class Product(val code: String, val name: String, val price: Double)
JSON
{"products":[{"code":"Voucher","name":"Voucher","price":3},{"code":"Ball","name":"Voucher Ball","price":10},{"code":"Milk","name":"Voucher Milk","price":8.5}]}
I think it's because you're wrapping your return type with Response in your Retrofit services interface. Just try to change like this:
#GET(Api.ENDPOINT.VOUCHER_ENDPOIN)
fun getVoucher(): Observable<Voucher>
I believe that the issue here might be that
fun getVouchers(): Observable<Voucher.Vouchers>
Are you sure that getVoucher returns the correct type? Shouldn't it be Observable<Voucher> ?
Edit:
It turned out that author was using excludeFieldsWithModifiers for his GsonConverterFactory, which was causing issues with parsing to model.
Related
I don't understand what is problem clearly. When I searched it in google, I don't decide my reponse model is problem or the json response is problem and should change. Which one? I can't find solution for Kotlin. How I should solve this?
response JSON:
"data":{
"productInfo":{
"data":{
"toBarcode":"2704439285463",
"productJson":{
"p_no":"28420000",
"p_name":"ASA"
}
}
},
"moves":{
"data":[
{
"fisAcik":"MALVERENDEN",
"toBarcode":"2704439285463",
"toJson":{
"to_Hks_Adi":"DAĞITIM MERKEZİ"
},
"movementJson":{
"isleme_Tarihi":"21/12/2022 02:19:30"
}
}
]
}
}
Data.kt
data class Data(
val productInfo: ProductInfo,
val moves: Moves
)
data class Moves (
val data: List<MovesItem>
)
data class MovesItem (
#SerializedName("fisAcik")
val receiptExplanation: String,
val toBarcode: String,
val toJson: ToJson,
val movementJson: MovementJson
)
data class MovementJson (
#SerializedName("isleme_Tarihi")
val processDate: String
)
data class ToJson (
#SerializedName("to_Hks_Adi")
val toUnitHksName: String
)
data class ProductInfo (
val data: ProductInfoItems
)
data class ProductInfoItems (
val toBarcode: String,
val productJson: ProductJson
)
data class ProductJson (
#SerializedName("p_No")
val migrosProductNo: String,
#SerializedName("p_Name")
val migrosProductName: String
)
method that using to call request.
suspend fun dataGetInfo(#Body request: DataRequest): NetworkResult<BaseResponse<Data>>
The framework you are using for this:
...fun dataGetInfo(#Body request: DataRequest)...
is implicitly taking a JSON request and deserializing.
The annotation #SerializedName is a from the Gson library, so I guessed that your framework must be using Gson. From that I was able to test using:
import com.google.gson.Gson
import com.google.gson.annotations.SerializedName
println(Gson().fromJson(src, Data::class.java))
which produces
Data(productInfo=ProductInfo(data=ProductInfoItems(toBarcode=2704439285463, productJson=ProductJson(migrosProductNo=null, migrosProductName=null))), moves=Moves(data=[MovesItem(receiptExplanation=MALVERENDEN, toBarcode=2704439285463, toJson=ToJson(toUnitHksName=DAĞITIM MERKEZİ), movementJson=MovementJson(processDate=21/12/2022 02:19:30))]))
So fundamentally your code is ok, but I think the problem is how the source JSON is "topped and tailed". To get that parse work, I was using
val src = """
{
"productInfo": {
"data": {
"toBarcode": "2704439285463",
"productJson": {
"p_no": "28420000",
"p_name": "ASA"
}
}
},
"moves": {
"data": [
{
"fisAcik": "MALVERENDEN",
"toBarcode": "2704439285463",
"toJson": {
"to_Hks_Adi": "DAĞITIM MERKEZİ"
},
"movementJson": {
"isleme_Tarihi": "21/12/2022 02:19:30"
}
}
]
}
}
"""
Notice how I removed, from your source, "data": since what you pasted is obviously not a JSON document. I guess, therefore, that this is where the problem occurs - something to do with the top or bottom of the JSON document or you need a container object around the JSON for Data
This error was from my wrong request. I saw Ios has same error also when request with wrong value. So, for who will look this quesiton, they should understand it's not from response or kotlin. Check your value it is clearly what request need.
I followed this guide and found this question for handling network exceptions with Retrofit. While they both are very useful, none of them explain how to extract the body of a successful request. Here is a snippet of code that might better explain what I'm trying to accomplish:
The sealed class remains the same from both examples...
// exception handling:
sealed class NetworkResponse<out T : Any, out U : Any> {
data class Success<T : Any>(val body: T) : NetworkResponse<T, Nothing>()
data class ApiError<U : Any>(val body: U, val code: Int) : NetworkResponse<Nothing, U>()
data class NetworkError(val error: IOException) : NetworkResponse<Nothing, Nothing>()
data class UnknownError(val error: Throwable) : NetworkResponse<Nothing, Nothing>()
}
but extracting the body on a successful call is unclear to me. Here is my attempt:
val oAuthRetrofit: Retrofit = Retrofit.Builder()
.baseUrl(OAUTH_BASE_URL)
.addConverterFactory(Json.asConverterFactory(contentType))
.build()
val salesforceOAuthAPI: SalesforceInterface =
oAuthRetrofit.create(SalesforceInterface::class.java)
val oAuthAccessTokenResponse: NetworkResponse<OAuthAccessTokenResponse, Error> =
salesforceOAuthAPI.getOAuthAccessToken(
GRANT_TYPE,
BuildConfig.SALESFORCE_CLIENT_ID,
BuildConfig.SALESFORCE_CLIENT_SECRET,
BuildConfig.SALESFORCE_USERNAME,
BuildConfig.SALESFORCE_PASSWORD + BuildConfig.SALESFORCE_SECURITY_TOKEN
)
val body = oAuthAccessTokenResponse.body // Unresolved reference
// Use body()
How can I extract the body of a successful request?
In the medium post you have access to the Source Code there you can check what you missing.
Answering your question
How can I extract the body of a successful request?
You can simply do this :
val response1 = salesforceOAuthAPI.getWhatever()
when (response1) {
is NetworkResponse.Success -> Log.d(TAG, "Success ${response1.body.name}")
is NetworkResponse.ApiError -> Log.d(TAG, "ApiError ${response1.body.message}")
is NetworkResponse.NetworkError -> Log.d(TAG, "NetworkError")
is NetworkResponse.UnknownError -> Log.d(TAG, "UnknownError")
}
And in the NetworkResponse.Success you have the data you want, but since you are using a sealed class to detect the error and the data, you don't know what is inside that object until you do a when.
By the way, don't know if you followed all the post but you missing the
.addCallAdapterFactory(NetworkResponseAdapterFactory())
In the Retrofit.Builder() otherwise it won't do the magic to encapsulate everything to a NetworkResponse
Create new class for response model like BaseResponse
data class BaseResponse (
val message: String? = "",
val code: Int? = 0,
)
When request success pass the value of retrofit response to generic class you use
NetworkResponse.Success(response)
and post this value to livedata & observe this livedata on view.
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)
}
I'm trying to get news from Guardian API. I'm getting null response, everything is below. I'm using Kotlin, Retrofit and RxJava. I know that there are some miscalled variables/objects but I will change them when I will get rid of that problem.
Retrofit interface
#get:GET("search?api-key=test")
val news:Observable<News>
Retrofit client
val instance : Retrofit
get() {
if (myInstance == null) {
myInstance = Retrofit.Builder()
.baseUrl("https://content.guardianapis.com/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
}
return myInstance!!
}
And function where I'm loading data
private fun loadUrlData() {
compositeDisposable.add(jsonApi.news
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe{news -> displayData(news)})
}
JSON example
{
response:{
status:"ok",
userTier:"developer",
total:2063064,
startIndex:1,
pageSize:10,
currentPage:1,
pages:206307,
orderBy:"newest",
results:[
{
id:"politics/2018/sep/24/keir-starmer-labour-does-not-rule-out-remaining-in-eu",
type:"article",
sectionId:"politics",
sectionName:"Politics",
webPublicationDate:"2018-09-24T18:57:48Z",
webTitle:"Keir Starmer: Labour does not rule out remaining in EU as option",
webUrl:"https://www.theguardian.com/politics/2018/sep/24/keir-starmer-labour-does-not-rule-out-remaining-in-eu",
apiUrl:"https://content.guardianapis.com/politics/2018/sep/24/keir-starmer-labour-does-not-rule-out-remaining-in-eu",
isHosted:false,
pillarId:"pillar/news",
pillarName:"News"
}
]
}
}
Model class
data class News( val status: String, val userTier: String, val total: Int, val startIndex: Int, val pageSize: Int, val currentPage: Int, val pages: Int, val orderBy: String, val results: List<Result>)
I suppose that the problem is with the last function or with the interface but I can't find the solution.
The issues lies within your data model class.
Your JSON has an outer node (response) and if you're trying to return a News you won't get it, because Retrofit can't map the JSON to the News class. Add an outer class called Response that holds a field called response that is of type News, that should fix it.
Like so:
class Response(val response: News)
Note: I haven't added data in front of the class as you don't necessarily need it. The data keyword just adds some extra things for you automatically, like toString(), equals() and hashCode(), but unless you're actually using them for anything I wouldn't recommend adding the data keyword as it's pretty useless then.
New to using API's (and kotlin for that matter) and I'm having some trouble figuring out how to pull data from an API into model objects and was hoping someone could point me in the right direction. Sample code below.
val request = Request.Builder().header("X-Mashape-Key", keyVal).url(url).build()
//make request client
val client = OkHttpClient()
//create request
client.newCall(request).enqueue(object: Callback {
override fun onResponse(call: Call?, response: Response?) {
//grab as string, works fine
val body = response?.body()?.string()
//make my builder, works fine
val gson = GsonBuilder().create()
// to pass type of class to kotlin ::
val cardFeed = gson.fromJson(body, CardFeed::class.java)
}
override fun onFailure(call: Call?, e: IOException?) {
println("Failed to execute request")
}
})
}
All of that seems to work as intended in debug, I get the string, and can see it but using the following it still dumps a null into the cardFeed/cards object.
class CardFeed(val cards: List<Card>)
class Card(val cardId: String, val name: String, val text: String, val flavor: String)
The body string I'm getting from the API reads as follows
body: "{Basic":[{"key":"value", etc
I'm assuming the [ is what's tripping me up, but not sure how to correct it.
Any ideas or nudges in the correct direction would be greatly appreciated and thanks ahead of time.
According to your class structure, the JSON object that you should get from the API should be
{
"cards":[
{
"cardId":"<cardID>",
"name":"<name>",
"flavor":"<flavor>"
},
{
"cardId":"<cardID>",
"name":"<name>",
"flavor":"<flavor>"
}
]
}
with the correct keys because when you use gson.fromJson(body, CardFeed::class.java), it looks for the variable names in the class assigned (CardFeed.java). If you don't have the correct keys ("cards", "cardId", "name", "flavor"), gson would return null for the values
okHttp is a low level library. In order to consume JSON based APIs, Retrofit is a much more suitable library, as it already have converters that use libraries like GSON or Moshi to do all the heavy lifting for you.