John Little
John Little

Reputation: 12403

Java: How to return a generic type

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:

  1. we need to log the raw response string (and this has to be done before any json object mapping which may fail)
  2. its "REST", so returns status codes of 400, 404 etc which cause an exception and no mapping, but we still need to read the json repose and convert it to the object (which has error fields in it for this case)

Upvotes: 0

Views: 2107

Answers (1)

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

Related Questions