【问题标题】:How to map a JSON response to a Java class using Java 11 HttpClient and Jackson?如何使用 Java 11 HttpClient 和 Jackson 将 JSON 响应映射到 Java 类?
【发布时间】:2019-08-23 13:19:41
【问题描述】:

我是 Java 11 HttpClient 的新手,想尝试一下。我有一个返回 JSON 的简单 GET 请求,我想将 JSON 响应映射到名为 Questionnaire 的 Java 类。

我知道我可以将响应转换为字符串或像这样的输入流

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());

如何编写将 JSON 字符串转换为我的 Questionnaire 类的东西?

HttpResponse<Questionnaire> response = this.client.send(request, HttpResponse.BodyHandlers./* what can I do here? */);

我使用 Jackson 将 JSON 转换为 Java 类实例。 Jackson 是否支持新的 Java 标准 HttpClient?

更新 1 我不够精确,对此感到抱歉。我正在寻找一个阻塞获取示例。我知道http://openjdk.java.net/groups/net/httpclient/recipes.html#jsonGet

【问题讨论】:

  • send 调用 HttpClient 本身在必要时阻塞以获取响应。您可以简单地使用 response.body() 并将其映射到您的参考模型。

标签: java http httpclient java-11 java-http-client


【解决方案1】:

仅适用于 Java 11 HttpClient::sendAsync 的解决方案

基于this link,您可以执行以下操作:

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
}

我使用了一些提供示例 JSON 输入和示例模型类的虚拟端点,使用 Jackson 将响应直接映射到 Model 类。

Java 11 HttpClient::sendHttpClient::sendAsync 的解决方案

我通过定义自定义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);
                    }
                });
    }
}

那我叫它:

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);

}

回复是:

Model{userId='1', id='1', title='delectus aut autem', completed=false}

HttpResponse.BodySubscribers::mapping 的 JavaDoc 对解决这个问题特别有用。可以进一步改进使用HttpResponse.BodySubscribers::ofInputStream 而不是HttpResponse.BodySubscribers.ofString(StandardCharsets.UTF_8) 来为JsonBodyHandler 定义BodySubscriber

【讨论】:

  • 是的,但那是异步的
  • 因为CompletableFuture::get 是一个阻塞操作,它无论如何都会等待。我不知道是否可以仅使用 send 使用 BodyHandler 来做这样的事情。
  • 太棒了。你帮了我很多。谢谢
【解决方案2】:

为 Java 11 HttpClient::send 简化 @michalk 解决方案

HttpService 类示例:

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));
}}

模型类示例:

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 + '\'' + '}'; }}

发送 HTTP GET 请求:

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());
    }
}}

回复:

Single Object: Model{id='1'}

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

所需的 Maven 依赖项 (pom.xml):

<dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.10.3</version>
    </dependency>

【讨论】:

    【解决方案3】:

    如果您可以包含依赖项,请查看Methanol(免责声明:我是库的作者)。该库对object mapping 有特殊的BodyHandler 实现。您可以通过安装 jackson adapter 来添加 JSON 支持。

    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>>() {}));
    

    【讨论】:

      猜你喜欢
      • 2023-03-30
      • 2020-09-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多