How to decode dynamic key from JSON using retrofit - Kotlin - android

I'm stuck decoding a dynamic value from a json file with kotlin
{
"values": {
"16694990259825982nLJ": {
"id": "16694990259825982nLJ",
"createdAt": "2022-11-26T21:43:45.982Z",
"name": "Some Text",
"owner": "xxxx#xxxx.xx",
"category": "Some Text",
"description": "Some Text.",
"template_id": "Some Text",
"last_update": "2022-11-27T00:11:51.863Z",
"users": [
"xxxx#xxxx.xx"
]
}
}
}
Here's my data class :
#Serializable
data class WorkflowsTest(
#field:SerializedName("values")
val values: Map<String, Id>
)
#Serializable
data class Id(
#field:SerializedName("owner")
val owner: String? = null,
#field:SerializedName("createdAt")
val createdAt: String? = null,
#field:SerializedName("last_update")
val lastUpdate: String? = null,
#field:SerializedName("name")
val name: String? = null,
#field:SerializedName("description")
val description: String? = null,
#field:SerializedName("template_id")
val templateId: String? = null,
#field:SerializedName("id")
val id: String? = null,
#field:SerializedName("category")
val category: String? = null,
#field:SerializedName("users")
val users: List<String?>? = null
)
This is my ApiResponse data class when fetching Data from GET HTTP URL :
#Serializable
data class ApiResponse(
#Transient
val success: Boolean? = null,
val message: String? = null,
val values: WorkflowsTest? = null,
val status: Int,
#Transient
val error: Exception? = null
)
And this is my retrofit provider network from network module
#Provides
#Singleton
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
Logs that I get when I get the response back :
ApiResponse(success=null, message=null, **values=WorkflowsTest(values=null)**, status=1, error=null)
as you can see values are null no matter what I do, status = 1 means the request is 200 OK, success, error and message are transient and manipulated for snackbar messages.
Test on main function :
fun main() {
val jsonString = """
{
"values": {
"16694990259825982nLJ": {
"id": "16694990259825982nLJ",
"createdAt": "2022-11-26T21:43:45.982Z",
"name": "Some Text",
"owner": "xxxx#xxxx.xx",
"category": "Some Text",
"description": "Some Text.",
"template_id": "Some Text",
"last_update": "2022-11-27T00:11:51.863Z",
"users": [
"xxxx#xxxx.xx"
]
}
}
}"""
val jsonTest: WorkflowsTest =
Gson().fromJson(jsonString, WorkflowsTest::class.java)
println(jsonTest)
}
print result works fine :
WorkflowsTest(values={16694990259825982nLJ=Id(owner=xxxx#xxxx.xx, createdAt=2022-11-26T21:43:45.982Z, lastUpdate=2022-11-27T00:11:51.863Z, name=Some Text, description=Some Text., templateId=Some Text, id=16694990259825982nLJ, category=Some Text, users=[xxxx#xxxx.xx])})
> **UPDATE**
I solved the issue by only changing the Api response data class :
Old :
#Serializable
data class ApiResponse(
#Transient
val success: Boolean? = null,
val message: String? = null,
val values: WorkflowsTest? = null,
val status: Int,
#Transient
val error: Exception? = null
to the new one (focus on the values field) :
#Serializable
data class ApiResponse(
#Transient
val success: Boolean? = null,
val message: String? = null,
val values: Map<String, Id>? = null,
val status: Int,
#Transient
val error: Exception? = null
)
and magically it works.

Related

Json String from websocket to dataClass

I need to convert some json string that im reciving from okhttp websocket binance connection to data class to manipulate the data
override fun onMessage(webSocket: WebSocket, text: String) {
//convert string "text" to dataclass
Log.d("Websocket", text)
}
Log: D/Websocket: {"e":"24hrTicker","E":1661477897574,"s":"BNBUSDT","p":"0.30000000","P":"0.100","w":"301.82156206","x":"298.60000000","c":"298.90000000","Q":"1.06900000","b":"298.80000000","B":"353.26400000","a":"298.90000000","A":"358.58100000","o":"298.60000000","h":"307.50000000","l":"296.00000000","v":"412516.01400000","q":"124506227.71920000","O":1661391497474,"C":1661477897474,"F":581001589,"L":581229754,"n":228166}
String recived:
{
"e": "24hrTicker", // Event type
"E": 123456789, // Event time
"s": "BNBBTC", // Symbol
"p": "0.0015", // Price change
"P": "250.00", // Price change percent
"w": "0.0018", // Weighted average price
"x": "0.0009", // First trade(F)-1 price (first trade before the 24hr rolling window)
"c": "0.0025", // Last price
"Q": "10", // Last quantity
"b": "0.0024", // Best bid price
"B": "10", // Best bid quantity
"a": "0.0026", // Best ask price
"A": "100", // Best ask quantity
"o": "0.0010", // Open price
"h": "0.0025", // High price
"l": "0.0010", // Low price
"v": "10000", // Total traded base asset volume
"q": "18", // Total traded quote asset volume
"O": 0, // Statistics open time
"C": 86400000, // Statistics close time
"F": 0, // First trade ID
"L": 18150, // Last trade Id
"n": 18151 // Total number of trades
}
which is the best implementation? Thanks!
First of all,you need to have a protocol with server end, and settle down all the message IDL models.
Then you can use some tools to transfer IDL to Java Model.
as for json to model, you can use https://www.jsonschema2pojo.org/
But I suggest you use protobuf. which is more effective than json. https://square.github.io/wire/
Gson dependency
implementation 'com.google.code.gson:gson:2.9.1'
Data class of your json is like below
import com.google.gson.annotations.SerializedName
data class Websocket(
#SerializedName("e" ) var e : String? = null,
#SerializedName("E" ) var E : Int? = null,
#SerializedName("s" ) var s : String? = null,
#SerializedName("p" ) var p : String? = null,
#SerializedName("P" ) var P : String? = null,
#SerializedName("w" ) var w : String? = null,
#SerializedName("x" ) var x : String? = null,
#SerializedName("c" ) var c : String? = null,
#SerializedName("Q" ) var Q : String? = null,
#SerializedName("b" ) var b : String? = null,
#SerializedName("B" ) var B : String? = null,
#SerializedName("a" ) var a : String? = null,
#SerializedName("A" ) var A : String? = null,
#SerializedName("o" ) var o : String? = null,
#SerializedName("h" ) var h : String? = null,
#SerializedName("l" ) var l : String? = null,
#SerializedName("v" ) var v : String? = null,
#SerializedName("q" ) var q : String? = null,
#SerializedName("O" ) var O : Int? = null,
#SerializedName("C" ) var C : Int? = null,
#SerializedName("F" ) var F : Int? = null,
#SerializedName("L" ) var L : Int? = null,
#SerializedName("n" ) var n : Int? = null
)
convert your json string to data model
var gson = Gson()
val modelClassOfJsonString: Websocket = gson.fromJson("YOUR JSON STRING", Websocket::class.java)
use modelClassOfJsonString
and if you went to convert model class to json string use
var stringOfmodel : String = gson.toJson(modelClassOfJsonString)

Moshi: Getting Null values to pass through Retrofit 2 from an API Response

The API I am calling has a response that looks like this...
[
{
"id": 755,
"listId": 2,
"name": ""
},
{
"id": 203,
"listId": 2,
"name": ""
},
{
"id": 684,
"listId": 1,
"name": "Item 684"
},
{
"id": 276,
"listId": 1,
"name": "Item 276"
},
{
"id": 736,
"listId": 3,
"name": null
},
{
"id": 926,
"listId": 4,
"name": null
}
]
There are null values inside the response, but I can't get the API call to work because of it. I tried having the 'name' field be to accept a null value, but I got an error. I heard that Moshi (the converter I am using) could have a way to serialize nulls, so that they pass, but not sure how to go about it.
Here is some more code for a better understanding
Network File
interface InfoCollections {
#GET(ENDPOINT)
suspend fun getInfoService(): Response<List<GetInfoJsonResponse>>
}
object NetworkingObject {
val moshi = Moshi.Builder().addLast(KotlinJsonAdapterFactory()).build()
val retrofit: Retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()
val networkingService: InfoCollections by lazy {
retrofit.create(InfoCollections::class.java)
}
val apiClient = ApiClient(networkingService)
The Repository
class InfoRepository(private val infoDao: InfoDao) {
private val allInfoFeeds: LiveData<List<GetInfoJsonResponse>> = infoDao.getAllInfo()
private val _infoFeeds: MediatorLiveData<List<GetInfoJsonResponse>> = MediatorLiveData()
val feeds: LiveData<List<GetInfoJsonResponse>>
get() = _infoFeeds
init {
_infoFeeds.addSource(allInfoFeeds){
_infoFeeds.value = it
}
}
suspend fun fetchInfo(): List<GetInfoJsonResponse>? {
val request = NetworkingObject.apiClient.fetchInfoJsonResponse()
if(request.isSuccessful){
infoDao.insertAll(*request.body()!!.toTypedArray())
return request.body()
}
return null
}
}
The DAO
#Dao
interface InfoDao {
#Query("SELECT * FROM info_response")
fun getAllInfo(): LiveData<List<GetInfoJsonResponse>>
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAll(vararg info: GetInfoJsonResponse)
}
The Data Class
#Parcelize
#Entity(tableName = "info_response")
data class GetInfoJsonResponse(
#PrimaryKey
val id: Int,
val listId: Int,
val name: String
): Parcelable
Here is the error message
Appreciate the help guys, thank you.

Use Moshi to deserialize generic type with child that can be of different types too

I have to work with a server that sends these responses for any request:
For OK:
HTTP 200
{
"jsonrpc": "2.0",
"id": null,
"result": {
"data": {
"exp": 1635637589,
...
...
},
"success": true,
"errors": [],
"messages": []
}
}
For error:
HTTP 200 (yes, and unfortunately that can't be changed)
{
"jsonrpc": "2.0",
"id": null,
"result": {
"data": {},
"success": false,
"errors": [{
"code": 1001,
"message": "Error"
}],
"messages": []
}
}
Notice that data is a json object of a specific type when the response is OK, and a different one when the response is an error. This format is used for all the responses, meaning that data can have different child fields.
I want to use Retrofit + Moshi + RxJava, but I am struggling to find a way to deserialize the response to handle that data field using two different types. I have this model:
data class BaseResponse<T>(
#Json(name = "jsonrpc") val jsonrpc: String,
#Json(name = "id") val id: String?,
#Json(name = "result") val result: BaseResponseResult<T>
)
data class BaseResponseResult<T>(
#Json(name = "data") val data: T, // This is what I have a problem with
#Json(name = "success") val success: Boolean,
#Json(name = "errors") val errors: List<Error>
)
// This would be the data field
data class LoginResponse(
#Json(name = "user_id") val userId: Long,
...
...
...
)
// This would be the data field
data class ProfileResponse(
#Json(name = "name") val name: String,
...
...
...
)
And this would be my Retrofit interface
interface UsersApi {
#POST("api/login")
fun loginReal(#Body request: BaseRequest<LoginRequest>): Single<BaseResponse<LoginResponse>>
#POST("api/profile")
fun loginReal(#Body request: BaseRequest<ProfileRequest>): Single<BaseResponse<ProfileResponse>>
}
I thought about adding a custom deserializer to parse BaseResponse<T> and throw some exception in case the response was an error one, but I am not able to register a deserializer using generics. I have read Moshi's documentation and several posts about deserializers, but I can't get it to work with a generic. Is that possible with Moshi?

Handle random keys with kotlinx serialization

I am trying to serialize the content of a json string that can take the following format:
-723232569: {
"lat": 8.2,
"lon": -90.3,
"schedule": {
"friday": [
{
"date_arr": "friday",
"remarks": " OK",
"time_arr": "07:10",
"time_dep": "06:40",
"trans_name": "C"
}
]
}
However I am struggling with my current serializable class implementation. The top key (-723232569) will vary, it will be generated randomly from one iteration to another. I would like to extract they key and its value with the following class implementation.
#Serializable
data class TimeSlot(val date_arr: String,
val remarks: String,
val time_arr: String,
val time_dep: String,
val trans_link: String,
val trans_name: String,
val trans_tel: String,
val to_lat: String? = null,
val to_lon: String? = null)
#Serializable
data class Schedule(val monday: List<TimeSlot>,
val tuesday: List<TimeSlot>,
val wednesday: List<TimeSlot>,
val thursday: List<TimeSlot>,
val friday: List<TimeSlot>,
val saturday: List<TimeSlot>,
val sunday: List<TimeSlot>)
#Serializable
data class Stop(val lat: Double,
val lon: Double,
val schedule: Schedule)
However when executing the following code I am encountering
try {
val neww = """-723232569: {
"lat": 8.2,
"lon": -90.3,
"schedule": {
"friday": [
{
"date_arr": "friday",
"remarks": " OK",
"time_arr": "07:10",
"time_dep": "06:40",
"trans_name": "C"
}
]
}"""
val res = format.decodeFromString<Stop>(neww)
} catch (ioException: IOException) {
ioException.printStackTrace()
}
Unexpected JSON token at offset 27: Encountered an unknown key '-723232569'.
Use 'ignoreUnknownKeys = true' in 'Json {}' builder to ignore unknown keys.

How to use Retrofit2 to GET list of addresses with the postcode JSON body

I'm using Retrofit2 for the first time, so I'm confused how to proceed. I have the following code
fun getAddressFromPostCode(postCode: String): List<PXAddress>{
val trimmedPostCode = postCode.replace("\\s".toRegex(),"").trim()
val dataBody = JSONObject("""{"postCode":"$trimmedPostCode"}""").toString()
val hmac = HMAC()
val hmacResult = hmac.sign(RequestConstants.CSSecretKey, dataBody)
val body = JSONObject("""{"data":"$dataBody", "data_signature":"$hmacResult"}""").toString()
val url = RequestConstants.getAddress
val retrofit: Retrofit = Retrofit.Builder()
.baseUrl(url)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build()
val address: PXAddress = retrofit.create(PXAddress::class.java)
}
with the idea that body needs to look like this:
"data":{
"postcode": "WA1 1LD"
},
"data_signature": "{{getSignature}}"
}
and the response should be
"success": 1,
"addresses": [
{
"address1": "47 Museum Street",
"address2": null,
"address3": null,
"town": "WARRINGTON",
"county": "",
"postcode": "WA1 1LD"
},
{
"address1": "49a Museum Street",
"address2": null,
"address3": null,
"town": "WARRINGTON",
"county": "",
"postcode": "WA1 1LD"
},
{
"address1": "Leda Recruitment",
"address2": "49 Museum Street",
"address3": null,
"town": "WARRINGTON",
"county": "",
"postcode": "WA1 1LD"
}
]
}
And I need to convert that response into a list of PXAddress which is
open class PXAddress : RealmObject() {
var addressLine1: String? = null
var addressLine2: String? = null
var addressLine3: String? = null
var town: String? = null
var county: String? = null
var postcode: String? = null
}
Your implementation is wrong for some reasons:
Use an interface to define the web service request, you must define a class like this:
interface ApiService {
#POST("your/webservice/path")
fun getPXAddress(#Body dataBody: YourBodyModel): Call<List<PXAddress>>
}
You must call your webservice with a data class as body, the gson converter will convert your models in json, in your main code you must do that:
fun getAddressFromPostCode(postCode: String): List<PXAddress>{
val trimmedPostCode = postCode.replace("\\s".toRegex(),"").trim()
val dataBody = DataBodyObject(postCode = trimmedPostCode)
val hmac = HMAC()
val hmacResult = hmac.sign(RequestConstants.CSSecretKey, dataBody)
val yourBodyModel = YourBodyModel(data = dataBody, data_signature = hmacResult)
val url = RequestConstants.getUrl() // This address must be only the host, the path is specified in ApiService interface
val retrofit: Retrofit = Retrofit.Builder()
.baseUrl(url)
.addConverterFactory(GsonConverterFactory.create())
.build()
val api: ApiService = retrofit.create(ApiService::class.java) // With this you create a instance of your apiservice
val myCall: Call<List<PXAddress>> = api.getPXAddress(yourBodyModel) //with this you can call your service synchronous
}
One last thing, you must call your method asynchronous mode with rxjava, livedata or coroutines. All of them offers converters to retrofit. By default, retrofit has a call method like the example that I show you, you can complete your code doing this:
myCall.enqueue(object : Callback<List<PXAddress>> {
override fun onFailure(call: Call<List<PXAddress>>?, t: Throwable?) {
// Error response
}
override fun onResponse(call: Call<List<PXAddress>>?, response: Response<List<PXAddress>>?) {
// Success response
val myList : List<PXAddress> = response?.body
}
})
Best regards!

Categories

Resources