I'm using retrofit2 to handle http request after calling from API. Let me explain this.
I have 2 java class(POJO) created to handle user and lecturer data which is User.java and Lecturer.java respectively. For the response data such as :
{
"users": [
{
"user_id": "28",
"user_email": "john#abc.com",
"user_password": "123"
}
]
}
i can use User.java class to handle this response. Nothing complex in this file, only contains getter and setter method. Same goes to lecturer data, here is the example of lecturer data :
{
"lecturers": [
{
"lecturer_id": "3",
"user_id": "28",
"lecturer_name": "johny2"
}
]
}
i can handle it by using Lecturer.java class.
But the problem is, if the response contains both user and lecturer data on a single json, how to handle it?? . Here is the example of request :
{
"users": [
{
"user_id": "28",
"user_email": "john#abc.com",
"user_password": "123",
"lecturer_id": "3",
"lecturer_name": "johny2"
}
]
}
To solve this problem, i think i need to create another java class that contains both User and Lecturer class on it, unfortunately at here i'm stuck.
This is new file, that i tried to create (Userlecturer.java) :
public class UserLecturer {
User user;
Lecturer lecturer;
// how to implement on this part
}
Here is UserLecturer interface :
public interface UserLecturerInterface {
#GET ( "api/endpoint/here" )
Call<UserLecturer> getLecturerByUserId (#Path( "userId" ) String userId );
}
Appreciated for any helps. Ask me for more inputs if above use case did't clear enough. Thanks
I think the POJO should be:
public class Users {
String userId;
String userEmail;
String userPassword;
String lecturerId;
String lecturerName;
}
Even though there are 2 models inside the JSON, you only need 1 model for Retrofit.
If you really want to split the 1 JSON response into 2 models, I think you have to implement custom JSON converter.
Gson gson = new GsonBuilder()
.registerTypeAdapter(UserLecture.class, new JsonDeserializer<UserLecture>() {
public UserLecture deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonArray usersJsonArray = json.getAsJsonObject().getAsJsonArray("users");
JsonObject userJsonObject = usersJsonArray.getAsJsonArray().get(0).getAsJsonObject();
User user = new User();
user.setUserId(userJsonObject.get("user_id").getAsString());
user.setUserEmail(userJsonObject.get("user_email").getAsString());
user.setUserPassword(userJsonObject.get("user_password").getAsString());
Lecturer lecturer = new Lecturer();
lecturer.setLecturerId(userJsonObject.get("lecturer_id").getAsString());
lecturer.setLecturerName(userJsonObject.get("lecturer_name").getAsString());
return new UserLecture(lecturer, user);
}
})
.create();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl([YOUR_BASE_URL])
.addConverterFactory(GsonFactoryConverter.create(gson))
.build();
This is some code I use to convert longs to Java Date objects.
Presumably, you can do the same thing for your UserLecture object. You should be able to extract the individual json objects for User and Lecture, create a new UserLecture object and let User and Lecture as objects in it.
Gson gson = new GsonBuilder().registerTypeAdapter(UserLecture.class, new JsonDeserializer<UserLecture>() {
public UserLecture deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject user = json.getAsJsonObject().getAsJsonObject("user");
JsonObject lecture = json.getAsJsonObject().getAsJsonObject("lecture");
return new UserLecture(user, lecture);
}
}).create();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com")
.addConverterFactory(GsonFactoryConverter.create(gson))
.build();
Then inside UserLecture:
public UserLecture(JsonObject userJson, JsonObject lectureJson) {
this.user = new User();
this.user.setUserId(userJson.get("user_id").getAsInt());
this.user.serUserEmail(userJson.get("user_email").getAsString());
//so on.
}
At first let me say that the JSON you need to process here is broken by design so you should urge the guy / department / company to fix it.
Secondly, JSON processors like Jackson allow to parse polymorphic data structures like this easily, but they require some kind of type flag to distinguish one of another type (i.e. type: "user" and type: "lecturer"). There is also a way to do this without such type flags, but there is a lot more hand work involved. The last example here shows how to do it.
Yes, it is one possible solution. Gson ignores all fields, which names doesnt match #SerializedName annotation. So, you may try another solution without creating any more pojo classes- return result as String, and try to parse this string as both classes. If one result is empty- then you have another. But, if both kbjects isnt empty- then original response contain fields from both pojos
Related
I Have a REST api that contains the data like this way
{
...
... //<- more data here
...
"currencies": {
"BTN": {
"name": "Bhutanese ngultrum",
"symbol": "Nu."
},
"INR": {
"name": "Indian rupee",
"symbol": "₹"
}
}
...
... //<- more data here
...
}
i am doing a project in java where i need to use okhttp and show information about a country from an available rest api and before when i used this api it had all the data in currencies in an data array and that was helpful as you can just get the first zero object from the array , but after they updated the api they made all data in currencies an object and i only want the first object , any way i can get it?
OK, so you have two options here ...
Option 1.
Create two classes like this and use ObjectMapper class to do automatic deserealisation for you.
class CurrencyData {
String name;
String symbol;
}
class CurrencyJsonResponse {
CurrencyData INR;
CurrencyData BTN;
}
public static void main(String[] args) {
OkHttpClient client = // build an instance;
ObjectMapper objectMapper = new ObjectMapper();
ResponseBody responseBody = client.newCall(request).execute().body();
CurrencyJsonResponse currencyResponse = objectMapper.readValue(responseBody.string(), CurrencyJsonResponse.class);
//Get data by using getters on currencyResponse object
}
Option 2
You can write a custom deserealizer by extending the StdDeserializer<T> class. You'll have to programmatically inspect the JsonNode parse tree and assemble the object that you want.
This article explains how to do it and comes with a working code sample
I am trying to map the following response:
{
"data": {
"id": "1574083",
"username": "snoopdogg",
"full_name": "Snoop Dogg",
"profile_picture": "http://distillery.s3.amazonaws.com/profiles/profile_1574083_75sq_1295469061.jpg",
"bio": "This is my bio",
"website": "http://snoopdogg.com",
"counts": {
"media": 1320,
"follows": 420,
"followed_by": 3410
}
}
into an object where I only want to retrieve the username, full_name, and id fields.
Ex: something like:
public class User{
String id;
String username;
String full_name;
// getters + setters
}
Is there a way of doing this without first having to store the data object into a Map?
Use Jackson API. It should be simple:
ObjectMapper mapper = new ObjectMapper();
User user = mapper.readValue(jsonString, User.class); //jsonString is your actual json string.
You might want to tweak your User class to match the JSON string. E.g. your user class needs to have a 'data' field as List<Data> data; where 'Data' is another POJO. You can add the "id", "userName", etc fields in the 'Data' pojo.
You can either do it by hand via, for example, regexp or utilize any of JSON libraries like Jackson, GSON etc.
With GSON it's pretty simple. Say your json is stored in a String jsonString variable.
Gson gson = new Gson();
YourObject = gson.fromJson(jsonString, YourObject.class);
Although I'm not sure what will happen, since your jsonString doesn't have a key called User. However, this should work if you first extract data from your jsonString and name your POJO Data.
I have an API that returns some arguably useful metadata along with requested data itself. It looks something like this:
{
"success": true,
"messages": [],
/* other metadata */
"result": { /* fields with useful data */ }
}
So, basically I want to serialize only stuff that is nested inside of "result" field, preferably still being able to work with meta (checking "success" on true/false and reading messages might be useful).
I thought I could use JSONObject to separate "result" and other meta, but this pipeline feels like a bit of overhead. Is there a way to do it purely with GSON?
The other problem is that I use Retrofit, which has a very neat workflow with pure GSON. If the above is the only adequate way of dealing with such API, how should I approach integrating it into Retrofit workflow?
to your retrofit builder add:
.addConverterFactory(new GsonConverterFactory(new GsonBuilder()
.registerTypeAdapter(Result.class, new JsonDeserializer<Result>() {
#Override
public Result deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
if(!(((JsonObject) json).getAsJsonPrimitive("success")).getAsBoolean()) {
return null;
}
JsonObject result = ((JsonObject) json).getAsJsonObject("result");
return new Gson().fromJson(result, Result.class);
}
}).create()))
there are npe and other checks to do of course :)
Create a POJO with #Expose annotation and use serialization = true/false. If you want to serialize only success, then your POJO would look something like this.
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
public class POJO {
#SerializedName("success")
#Expose(serialize = true, deserialize = false)
private Boolean success;
///Your getter / setter methods
}
I have used this above with Retrofit and it works fine.
Hope this helps!
EDIT:
Also you need to mention this while creating your Retrofit Service
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.excludeFieldsWithoutExposeAnnotation();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(YOUR_BASE_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create(gsonBuilder.create()))
.build();
I'm trying to create a custom deserializer in Gson for an object that is really just a wrapper for a bunch of disparate types with a type identifier.
Here's a simplified overview of my problem domain:
I have users sending messages to each other that can contain a variety of unrelated domain objects, and I want to deserialize it to something like:
public class Message {
public String messageType;
public Object messageData;
}
The messageData object is constructed via JavaScript the programmers decided to just jam every object type into one field "messageData". messageData can be any number of domain objects like: User, Video, Website, Picture, which do not share a base class or interface.
So the (simplified) json object could look like:
{ "messageType": "video", "messageData": { "videoId": 1, "videoTitle": "my vid" } }
or
{ "messageType": "picture", "messageData": { "pictureId": 1, "pictureUrl": "http://www.example.com/cat.jpg" } }
The goal would be to take the messageType and use that to choose a proper class to deserialize it into.
I have come up with something like this:
public class MessageJsonDeserializer implements JsonDeserializer<Message> {
#Override
public Message deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
JsonObject obj = json.getAsJsonObject();
Message message = new Message();
message.messageType = obj.get("messageType").getAsString();
message.messageData = MessageDataMapper.map(message.messageType, obj.get("messageData")); // maps and casts to the correct Video/Picture,Website etc..
return message;
}
}
This seems to work ok, but let's say that Message has a LOT of other fields that could be automatically serialized, then I'd have to manually parse the JsonObject and extract those fields.
Is there a way that I can use a hybrid approach and have Gson automatically serialize the non-Object fields, but use a custom serializer for that messageData field that ALSO takes into account the messageType?
It might be a bit late, but this answer regarding the RuntimeTypeAdapter might help you in solving this:
https://stackoverflow.com/a/15737704/433421
The RuntimeTypeAdapter enables you to evaluate a self-configured type-attribute in the JSON, in your case "messageType", and register its values with provided POJO-classes.
Look at the link provided in this answer to see an usage-example in the javadoc.
GSon would handle serialization and deserialization of those POJO-classes itself, as I understand it.
If those fields in messageData contain arbitrary objects, you would have to register custom JsonDeserializer-instances as you did with MessageJsonDeserializer.
Hope this helps.
Sadly, no. At least not that I was ever able to determine. There is no way to have it do something like "custom deserialize field X, but super.deserialize() for the rest of it". God knows I tried. If it's any consolation, that seems to be true with every Json deserializer I've looked at.
I am using google gson-2.2.1 library for parsing large response of JSON.
I have to parse a JSON response where structure may vary.
First case, when the response contains more than one team:
"Details":{
"Role":"abc",
"Team":[
{
"active":"yes",
"primary":"yes",
"content":"abc"
},
{
"active":"yes",
"primary":"yes",
"content":"xyz"
}
],
Second case, when only one team is passed:
"Details":{
"Role":"abc",
"Team":
{
"active":"yes",
"primary":"yes",
"content":"abc"
}
}
There are my base classes used for parsing:
class Details {
public String Role;
public ArrayList<PlayerTeams> Team = new ArrayList<PlayerTeams>();
PlayerTeams Team; // when JsonObject
}
class PlayerTeams {
public String active;
public String primary;
public String content;
}
The problem is that I can not use ArrayList<PlayerTeams> when I have only one of them and it's returned as JsonObject.
Gson can identify static format of JSON response. I can trace full response dynamically by checking if "Team" key is instance of JsonArray or JsonObject but it would be great if a better solution is available for that.
Edit :
If my response is more dynamic..
"Details":{
"Role":"abc",
"Team":
{
"active":"yes",
"primary":"yes",
"content":"abc"
"Test":
{
"key1":"value1",
"key2":"value2",
"key3":"value3"
}
}
}
In my edited question, I am facing problem while my response is more dynamic..Team and Test can be JsonArray or JsonObject.. It really harassing me because sometime Test object may array when more data, may object when single data, string when no data. There is no consistency in response.
You need a type adapter. This adapter would be able to distinguish which format is coming and instance the right object with the right values.
You can do this by:
implement your own type adapter by creating a class that implements JsonSerializer<List<Team>>, JsonDeserializer<List<Team>>, of course JsonSerializer is just needed in case you need to serialize it in that matter too.
Register the type adapter to you GsonBuilder like: new GsonBuilder().registerTypeAdapter(new TypeToken<List<Team>>() {}.getType(), new CoupleAdapter()).create()
The deserialize method could look like:
public List<Team> deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context)
throws com.google.gson.JsonParseException {
if (json.isJsonObject()) {
return Collections.singleton(context.deserialize(json, Team.class));
} else {
return context.deserialize(json, typeOfT);
}
}