The process of using a request body is described in the official API Declaration page as such:
#POST("users/new")
Call<User> createUser(#Body User user);
While there is no guide for creating the User object, I imagine it can look something like this:
public class User {
public String name;
public String group;
}
By extension, this would result in a request body like this:
{
"name": string,
"group": string
}
By default, these fields seem to be optional. What is the best way I can make them required?
There are many ways of accomplishing such a behavior. You can:
... validate your objects to be POSTed before you invoke a Retrofitted-service (user input forms, etc), and let it fail fast.
... validate your domain objects, centralized, in a Retrofit request converter and use chained converters
... validate your data transfer objects objects (if you have any), centralized, after they are converted from domain objects and prepared to be sent
... rely on the server API implementation and don't care for validation at the client side: no need to duplicate server logic ad-hoc, you may run out sync with the server API validation, you write more code, etc. This is what I was suggesting you in that comment.
If you really need to validate the request bodies before they are sent, you should go with the first option. If you want to make the validation fully centralized, you can implement a custom Retrofit converter to make pre-validation on fly. (The code below uses Java 8 and a little bit of Google Guava, Retrofit 2 and Gson, however it can be easily reworked for another components).
Consider these:
interface IService {
#POST("/")
Call<String> post(
#Body User user
);
}
final class User {
final String name;
final String group;
User(final String name, final String group) {
this.name = name;
this.group = group;
}
}
Now we can implement Retrofit-stuff. The following mockOkHttpClient is a mock OkHttpClient to consume any request and respond with HTTP 200 OK and "OK".
private static final OkHttpClient mockOkHttpClient = new OkHttpClient.Builder()
.addInterceptor(chain -> new Response.Builder()
.request(chain.request())
.protocol(HTTP_1_0)
.code(HTTP_OK)
.body(ResponseBody.create(MediaType.parse("application/json"), "\"OK\""))
.build()
)
.build();
Now let's make a simple test:
final Iterable<Retrofit> retrofits = ImmutableList.of(
getAsIsRetrofit(),
getValidatedDomainObjectsRetrofit(),
getValidatedDataTransferObjectsRetrofit()
);
final User user = new User("user", "group");
for ( final Retrofit retrofit : retrofits ) {
final IService service = retrofit.create(IService.class);
final String message = service.post(user).execute().body();
System.out.println(message);
}
As you can see, there are three Retrofit instances that are instantiated with different configurations to demonstrate each of them.
The following Retrofit instance does not care the validation itself. And this is another time I recommend you to go with: simply post what you get as is and let the server API implementation deal with it itself. Consider the API implementation to return nice responses like HTTP 400 Bad Request, etc.
private static Retrofit getAsIsRetrofit() {
return new Retrofit.Builder()
.client(mockOkHttpClient)
.baseUrl("http://whatever")
.addConverterFactory(GsonConverterFactory.create())
.build();
}
The following Retrofit instance validates the given User object before it's converted to a Gson-friendly representation (depends on if you have domain objects to data transfer object transformations in your application):
private static Retrofit getValidatedDomainObjectsRetrofit() {
return new Retrofit.Builder()
.client(mockOkHttpClient)
.baseUrl("http://whatever")
.addConverterFactory(new Converter.Factory() {
#Override
public Converter<?, RequestBody> requestBodyConverter(final Type type, final Annotation[] parameterAnnotations,
final Annotation[] methodAnnotations, final Retrofit retrofit) {
if ( type != User.class ) {
return null;
}
final Converter<Object, RequestBody> nextConverter = retrofit.nextRequestBodyConverter(this, type, parameterAnnotations, methodAnnotations);
return (Converter<Object, RequestBody>) value -> {
if ( value instanceof User ) {
final User user = (User) value;
requireNonNull(user.name, "name must not be null");
requireNonNull(user.group, "group must not be null");
}
return nextConverter.convert(value);
};
}
})
.addConverterFactory(GsonConverterFactory.create())
.build();
}
And the next one validates data transfer objects before they are written to output streams. Probably the most low-level instance here.
private static Retrofit getValidatedDataTransferObjectsRetrofit() {
final Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(new TypeAdapterFactory() {
#Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
if ( typeToken.getRawType() != User.class ) {
return null;
}
final TypeAdapter<T> delegateTypeAdapter = gson.getDelegateAdapter(this, typeToken);
return new TypeAdapter<T>() {
#Override
public void write(final JsonWriter out, final T value)
throws IOException {
if ( value instanceof User ) {
final User user = (User) value;
requireNonNull(user.name, "name must not be null");
requireNonNull(user.group, "group must not be null");
}
delegateTypeAdapter.write(out, value);
}
#Override
public T read(final JsonReader in)
throws IOException {
return delegateTypeAdapter.read(in);
}
};
}
})
.create();
return new Retrofit.Builder()
.client(mockOkHttpClient)
.baseUrl("http://whatever")
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
}
Note that requireNonNull is a JDK 8 method, and if you want something like #NotNull, you can implement your own annotation processor, or find such an implementation in the Internet considering my implementation idea useless. :) However, I think you'd like the as-is approach the most.
Related
Here is the following flow in my code:
User calls create agency endpoint, containing the agency name and a boolean field ->
Router functions receives call, passes to handler ->
Handler converts request body to Mono, passes that mono so a service ->
Service saves agency to DB, generating an ID in the process, then creates a Response object containing the ID wrapped in a Mono, returns Response back to the Handler.
This is the part where I am stuck. Maintaining a reactive approach, I must save the Agency to Db and create the ID within a "doOnNext() part of the Mono. However, I can't return the ID that I get from the DB in order to create the response object.
What is the reactive way to accomplish this? Thanks in advance!
public RouterFunction<ServerResponse> createAgency() {
return RouterFunctions
.route(POST("/agency").and(accept(MediaType.APPLICATION_JSON)), handler::createAgency);
}
#Component
#AllArgsConstructor
public class AgencyHandler {
private final AgencyServiceImpl service;
#NonNull
public Mono<ServerResponse> createAgency(ServerRequest request){
Mono<CreateAgencyRequest> agency = request.bodyToMono(CreateAgencyRequest.class);
Mono<CreateAgencyResponse> response = service.createAgency(agency);
return ServerResponse.created(URI.create("/agency"))
.contentType(MediaType.APPLICATION_JSON)
.body(response, CreateAgencyResponse.class);
}
}
#Service
#AllArgsConstructor
public class AgencyServiceImpl implements AgencyService {
private final AgencyRepository agencyRepository;
private final UuidGenerator uuidGenerator;
public Mono<CreateAgencyResponse> createAgency(Mono<CreateAgencyRequest> request) {
UUID id;
request.doOnNext(agency -> {
UUID agencyId = uuidGenerator.getUUID();
Mono<Agency> newAgency = Mono.just(
new Agency(
agencyId,
agency.getFields().getName()
));
//repository.save(newAgency)
//return Mono.just(new CreateAgencyResponse(new CreateAgencyResponseData(agencyId.toString())));
});
// return request
return Mono.just(new CreateAgencyResponse(new CreateAgencyResponseData(agencyId.toString())));
}
}
Something like the following should do the trick:
#Service
#AllArgsConstructor
public class AgencyServiceImpl implements AgencyService {
private final AgencyRepository agencyRepository;
private final UuidGenerator uuidGenerator;
public Mono<CreateAgencyResponse> createAgency(Mono<CreateAgencyRequest> request) {
UUID agencyId = uuidGenerator.getUUID();
return request.flatMap(createAgencyRequest -> {
Agency agency = new Agency(agencyId, agency.getFields().getName();
return repository.save(newAgency);
}).map(saved ->
new CreateAgencyResponse(new CreateAgencyResponseData(agencyId.toString()));
)
}
}
You would create the Agency in the flatMap operation and store it in the database. I assume your repository is also reactive so it should return Mono as well, hence the flatMap operation. Afterwards you just need to map whatever the repository returned (you may want to have some logic here based on the successful operation on the database) to create the CreateAgencyResponse.
ThedoOnNext is not a good option to perform I/O bound operations such as database access. You should use flatmap instead. Also, have a look at map operator for synchronous, 1-to-1 transformations.
The final code should looks like this:
public Mono<CreateAgencyResponse> createAgency(Mono<CreateAgencyRequest> request) {
return request.map(req -> new Agency(uuidGenerator.getUUID(), req.getFields().getName()))
.flatMap(agency -> repository.save(agency))
.map(agency -> new CreateAgencyResponse(new CreateAgencyResponseData(agency.getAgencyId())));
}
doOnNext is for side effects, what you are looking for is flatMap
return request.flatMap(agency -> {
final UUID agencyId = uuidGenerator.getUUID();
return repository.save(new Agency(agencyId, agency.getFields().getName()))
.thenReturn(agencyId);
}).flatMap(id -> ServerResponse.ok()
.bodyValue(new CreateAgencyResponseData(agencyId.toString()))
.build());
Wrote this without a compiler to check the code but you should get the gist of it.
I am trying to learn about Retrofit since it seems to take care of a lot of the issues I am currently having with JSON requests and handling.
first and foremost, I understand that the methods we use are defined inside of interfaces, while making simple requests to obtain data it is quite simple to specify what is to be retrieved from the url as well as all the necessary endpoints based on the famous github example.
So if we are retrieving information form the github api, we would first create all the necessary pojo models and such and then define the interface as:
public interface GithubService {
#GET("users/{username}")
Observable<Github>getGithHubUser(#Path("username")String userName);
}
From that on the main activity we would have something like:
Retrofit retrofit = new Retrofit.Builder()
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("https://api.github.com/")
.build();
GithubService githubService = retrofit.create(GithubService.class);
Observable<Github> githubUser = githubService.getGithHubUser("usersName");
githubUser.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.map(user -> "Github Username: " + user.getName() + "\nUrl:" +user.getUrl() + "\nfollowing: "+ user.getHireable())
.subscribe(userInfo -> Log.d("Output", userInfo));
My question here would be how to send JSON information if the url requires something like this:
"data={\"process\":\"procesNumber\", \"phone\":\"123456\"}"
Basically, in order to get any response form the server I have been doing this using simple okhttp:
OkHttpClient client = new OkHttpClient();
RequestBody body = RequestBody.create(CREATE_MEDIA_TYPE, "data={\"process\":\"procesNumber\", \"phone\":\"123456\"}");
String ALLWAYS_API = "http://something something bla bla";
Request request = new Request.Builder()
.url("https://blablabla")
.post(body)
.build();
Call call = client.newCall(request);
call.enqueue(new Callback() {
#Override
public void onFailure(Call call, IOException e) {
... etc etc etc
}
To my understanding, even I need to create a pojo class that represents the data that needs to be sent to retrofit, something along the lines of:
public class DataRequest {
final String proces;
final String phone;
DataRequest(String process, String phone) {
this.process = process;
this.phone = phone;
}
}
Which would comply to the information being sent to the request, but how would I actually parse that to the interface implementation?
interface DataService {
#Post(not a clue what to place here)
DataRequest postJson(#Body how?)
}
And how would I actually add that to the retrofit builder? The examples that I am using come from different forums on the web as well as other questions asked by other users, this one in particular helped a lot in understanding a couple of things: How to POST raw whole JSON in the body of a Retrofit request? but I still don't understand where everything goes and some of the other questions and examples are far too complex for what I need to do.
Ok, so in order to leave an answer here for anyone trying to do this. By default, retrofit comes with many utilities which handle the passing of data as JSON, but in this case what I am passing is a string that looks like json inside of a tag called data......I know..
But in order to answer this for the people facing similar issues, in order to pass in the string we need to import a scalar convertor much in the same way that we need to import a gson converter to work with our retrofit services:
compile 'com.squareup.retrofit2:converter-scalars:2.0.2'
After that, our service can be handled as:
public interface CreateService {
#Headers({ "Content-Type: application/x-www-form-urlencoded;charset=UTF-8"})
#POST("your/post/path/goes/here")
Call<String> getStringScalar(#Body String body);
}
I write my service generators in a separate file, in this case, the whole thing can be used in this way:
public class ServiceGeneratorWithScalarConvertor {
private static final String API_BASE_URL = "your/base/url/goes/here";
private static OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
// basically, this code is the same as the one from before with the added instance of creating and making use of the scalar converter factory.....scratch that i took it off
private static Retrofit.Builder builder =
new Retrofit.Builder()
.baseUrl(API_BASE_URL)
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create());
public static <S> S createService(Class<S> serviceClass) {
builder.client(httpClient.build());
Retrofit retrofit = builder.build();
return retrofit.create(serviceClass);
}
}
From there, we can access the results with this particular method(i am using this method inside my main activity:
public void retroFitCreateAPIExample() {
CreateService service = ServiceGeneratorWithScalarConvertor.createService(CreateService.class);
String body = "data={\"process\":\"process1\",\"phone\":\"12345\"}";
Call<String> call = service.getStringScalar(body);
call.enqueue(new Callback<String>() {
#Override
public void onResponse(Call<String> call, Response<String> response) {
if(response.isSuccessful()){
Log.d("Response Body>>>>>", response.body());
createR = new Gson().fromJson(response.body().toString(), CreateModels.class);
Log.d("CREATED RESPONSE",createR.getCreate().getStops().get(0).getCity());
}
}
#Override
public void onFailure(Call<String> call, Throwable t) {
}
});
}
The instance is this passed to the service generator that uses scalar convertors, the body of the post request is saved as a simple string(as it was specified in the interface) and we can do with the response whatever we want.
I am planning to make one common service class with the use of Retrofit,
#GET
Call<ResponseBody> makeGetRequest(#Url String url);
#POST
Call<ResponseBody> makePostRequest(#Url String url, #Body RequestBody parameters);
In this code i need to pass (ResponseBody) as a dynamic JSON POJO class name , Like LoginRes
Say for Example ,
Call<LoginRes> // But this class will be dynamic
I will pass ResponseBody but that ResponseBody does not know which class i wanted to prefer.
why i want this because , after result
gson.fromJson(response, LoginRes.class);
so, after getting result from Retrofit we again need to convert to gson.fromJson.
so i wanted to pass dynamic Response as Retrofit so that it will response according to my pojo class,
I know this is working fine when i pass LoginRes instead of ResponseBody because as i already told to Response that we need that response in LoginRes.
So if i pass
Call<LoginRes> // if i pass this way its working fine no need to convert my response i can access my all properties from that LoginRes class directly.
This is my example to call a Web service.
Call<ResponseBody> call = apiService.makePostRequest("/Buyer/LoginApp", requestBody);
This is how i call the Service.
Let me know if i am unclear with explanation of my problem.
waiting for some good response and suggestions on this.
Thanks
Madhav
This is a bit tricky but you'll need to use a custom Retrofit Converter Factory with a custom GsonBuilder which uses a custom JsonDeserializer.
Furthermore you should define an interface (CustomResonse in my Example) for which the CustomJsonDeserializer is used. This is not needed, but otherwise the Deserializer gets used for every request.
public class CustomConverterFactory {
public static GsonConverterFactory create() {
return GsonConverterFactory.create(createGsonCustomDeserializer());
}
public static Gson createGsonCustomJsonDeserializer() {
return new GsonBuilder()
.registerTypeAdapter(CustomResponse.class, new CustomJsonDeserializer())
.serializeNulls()
.create();
}
}
And for the Deserializer:
public class CustomJsonDeserializer implements JsonDeserializer<CustomResponse> {
#Override
public CustomResponse deserialize(final JsonElement json, final Type typeOfT,
final JsonDeserializationContext context) throws JsonParseException {
if (json.isJsonObject()) {
JsonObject jsonObject = json.getAsJsonObject();
// Here you have to distinguish your class somehow
// Maybe read some Json field from your response
if (jsonObject.has("name")) {
JsonElement classes = jsonObject.get("name");
...
return context.deserialize(json, MyName.class);
}
...
// Default fallback: Deserialize as a fallback object
return context.deserialize(json, MyFallback.class);
} else {
throw new IllegalStateException();
}
}
Handle GET,POST or OTHER method Like this,
if (methodType.equalsIgnoreCase(CommonConfig.WsMethodType.GET)) {
apicall = getClient(CommonConfig.WsPrefix).create(ApiInterface.class).makeGetRequest(url + CommonConfig.getQueryString(new Gson().toJson(requestBody)), getAllHeader);
} else if (methodType.equalsIgnoreCase(CommonConfig.WsMethodType.POST)) {
apicall = getClient(CommonConfig.WsPrefix).create(ApiInterface.class).makePostRequest(url, RequestBody.create(MediaType.parse("application/json"), new Gson().toJson(requestBody)), getAllHeader);
}
Handle Response Like this.
apicall.enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
}
}
Retrofit Code
private Retrofit getClient(String WsPrefix) {
//TODO 60 to 30 second at everywhere
OkHttpClient okHttpClient = new OkHttpClient().newBuilder()
.connectTimeout(60, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.writeTimeout(60, TimeUnit.SECONDS)
.build();
retrofit = new Retrofit.Builder()
.baseUrl(WsPrefix)
.client(okHttpClient)
.build();
return retrofit;
}
Common Interface
interface ApiInterface {
#GET
Call<ResponseBody> makeGetRequest(#Url String url, #HeaderMap() Map<String, String> header);
#POST
Call<ResponseBody> makePostRequest(#Url String url, #Body RequestBody requestBody, #HeaderMap() Map<String, String> header);
}
ApiCallback
public interface ApiCallback {
void success(String responseData);
void failure(String responseData);
}
I'm using AndroidAnnotations to build a Rest for an Android Application.
On the Serverside im using PHP, which send a json looking like :
{"tag":"register","success":0,"error":2,"msg":"User already existed","body":[]}
I have two POJOS :
User.java:
public class User implements Serializable {
private String name;
private String email;
private String password;
//getter and setter Methods
}
Response.java:
public class RegistrationResponse implements Serializable {
private String tag;
private int success;
private int error;
private String msg;
private String body;
//getter and setter Methods
}
Rest Client:
#Rest(rootUrl = "http://my.domain.com", converters = {
MappingJacksonHttpMessageConverter.class,
StringHttpMessageConverter.class, GsonHttpMessageConverter.class }, interceptors = { MyInterceptor.class })
public interface RestClient extends RestClientErrorHandling {
#Post("/user/register/{name}/{email}/{pass}")
#Accept(MediaType.APPLICATION_JSON_VALUE)
Response sendUserRegistration(User user, String name, String email,
String pass);
RestTemplate getRestTemplate();
}
Activity.java:
//User and Response are POJOs
Response result = RestClient.sendUserRegistration(user,
user.getName(),user.getEmail(),user.getPassword());
But i got an Null Pointer Exception error on Activity.java. But if i change the return value of "sendUserRegistration" function to String all work. So my "Response" POJO seems not to be converted from AndroidAnnotations.
How can i convert the Rest Response to my "Response"-POJO using AndroidAnnotations?
You don't need to return the entire response object per rest call, just set the response to your custom object. Or you can also return a JsonObject also and use gson to convert it later on.
#Rest(rootUrl = "http://my.domain.com", converters = {
MappingJacksonHttpMessageConverter.class,
StringHttpMessageConverter.class, GsonHttpMessageConverter.class }, interceptors = { MyInterceptor.class })
public interface RestClient extends RestClientErrorHandling {
#Post("/user/register/{name}/{email}/{pass}")
#Accept(MediaType.APPLICATION_JSON_VALUE)
User sendUserRegistration(User user, String name, String email,
String pass);
RestTemplate getRestTemplate();
}
then just simply call
User newUser = RestClient.sendUserRegistration(user,
user.getName(),user.getEmail(),user.getPassword());
AA relies on Spring Android RestTemplate to make the rest call. And in order to build requests and handle responses this lib uses converters. And to know which converter the RestTemplate should use, it checks the content-type response header.
As MappingJacksonHttpMessageConverter and GsonHttpMessageConverter handles only http response with content-type=application/json and your result is converted to string, I'm pretty sure you forgot to set this header in your php server. So it send the default one (ie: text/plain) which is only handle by StringHttpMessageConverter.
Also, the body field is an object in your json example, but in your POJO you declared it as a String. So parsing will fail on this point.
I am working on some server code, where the client sends requests in form of JSON. My problem is, there are a number of possible requests, all varying in small implementation details.
I therefore thought to use a Request interface, defined as:
public interface Request {
Response process ( );
}
From there, I implemented the interface in a class named LoginRequest as shown:
public class LoginRequest implements Request {
private String type = "LOGIN";
private String username;
private String password;
public LoginRequest(String username, String password) {
this.username = username;
this.password = password;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
/**
* This method is what actually runs the login process, returning an
* appropriate response depending on the outcome of the process.
*/
#Override
public Response process() {
// TODO: Authenticate the user - Does username/password combo exist
// TODO: If the user details are ok, create the Player and add to list of available players
// TODO: Return a response indicating success or failure of the authentication
return null;
}
#Override
public String toString() {
return "LoginRequest [type=" + type + ", username=" + username
+ ", password=" + password + "]";
}
}
To work with JSON, I created a GsonBuilder instance and registered an InstanceCreator as shown:
public class LoginRequestCreator implements InstanceCreator<LoginRequest> {
#Override
public LoginRequest createInstance(Type arg0) {
return new LoginRequest("username", "password");
}
}
which I then used as shown in the snippet below:
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(LoginRequest.class, new LoginRequestCreator());
Gson parser = builder.create();
Request request = parser.fromJson(completeInput, LoginRequest.class);
System.out.println(request);
and I get the expected output.
The thing I wish to do is replace the line Request request = parser.fromJson(completeInput, LoginRequest.class); with something similar to Request request = parser.fromJson(completeInput, Request.class); but doing that will not work, since Request is an interface.
I want my Gson to return the appropriate type of request depending on the received JSON.
An example of the JSON I passed to the server is shown below:
{
"type":"LOGIN",
"username":"someuser",
"password":"somepass"
}
To reiterate, I am looking for a way to parse requests (In JSON) from clients and return objects of classes implementing the Request interface
Polymorphic mapping of the type described is not available in Gson without some level of custom coding. There is an extension type adapter available as an extra that provides a bulk of the functionality you are looking for, with the caveat that the polymorphic sub-types need to be declared to the adapter ahead of time. Here is an example of its use:
public interface Response {}
public interface Request {
public Response process();
}
public class LoginRequest implements Request {
private String userName;
private String password;
// Constructors, getters/setters, overrides
}
public class PingRequest implements Request {
private String host;
private Integer attempts;
// Constructors, getters/setters, overrides
}
public class RequestTest {
#Test
public void testPolymorphicSerializeDeserializeWithGSON() throws Exception {
final TypeToken<List<Request>> requestListTypeToken = new TypeToken<List<Request>>() {
};
final RuntimeTypeAdapterFactory<Request> typeFactory = RuntimeTypeAdapterFactory
.of(Request.class, "type")
.registerSubtype(LoginRequest.class)
.registerSubtype(PingRequest.class);
final Gson gson = new GsonBuilder().registerTypeAdapterFactory(
typeFactory).create();
final List<Request> requestList = Arrays.asList(new LoginRequest(
"bob.villa", "passw0rd"), new LoginRequest("nantucket.jones",
"crabdip"), new PingRequest("example.com", 5));
final String serialized = gson.toJson(requestList,
requestListTypeToken.getType());
System.out.println("Original List: " + requestList);
System.out.println("Serialized JSON: " + serialized);
final List<Request> deserializedRequestList = gson.fromJson(serialized,
requestListTypeToken.getType());
System.out.println("Deserialized list: " + deserializedRequestList);
}
}
Note that you don't actually need to define the type property on the individual Java objects - it exists only in the JSON.
Assuming that the different possible JSON requests you may have are not extremely different to each other, I suggest a different approach, simpler in my opinion.
Let's say that you have these 3 different JSON requests:
{
"type":"LOGIN",
"username":"someuser",
"password":"somepass"
}
////////////////////////////////
{
"type":"SOMEREQUEST",
"param1":"someValue",
"param2":"someValue"
}
////////////////////////////////
{
"type":"OTHERREQUEST",
"param3":"someValue"
}
Gson allows you to have a single class to wrap all the possible responses, like this:
public class Request {
#SerializedName("type")
private String type;
#SerializedName("username")
private String username;
#SerializedName("password")
private String password;
#SerializedName("param1")
private String param1;
#SerializedName("param2")
private String param2;
#SerializedName("param3")
private String param3;
//getters & setters
}
By using the annotation #SerializedName, when Gson try to parse the JSON request, it just look, for each named attribute in the class, if there's a field in the JSON request with the same name. If there's no such field, the attribute in the class is just set to null.
This way you can parse many different JSON responses using only your Request class, like this:
Gson gson = new Gson();
Request request = gson.fromJson(jsonString, Request.class);
Once you have your JSON request parsed into your class, you can transfer the data from the wrap class to a concrete XxxxRequest object, something like:
switch (request.getType()) {
case "LOGIN":
LoginRequest req = new LoginRequest(request.getUsername(), request.getPassword());
break;
case "SOMEREQUEST":
SomeRequest req = new SomeRequest(request.getParam1(), request.getParam2());
break;
case "OTHERREQUEST":
OtherRequest req = new OtherRequest(request.getParam3());
break;
}
Note that this approach gets a bit more tedious if you have many different JSON requests and those requests are very different to each other, but even so I think is a good and very simple approach...
Genson library provides support for polymorphic types by default. Here is how it would work:
// tell genson to enable polymorphic types support
Genson genson = new Genson.Builder().setWithClassMetadata(true).create();
// json value will be {"#class":"mypackage.LoginRequest", ... other properties ...}
String json = genson.serialize(someRequest);
// the value of #class property will be used to detect that the concrete type is LoginRequest
Request request = genson.deserialize(json, Request.class);
You can also use aliases for your types.
// a better way to achieve the same thing would be to use an alias
// no need to use setWithClassMetadata(true) as when you add an alias Genson
// will automatically enable the class metadata mechanism
genson = new Genson.Builder().addAlias("loginRequest", LoginRequest.class).create();
// output is {"#class":"loginRequest", ... other properties ...}
genson.serialize(someRequest);
By default, GSON cannot differentiate classes serialized as JSON; in other words, you will need to explicitly tell the parser what class you are expecting.
A solution could be custom deserializing or using a type adapter, as described here.
I found this answer: https://stackoverflow.com/a/28830173 which solved my issue when using Calendar as the interface as the RunTimeType would be GregorianCalendar.
Have a utility method to create GSON for an interface of generic type.
// Utility method to register interface and its implementation to work with GSON
public static <T> Gson buildInterface(Class<T> interfaceType, List<Class<? extends T>> interfaceImplmentations) {
final RuntimeTypeAdapterFactory<T> typeFactory = RuntimeTypeAdapterFactory.of(interfaceType, "type");
for (Class<? extends T> implementation : interfaceImplmentations) {
typeFactory.registerSubtype(implementation);
}
final Gson gson = new GsonBuilder().registerTypeAdapterFactory(typeFactory).create();
return gson;
}
// Build Gson
List<Class<? extends Request>> customConfigs = new ArrayList<>();
customConfigs.add(LoginRequest.getClass());
customConfigs.add(SomeOtherRequest.getClass());
Gson gson = buildInterface(Request.class, customConfigs);
Use this gson to serialize or deserialize and this works.