Reputation: 12403
We have a service which calls various rest end points, and converts the JSON response into an object.
We have seen it done in java, that the service can return a generic type, but cant figure out the syntax.
Lets say we have a bunch of models for the different API reponses, and one method to call the endpoints and return one of these. e.g.
public class MyServiceImpl implements MyService{
@Override
public <T> T doGet( String endpoint) {
:
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
:
Gson gson = new Gson();
T model = new T();
model = gson.fromJson(response.getBody(), model.class));
return(model);
}
and called thus:
SomeModel model = myService.doGet("https://somesite.com/someendpoint")
Obviously, the above code wont work, because you cant do "new T()"
httpClient has a built in way to do this, instead of returning the response as a string, but we cant use it for two reasons:
Upvotes: 0
Views: 2107
Reputation: 1787
As Yuliya Sheludyakova mentioned, you can't do new T();
because javac
lacks type information due to generics erasure. Also creating a new object even if the type and constructor were known at that point is unnecessary since Gson returns a new object on deserialization (and this was mentioned by Yuliya Sheludyakova too).
What you can do is providing the type (an instance of java.lang.reflect.Type
, java.lang.Class
is one of them with type-limited capabilities) to the fromJson
method invocation so that Gson would deserialize the payload into an object of the provided type.
public interface IService {
@Nullable
<T> T doGet(@Nonnull URL url, @Nonnull Type type)
throws IOException;
@Nullable
<T> T doGet(@Nonnull URL url, @Nonnull TypeToken<? extends T> typeToken)
throws IOException;
}
final class Service
implements IService {
// Gson instances are thread-safe and may be expensive on instantiation
private static final Gson gson = new Gson();
// Unsafe: the return type T and Type type are not bound to each other
@Nullable
@Override
public <T> T doGet(@Nonnull final URL url, @Nonnull final Type type)
throws IOException {
// Prefer not using string buffers that may be very expensive for large payloads
// Streams are much cheaper
try ( final JsonReader jsonReader = new JsonReader(new InputStreamReader(doGet(url))) ) {
return gson.fromJson(jsonReader, type);
}
}
// Safe: javac can detect if the return type and the type token are bound
@Nullable
@Override
public <T> T doGet(@Nonnull final URL url, @Nonnull final TypeToken<? extends T> typeToken)
throws IOException {
// Prefer not using string buffers that may be very expensive for large payloads
// Streams are much cheaper
try ( final JsonReader jsonReader = new JsonReader(new InputStreamReader(doGet(url))) ) {
// TypeToken.getType() is guaranteed to provide a correct bound type
return gson.fromJson(jsonReader, typeToken.getType());
}
}
private static <T> T doGet(@Nonnull final URL url) {
throw new AssertionError("Stub! " + url);
}
}
Example of use for the safe method:
private static final TypeToken<List<User>> userListTypeToken = new TypeToken<List<User>>() {};
public static void main(final String... args)
throws IOException {
final IService service = new Service();
final List<User> users = service.doGet(new URL("http://localhost:8080/users"), userListTypeToken);
for ( final User user : users ) {
System.out.println(user);
}
}
Note that Spring RestTemplate, Retrofit and other libraries do the same and it's probably worth using those libraries in your code.
Upvotes: 1