Estoy tratando de hacer algo que es trivial en JavaScript, pero parece complicado con Java. Estoy esperando que alguien puede señalar cómo hacerlo simplemente en Java también.
Quiero llamar a una API REST JSON, por ejemplo, https://images-api.nasa.gov/search?q=clouds
Regrese una estructura de datos que, en una forma simplificada, se ve algo como esto:
{
"collection": {
"items": [
{
"links": [
{
"href": "https://images-assets.nasa.gov/image/cloud-vortices_22531636120_o/cloud-vortices_22531636120_o~thumb.jpg",
"rel": "preview"
}
]
}
]
}
}
En Java, quiero llamar la URL y obtener las href
cadenas como una lista.
En JavaScript, lo que escribiría simplemente
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. Mi solución inicial
Con un poco de búsqueda, me encontré con este enfoque, que parece ir en la dirección correcta, pero todavía muy prolijo.
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;
Mis preguntas restantes son:
- ¿Hay una manera de utilizar RestTemplate o alguna otra biblioteca para hacer la solicitud GET menos detallado y aún así mantener la flexibilidad genérica de GSON?
- ¿Hay una manera de aplanar JsonArrays anidados y / o JsonArrays filtro con GSON por lo que no es necesario crear JsonArrays temporales adicionales?
- ¿Hay otras formas de hacer que el código menos detallado?
Editado
Se añadieron los siguientes apartados después de leer los comentarios y respuestas a continuación.
2. solución menos verbosa
(Como se propone en la respuesta por @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. Solución usando POJOs Mapper
(Inspirado por @JBNizet y @diutsu)
La solicitud GET y Actuall tranformation es ahora una sola línea y casi idéntico al código JavaScript que planteé anteriormente, ...
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());
... pero para que esto funcione, tuve que crear los siguientes 4 clases:
CollectionWrapper
public class CollectionWrapper {
private Collection collection;
public CollectionWrapper(){}
public CollectionWrapper(Collection collection) {
this.collection = collection;
}
public Collection getCollection() {
return collection;
}
}
Colección
public class Collection {
private List<Item> items;
public Collection(){}
public Collection(List<Item> items) {
this.items = items;
}
public List<Item> getItems() {
return items;
}
}
Articulo
public class Item {
private List<Link> links;
public Item(){}
public Item(List<Link> links) {
this.links = links;
}
public List<Link> getLinks() {
return links;
}
}
Enlace
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. El uso de Kotlin
(Inspirado por @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()
Usando Kotlin hace que las clases asignador más simple, aún más simple que el uso de Java con 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. Mapeo directamente al mapa y una lista
No estoy convencido de que esto es una idea buena, pero bueno saber que se puede hacer:
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());
1) se obtiene como una cadena
restTemplate.getForObject("https://images-api.nasa.gov/search?q=clouds", String.class)
2) simple, no utilice matrices. Yo diría que es menos legible, pero se puede extraer algunos métodos para ayudar con eso.
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) No, si quieres probar que sea sencillo: D Se podría definir su propia estructura y luego llegar a esa estructura directamente desde el restTemplate. Sería un un trazador de líneas. pero dado que sólo se preocupan por los hrefs, no tiene sentido.