【问题标题】:Simplest way to call REST API and parse JSON data with Java使用 Java 调用 REST API 和解析 JSON 数据的最简单方法
【发布时间】:2019-08-01 19:14:28
【问题描述】:

我正在尝试做一些在 JavaScript 中微不足道但在 Java 中看起来很复杂的事情。我希望有人能指出如何在 Java 中简单地做到这一点。

我想调用一个 REST JSON API,例如https://images-api.nasa.gov/search?q=clouds

我得到一个简化形式的数据结构,看起来像这样:

{
  "collection": {
    "items": [
      {
        "links": [
          {
            "href": "https://images-assets.nasa.gov/image/cloud-vortices_22531636120_o/cloud-vortices_22531636120_o~thumb.jpg",
            "rel": "preview"
          }
        ]
      }
    ]
  }
}

在 Java 中,我想调用 URL 并将 href 字符串作为列表。

在 JavaScript 中,我会简单地写

fetch("https://images-api.nasa.gov/search?q=moon")
  .then(data => data.json())
  .then(data => {
    const items = data
      .collection
      .items
      .map(item => item.links)
      .flat()
      .filter(link => link.rel === "preview")
      .map(link => link.href);

    // do something with "items"
})

1。我最初的解决方案

经过一番搜索,我发现了这种方法,它似乎朝着正确的方向发展,但仍然非常冗长。

String uri = "https://images-api.nasa.gov/search?q=clouds";
List<String> hrefs = new ArrayList<>();

try {
    // make the GET request
    URLConnection request = new URL(uri).openConnection();
    request.connect();
    InputStreamReader inputStreamReader = new InputStreamReader((InputStream) request.getContent());

    // map to GSON objects
    JsonElement root = new JsonParser().parse(inputStreamReader);

    // traverse the JSON data 
    JsonArray items = root
            .getAsJsonObject()
            .get("collection").getAsJsonObject()
            .get("items").getAsJsonArray();

    // flatten nested arrays
    JsonArray links = new JsonArray();
    items.forEach(item -> links.addAll(item
            .getAsJsonObject()
            .get("links")
            .getAsJsonArray()));

    // filter links with "href" properties
    links.forEach(link -> {
        JsonObject linkObject = link.getAsJsonObject();
        String relString = linkObject.get("rel").getAsString();
        if ("preview".equals(relString)) {
            hrefs.add(linkObject.get("href").getAsString());
        }
    });

} catch (IOException e) {
    e.printStackTrace();
}

return hrefs;

我剩下的问题是:

  • 有没有办法 使用 RestTemplate 或其他一些库来减少 GET 请求 冗长但仍保持 GSON 的通用灵活性?
  • 有没有办法用 GSON 展平嵌套的 JsonArray 和/或过滤 JsonArray,所以我不需要 创建额外的临时 JsonArrays?
  • 还有其他方法可以让代码不那么冗长吗?

已编辑

以下部分是在阅读下面的cmet和答案后添加的。


2。不那么冗长的解决方案

(正如@diutsu 的回答所建议的那样)

List<String> hrefs = new ArrayList<>();
String json = new RestTemplate().getForObject("https://images-api.nasa.gov/search?q=clouds", String.class);
new JsonParser().parse(json).getAsJsonObject()
    .get("collection").getAsJsonObject()
    .get("items").getAsJsonArray()
    .forEach(item -> item.getAsJsonObject()
        .get("links").getAsJsonArray()
        .forEach(link -> {
            JsonObject linkObject = link.getAsJsonObject();
            String relString = linkObject.get("rel").getAsString();
            if ("preview".equals(relString)) {
                hrefs.add(linkObject.get("href").getAsString());
            }
        })
    );
return hrefs;

3。使用 Mapper POJO 的解决方案

(受@JBNizet 和@diutsu 启发)

实际的 GET 请求和转换现在是单行的,几乎与我上面提出的 JavaScript 代码相同,...

return new RestTemplate().getForObject("https://images-api.nasa.gov/search?q=clouds", CollectionWrapper.class)
    .getCollection()
    .getItems().stream()
    .map(Item::getLinks)
    .flatMap(List::stream)
    .filter(item -> "preview".equals(item.getRel()))
    .map(Link::getHref)
    .collect(Collectors.toList());

...但要让它工作,我必须创建以下 4 个类:

CollectionWrapper

public class CollectionWrapper {

    private Collection collection;

    public CollectionWrapper(){}

    public CollectionWrapper(Collection collection) {
        this.collection = collection;
    }

    public Collection getCollection() {
        return collection;
    }
}

收藏

public class Collection {

    private List<Item> items;

    public Collection(){}

    public Collection(List<Item> items) {
        this.items = items;
    }

    public List<Item> getItems() {
        return items;
    }
}

物品

public class Item {

    private List<Link> links;

    public Item(){}

    public Item(List<Link> links) {
        this.links = links;
    }

    public List<Link> getLinks() {
        return links;
    }
}

链接

public class Link {

    private String href;
    private String rel;

    public Link() {}

    public Link(String href, String rel) {
        this.href = href;
        this.rel = rel;
    }

    public String getHref() {
        return href;
    }

    public String getRel() {
        return rel;
    }
}

4。使用 Kotlin

(受@NBNizet 启发)

val collectionWrapper = RestTemplate().getForObject("https://images-api.nasa.gov/search?q=clouds", CollectionWrapper::class.java);
return collectionWrapper
        ?.collection
        ?.items
        ?.map { item -> item.links }
        ?.flatten()
        ?.filter { item -> "preview".equals(item.rel) }
        ?.map { item -> item.href }
        .orEmpty()

使用 Kotlin 使映射器类更简单,甚至比使用 Java 和 Lombok 更简单

data class CollectionWrapper(val collection: Collection)
data class Collection(val items: List<Item>)
data class Item(val links: List<Link>)
data class Link(val rel: String, val href: String)

5。直接映射到 Map 和 List

我不相信这是个好主意,但很高兴知道它可以做到:

return 
    ( (Map<String, Map<String, List<Map<String, List<Map<String, String>>>>>>) 
        new RestTemplate()
        .getForObject("https://images-api.nasa.gov/search?q=clouds", Map.class)
    )
    .get("collection")
    .get("items").stream()
    .map(item -> item.get("links"))
    .flatMap(List::stream)
    .filter(link -> "preview".equals(link.get("rel")))
    .map(link -> link.get("href"))
    .collect(Collectors.toList());

【问题讨论】:

  • 不管 HTTP 客户端库如何,我通常更喜欢让 JSON 映射器(通常是 Jackson 或 Gson)使用标准集合以及类型化和命名属性将 JSON 映射到自定义 Java 类。
  • @JBNizet 我理解这个想法,但在我看来,在上面的示例中访问我需要的数据需要做很多工作,不是吗?在创建整个模型类结构之前,您甚至无法发出 GET 请求,在这种情况下,这意味着至少要创建 ItemsList、Item、LinkList 和 Link 类,每个类都有它们的构造函数和 getter、注释和 toString(), hashCode() 和 equals()。你指的是这个吗?
  • 是的。 Java 不是动态语言,因此要么使用 JSONArray/JSONObject,要么使用 Maps 和 Lists,要么使用实际的 Java 对象。我尽可能使用 Kotlin,这使得创建这些 POJOS 变得更加简单和简洁。但话虽如此,对于此类 POJO,您不需要 hashCode/equals/toString。而且你也不需要注释。
  • 你不需要为每个类定义注解、toString、hashCode和equals。另外,看看lombook,它会有所帮助。

标签: java json rest gson resttemplate


【解决方案1】:

1) 以字符串形式获取

restTemplate.getForObject("https://images-api.nasa.gov/search?q=clouds", String.class)

2) 简单,不要使用数组。 我会说它的可读性较差,但您可以提取一些方法来帮助解决这个问题。

root.getAsJsonObject()
    .get("collection").getAsJsonObject()
    .get("items").getAsJsonArray()
    .forEach(item -> item.getAsJsonObject()
       .get("links").getAsJsonArray()
       .forEach(link -> {
            JsonObject linkObject = link.getAsJsonObject();
            String relString = linkObject.get("rel").getAsString();
            if ("preview".equals(relString)) {
               hrefs.add(linkObject.get("href").getAsString());
            }));

3) 如果您不想保持简单,则不会:D 您可以定义自己的结构,然后直接从 restTemplate 获取该结构。这将是一个班轮。但是由于您只关心href,因此没有任何意义。

【讨论】:

  • 这是完美的。谢谢你。我会用这个提议更新我的解决方案。
猜你喜欢
  • 1970-01-01
  • 2014-08-19
  • 2017-10-19
  • 1970-01-01
  • 2016-03-06
  • 2017-06-30
  • 2017-07-23
  • 2011-08-22
  • 1970-01-01
相关资源
最近更新 更多