{
"image_url": [
{
"image_url": "url1"
},
{
"image_url": "url1"
},
{
"image_url": "url1"
}
]
}
I Want To Pass image_url As An Array To API With Multiple Image URL [String] How Can I Pass Like This Using Retrofit [Android - Kotlin]
val jsResult = JSONObject()
val jsArray = JSONArray()
for (i in 0 until imgOnlineList.size) {
val jGroup = JSONObject()
jGroup.put("imageURL", imgOnlineList[i])
jsArray.put(jGroup)
}
jsResult.put("productCategory", jsArray)
Constant.logD(mTAG, "productCategoryOnlineList : ", jsResult.toString())
Since you are using Retrofit with Kotlin, you don't need to write plain Json objects. You can set Gson converter in Retrofit Builder.
You can create two data classes
data class Image(val image_url: String)
data class ImageList(val image_url: List<Image>)
And then pass them to API call as #Body payload or however is required by API provider. e.g.,
#POST("{api_end_point}")
fun sendImages(#Body imageList: ImageList)
Related
The data that I want to use has this structure:
{
"1": {
"id": 1,
"name": "Bulbasaur"
},
"2": {
"id": 2,
"name": "Ivysaur"
},
"3": {
"id": 3,
"name": "Venusaur"
}
}
Note:
The number labeling each object matches the id of the Pokémon, not the number of Pokémon
My problem is that when I try to create data classes for this it ends up creating a data class for each object. Not one data class that fits each object. I believe this is due to the number labeling the object(Pokémon) being different for each object.
Is there a way I can format this data in maybe one or two data classes and not over 800?
Ideally I would like the data to be structured like this but it does not work when run.
data class ReleasedPokemonModel(
val id: Int,
val name: String
)
When parsing Json to Object with this special case, you should custom Json Deserializer yourself.
Here I use Gson library to parse Json to Object.
First, create a custom Json Deserializer with Gson. As follows:
PokemonResponse.kt
data class PokemonResponse(
val pokemonMap: List<StringReleasedPokemonModel>
)
data class ReleasedPokemonModel(
val id: Int,
val name: String
)
GsonHelper.kt
object GsonHelper {
fun create(): Gson = GsonBuilder().apply {
registerTypeAdapter(PokemonResponse::class.java, PokemonType())
setLenient()
}.create()
private class PokemonType : JsonDeserializer<PokemonResponse> {
override fun deserialize(
json: JsonElement?,
typeOfT: Type?,
context: JsonDeserializationContext?
): PokemonResponse {
val list = mutableListOf<ReleasedPokemonModel>()
// Get your all key
val keys = json?.asJsonObject?.keySet()
keys?.forEach { key ->
// Get your item with key
val item = Gson().fromJson<ReleasedPokemonModel>(
json.asJsonObject[key],
object : TypeToken<ReleasedPokemonModel>() {}.type
)
list.add(item)
}
return PokemonResponse(list)
}
}
}
Next I will create a GsonConverterFactory so that I can addConvertFactory to Retrofit.
val gsonConverterFactory = GsonConverterFactory.create(GsonHelper.create())
And now I will add retrofit.
val retrofit = Retrofit.Builder()
// Custom your Retrofit
.addConverterFactory(gsonConverterFactory) // Add GsonConverterFactoty
.build()
Finally in ApiService, your response will now return type PokemonResponse.
interface ApiService {
#GET("your_link")
suspend fun getGenres(): PokemonResponse
}
The problem is that there's no JSON array there. it's literally one JSON object with each Pokemon listed as a property. I would recommend that you reformat the JSON beforehand to look like this:
[
{
"id": 1,
"name": "Bulbasaur"
},
{
"id": 2,
"name": "Ivysaur"
},
{
"id": 3,
"name": "Venusaur"
}
]
And then you could model it like this:
data class ReleasedPokemonModel(
val id: Int,
val name: String
)
data class Response(
val items: List<ReleasedPokemonModel>
)
See more here.
And see here for discussion about reformatting the data before handing it to Retrofit.
You can use Map to store the key like the following
data class PokemonResponse(
val pokemonMap:Map<String,ReleasedPokemonModel>
)
data class ReleasedPokemonModel(
val id: Int,
val name: String
)
I need to parse this json. It works fine by default, but I need to add timestamp, so i use custom deserialize factory.
[
{
"ccy": "USD",
"base_ccy": "UAH",
"buy": "26.60000",
"sale": "26.96000"
},
{
"ccy": "EUR",
"base_ccy": "UAH",
"buy": "28.95000",
"sale": "29.60000"
},
{
"ccy": "RUR",
"base_ccy": "UAH",
"buy": "0.35000",
"sale": "0.38500"
},
{
"ccy": "BTC",
"base_ccy": "USD",
"buy": "8610.8989",
"sale": "9517.3093"
}
]
But json: JsonElement? parametr in MyDeserializer never come with ArrayList, always in single object. How i can read remote json for ArrayList, аnd modify it?
class MyDeserializer : JsonDeserializer<ArrayList<CurrencyItem>> {
override fun deserialize(
json: JsonElement?,
typeOfT: Type?,
context: JsonDeserializationContext?
): ArrayList<CurrencyItem> {
//Clean array save result
var currrencyList = ArrayList<CurrencyItem>()
// Get remote json
val itemsJsonArray = jsonObject.asJsonArray
//Modify remote json to custom object with timestamp
for (item in itemsJsonArray) {
var JsonObject = item.asJsonObject
var ccy = JsonObject.get("ccy").asString
var base_ccy = JsonObject.get("base_ccy").asString
var buy = JsonObject.get("buy").asString
var sale = JsonObject.get("sale").asString
var timestamp = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())
currrencyList.add(CurrencyItem(1, ccy, base_ccy, buy, sale, time))
}
return currrencyList
}
}
Add custom converter to retrofit
//Add converter to retrofit
val retrofit =
Retrofit.Builder().baseUrl("https://api.privatbank.ua/")
//My custom converter
.addConverterFactory(GsonConverterFactory.create(customGson))
.client(okkHttpclient)
.build()
The problem is you're registering your deserializer with the wrong type:
JsonDeserializer<ArrayList<CurrencyItem>>
The type for your deserializer is
ArrayList<CurrentItem>
Yet, you're registering it with
CurrencyItem::class.java
Now, registering generic types is not straightforward as plain types.
In your case you need:
Type currencyItemListType = new TypeToken<ArrayList<CurrencyItem>>() {}.getType();
registerTypeAdapter(currencyItemListType, MyDeserializer())code here
With that, when de retrofit call returns with a success code (2xx) it shoud automatically try to deserialize the json with your deserializer
I use Retrofit2 and I need to upload various files using file in an array of objects Media like this :
{
"state" = "done",
"medias" = [
{
"file" = THE_FILE1
},
{
"file" = THE_FILE2
},
{
"file" = THE_FILE3
}
]
}
This is the function of my Interface :
#Multipart
#POST("api/exercice/{id}")
fun submitExercice(
#Path("id") id: Int,
#Header("Authorization") token: String,
#Body data: AnswerExercice
): Call<Void>
And this is my object Media :
data class AnswerExercice(
val state: String = "done",
val medias: List<Media>
) : Serializable {
data class Media(
#Part val file: MultipartBody.Part
) : Serializable
}
But I have this error :
#Body parameters cannot be used with form or multi-part encoding.
(parameter #3)
What am i not doing well?
This is what the API documentation say :
The result have to be like this :
Solution 1
If you like to send your data exactly like the structure you mentioned, you should convert files content to Base64 and wrap them in a serializable class and post it as the body. Here is the sample of wrapper class:
data class AnswerExerciceBase64(val state: String, val medias: List<Media>) : Serializable
data class Media(val file: Base64File) : Serializable
class Base64File(file: File) : Serializable {
val name: String
val content: String
init {
name = file.name
content = Base64.encodeToString(FileInputStream(file).readBytes(), Base64.DEFAULT)
}
}
And your Api is like this:
#POST("api/exercice/{id}")
fun submitExercice(
#Path("id") id: Int,
#Header("Authorization") token: String,
#Body data: AnswerExerciceBase64
): Call<Void>
Then posted data to server will be like below:
{
"state": "this is state",
"medias": [{
"file": {
"content": "Base64 file content",
"name": "f1.txt"
}
}, {
"file": {
"content": "Base64 file content",
"name": "f2.txt"
}
}, {
"file": {
"content": "Base64 file content",
"name": "f3.txt"
}
}]
}
This approach is so close to what you want but you should know you must decode files content on the server-side by yourself, so you need more effort on the server-side.
Solution 2
It's better to use multipart/form-data to upload files and data. Based on "Is it possible to have a nested MultipartEntities or FormBodyPart in a multipart POST?" question and its answer, multipart/form-data has a flat structure and there is no hierarchy, so you can't have desired data structure but you can still pass all of the inputs to Api through a single object.
According to this article, you can send multiple files in a List, so if your Api be like this
#Multipart
#POST("post")
fun submitExercice(#Part data: List<MultipartBody.Part>): Call<ResponseBody>
then you will be able to upload multiple files. You just need to create a List of MultipartBody.Part and add your files to it like below:
list.add(MultipartBody.Part.createFormData(name, fileName, RequestBody.create(mediaType, file)))
Now you must add the state parameter to this list. You can do it like this:
list.add(MultipartBody.Part.createFormData("state", state))
I developed a class that handles all this stuff. You can use it.
class AnswerExerciceList(state: String) : ArrayList<MultipartBody.Part>() {
init {
add(MultipartBody.Part.createFormData("state", state))
}
fun addFile(name: String, fileName: String, mediaType: MediaType?, file: File) {
add(MultipartBody.Part.createFormData(name, fileName,
RequestBody.create(mediaType, file)))
}
}
You can create an instance of this class, add your files and then pass it to the submitExercice Api method as input.
Update
This answer is based on your Api documnetation. I tested my answer and the example that you mentioned in your question via https://postman-echo.com and result was the same. Please try the following code snippet:
Api
#Multipart
#POST("api/exercice/{id}")
fun submitExercice(#Path("id") id: Int,
#Header("Authorization") authorization: String,
#Part("answer") answer: String,
#Part medias: List<MultipartBody.Part>,
#Part("state") state: String): Call<ResponseBody>
Media Class
data class Media(val urlVidel: String, val file: File?, val mediaType: MediaType?) {
companion object {
fun mediaListToMultipart(mediaList: List<Media>): List<MultipartBody.Part> {
val list = ArrayList<MultipartBody.Part>()
for (i in mediaList.indices) {
mediaList[i].let {
if (!TextUtils.isEmpty(it.urlVidel))
list.add(MultipartBody.Part.createFormData("medias[$i][urlVideo]", it.urlVidel))
if (it.file != null) {
val requestFile = RequestBody.create(
it.mediaType,
it.file
)
list.add(MultipartBody.Part.createFormData("medias[$i][file]", it.file.getName(), requestFile))
}
}
}
return list
}
}
}
and then call Api like this:
ApiHelper.Instance.submitExercice(1, "Authorization Token", "Answer", Media.mediaListToMultipart(mediaList), "State").enqueue(callback)
Note: Newbie here, please let me know if i need to provide more information or clarify on anything.
To give you some context: I am practising building a Messenger-clone application with lots of Retrofit methods. For that purpose, i am using a small local JSON server, with which the application communicates.
When a user of the application creates an account, the application creates a profile object in the JSON server using the following method:
#FormUrlEncoded
#POST("profiles")
suspend fun createProfile(#Field("username") username: String?,
#Field("picture") picture: String?,
#Field(value = "nickname") nickname: String?,
#Field(value = "contacts") contacts: ArrayList<String?>,
#Field(value = "status") status: Int?): Response<Profile>
Initially, the contacts ArrayList is empty, because the user has not yet added any contacts. Creating a random profile with an empty ArrayList() for the contacts parameter, this is the result inside the JSON server:
{
"username": "username.example",
"picture": "picture's URL",
"nickname": "Nikola",
"status": 1,
"id": 4
}
The class that represents the Profile model inside the application is this:
class Profile(
val username: String? = "",
var picture: String? = "",
var nickname: String? = "",
var contacts: ArrayList<String?>? = ArrayList(),
var status: Int? = 1,
val id: Int? = 0
)
Once the profile is created, naturally the user can add new contacts, which happens using the following method:
#FormUrlEncoded
#PATCH("profiles/{id}")
suspend fun addContact(#Path("id") id: Int?,
#Field("contacts") contacts: ArrayList<String?>?): Response<Profile>
And here is where the problem occurs, on the very first contact added. The ArrayList, which is sent to server contains just one item and the result inside the JSON server looks like this:
{
"username": "username.example",
"picture": "picture's URL",
"nickname": "Nikola",
"status": 1,
"id": 4,
"contacts": "first.contact"
}
Basically, because the arraylist contains just one item, it saves it as a String. This creates all kinds of problems later on because, once the application uses a #GET method for that profile, it expects an ArrayList for the contacts attribute, but it receives a String.
What can i do to make the the JSON profile look like this:
{
"username": "username.example",
"picture": "picture's URL",
"nickname": "Nikola",
"status": 1,
"id": 4,
"contacts": ["first.contact"]
}
The contacts parameter needs to be an array, even when there is only one item in it.
Use #Body instead of #Form and #FormUrlEncoded:
data class ProfileContacts(val contacts: List<String>)
#PATCH("profiles/{id}")
suspend fun addContact(#Path("id") id: Int?, #Body contacts: ProfileContacts): Response<Profile>
and add a converter, if you haven't already had one, a Gson one for example:
// build.gradle
dependencies {
implementation 'com.squareup.retrofit2:converter-gson:2.6.1' // latest version
}
// Retrofit Builder
val retrofit = Retrofit.Builder()
... // other methods
.addConverterFactory(GsonConverterFactory.create())
.build()
#Body lets you define the request body as a Kotlin class, which will eventually get serialized using the provided Converter (in case of Gson, it will be converted to JSON). #Field on the other hand is used for sending data as application/x-www-form-urlencoded (as the required #FormUrlEncoded annotation also suggests). This means that the body of your request will be encoded into a list of key-value pairs, separated by '&', e.g. (based on the createProfile method):
username=username.example&picture=picture%27s%20URL&nickname=Nikola&status=1&id=4
You can POST an array as application/x-www-form-urlencoded by using the same key more than once. That's what basically happens when you annotate a list with the Retrofit #Field annotation - every element from the list is paired with the common key, e.g.:
#FormUrlEncoded
#PATCH("profiles/{id}")
suspend fun addContact(#Path("id") id: Int?,
#Field("contacts") contacts: ArrayList<String?>?): Response<Profile>
// ...
addContact(1, arrayListOf("first.contact", "second.contact"))
// request body:
contacts=first.contact&contacts=second.contact
So when you try to update the profile using only one element contacts list, a single "contacts" pair gets created (contacts=first.contact), and it's treated like a string value.
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)
}