I’m new to the Java 11 HttpClient and would like to give it a try. I have a simple GET request that return JSON and I would like to map the JSON response to a Java class called Questionnaire
.
I understand that I can turn the response out of box into a String or an input stream like this
HttpRequest request = HttpRequest.newBuilder(new URI(String.format("%s%s", this.baseURI, "/state"))) .header(ACCEPT, APPLICATION_JSON) .PUT(noBody()).build(); HttpResponse<String> response = this.client.send(request, HttpResponse.BodyHandlers.ofString());
How can I write something that converts the JSON string to my Questionnaire class like this?
HttpResponse<Questionnaire> response = this.client.send(request, HttpResponse.BodyHandlers./* what can I do here? */);
I use Jackson to transform JSON into Java class instances. Is there Jackson support for the new Java standard HttpClient yet?
UPDATE 1 I was not precise enough, sorry about that. I am looking for a blocking get example. I was aware of http://openjdk.java.net/groups/net/httpclient/recipes.html#jsonGet
Advertisement
Answer
Solution for Java 11 HttpClient::sendAsync
only
Based on this link you can do something like this :
public static void main(String[] args) throws IOException, URISyntaxException, ExecutionException, InterruptedException { UncheckedObjectMapper uncheckedObjectMapper = new UncheckedObjectMapper(); HttpRequest request = HttpRequest.newBuilder(new URI("https://jsonplaceholder.typicode.com/todos/1")) .header("Accept", "application/json") .build(); Model model = HttpClient.newHttpClient() .sendAsync(request, HttpResponse.BodyHandlers.ofString()) .thenApply(HttpResponse::body) .thenApply(uncheckedObjectMapper::readValue) .get(); System.out.println(model); } class UncheckedObjectMapper extends com.fasterxml.jackson.databind.ObjectMapper { /** * Parses the given JSON string into a Map. */ Model readValue(String content) { try { return this.readValue(content, new TypeReference<Model>() { }); } catch (IOException ioe) { throw new CompletionException(ioe); } } } class Model { private String userId; private String id; private String title; private boolean completed; //getters setters constructors toString }
I used some dummy endpoint which provides sample JSON input and sample model class to map the response directly to Model
class using Jackson.
Solution for Java 11 HttpClient::send
and HttpClient::sendAsync
I found a way by defining custom HttpResponse.BodyHandler
:
public class JsonBodyHandler<W> implements HttpResponse.BodyHandler<W> { private Class<W> wClass; public JsonBodyHandler(Class<W> wClass) { this.wClass = wClass; } @Override public HttpResponse.BodySubscriber<W> apply(HttpResponse.ResponseInfo responseInfo) { return asJSON(wClass); } public static <T> HttpResponse.BodySubscriber<T> asJSON(Class<T> targetType) { HttpResponse.BodySubscriber<String> upstream = HttpResponse.BodySubscribers.ofString(StandardCharsets.UTF_8); return HttpResponse.BodySubscribers.mapping( upstream, (String body) -> { try { ObjectMapper objectMapper = new ObjectMapper(); return objectMapper.readValue(body, targetType); } catch (IOException e) { throw new UncheckedIOException(e); } }); } }
Then I call it :
public static void main(String[] args) throws URISyntaxException, IOException, InterruptedException { HttpRequest request = HttpRequest.newBuilder(new URI("https://jsonplaceholder.typicode.com/todos/1")) .header("Accept", "application/json") .build(); Model model = HttpClient.newHttpClient() .send(request, new JsonBodyHandler<>(Model.class)) .body(); System.out.println(model); }
The response is :
Model{userId='1', id='1', title='delectus aut autem', completed=false}
The JavaDoc of HttpResponse.BodySubscribers::mapping
was particulary useful to solve this. It can be further improved to use HttpResponse.BodySubscribers::ofInputStream
instead of HttpResponse.BodySubscribers.ofString(StandardCharsets.UTF_8)
to define the BodySubscriber
for the JsonBodyHandler
.