How to consume JSON data and ignore repeated values?

177 views Asked by At

I'm consuming a Google Books API to display results from a simple book search. However, when performing the search, some items return repeated. See: book-search

To GET this data and display in HTML, I am doing this:

var result = document.getElementById("result");
var cover, title, author, book_id;
function handleResponse(response) {
  for (var i = 0; i < response.items.length; i++) {
    var item = response.items[i];
    // Create elements
    figure = document.createElement("figure");
    aEmp = document.createElement("a");
    aId = document.createElement("a");
    img = document.createElement("img");
    figcap = document.createElement("figcaption");
    // Get data from JSON
    try {
      cover = item.volumeInfo.imageLinks.thumbnail;
      title = item.volumeInfo.title;
      author = item.volumeInfo.authors;
      book_id = item.id;
    } catch (error) {
      continue;
    } finally {
      // Set value to elements e send to HTML
      result.appendChild(figure);
      aEmp.appendChild(img);
      aId.innerHTML = "ADICIONAR";
      aId.href = `/add/${book_id}`;
      aId.classList.add("dropdown");
      figure.appendChild(aId);
      figure.appendChild(aEmp);
      img.src = cover;
      figcap.innerHTML += `${title}<br>${author}`;
      figure.appendChild(figcap);
    }
  }
}
document.querySelector("form").addEventListener("submit", function (e) {
  result.innerHTML = "";
  search = document.getElementById("search").value;
  var script = document.createElement("script");
  script.src = `https://www.googleapis.com/books/v1/volumes?q=${search}&callback=handleResponse`;
  document.body.appendChild(script);
  e.preventDefault();
});

This is the JSON example used on the image I uploaded:

https://www.googleapis.com/books/v1/volumes?q=$harry+potter&callback=handleResponse

3

There are 3 answers

1
Alex Weinstein On

Looks like each of the items in Google's response array includes a unique ID. In your callback function, iterate through the array of returned results; remember ID's that have been seen already in a dictionary/hash table. If an ID that's been seen before is showing up again, just skip over that record.

2
jateen On

Went through the JSON data that you provided at the end, but I didn't find the API response to have any duplication. Couldn't figure out where exactly that repetition may have been done in the data.

1
Mr. Polywhirl On

I don't see any repeated values in your API search.

I did however replace the script insert callback hack with a fetch promise. This should flow better.

const results = document.querySelector('.results');
const googleBooksApi = 'https://www.googleapis.com/books/v1';

const formatList = (items) => {
  switch (items.length) {
    case 0: return '';
    case 1: return items[0];
    case 2: return items.join(' and ');
    default: return `${items.slice(0, -1).join(', ')} and ${[...items].pop()}`;
  }
};

// Convenience promise
const fetchBooks = (path) =>
  fetch(`${googleBooksApi}/${path}`)
    .then(response => response.json());

const queryBooksVolumes = (term) =>
  fetchBooks(`volumes?q=${term.replace(' ', '+')}`);

const addBook = (book) => {
  // Create elements
  const figure = document.createElement('figure');
  const aEmp = document.createElement('a');
  const aId = document.createElement('a');
  const img = document.createElement('img');
  const figcap = document.createElement('figcaption');
  const figcapTitle = document.createElement('div');
  const figcapAuthors = document.createElement('div');
  
  // Destructure data fields from book object (JSON)
  const {
    id: bookId,
    volumeInfo: {
      title,
      authors,
      publishedDate,
      imageLinks: { thumbnail }
    }
  } = book;

  const year = new Date(publishedDate).getFullYear();

  figure.classList.add('figure');
  aEmp.appendChild(img);
  aId.innerHTML = 'ADICIONAR';
  aId.href = `/add/${bookId}`;
  aId.classList.add('dropdown');
  figure.appendChild(aId);
  figure.appendChild(aEmp);
  img.src = thumbnail;
  figcapTitle.classList.add('figcap-title');
  figcapTitle.textContent = `${title} (${year})`;
  figcapAuthors.classList.add('figcap-authors');
  figcapAuthors.textContent = formatList(authors);
  figcap.append(figcapTitle);
  figcap.append(figcapAuthors);
  figure.appendChild(figcap);
  results.appendChild(figure);
};

const populateResults = ({ items }) => items
  .sort((
      { volumeInfo: { title: a } },
      { volumeInfo: { title: b } }
    ) =>
      a.localeCompare(b))
  .forEach(addBook);

const handleSearch = (e) => {
  e.preventDefault();
  const query = e.target.elements.search.value.trim();
  results.innerHTML = '';
  if (query.length > 0) {
    queryBooksVolumes(query).then(populateResults);
  }
};

document.forms['book-search'].addEventListener('submit', handleSearch);
html, body { width 100%; height: 100%; margin: 0; padding: 0; }

form { padding: 0.5em; }

.results {
  display: flex;
  flex-direction: row;
  /*flex-wrap: wrap;*/
  align-items: top;
}

form label { font-weight: bold; }
form label::after { content: ':'; }

.figure {
  display: flex;
  flex-direction: column;
  justify-content: center;
  border: thin solid grey;
  align-items: center;
  padding: 0.5em;
}

.figcap-title { font-weight: bold; }
.figcap-authors { font-style: italic; }

.figure img {
  margin: 0.5em 0;
}
<form name="book-search">
  <label>Book Title</label>
  <input type="text" name="search" value="Harry Potter" placeholder="Enter a book title..."/>
  <button>Search</button>
</form>
<div class="results"></div>