Moshi JSON adapter with generic type - android

I want to use a Moshi adapter with a generic type.
Here is my generic type adapter code,
fun <T> getObjectFromJson(typeOfObject: Class<T>, jsonString: String): T? {
val moshi = Moshi.Builder().build()
val jsonAdapter: JsonAdapter<T> = moshi.adapter<T>(
typeOfObject::class.java
)
return jsonAdapter.fromJson(jsonString)!!
}
This code is not working. It is throwing an error,
Platform class java.lang.Class requires explicit JsonAdapter to be registered
But, If I don’t use a generic type like this,
fun getObjectFromJson(jsonString: String): UserProfile? {
val moshi = Moshi.Builder().build()
val jsonAdapter: JsonAdapter<UserProfile> = moshi.adapter<UserProfile>(
UserProfile::class.java
)
return jsonAdapter.fromJson(jsonString)!!
}
Then the code is working fine.
Here is the UserProfile class,
#Parcelize
#JsonClass(generateAdapter = true)
data class UserProfile(
#get:Json(name = "p_contact")
val pContact: String? = null,
#get:Json(name = "profile_pic")
var profilePic: String? = null,
#get:Json(name = "lname")
val lname: String? = null,
#get:Json(name = "token")
var token: String? = null,
#get:Json(name = "fname")
val fname: String? = null,
#SerializedName("_id")
#get:Json(name = "_id")
var id: String? = null,
#get:Json(name = "email")
var email: String? = null,
#SerializedName("refresh_token")
#get:Json(name = "refresh_token")
var refreshToken: String? = null
) : Parcelable

The typeOfObject is an instance of the Class<T> class already, you are calling ::class.java on it unnecessary: it returns Class<Class> and that's not what you want.
Just change
val jsonAdapter: JsonAdapter<T> = moshi.adapter<T>(typeOfObject::class.java)
to
val jsonAdapter: JsonAdapter<T> = moshi.adapter<T>(typeOfObject)
By the way: creating a new Moshi instance on each deserialization is suboptimal. You should reuse it.

Related

Get a specific value of JSON data Kotlin

I am trying to get a specific value from my JSON data. I could successfully call the entire json data,jsonOutput. But the thing is when I call a specific value in the jsonOutput, it shows me nullPointerError. I do not know why I lost the data when I call my data class. I marked the part I lost them with The problem occurs here. How can I get adminArea1?
I shared one data class as a sample. You can create the data classes with "Kotlin data class File from JSON".
I referred to many answers and examples but was hard to know the reason.
My code
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.networkBtn.setOnClickListener(View.OnClickListener {
var thread = NetworkThread()
thread.start()
})
}
inner class NetworkThread : Thread() {
override fun run() {
var url =
URL("https://www.mapquestapi.com/geocoding/v1/reverse?key=LBK8QWxDPYHfmeYVlEP1IO3BVbWHyznB&" +
"location=Your_laptitue,Your_longitute&includeRoadMetadata=true&includeNearestIntersection=true")
var countryCodeBufferedReader =
BufferedReader(InputStreamReader(url.openConnection().getInputStream()))
var stringBuffer = StringBuffer()
do {
var string = countryCodeBufferedReader.readLine()
if (string != null) {
stringBuffer.append(string)
}
} while (string != null)
var jsonObject = JSONObject(stringBuffer.toString())
val gson: Gson = GsonBuilder().setPrettyPrinting().create()
val jsonOutput: String = gson.toJson(jsonObject)
//The problem occurs here
var countryData = gson.fromJson(jsonOutput, NameValuePairsXXXXX::class.java)
val jsonOutput2 = countryData.adminArea1
Log.d("jsonOutput", jsonOutput)
Log.d("jsonOutput2", jsonOutput2)
runOnUiThread {
binding.lapLonText.text = jsonOutput2
}
}
}
}
Data class
Use this class and use Response data class to parse json,
data class Response(
val options: Options? = null,
val results: List<ResultsItem?>? = null,
val info: Info? = null
)
data class Options(
val thumbMaps: Boolean? = null,
val maxResults: Int? = null,
val ignoreLatLngInput: Boolean? = null
)
data class LatLng(
val lng: Double? = null,
val lat: Double? = null
)
data class Info(
val statuscode: Int? = null,
val copyright: Copyright? = null,
val messages: List<Any?>? = null
)
data class ProvidedLocation(
val latLng: LatLng? = null
)
data class Copyright(
val imageAltText: String? = null,
val imageUrl: String? = null,
val text: String? = null
)
data class DisplayLatLng(
val lng: Double? = null,
val lat: Double? = null
)
data class LocationsItem(
val dragPoint: Boolean? = null,
val displayLatLng: DisplayLatLng? = null,
val adminArea4: String? = null,
val unknownInput: String? = null,
val adminArea5: String? = null,
val adminArea6: String? = null,
val postalCode: String? = null,
val adminArea1: String? = null,
val adminArea3: String? = null,
val sideOfStreet: String? = null,
val type: String? = null,
val adminArea6Type: String? = null,
val geocodeQualityCode: String? = null,
val adminArea4Type: String? = null,
val linkId: String? = null,
val roadMetadata: Any? = null,
val street: String? = null,
val nearestIntersection: Any? = null,
val adminArea5Type: String? = null,
val mapUrl: String? = null,
val geocodeQuality: String? = null,
val adminArea1Type: String? = null,
val adminArea3Type: String? = null,
val latLng: LatLng? = null
)
data class ResultsItem(
val locations: List<LocationsItem?>? = null,
val providedLocation: ProvidedLocation? = null
)
var countryData = gson.fromJson(jsonOutput, Reponse::class.java)
It was caused due to API communication. I solved my problem by putting okHttpClient. I added the code to help anybody having the same question.
val client = OkHttpClient()
val request = Request.Builder().url(url).build()
client.newCall(request).enqueue(object :Callback{
override fun onFailure(call: Call, e: IOException) {
Log.d("fail", "fail")
}
override fun onResponse(call: Call, response: okhttp3.Response) {
var body = response.body?.string()
Log.d("body", "$body")
val jsonObject2 : JSONObject = JSONObject(body.toString())
val jsonOutput2 = gson.fromJson(body, Response::class.java)
val test2 = jsonOutput2.results?.get(0)?.locations?.get(0)?.adminArea1.toString()
Log.d("test2", test2) }}

Kotlin-Moshi adapter for parameterized type

I have a parameterized base class
#JsonClass(generateAdapter = true)
data class BaseResponse<T>(
#Json(name = "message")
val message: String?,
#Json(name = "data")
val data: T? = null
)
I want to get parse a JSON string and get the message value
private inline fun <reified T> getMessage(): String? {
return try {
val jsonStr = "{\"message\":\"Email or password not provided\"}"
val types = Types.newParameterizedType(
BaseResponse::class.java,
T::class.java
)
val moshiAdapter = Moshi.Builder().build().adapter(types)
val baseResponse = moshiAdapter.fromJson(jsonStr)
baseResponse?.message
} catch (exception: Exception) {
null
}
}
Got compile error at the adapter function
How I call this function
val str = getMessage<Any>()
You're not specifying that you're parsing a BaseResponse, just replace your adapter creation by this
val moshiAdapter = Moshi.Builder().build().adapter<BaseResponse<T>>(types)

Moshi: Platform class java.lang.Class requires explicit JsonAdapter to be registered

I'm using a Moshi adapter to get the object from a JSON string. But, I'm getting an error,
java.lang.IllegalArgumentException: Platform class java.lang.Class requires explicit JsonAdapter to be registered
All the data members of the UserProfile class are only String type then why it is asking to create an explicit JsonAdapter?
MoshiDataConverter
class MoshiDataConverter() {
fun <T> getObjectFromJson(typeOfObject: Class<T>, jsonString: String): T? {
val moshi = Moshi.Builder().build()
val jsonAdapter: JsonAdapter<T> = moshi.adapter<T>(
typeOfObject::class.java
)
return jsonAdapter.fromJson(jsonString)!!
}
}
getObject method
fun <T> getObject(#Keys key: String?, typeOfObject: Class<T>?): T? {
val value = getString(key, null) ?: return null
return MoshiDataConverter().getObjectFromJson(typeOfObject!!, value)
}
UserProfile.kt
#Parcelize
#JsonClass(generateAdapter = true)
data class UserProfile(
#get:Json(name = "p_contact")
val pContact: String? = null,
#get:Json(name = "profile_pic")
var profilePic: String? = null,
#get:Json(name = "lname")
val lname: String? = null,
#get:Json(name = "token")
var token: String? = null,
#get:Json(name = "fname")
val fname: String? = null,
#SerializedName("_id")
#get:Json(name = "_id")
var id: String? = null,
#get:Json(name = "email")
var email: String? = null,
#SerializedName("refresh_token")
#get:Json(name = "refresh_token")
var refreshToken: String? = null
) : Parcelable
Your typeOfObject parameter is already a class reference, so you don't need to call ::class.java on it before passing it to the Moshi adapter.
Try replacing
val jsonAdapter: JsonAdapter<T> = moshi.adapter<T>(
typeOfObject::class.java
)
with
val jsonAdapter: JsonAdapter<T> = moshi.adapter<T>(typeOfObject)

Kotlin Retrofit response.body() returned null

So I'm trying to fetch an API with endpoint https://someURL.com/switching-product/switch?orderID=A001,
This is the returned JSON format as seen from Postman.
So I use this Interface in the NetworkConfig.kt class to fetch this endpoint:
//Endpoint: https://someURL.com/switching-product/switch?orderID=A001
interface getOutstandingOrderDetail{
#GET("switch")
fun getOutstandingOrderDetail(#Query("orderID") orderID: String): Call<OutstandingOrderDetailPOJODataClassData>
}
and store it in this data class (Generated by an Android Extension that generate POJO from JSON):
data class OutstandingOrderDetailPOJODataClass(
#field:SerializedName("data")
val data: OutstandingOrderDetailPOJODataClassData? = null,
#field:SerializedName("error")
val error: Error? = null
)
data class OutstandingOrderDetailPOJODataClassData(
#field:SerializedName("Header")
val header: OutstandingOrderDetailPOJODataClassHeader? = null,
#field:SerializedName("Detail")
val detail: List<OutstandingOrderDetailPOJODataClassDetailItem?>? = null
)
data class OutstandingOrderDetailPOJODataClassHeader(
#field:SerializedName("buyer_address")
val buyerAddress: String? = null,
#field:SerializedName("total_price")
val totalPrice: Int? = null,
#field:SerializedName("buyer_name")
val buyerName: String? = null,
#field:SerializedName("status_confirmed_yn")
val statusConfirmedYn: String? = null,
#field:SerializedName("order_date")
val orderDate: String? = null,
#field:SerializedName("outlet_id")
val outletId: String? = null,
#field:SerializedName("nip")
val nip: String? = null,
#field:SerializedName("jumlah_product")
val jumlahProduct: Int? = null,
#field:SerializedName("last_update")
val lastUpdate: String? = null,
#field:SerializedName("phone_number")
val phoneNumber: String? = null,
#field:SerializedName("order_running_id")
val orderRunningId: Int? = null,
#field:SerializedName("status_tagged_yn")
val statusTaggedYn: String? = null,
#field:SerializedName("order_id")
val orderId: String? = null
)
data class OutstandingOrderDetailPOJODataClassError(
#field:SerializedName("msg")
val msg: String? = null,
#field:SerializedName("code")
val code: Int? = null,
#field:SerializedName("status")
val status: Boolean? = null
)
data class OutstandingOrderDetailPOJODataClassDetailItem(
#field:SerializedName("item_price_new")
val itemPriceNew: Int? = null,
#field:SerializedName("item_name_new")
val itemNameNew: String? = null,
#field:SerializedName("total_price")
val totalPrice: Int? = null,
#field:SerializedName("item_price")
val itemPrice: Int? = null,
#field:SerializedName("item_name")
val itemName: String? = null,
#field:SerializedName("status_refund")
val statusRefund: String? = null,
#field:SerializedName("detail_id")
val detailId: Int? = null,
#field:SerializedName("procode_new")
val procodeNew: String? = null,
#field:SerializedName("refund_date")
val refundDate: String? = null,
#field:SerializedName("request_refund")
val requestRefund: String? = null,
#field:SerializedName("procode")
val procode: String? = null,
#field:SerializedName("last_update")
val lastUpdate: String? = null,
#field:SerializedName("item_qty_new")
val itemQtyNew: Int? = null,
#field:SerializedName("order_id")
val orderId: String? = null,
#field:SerializedName("total_price_new")
val totalPriceNew: Int? = null,
#field:SerializedName("item_qty")
val itemQty: Int? = null,
#field:SerializedName("refund")
val refund: Int? = null
)
This is a code snippet of the fragment where I fetch the URL data:
private fun fetchOrderedItemListData() {
NetworkConfig()
.getOutstandingDetailOrderedItemListService()
.getOutstandingOrderDetailOrderedItemList("A001")
.enqueue(object :
Callback<OutstandingOrderDetailPOJODataClassData> {
override fun onFailure(
call: Call<OutstandingOrderDetailPOJODataClassData>,
t: Throwable
) {
Log.i("Order", "It Failed!!")
if (call.isCanceled) {
Toast.makeText(
(activity as AppCompatActivity),
"Request Aborted",
Toast.LENGTH_SHORT
).show()
} else {
Toast.makeText(
(activity as AppCompatActivity),
t.localizedMessage,
Toast.LENGTH_SHORT
).show()
}
}
override fun onResponse(
call: Call<OutstandingOrderDetailPOJODataClassData>,
response: Response<OutstandingOrderDetailPOJODataClassData>
) {
Log.i("Order", "Ordered Item FEtched -> \n ${response.body()}") <= This is for debugging purpose
binding.rvOrderedItemList.adapter =
response.body()
?.let { OrderedItemListAdapter(it, this#OrderDetailFragment) }
binding.rvOrderedItemList.layoutManager =
LinearLayoutManager((activity as AppCompatActivity))
}
})
}
As you can see above I use Log.i to try to print the response.body() in the LogCat, However it returns null in the Log Cat. You can see it here.
Am I missing something ? If there's any detail that I left out for this question, feel free to ask.
Your Retrofit function is set up as:
#GET("switch")
fun getOutstandingOrderDetail(#Query("orderID") orderID: String): Call<OutstandingOrderDetailPOJODataClassData>
The data type in the Call<> needs to match your JSON. You have data classes that match your JSON... but the root of that hierarchy is OutstandingOrderDetailPOJODataClass, not OutstandingOrderDetailPOJODataClassData. So, change your Retrofit function to:
#GET("switch")
fun getOutstandingOrderDetail(#Query("orderID") orderID: String): Call<OutstandingOrderDetailPOJODataClass>

No args error retrofit request body

I am facing problem while sending json object body using retrofit to the server. Below is the error.
Failed to invoke public
com.nitesh.brill.saleslines._User_Classes.User_PojoClass.UpdatePreviousDetails()
with no args
code snippet
// Api endpoint
#Headers("Content-Type: application/json")
#POST("UpdatePreviousDetails/{Id}")
fun updatePreviousDetails(#Path("Id") Id: Int, #Body updateDetails :UpdatePreviousDetails): Call<UpdatePreviousDetails>
//pojo class
package com.nitesh.brill.saleslines._User_Classes.User_PojoClass
import java.util.*
/**
* Created by Nitesh Android on 16-08-2017.
*/
class UpdatePreviousDetails(
var CompanyName: String? = null!!,
var Designation: String? = null!!,
var DateOfJoin: Date? = null!!,
var DateOfLeaving: Date? = null!!,
var SectorPreviouslyWorked: String? = null!!,
var Id: Int? = null!!
) {
}
//sending data
val details = UpdatePreviousDetails("rr", "asm", date, date, "Pharmaceuticals",3)
val call = apiEndpointInterface!!.updatePreviousDetails(5, details)
call.enqueue(object :Callback<UpdatePreviousDetails> {
override fun onResponse(call: Call<UpdatePreviousDetails>?, response: Response<UpdatePreviousDetails>?) {
objUsefullData.showSnackBar("success")
UsefullData.Log("============="+response!!.body().toString())
}
override fun onFailure(call: Call<UpdatePreviousDetails>?, t: Throwable?) {
objUsefullData.showSnackBar("fail")
UsefullData.Log("============="+t)
}
})
I am using kotlin language
Your UpdatePreviousDetails class has to have a constructor with no params to enable Gson (inside Retrofit) to convert your object into JSON.
EDIT
class UpdatePreviousDetails() {
var CompanyName: String? = null
var Designation: String? = null
var DateOfJoin: Date? = null
var DateOfLeaving: Date? = null
var SectorPreviouslyWorked: String? = null
var Id: Int? = null
constructor(
CompanyName: String?,
Designation: String?,
DateOfJoin: Date?,
DateOfLeaving: Date?,
SectorPreviouslyWorked: String?,
Id: Int?
) : this() {
this.CompanyName = CompanyName
this.Designation = Designation
this.DateOfJoin = DateOfJoin
this.DateOfLeaving = DateOfLeaving
this.SectorPreviouslyWorked = SectorPreviouslyWorked
this.Id = Id
}
}

Categories

Resources