I am using Retrofit to get the data from the API and parse it into a POJO object.
However, the API is not documented and I am not very sure what data does the JSON contain.
I have field in my POJO for the data I am sure is coming, but at times they are fields in the JSON that I have not accounted for.
Obviously, Retrofit just ignores these fields.
How can I make it send a warning when a field in the JSON is NOT in the POJO?
class User {
#SerializedName("id")
private Integer id;
#SerializedName("name")
private String name;
[relevant getters and setters]
}
The JSON that is coming is:
{
id: 5,
name: "John",
age: 23
}
Age field is not in the POJO, but at application does not throw any error; how do I make it display an error in such instances?
You may consider using Jackson to deserialize your POJO.
Then you can use the following annotations:
#JsonIgnoreProperties(ignoreUnknown = false)
Related
I use Retrofit (v2.9.0) and Moshi (v1.11.0) in my app. I try to call an endpoint this way:
#FormUrlEncoded
#PATCH("anime/{anime_id}/my_list_status")
fun updateListStatus(
#Path("anime_id") animeId: Long,
#Field("num_watched_episodes") nbWatchedEpisodes: Int,
#Field("score") score: Double,
#Field("status") watchStatus: WatchStatus,
): Single<MyListStatus>
But the WatchStatus->Json conversion is not working as expect. WatchStatus is a simple enum class:
enum class WatchStatus {
COMPLETED,
DROPPED,
ON_HOLD,
PLAN_TO_WATCH,
WATCHING,
}
and I created a custom adapter because my app uses uppercase enum names while the back-end uses lowercase names:
class AnimeMoshiAdapters {
/* Others adapters */
#ToJson
fun watchStatusToJson(watchStatus: WatchStatus): String =
watchStatus.toString().toLowerCase(Locale.getDefault())
#FromJson
fun watchStatusFromJson(watchStatus: String): WatchStatus =
WatchStatus.valueOf(watchStatus.toUpperCase(Locale.getDefault()))
}
I create my Moshi instance this way:
Moshi.Builder()
.addLast(KotlinJsonAdapterFactory())
.add(AnimeMoshiAdapters())
.build()
and my Retrofit instance uses it (with Koin injection):
Retrofit.Builder()
.baseUrl(get<String>(named("baseUrl")))
.client(get(named("default")))
.addConverterFactory(MoshiConverterFactory.create(get()))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
When parsing a Json to create a WatchStatus enum the adapter is used. It is noticeable because the call fails with an error "com.squareup.moshi.JsonDataException: Expected one of [COMPLETED, DROPPED, ON_HOLD, PLAN_TO_WATCH, WATCHING]" if I remove my custom adapter.
When I try to call the endpoint specified above the transformation of a WatchStatus in Json is wrong and the enum name stay in Uppercase, meaning my custom adapter is not used. If I check the Retrofit logs I can see that it send "num_watched_episodes=12&score=6.0&status=ON_HOLD", so the status is not converted in lowercase.
If I try to manually convert a WatchStatus in Json using the same Moshi instance it works as expected, so I believe my custom adapter implementation is correct.
How can I make Retrofit uses my custom Moshi adapter in this call?
Moshi adapters' toJson apply to the retrofit requests' body (== params annotated with #BODY), not query parameters (== params annotated with #FIELD). And it is correct and expected behavior, as query parameters are by standards not expected to be JSON formatted strings (eventhough in your case it's just a String). If your server expects the status field as query parameter, then there is no other way than to provide it lowerCased by yourself:
#FormUrlEncoded
#PATCH("anime/{anime_id}/my_list_status")
fun updateListStatus(
...
#Field("status") watchStatus: String
): Single<MyListStatus>
and feed your updateListStatus with already lowerCased value:
updateListStatus(..., COMPLETED.name.toLowerCase())
If you have no influence on the server's implementation, then skip the rest of this post.
If you want to utilize your custom adapter's toJson function, your server needs to change the request to accept JSON body instead of query params, say like this:
PUT: anime/{anime_id}/my_list_status
BODY:
{
"anime_id" : Long,
"num_watched_episodes" : Int,
"score" : Double,
"status" : WatchStatus
}
Then you would create a data class for the body, say named RequestBody, then you could change your request to:
#PUT("anime/{anime_id}/my_list_status")
fun updateListStatus(
...
#BODY body: RequestBody
): Single<MyListStatus>
in which case your custom adapter will take effect and transform the WatchStatus inside the RequestBody by its defined toJson logic.
I have a model class that fills with server response by gson.
the gson converter factory is:
Retrofit.Builder().addConverterFactory(GsonConverterFactory.create())
I have a transient field inside my model to prevent serialize/deserialize by gson.
data class Testy(
val id:Int = 1,
#Transient var isShowing:Boolean = true
):Serializable
after getting server response and get the converted gson object, the value of isShowing field is false while I expected to be true(as init value).
Workaround
I have used below ways but I didn't get the desired behavior:
using #Expose(serialize = false, deserialize = false) instead of #Transient
as mentiond here adding GsonBuilder.excludeFieldsWithModifiers(Modifier.TRANSIENT)
I'm using Retrofit 2 and Moshi to read and parse JSON from an endpoint. My retrofit instance is defined like so:
val retrofit: Retrofit = Retrofit.Builder()
.baseUrl("https://myendpoint.com")
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(MoshiConverterFactory.create())
.build()
And I'm using the Kotlin data class to store the information in a model:
#GET("data/getlist")
fun getData(): Single<Data>
Data class:
data class Data(val Response : String,
val Message : String,
val BaseImageUrl : String)
Now, because the JSON is formatted like so, the JSON is parsed and the model is populated just fine:
{
"Response": "Success",
"Message": "Api successfully returned",
"BaseImageUrl": "https://www.endpoint.com/image/xxx.jpg",
}
This is because the objects map 1:1 with the model. So in the above example, the "Response" key mapped to the "Response" variable name in the Data class.
My question is this: what if the keys are all variable? How can you represent this in the Kotlin data class?
Sample JSON file to be parsed:
{
"RandomX": "xxxxxx",
"RandomY": "yyyyyy",
"RandomZ": "zzzzzz",
}
As #eric-cochran pointed out, you don't really need a new data class to represent this. It'd end up being a Map, and you'd use it like so:
#GET("data/getlist")
fun getVariableData(): Single<Map<String, String>>
I am using retrofit w/ gson annotations to bind my incoming JSON to a model and its member attributes to types like String, int, float, etc...
public class Location {
#SerializedName("user_id")
#Expose
private int userId;
But how do I bind to a member variable that's of type JSON? I want it to be unstructured which is why I can't just map it to a well-defined model.
#SerializedName("metadata")
#Expose
private JsonObject metadata;
How do I get the above to work?
Looks like I can set the member attribute to JsonElement
#SerializedName("metadata")
#Expose
private JsonElement metadata;
This resolves the issue of binding to JsonNull + JsonObject.
I'm testing/creating a REST client to the new Basecamp API using Retrofit. It looks something like this:
class Project {
String name;
String appUrl;
}
interface Basecamp {
#GET("/projects.json")
List<Project> projects();
}
In the json response, the source field for appUrl is called app_url. Asides from renaming the field in the class, is there a simple way to map the response data with my data structure?
So I found the answer in this question. Turns out this can be solved using Gson:
class Project {
String name;
#SerializedName("app_url")
String appUrl;
}
If you are using Jackson for JSON parsing with Retrofit you can use the #JsonProperty annotation:
Example:
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
#JsonIgnoreProperties (ignoreUnknown = true)
class Project {
#JsonProperty("name")
String name;
#JsonProperty("app_url")
String appUrl;
}