Reputation: 93658
I'm getting responses from my server that look like:
{
"timestamp" : 1,
"some other data": "blah",
"result" : {
...
}
}
for a variety of calls. What I want to do client side is:
class ServerResponse<T> {
long timestamp;
T result;
}
and then deserialize that with GSON or Jackson. I've been unable to do so thanks to type erasure. I've cheated that using subclasses like this:
class SpecificResponse extends ServerRequest<SpecificType> {}
but that requires a bunch of useless classes to lie around. Anyone have a better way?
I also need to be able to handle the case of result being an array.
Upvotes: 1
Views: 1531
Reputation: 14731
I support @Pillar
solution to use Jackson
because it is so straiforward. 2 lines of code...
Here is Gson version that will work the same way, but you will need custom deserializer and a little reflection to achieve this.
public static class CustomDeserializer implements JsonDeserializer<ServerResponse> {
@Override
public ServerResponse deserialize(JsonElement je, Type respT,
JsonDeserializationContext jdc) throws JsonParseException {
Type t = (respT instanceof ParameterizedType) ?
((ParameterizedType) respT).getActualTypeArguments()[0] :
Object.class;
JsonObject jObject = (JsonObject) je;
ServerResponse serverResponse = new ServerResponse();
//can add validation and null value check here
serverResponse.timestamp = jObject.get("timestamp").getAsLong();
JsonElement dataElement = jObject.get("result");
if (dataElement != null) {
if (dataElement.isJsonArray()) {
JsonArray array = dataElement.getAsJsonArray();
// can use ((ParameterizedType) respT).getActualTypeArguments()
// instead of new Type[]{t}
// if you 100% sure that you will always provide type
Type listT = ParameterizedTypeImpl.make(List.class, new Type[]{t}, null);
serverResponse.result = jdc.deserialize(array, listT);
} else if(dataElement.isJsonObject()) {
serverResponse.result = new ArrayList();
serverResponse.result.add(jdc.deserialize(dataElement, t));
}
}
return serverResponse;
}
}
Use case is very simmilar to Jackson:
Gson gson = new GsonBuilder()
.registerTypeAdapter(ServerResponse.class, new CustomDeserializer())
.create();
ServerResponse<MyObject> s = gson.fromJson(json, new TypeToken<ServerResponse<MyObject>>(){}.getType())
Upvotes: 0
Reputation: 3531
The typical solution to type erasure in this case is the type token hack which takes advantage of anonymous classes maintaining superclass information for use with reflection.
Jackson provides the TypeReference
type as well as an ObjectMapper#readValue
overload to use it.
In your example, you'd use
ServerResponse response = objectMapper.readValue(theJsonSource, new TypeReference<ServerResponse<SpecificType>>() {});
Note that this is not type-safe so you must be careful that the type you try to assign to is compatible with the generic type argument you used in the anonymous class instance creation expression.
As for supporting both single values and arrays in the JSON, you can change your field to be of some Collection
type. For example,
List<T> results
Then, enable DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY
.
Feature that determines whether it is acceptable to coerce non-array (in JSON) values to work with Java collection (arrays,
java.util.Collection
) types. If enabled, collection deserializers will try to handle non-array values as if they had "implicit" surrounding JSON array.
Upvotes: 1