Using Gson with Interface Types
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);
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...