Trouble parsing a JSON string with unknown keys using Retrofit and Gson - android

I have a JSON response that includes unknown keys (the numbers), which I'm finding difficult to parse using the Gson converter. The cut-down version is
{
"list": {
"14": {
"nickname": "Dave",
"fullname": "David Collins"
},
"68": {
"nickname": "Phil",
"fullname": "Phillip Jordan"
}
},
"action": "load",
"status": "LOADED"
}
The following class structure gives an error Expected BEGIN_OBJECT but was NUMBER at line 1 column 23 path $.list..
data class NameNW(
val fullname: String,
val nickname: String
)
data class NamesListNWEntity(
val action: String,
val list: Map<Int, Map<String, NameNW>>,
val status: String
)
I'm not sure why it's expecting BEGIN_OBJECT when the type is Map<Int... (or perhaps it's not seeing the '{' before the number when it's clearly there), so I'm stuck here. How do I get it to parse the string? Even better, how do I get it to record the number for NameNW? If it's not possible, I can adjust the server output, but that means updating a lot of code on the web client as well, which I'd rather avoid.
My Retrofit code is
interface Network {
#FormUrlEncoded
#POST("serverfile")
suspend fun getNames(
#Field("action") action: String,
): NamesListNWEntity
}
class RetrofitWithCookie #Inject constructor(
context: Context,
gson: Gson
) {
private val mContext = context
private val gson = gson
fun createRetrofit(): Retrofit {
val client: OkHttpClient
val builder = OkHttpClient.Builder()
builder.addInterceptor(AddCookiesInterceptor(mContext))
builder.addInterceptor(ReceivedCookiesInterceptor(mContext))
client = builder.build()
return Retrofit.Builder()
.baseUrl("http://192.168.0.19/")
.client(client)
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
}
}
And I'm calling it using
val namesListNWEntity = Network.getNames("load")

Try with the following code.
data class NameNW(
val fullname: String,
val nickname: String
)
data class NamesListNWEntity(
val action: String,
val list: Map<String, NameNW>,
val status: String
)
// You can also test with static json string
private fun test() {
val apiResponse = "{\n" +
" \"list\": {\n" +
" \"14\": {\n" +
" \"nickname\": \"Dave\",\n" +
" \"fullname\": \"David Collins\"\n" +
" },\n" +
" \"68\": {\n" +
" \"nickname\": \"Phil\",\n" +
" \"fullname\": \"Phillip Jordan\"\n" +
" }\n" +
" },\n" +
" \"action\": \"load\",\n" +
" \"status\": \"LOADED\"\n" +
"}"
val namesListNWEntity: NamesListNWEntity = Gson().fromJson(apiResponse, NamesListNWEntity::class.java)
}

Related

{ "status": "FAIL", "code": "400002", "errorMessage": "Signature for this request is not valid." }

Im trying to get "create order" api from binance pay api to make payments in my kotlin android application. I am following api documentation to build the payload and signature but unfortunately its not working.
Im getting this error : "errorMessage": "Signature for this request is not valid."
Anyone can help?
private fun createSignature(payload: String, secretKey: String): String {
val sha512HMAC = Mac.getInstance("HmacSHA512")
val secretKeySpec =
SecretKeySpec(secretKey.toByteArray(), "HmacSHA512")
sha512HMAC.init(secretKeySpec)
val digest = sha512HMAC.doFinal(payload.toByteArray())
return digest.toHex()
}
val timestamp = System.currentTimeMillis() + clockOffset
val nonceStr = generateNonce()
val body = Gson().toJson(
OrderRequest(
Env("APP"),
merchantTradeNb,
0.02,
"USDT",
Goods("01", "D000", "7876763A3C", "phone", "Good new phone")
)
)
val payload = timestamp.toString() + "\n" + nonceStr + "\n" + body + "\n"
val signature: String = createSignature(
payload.toByteArray(Charsets.UTF_8).toString(), secretKey
).uppercase()
val retrofitData = retrofitBuilder.binanceApi.createOrder(
"application/json", timestamp, nonceStr, certSn, signature, body
)
retrofitData.enqueue(object : Callback<OrderResponse> {
override fun onResponse(
call: Call<OrderResponse>,
response: Response<OrderResponse>,
) {
response.body()
}
override fun onFailure(call: Call<OrderResponse>, t: Throwable) {
}
})
}
interface BinancePayApi {
#POST("/binancepay/openapi/v2/order")
fun createOrder(
#Header("Content-type") contentType: String,
#Header("BinancePay-Timestamp") timestamp: Long,
#Header("BinancePay-Nonce") nonce: String,
#Header("BinancePay-Certificate-SN") apiKey: String,
#Header("BinancePay-Signature") signature: String,
#Body request: String
): Call<OrderResponse>
}
I guess the problem is in the signature as per the documentation you need to do this
String signature = hex(hmac("sha512", payload, secretKey)).toUpperCase()
So, to do this in kotlin you can do it as follows
fun hmac(algorithm: String, data: String, secretKey: String): ByteArray {
val secret = SecretKeySpec(secretKey.toByteArray(), algorithm)
val mac = Mac.getInstance(algorithm)
mac.init(secret)
return mac.doFinal(data.toByteArray())
}
Then you need to convert it to hex so you can create this method (I see that you are using this .toHex() but I don't know your implementation)
fun hex(bytes: ByteArray): String {
val hexChars = "0123456789ABCDEF".toCharArray()
val result = StringBuilder(bytes.size * 2)
for (b in bytes) {
result.append(hexChars[b.toInt().shr(4) and 0x0f])
result.append(hexChars[b.toInt() and 0x0f])
}
return result.toString()
}
Then the only thing you need to do is wrap it up, you can do it as follows :
val signature = hex(hmac("SHA-512", payload, secretKey)).toUpperCase()
I'm wondering if the problem you are having is that you are using HmacSHA512 instead of SHA-512.
I'm reading documentation you posted, and I read this example:
(https://developers.binance.com/docs/binance-pay/app-integration)
String payload = "certSn=317c110ebf7baf641e8f49ab6851331737dbe98541947c53b9dbba27f8c8414f" + "&" + "merchantId=98765987" + "&" + "noncestr=5K8264ILTKCH16CQ2502SI8ZNMTM67VS" + "&" + "prepayId=98765987" + "&"+ "timeStamp=1618302830073";String signature = hex(hmac("sha512", payload, secretKey)).toUpperCase();
And in your createSignature method I don't see this logic.

Use JsonReader.setLenient(true) to accept malformed JSON in kolin using retrofit

I am using retrofit2 for API calls. When it called to my API it shows below error;
Use JsonReader.setLenient(true) to accept malformed JSON at line 1 column 1 path $
#Step1: Here is my JONSON
[
{
"branchCode": "053",
"logitude": " ",
"phone": " +8809613225301",
"latitude": " ",
"sl": "1",
"branchName": "A K KHAN MOOR BRANCH",
"branchAddress": "The Downing(1st Floor), H-825,, Zakir Hossain Road, A K Khan,",
"opendate": " ",
"fax": " n/a"
},
{
"branchCode": "058",
"logitude": " ",
"phone": " ",
"latitude": " ",
"sl": "2",
"branchName": "AGANAGAR BRANCH",
"branchAddress": "Aganagar Branch, Shawon Plaza (1st Floor)",
"opendate": " ",
"fax": " "
}
]
#2Step: here is my data class
data class LocationdataModel(
#SerializedName("sl")
var sl: String = "",
#SerializedName("atmCode")
var atmCode: String = "",
#SerializedName("atmLocation")
var atmLocation: String = "",
#SerializedName("atmLogitude")
var atmLogitude: String = "",
#SerializedName("atmLatitued")
var atmLatitued: String = "",
#SerializedName("branchCode")
var branchCode: String = "",
#SerializedName("branchName")
var branchName: String = "",
#SerializedName("branchAddress")
var branchAddress: String = "",
#SerializedName("phone")
var phone: String = "",
#SerializedName("fax")
var fax: String = "",
#SerializedName("logitude")
var logitude: String = "",
#SerializedName("latitude")
var latitude: String = "",
#SerializedName("opendate")
var opendate: String = "",
#SerializedName("atmName")
var atmName: String = ""
)
#Step3: here is my API
#GET("api/location_api")
fun getBranchLocation(
#Query("requestCode") requestCode: String?
): Single<ArrayList<LocationdataModel>>
#Step4: My API Server from where I called my api
var okHttpClient: OkHttpClient? = OkHttpClient.Builder()
.connectTimeout(50, TimeUnit.SECONDS)
.readTimeout(50, TimeUnit.SECONDS)
.writeTimeout(50, TimeUnit.SECONDS)
.callTimeout(50, TimeUnit.SECONDS)
.addInterceptor { chain ->
val original = chain.request()
val requestBuilder = original.newBuilder()
.header("Authorization", "myauth")
val request = requestBuilder.build()
chain.proceed(request)
}
.build()
private val api = Retrofit.Builder()
.baseUrl(baseurl)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(okHttpClient)
.build()
.create(Api::class.java)
fun getBranchLocation(model: LocationModel): Single<ArrayList<LocationdataModel>> {
return api.getBranchLocation(
model.requestCode
)
}
#Step5: here is my ViewModel
fun getBranchLocation(model: LocationModel){
disposable.add(apiService.getBranchLocation(model)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(object : DisposableSingleObserver<List<LocationdataModel>>() {
override fun onSuccess(model: List<LocationdataModel>) {
model.let {
getBranchLocationonList_response.value = model
}
}
override fun onError(e: Throwable) {
Log.e("error-->", e.message.toString())
e.printStackTrace()
}
})
Whate is the wrong with my code? Please help me

JSON parsing using Gson returns an error for a boolean value

I have the following json generated from a WordPress Post:
{
"events":[
{
"id":4651,
"title":"Test title",
"description":"testttt",
"image": {
url: "https://myhost.tv/wp-content/uploads/2019/07/event2.jpg",
id: "4652"}
}
]
}
My model for json is as follows:
data class EventsFeed(val events: Array<Events>)
data class Events (
val id : Int,
val title : String,
val description : String,
val image : Image
)
data class Image (
val url : String,
val id : Int,
)
I do the parsing with Json and everything works fine, but when I do a POST in wordpress and I don't put an image, the value of the Image key puts me as afalse, like this:
{
"events":[
{
"id":4651,
"title":"Stand Up Comedy",
"description":"testttt",
"image":false
}
]
}
And just because image has the valuefalse, the parsing returns an error: Expected BEGIN_OBJECT but was BOOLEAN at line 1 column 5781 path $ .events [1] .image
What can I do so that when the post has no image to parse correctly ignoring the value false or in any case if it isfalse please keep it a default image (https://myhost.com/image_defaul.jpg)
The json is generated by the plugin for Wordpress: The Events Calendar: Demo json here
My function (usen Volley and Gson) for parsing is as follows (the data array is sent to an adapter for display in a recyclerview)
fun jsonObjectRequest() {
Log.i(LOG_TAG, "jsonObjectRequest")
// Instantiate the RequestQueue.
val queue = Volley.newRequestQueue(activity)
val url = "https://myhost.tv/wp-json/tribe/events/v1/events"
// Request a JSONObject response from the provided URL.
val jsonObjectRequest = JsonObjectRequest( url, null,
Response.Listener { response ->
Log.i(LOG_TAG, "Response is: $response")
val gson = Gson()
val homeEvents = gson.fromJson(response.toString(), EventsFeed::class.java)
activity?.runOnUiThread {
recyclerEvents.adapter = AdaptadorEventos(homeEvents)
}
},
Response.ErrorListener { error ->
error.printStackTrace()
Log.e(LOG_TAG, "That didn't work!")
}
)
// Add the request to the RequestQueue.
queue.add(jsonObjectRequest)
}
The reason why your post method is not getting the Image Class object is because of the json is not a valid json, You may validate it on https://jsonlint.com/. The reason is: "url" and "id" keys are not surrounded by "". Take a look at the below solution, it works perfectly:
package com.example.myapplication
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*
import com.google.gson.Gson
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
getEvemtsObject()
}
private val jsonString = " {\n" +
" \t\"events\": [{\n" +
" \t\t\"id\": 4651,\n" +
" \t\t\"title\": \"Test title\",\n" +
" \t\t\"description\": \"testttt\",\n" +
" \t\t\"image\": {\n" +
" \t\t\t\"url\": \"https://myhost.tv/wp-content/uploads/2019/07/event2.jpg\",\n" +
" \t\t\t\"id\": \"4652\"\n" +
" \t\t}\n" +
" \t}]\n" +
" }"
private fun getEvemtsObject() {
val gson = Gson()
System.out.println("from_gson ---> " + gson.fromJson<EventsFeed>(jsonString,EventsFeed::class.java))
}
}
You can use custom Gson deserializer like this:
class EventsDeserializer : JsonDeserializer<Events> {
override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Events {
val jsonObject = json.asJsonObject
return Events(
id = jsonObject.get("id").asInt,
title = jsonObject.get("title").asString,
description = jsonObject.get("description").asString,
image = parseImage(context, jsonObject.get("image"))
)
}
private fun parseImage(context: JsonDeserializationContext, json: JsonElement): Image =
try {
context.deserialize(json, Image::class.java)
} catch (_: Throwable) {
Image("https://myhost.com/image_defaul.jpg", 0)
}
}
and here is the test of your json:
fun main() {
val gson = GsonBuilder()
.registerTypeAdapter(Events::class.java, EventsDeserializer())
.create()
val json = """
{
"events": [
{
"id": 4651,
"title": "Stand Up Comedy",
"description": "testttt",
"image": false
}
]
}
""".trimIndent()
val events = gson.fromJson(json, EventsFeed::class.java)
}
the image parameter in constructor should be nullable.
rewrite your class like this
data class Events (
val id : Int,
val title : String,
val description : String,
var image : Image? = null)
and change your API to send null instead of false.
This might also be an option (but fixing the API is the better option):
val image: Any

Gson - How to parse partial json in Kotlin

I have a data class called Model,
data class Model(var name: String? = null, var address: String? = null) {
override fun toString(): String {
return "name: $name address: $address"
}
}
I have two json strings,
val json1 = "{ \"name\": \"Alex\", \"address\": \"rome, 1000\" }"
val json2 = "{ \"name\": \"Alex\", \"address\": {\"city\": \"rome\", \"post\": \"1000\" } }"
Below conversion is working fine,
val model1 = Gson().fromJson<Model>(json1, Model::class.java)
But, this version does not work. Giving an exception.
val model2 = Gson().fromJson<Model>(json2, Model::class.java)
Exception:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.achellies.kotlin, PID: 11211
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.achellies.kotlin/com.achellies.kotlin.MainActivity}: com.google.gson.JsonSyntaxException: com.google.gson.stream.MalformedJsonException: Unterminated object at line 1 column 40 path $.address
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3086)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3229)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1926)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:214)
at
How can i parse json2 so that address field will be holding the string value of the inner address json data? For example after parsing address should be equal to
{"city": "rome", "post": "1000"}
Did you try with the Any?
data class Model(var name: String? = null, var address: Any? = null) {
override fun toString(): String {
return "name: $name address: $address"
}
}
Try with this, it will work.
Have a good day. :)
You may use a JsonDeserializer specialized for your Model class, like so:
fun main() {
val json1 = "{ \"name\": \"Alex\", \"address\": \"rome, 1000\" }"
val json2 = "{ \"name\": \"Alex\", \"address\": {\"city\": \"rome\", \"post\": \"1000\" } }"
val gson = GsonBuilder().registerTypeAdapter(Model::class.java, ModelDeserializer()).create()
println(gson.fromJson(json1, Model::class.java))
println(gson.fromJson(json2, Model::class.java))
}
data class Model(val name: String, val address: String)
class ModelDeserializer : JsonDeserializer<Model> {
override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Model {
json as JsonObject
val name = json.get("name").asString
val addressJson = json.get("address")
val address = if (addressJson.isJsonObject) addressJson.asJsonObject.toString() else addressJson.asString
return Model(name, address)
}
}
Which outputs:
Model(name=Alex, address=rome, 1000)
Model(name=Alex, address={"city":"rome","post":"1000"})
If you use the Kotson library, which seems you are, things get even better:
val gson = GsonBuilder()
.registerTypeAdapter<Model> {
deserialize {
val json = it.json.asJsonObject
val name = json["name"].asString
val addressJson = json["address"]
val address = if (addressJson.isJsonObject) addressJson.asJsonObject.toString() else addressJson.asString
Model(name, address)
}
}
.create()
println(gson.fromJson<Model>(json1))
println(gson.fromJson<Model>(json2))
Please try with Any, it should work.
var address: String? = null
If address is a valid json by it self then you can do this:
val jsonObject = JSONObject(jsonString)
val address = jsonObject.optJSONObject("address").toString()
If it is a string then
val address = jsonObject.optString("address").toString()

Why LinkedTreeMap is generated after deserializing to another class using Gson?

I have JSON structure like following:
{
"status": 0,
"error_message": null,
"data": {
"1": {
"7": [
{
"id": "1",
"person_id": "3",
"friend_id": "2"
}
]
}
}
As you've noticed, the number of indexes(1, 7) are dynamic, I mean, they can be more. So, to parse this JSON, I decided to use Map. 7 can be used as a key, when id, person_id, friend_id can be a class named, for example, Model. So, firstly, I tried to get 1 from data in JSON and convert it to Map as I described above. Here is how I did it:
val data = myJson.getAsJsonObject("data").get("1")
val gson = Gson()
val type = object : TypeToken<Map<String, ArrayList<Model>>>() {
}.type
val myMap: Map<String, ArrayList<Model>> = gson.fromJson(data, type)
Everything was alright, until I referred to some field of my Model. Here is how I did it:
First of all I tried to refer to myMap as following:
As you see, instead of getting Model, there are list of LinkedTreeMap. So, when I refer to the field of Model, I got CastException.
So, how to solve this problem? I tried this not using TypeToken, but it didn't help.
you gave the wrong json one single closure "}" I think you've copied wrong
create Gson2Application class
class Gson2Application
val jj = "{\n" +
"\"status\": 0,\n" +
"\"error_message\": null,\n" +
"\"data\": {\n" +
"\"1\": {\n" +
" \"7\": [\n" +
" {\n" +
" \"id\": \"1\",\n" +
" \"person_id\": \"3\",\n" +
" \"friend_id\": \"2\"\n" +
" }\n" +
" ]\n" +
" }\n" +
"}\n" +
"}"
data class Model(val id: Long, val person_id: Long, val friend_id: Long)
fun main(args: Array<String>) {
var myJson = JsonParser().parse(jj).asJsonObject
val data = myJson.getAsJsonObject("data").get("1")
val type = object : TypeToken<Map<String, ArrayList<Model>>>() {}.type // the Map is interfacem then gson get default LinkedTreeMap extedn AbstractMap<K,V> implements Map
val fromMapDefault = Gson().fromJson<Map<String, List<Model>>>(data, type)
printResult(fromMapDefault)
// result
// {{7=[Model(id=1, person_id=3, friend_id=2)]} class com.google.gson.internal.LinkedTreeMap--> 1}
// {[Model(id=1, person_id=3, friend_id=2)] class java.util.ArrayList--> 1}
// {Model(id=1, person_id=3, friend_id=2) class pl.jac.gson2.Model --> 2}
// {1 long--> 3}
val typeHashMap = object : TypeToken<HashMap<String, ArrayList<Model>>>() {}.type // the HashMap is an implementation
val fromHashMap = Gson().fromJson<Map<String, List<Model>>>(data, typeHashMap)
printResult(fromHashMap)
//result
// {{7=[Model(id=1, person_id=3, friend_id=2)]} class java.util.HashMap--> 1}
// {[Model(id=1, person_id=3, friend_id=2)] class java.util.ArrayList--> 1}
// {Model(id=1, person_id=3, friend_id=2) class pl.jac.gson2.Model --> 2}
// {1 long--> 3}
}
private fun printResult(fromMapDefault: Map<String, List<Model>>) {
println("""
result
{${fromMapDefault} ${fromMapDefault?.javaClass}--> 1}
{${fromMapDefault["7"]} ${fromMapDefault["7"]?.javaClass}--> 1}
{${fromMapDefault["7"]?.get(0)} ${fromMapDefault["7"]?.get(0)?.javaClass} --> 2}
{${fromMapDefault["7"]?.get(0)?.id} ${fromMapDefault["7"]?.get(0)?.id?.javaClass}--> 3}
""".trimIndent())
}
}
private fun printResult(fromMapDefault: Map<String, List<Model>>) {
println("""
result
{${fromMapDefault} ${fromMapDefault?.javaClass}--> 1}
{${fromMapDefault["7"]} ${fromMapDefault["7"]?.javaClass}--> 1}
{${fromMapDefault["7"]?.get(0)} ${fromMapDefault["7"]?.get(0)?.javaClass} --> 2}
{${fromMapDefault["7"]?.get(0)?.id} ${fromMapDefault["7"]?.get(0)?.id?.javaClass}--> 3}
""".trimIndent())
}
i change to yours model ClassStatus
data class ClassStatus(val status: Int, val error_message: String?, val data: Map<String,Map<String, ArrayList<Model>>>)
data class Model(val id: Long, val person_id: Long, val friend_id: Long)
fun main(args: Array<String>) {
val fromJson = Gson().fromJson<ClassStatus>(jj, ClassStatus::class.java)
printResult(fromJson)
//result
//ClassStatus(status=0, error_message=null, data={1={7=[Model(id=1, person_id=3, friend_id=2)]}}) class pl.jac.gson2.not.ClassStatus--> 1
//{1={7=[Model(id=1, person_id=3, friend_id=2)]}} class com.google.gson.internal.LinkedTreeMap--> 2
//{7=[Model(id=1, person_id=3, friend_id=2)]} class com.google.gson.internal.LinkedTreeMap--> 3
//[Model(id=1, person_id=3, friend_id=2)] class java.util.ArrayList--> 4
//Model(id=1, person_id=3, friend_id=2) class pl.jac.gson2.not.Model--> 5
//1 long--> 6
}
private fun printResult(fromMapDefault: ClassStatus) {
println("""
result
${fromMapDefault} ${fromMapDefault.javaClass}--> 1
${fromMapDefault.data} ${fromMapDefault.data.javaClass}--> 2
${fromMapDefault.data["1"]} ${fromMapDefault.data["1"]?.javaClass}--> 3
${fromMapDefault.data["1"]!!["7"]} ${fromMapDefault.data["1"]!!["7"]!!.javaClass}--> 4
${fromMapDefault.data["1"]?.get("7")!![0]} ${fromMapDefault.data["1"]!!["7"]!![0].javaClass}--> 5
${fromMapDefault.data["1"]?.get("7")!![0].id} ${fromMapDefault.data["1"]!!["7"]!![0].id!!.javaClass}--> 6
""".trimIndent())
}
val jj = "{\n" +
"\"status\": 0,\n" +
"\"error_message\": null,\n" +
"\"data\": {\n" +
"\"1\": {\n" +
" \"7\": [\n" +
" {\n" +
" \"id\": \"1\",\n" +
" \"person_id\": \"3\",\n" +
" \"friend_id\": \"2\"\n" +
" }\n" +
" ]\n" +
" }\n" +
"}\n" +
"}"

Categories

Resources