Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to map a JSON response to a Java class using Java 11 HttpClient and Jackson?

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

like image 749
saw303 Avatar asked Aug 23 '19 13:08

saw303


People also ask

How do I map one JSON to another?

You can use the Jackson API to add field or transform a JSON without creating POJO's. It provides a object form called JsonNode , JsonObject and JsonArray types which can be transformed like i did in the below code. I hope this helps you.

How does Jackson JSON work?

The Jackson ObjectMapper can parse JSON from a string, stream or file, and create a Java object or object graph representing the parsed JSON. Parsing JSON into Java objects is also referred to as to deserialize Java objects from JSON. The Jackson ObjectMapper can also create JSON from Java objects.

What does ObjectMapper readValue do?

The simple readValue API of the ObjectMapper is a good entry point. We can use it to parse or deserialize JSON content into a Java object. Also, on the writing side, we can use the writeValue API to serialize any Java object as JSON output.


3 Answers

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.

like image 131
Michał Krzywański Avatar answered Sep 29 '22 08:09

Michał Krzywański


Simplifying @michalk solution for Java 11 HttpClient::send

HttpService Class Example:

public class HttpService {

private final HttpClient httpClient= HttpClient.newBuilder().version(HttpClient.Version.HTTP_2).build();

public HttpService() {}

public <T> T sendGetRequest(String url, Class<T> responseType) throws IOException, InterruptedException {
    HttpRequest request = HttpRequest.newBuilder().GET().uri(URI.create(url)).header("Accept", "application/json").build();

    HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());

    return new ObjectMapper().readValue(response.body(), responseType);
}

public <T> List<T> sendGetListRequest(String url, Class<T> responseType) throws IOException, InterruptedException {

    HttpRequest request = HttpRequest.newBuilder().GET().uri(URI.create(url)).header("Accept", "application/json").build();

    HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());

    ObjectMapper objectMapper = new ObjectMapper();
    return objectMapper.readValue(response.body(), objectMapper.getTypeFactory().constructCollectionType(List.class, responseType));
}}

Model Class Example:

public class Model {

private String id;

public Model() {}

public String getId() { return this.id; }

public void setId(String id) { this.id = id; }

@Override
public String toString() { return "Model{" + "id='" + id + '\'' + '}'; }}

Sending HTTP GET request:

public class Main {

public static void main(String[] args) {
    try {
        HttpService httpService = new HttpService();

        Model model = httpService.sendGetRequest("http://localhost:8080/api/v1/models/1", Model.class);
        System.out.println("Single Object:" + model);

        System.out.print('\n');

        List<Model> models = httpService.sendGetListRequest("http://localhost:8080/api/v1/models", Model.class);
        for(Model m: models) { System.out.println("Object:" + m); }

    }
    catch (IOException | InterruptedException e) {
        System.err.println("Failed to send GET request: " + e.getMessage());
    }
}}

Response:

Single Object: Model{id='1'}

Object: Model{id='1'}
Object: Model{id='2'}
Object: Model{id='3'}

Required Maven Dependency (pom.xml):

<dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.10.3</version>
    </dependency>
like image 44
ChristopherV Avatar answered Sep 29 '22 08:09

ChristopherV


If you're OK with including a dependency, check out Methanol (disclaimer: I'm the library's author). The library has special BodyHandler implementations for object mapping. You can add JSON support by installing the jackson adapter.

var request = MutableRequest.GET("https://example.com")
    .header("Accept", "application/json");

var modelResponse = client.send(request, MoreBodyHandlers.ofObject(Model.class));

// Use TypeRef<T> for complex types
var modelListResponse = client.send(request, MoreBodyHandlers.ofObject(new TypeRef<List<Model>>() {}));
like image 25
Moataz Abdelnasser Avatar answered Sep 29 '22 07:09

Moataz Abdelnasser