Android Retrotfit modify fields of object after receive - android

I need to modify some fields of object which I receive from server using Retrofit before I store it internally:
For example, server object model
{
"field1":boolean;
"field2":String
}
app object model:
{
"field1":int=boolean?1:0;
"field2":my prefix + String;
}
Should I write my own parser? Or will parsing using GSON would be enough with some interception before object is stored locally?

Why not changing them after deserilization takes place? It's not that clear what you're trying to do but here is a solution.
(1) Write a deserilizer
public class MyDeserializer implements JsonDeserializer<MyModel> {
...
}
(2) Register type adapter
Gson gson = new GsonBuilder()
.registerTypeAdapter(MyModel.class, MyDeserializer)
.create();
(3) Add it to retrofit
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(...)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
Alternatively you could just change your field's content after you get your instances.

Using GSON you can write your own JsonDeserializer.
This example shows how translate int in JSON to boolean in your object.
public class BooleanTypeAdapter implements JsonDeserializer<Boolean> {
#Override
public Boolean deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
int code = json.getAsInt();
return code != 0;
}
}
And this:
new GsonBuilder().registerTypeAdapter(boolean.class, new BooleanTypeAdapter()).create();
Hope it helps.

Related

Retrofit access to list withouth reference

I have this JSON and when trying to access the list I don't know how to pass the value "Serialized", I don't have any value.
I work on android.
Does anyone know how to access?
Thank you.
[
    {
        "id": “111”,
        "dateStart": "2020-02-26T00:00:00+01:00",
        "dateEnd": "2020-02-26T01:30:00+01:00",
        "sectionId": 0,
        "description": “Test”,
        "comment": “Test comment”,
        "emissionType": “A”,
        "priority": 5,
        "audios": [],
        "idCollection": “1”
    },
    {
        "id": “222”,
        "dateStart": "2020-02-26T00:00:00+01:00",
        "dateEnd": "2020-02-26T01:30:00+01:00",
        "sectionId": 0,
        "description": “Test”,
        "comment": “Test comment”,
        "emissionType": “B”,
        "priority": 5,
        "audios": [],
        "idCollection": “12”
 }
]
public static Gson getGSONBuilder()
{
Gson gson = new GsonBuilder().
registerTypeAdapter(Double.class, new JsonSerializer<Double>()
{
#Override
public JsonElement serialize(Double src, Type typeOfSrc,
JsonSerializationContext context) {
if (src == src.longValue())
return new JsonPrimitive("" + src.longValue());
return new JsonPrimitive("" + src);
}
}).create();
return gson;
}
String responseString=getGSONBuilder().toJson(response.body());//response is Retrofit
response
in retrofit just call the ArrayList> in responce you get all the arraylist data into hashmap with its key and value .
for(int i=0;i<responce.body().size;i++){
arrlist.add(responce.body().get(i))
}

Using retrofit to get url from different relative paths

I am trying to get the CompanyEndpoint for each client's site but I am confused with the use of retrofit on the interface.
Here's what I have so far:
CompanyName : "company1"
CompanyEndpoint : "https://example.com"
IdentityEndpoint : "https://example.com/identity"
AppLoginMode : "Anonymous"
AppRouterApi.java
public interface AppRouterApi {
#GET("api/sites/{CompanyName}")
Call<Company> getCompanyName (#Url String companyName);
}
Company.java
public class Company {
String Endpoint;
public String getEndpoint() {
return endpoint;
}
}
MainActivity.java
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://example.com/")
.addConverterFactory(GsonConverterFactory.create())
.build();
appRouterApi = retrofit.create(AppRouterApi.class);
getCompany();
}
private void getCompany(){
retrofit2.Call<Company> companyRequest = appRouterApi.getCompanyName(); //Error here saying a string cant be applied to ()
companyRequest.enqueue(new retrofit2.Callback<Company>() {
#Override
public void onResponse(retrofit2.Call<Company> call, retrofit2.Response<Company> response) {
if(!response.isSuccessful()){
textViewResult.setText("Code:" + response.code());
return;
}
Company company = response.body();
String content = "";
content += "Url" + company.getEndpoint();
textViewResult.setText(content);
}
#Override
public void onFailure(retrofit2.Call<Company> call, Throwable t) {
}
});
}
https://example/sites/{companyName}
So if I search for:
https://example/sites/company1
The JSON will have one object and I need to get the endpoint URL value which would be: https://company1.com
Edit: My textViewReslt is returning 403
There are several things going on as far as I can tell. Let me break it into chunks.
First thing is you're confusing the annotation #Path with the annotation #Url. They serve different purposes.
You use #Path when you want to format a bit of the path into the url inside the annotations like #GET.
public interface AppRouterApi {
#GET("api/sites/{CompanyName}")
Call<Company> getCompanyName (#Path("CompanyName") String companyName);
}
This interface will format the argument passed to getCompanyName as part of the path. Calling getCompanyName("foo") will call the endpoint "https://example.com/api/sites/foo".
You use #Url when you want to simply call that url. In this case, you only annotate the interface method with the http method. For example,
public interface AppRouterApi {
#GET
Call<Company> getCompanyName (#Url String url);
}
You then would have to call the method with the entire url. To call the same url as before you'd have to call getCompanyName("https://example.com/api/sites/foo").
This is the main difference of usage between these 2 annotations. The reason why you're seeing null in your text view is because you're model's attribute name doesn't match the json. You have 2 options.
First, you can change the model to:
public class Company {
String CompanyEndpoint;
public String getEndpoint() {
return endpoint;
}
}
CompanyEndpoint is the exact same name as you have in the json. Another approach, is to tell your json serializer what name you want to use. Since you're using gson, you can use #SerializedName like so:
public class Company {
#SerializedName("CompanyEndpoint")
String Endpoint;
public String getEndpoint() {
return endpoint;
}
}
#SerializedName("CompanyEndpoint") tells gson which name to use while serializing and deserializing.
In essence, you have 2 options. You either use the endpoint, or the company's name. If you don't expect the domain to change, I'd suggest using the first approach with the #Path annotation. This is what it's usually done with Retrofit and personally, I think it's easier to handle than passing urls around. My suggestion is, use a model like:
public class Company {
#SerializedName("CompanyName")
String name;
public String getName() {
return name;
}
}
This would let you access the company's name property and call getCompanyName(company.getName()). Retrofit would format the company's name into the path and you'd call the right url.

Retrofit 2, GSON and custom deserializer

I've been using Retrofit 2 with some POJO objects for a while now. It's a lovely library and works very well, but it's necessitating some horrendous and messy models that I want to get rid of.
I'll show you... I have the following JSON to peruse:
{
"things": [
{
"thing": {
"id": 823,
"created_at": "2016-02-09T22:55:07.153Z",
"published_at": "2016-02-10T19:23:42.666Z",
"downloads": 16073,
"size": 10716291
}
},
],
"count": 4,
"links": {}
}
Using the POJO Schema generator this creates unnecessary classes that make maintaining code hard to do.
This would create:
Things.java
#SerializedName("things")
#Expose
public List<Things_> things = new ArrayList<>();
Things_.java
#SerializedName("thing")
#Expose
private Thing__ thing;
Things__.java
// Insert normal variables and getter/setters here
I've reduced that down a little as it's just for the idea. In my usage I have of course renamed these classes to make them more managable. But I figured there was a way of simply skipping over Thing and Thing_ and allowing me to just return a list of the actual model data (Thing__) and this two of those classes could be removed and "Thing__" could simple be "Thing".
I was right. Gson allows custom deserialization that lets me achieve this end. I threw together a quick Deserializer and used an appropriate TypeToken
Gson gson = new GsonBuilder()
.registerTypeAdapter(new TypeToken<ArrayList<Thing>>(){}.getType(), new AddonDeserializer())
.create();
List<Thing> model = gson.fromJson(jsonString, new TypeToken<ArrayList<Thing>>(){}.getType());
Sure enough, passing this that exact Json above give me a List of Things that were usable.
Enter Retrofit 2! Having added the registerTypeAdapter() to my Retrofit 2 instance (via my gson instance) I now get an error message:
Expected BEGIN_ARRAY but was BEGIN_OBJECT
This is because, probably, my call is:
#Get("end/of/url/here/{slug}.json")
Call<List<Thing>> getThings(#Path("slug") String slug);
My Json starts with an object ("things") which contains an array of "thing" objects. My deserializer had no issues with this:
public class ThingDeserializer implements JsonDeserializer<List<Thing>> {
#Override
public List<Thing> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonArray array = json.getAsJsonObject().getAsJsonArray("things");
ArrayList<Thing> list = new ArrayList<>();
for (JsonElement anArray : array) {
list.add((Thing) context.deserialize(anArray.getAsJsonObject().get("thing").getAsJsonObject(), Thing.class));
}
return list;
}
}
Anyway, thanks for sticking with this very long question!
What do I need to do differently or how can I manipulate Retrofit to act the same as the Gson Deserializer I wrote? What I have works, but in the interests of learning something new and writing nicer and more maintainable code I want to figure this out. I could just resort to using ResponseBody callbacks and throwing the Json through my Deserializer but there has to be a better method.
Thanks to #JonathanAste I figured it out.
Instead of a Deserializer, I needed a TypeAdapterFactory implementation.
public class ThingTypeAdapterFactory implements TypeAdapterFactory {
public <T> TypeAdapter<T> create(Gson gson, final TypeToken<T> type) {
final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
final TypeAdapter<JsonElement> elementAdapter = gson.getAdapter(JsonElement.class);
return new TypeAdapter<T>() {
public void write(JsonWriter out, T value) throws IOException {
delegate.write(out, value);
}
public T read(JsonReader in) throws IOException {
JsonElement jsonElement = elementAdapter.read(in);
if (jsonElement.isJsonObject()) {
JsonObject jsonObject = jsonElement.getAsJsonObject();
if (jsonObject.has("things") && jsonObject.get("things").isJsonArray())
{
jsonElement = jsonObject.get("things");
}
}
if (jsonElement.isJsonObject()) {
JsonObject jsonObject = jsonElement.getAsJsonObject();
if (jsonObject.has("thing") && jsonObject.get("thing").isJsonObject())
{
jsonElement = jsonObject.get("thing");
}
}
return delegate.fromJsonTree(jsonElement);
}
}.nullSafe();
}
}
And this allows you to then use
#GET("end/of/url/here/{slug}.json")
Call<List<Thing>> getThings(#Path("slug") String slug);
Without issue.

Android: Handle different type of value with GSON

In my app, I get and parse a JSON stream from a wordpress (RESP API v2) website.
I use, OKHTTP, RETROFIT with GSON converter to read and parse the stream into my objects.
Usually, my GSON converter expect an object but, because of a recent update, the website gives me a boolean (false). The value isn't set yet.
This is my question: "Can I handle different type of values for the same variable name with GSON Serialize and how?"
Thank you!
This is my object:
public static class StageProfileImage {
//////////////////////////////////////////////////
// Variables
//////////////////////////////////////////////////
#SerializedName("url")
private String stageProfileImageUri;
//////////////////////////////////////////////////
//////////////////////////////////////////////////
// Setters & Getters
//////////////////////////////////////////////////
public String getStageProfileImageUri() {
return stageProfileImageUri;
}
public void setStageProfilUri(String stageProfileImageUri) {
this.stageProfileImageUri = stageProfileImageUri;
}
/////////////////////////////////////////////
}
Important: I can't modify the stream.
try #SerializedName(value = "url", alternate = {"altkey1", "altkey2"})
Edit: Changed to a more generic example.
Thanks but it isn't my problem. In fact, GSON can't convert the stream from the website because instead of this:
"stage_profile_image" : {
...
}
My stream give me that:
"stage_profile_image" : false
In the first one, I get an object but in the second one I get a boolean which is not the type of value it expects and GSON is unable to do the convertion.

Parse nested object using GsonConverter with Retrofit2

I`m trying to convert the next JSON to object:
{
AccountName:"temnoi",
Parts:{
part-0:{
Name:"HOME",
UptimeSeconds:"2143943",
},
part-1:{
Name:"WORK",
UptimeSeconds:"2276958",
}
}
}
The problem is that Parts isn't an array so I don't have any idea how
to obtain them as List or any other data structure.
For now I have such DTO class
public class Info {
private String AccountName;
private List<Parts> Parts;
}
But obviously program crash as there are no array. I use Retrofit2 with GsonConverter.
Can anyone suggest something to solve this problem?
Unfortunately, as I don't have a lot of time, I came with the next solution.
I replace Retrofit2 by OkHTTP and Gson with built-in JSON parser.
After I get a response with OkHttpClient I manually convert JSON to my object.
JSONObject root = new JSONObject(responseFromServer);
JSONObject parts = root.getJSONObject("Parts");
Iterator<String> jsonPartsIterator = parts.keys();
List<Part> partsList = new ArrayList<>();
while (jsonPartsIterator.hasNext()) {
try{
String key = jsonRootIterator.next();
partsList.add(convertPartJsonToObject(computers.getJSONObject(key)));
} catch(Exception e){
// in case if there will be number '0' return empty List
}
}
Here Part convertPartJsonToObject(JSONObject object) is method to convert part-0, part-1... to object which I need.

Categories

Resources