【发布时间】: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