So, I have given an API that returns this on a POST call
{
"JHK":"One piece",
"LKJ":"Two pieces",
"OEN":"Three pieces"
}
And since this is a list but is not good formated from the backend I dont know how to get it from Android, this is what I have tried
WebService
#POST("get_user_codes.php")
suspend fun getUserCodesByMemberId(#Body member_id:String): CodesResponse
CodesResponse
data class CodesResponse(val data: List<Code>)
data class Code(val code_id:String,val name:String)
Repository
suspend fun getUserCodes(memberId:String):Result<CodesResponse>{
return Result.Success(webService.getUserCodesByMemberId(memberId))
}
But his is outping Null as the response, I really dont know how to bring those different objects that are supposed to be an array of object but instead they are all inside one object
Any idea ?
API INPUTS
member_id as text string
Example of input:
{ "member_id": "1"}
API OUTPUTS
code_id: as a String
name: as a String
{
"JHK":"One piece",
"LKJ":"Two pieces",
"OEN":"Three pieces"
}
EDIT
the values can be more than those 3 I posted, it depends on how many the response returns
suppose you have response like that
String json = {
"JHK":"One piece",
"LKJ":"Two pieces",
"OEN":"Three pieces"
}
then you can get a list of values ignoring keys like:
ArrayList<String> arr = new ArrayList<>();
try {
JSONObject response = new JSONObject(json);
Iterator keys = response.keys();
while (keys.hasNext()) {
// loop to get the dynamic key
String currentKey = (String) keys.next();
// get the value of the dynamic key
String value = response.getJSONObject(currentKey).toString();
arr.add(value);
}
} catch (Throwable t) {
Log.e("My App", "Could not parse malformed JSON: \"" + json + "\"");
}
Are field names JHK LKJ OEN always the same? You said there can be more than 3, what will be the other name when it's more than 3?
AbdelraZek posted a great solution to this problem: https://stackoverflow.com/a/64246981/14259754
My version of it in Kotlin with Retrofit in mind:
Retrofit:
// Here we use ScalarsConverter to be able to return String as a response.
Retrofit.Builder()
.baseUrl("http://YourURL")
.addConverterFactory(ScalarsConverterFactory.create())
.build()
.create(YourInterfaceClass::class.java)
// Then you can proceed to your POST function
suspend fun getUserCodesByMemberId(#Body member_id:String): String
// I suggest using Response<String> so you can check the response code and process it if needed.
Then, wherever you need, just do the following:
val response = getUserCodesByMemberId
val json = JSONObject(response.body()!!)
val array = mutableListOf<String>()
val keys: Iterator<String> = json.keys()
while (keys.hasNext()) {
val key = keys.next()
array.add(json[key].toString())
}
This way you can work with Json response that is unknown to you.
It will return null because the JSON inside has different keys("JHK", "LKJ" etc).
As Retrofit uses GSON, you need to create a variable with the same name as JSON keys. You will have to use JSONObject and parse the response.
Instead of using
#POST("get_user_codes.php")
suspend fun getUserCodesByMemberId(#Body member_id:String): CodesResponse
use
#POST("get_user_codes.php")
suspend fun getUserCodesByMemberId(#Body member_id:String): JSONObject
Related
I have class in Flutter:
class Foo {
String id;
int power;
Foo (this.id, this.power);
Map toJson() => {
'id': id,
'power': power
};
}
I've created a instance of this object and pass it to Android via MethodChannel as a String:
_myChannel.invokeMethod('custom_event', {
'foo': foo.toJson().toString(),
});
In Android I retrive that string and want to convert to Foo object on Android side so:
val fooString = call.method.argument<String>("foo")
//output: {id: my_id, power: 23}
val response = fooString?.let { Response(it) }
//output {"id":"my_id","power":"23"}
Helper class:
class Response(json: String) : JSONObject(json) {
val data = this.optJSONArray(null)
?.let { 0.until(it.length()).map { i -> it.optJSONObject(i) } } // returns an array of JSONObject
?.map { FooJson(it.toString()) } // transforms each JSONObject of the array into Foo
}
class FooJson(json: String) : JSONObject(json) {
val id: String = this.optString("id")
val power: String = this.optInt("power")
}
How I can convert response to my Kotlin's class?
Since Foo just contains a string and an integer, both of which are supported by the default message codec, it would make sense to simply pass those two values in a map. (In fact, you already have a method that creates that map, toJson, though a better name might be toMap.)
Don't then call toString on that map; instead use:
_myChannel.invokeMethod('custom_event', foo.toMap());
You are now invoking the method and passing the map. At the native end, you are going to look for the members using their map keys (i.e. id and power) using .argument<T> as you've tried, as follows:
val id = call.method.argument<String>("id");
You could, of course, then create a POJO that wraps an object around those two values, if really necessary.
For more complex messages, consider Pigeon. If you really want to use JSON as your method of serialization, look at the existing JSON message codec.
I have an JSONObject which is creating in the following way.
For a special reason, I have to convert it to Gson in this way, as i can modify only extractDataToString() method. But I do not get proper value after conversion.
fun createJsonString() {
val headerJson = JSONObject()
headerJson.put("typ1", "value1")
headerJson.put("typ2", "value2")
extractData(headerJson.toString())
}
fun extractDataToString(jsonString: String) {
val headerJson = Gson().fromJson(jsonString, JsonObject::class.java)
val resultType1 = headerJson.remove("typ1")?.toString() // Here, resultType1 becomes
"value1", but it should be
value1 that is extra qutomation
mark is being added.
}
Why extra quotation mark is being added? Can anyone please help? I am a newbie.
Call
.getAsString()
Instead of
toString()
at the end
ref : docs
I use the Apollo Android library to make queries to a GraphQL endpoint.
Everything works OK until I try to convert the results back to JSON strings (to store them in a Room database). I naively tried to use Moshi, however this fails with the following error:
Cannot get available extra products: No JsonAdapter for interface com.example.MyQuery$MyFragmentInterface
where MyFragmentInterface in an interface generated by Apollo to handle query fragments.
So, I tried to find whether the Apollo library has/generates any conversion methods, i.e. sth like toJson()/fromJson(), for the generated models, however I couldn't find anything usable.
Am I missing something obvious?
Since Apollo 1.3.x there is an Operation.Data.toJson() extension function (Kotlin) and a corresponding serialize static method in Java.
Check https://www.apollographql.com/docs/android/advanced/no-runtime/#converting-querydata-back-to-json
For the opposite, Json string to Query.Data, I use something like the following:
fun String?.toMyData(): MyQuery.Data? {
this ?: return null
val query = MyQuery()
val response: Response<MyQuery.Data> =
OperationResponseParser(query,
query.responseFieldMapper(),
ScalarTypeAdapters.DEFAULT)
.parse(Buffer().writeUtf8(this))
return response.data
}
Here is an example how you can deserialize string as generated object. Inlined comments for further details.
// Sample string response
val jsonResponse = """
{
"data": {
{
"id": "isbn-889-333",
"title": "Awesome title",
"rating": 5
}
}
""".trimIndent()
// Create query that is expect to return above response
val query = QueryBookDetails("book_id")
// convert string to input stream
val inputStream = ByteArrayInputStream(jsonResponse.toByteArray())
// Read bytes into okio buffer
val buffer = Buffer().readFrom(inputStream)
// Use the query to parse the buffer.
val response = query.parse(buffer)
// response.data might be the one needed by you
Bear in mind that the response must honour the schema.
Try Postman and see if the error also appears there https://blog.getpostman.com/2019/06/18/postman-v7-2-supports-graphql/
I'm really new in programming, and recently started a project in Kotlin with Android Studio.
So, I have a problem with a JSON object. I get data from an BroadcastReceiver object, a String to be more specific, with the next format:
{"s1":1}
This, is a simple string. So I took in a function call toJson and I do this.
private fun toJson(data:String): JSONObject {
var newData: String = data.replace("\"","")
newData = newData.replace("{","")
newData = newData.replace("}","")
val newObject = newData.split(":")
val name = newObject[0]
val value = newObject[1]
val rootObject = JSONObject()
rootObject.put(name,value)
return rootObject
}
Im doing this the right way?, how can I improve my code?
Thanks for your help, and sorry for my english!
Welcome to StackOverflow!
In 2019 no-one is really parsing JSON manually. It's much easier to use Gson library. It takes as an input your object and spits out JSON string and vice-versa.
Example:
data class MyClass(#SerializedName("s1") val s1: Int)
val myClass: MyClass = Gson().fromJson(data, MyClass::class.java)
val outputJson: String = Gson().toJson(myClass)
This way you're not working with JSON string directly but rather with Kotlin object which is type-safe and more convenient.
Look at the docs. It's pretty big and easy to understand
Here is some tutorials:
https://www.youtube.com/watch?v=f-kcvxYZrB4
http://www.studytrails.com/java/json/java-google-json-introduction/
https://www.tutorialspoint.com/gson/index.htm
UPDATE: If you really want to use JSONObject then use its constructor with a string parameter which parses your JSON string automatically.
val jsonObject = JSONObject(data)
Best way is using kotlinx.serialization. turn a Kotlin object into its JSON representation and back marking its class with the #Serializable annotation, and using the provided encodeToString and decodeFromString<T> extension functions on the Json object:
import kotlinx.serialization.*
import kotlinx.serialization.json.*
#Serializable
data class User(val name: String, val yearOfBirth: Int)
// Serialization (Kotlin object to JSON string)
val data = User("Louis", 1901)
val string = Json.encodeToString(data)
println(string) // {"name":"Louis","yearOfBirth":1901}
// Deserialization (JSON string to Kotlin object)
val obj = Json.decodeFromString<User>(string)
println(obj) // User(name=Louis, yearOfBirth=1901)
Further examples: https://blog.jetbrains.com/kotlin/2020/10/kotlinx-serialization-1-0-released/
I am adding 3 templates here for Kotlin Developers, It will solve json converting & parsing problems.
//Json Array template
{
"json_id": "12.4",
"json_name": "name of the array",
"json_image": "https://image_path",
"json_description": "Description of the Json Array"
}
Kotlin Model class
data class JsonDataParser(
#SerializedName("json_id") val id: Long,
#SerializedName("json_name") val name: String,
#SerializedName("json_image") val image: String,
#SerializedName("json_description") val description: String
)
Converting to Json String from the Model Class
val gson = Gson()
val json = gson.toJson(jsonDataParser)
Parsing from Json file/Strong
val json = getJson()
val topic = gson.fromJson(json, JsonDataParser::class.java)
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)
}