forma más sencilla de llamada de API REST y JSON datos de análisis sintáctico con Java

Chris:

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 hrefcadenas 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());
diutsu:

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.

Supongo que te gusta

Origin http://43.154.161.224:23101/article/api/json?id=237083&siteId=1
Recomendado
Clasificación