# Build a Heritage Library Catalog - Step 28

**URL:** <https://forum.freecodecamp.org/t/build-a-heritage-library-catalog-step-28/790804>\
**Category:** Curriculum Help\
**Tags:** javascript\
**Created:** [June 5, 2026, 4:57pm UTC](https://forum.freecodecamp.org/t/build-a-heritage-library-catalog-step-28/790804 "2026-06-05T16:57:45Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![besaidaurochs07](https://sea3.discourse-cdn.com/freecodecamp/user_avatar/forum.freecodecamp.org/besaidaurochs07/32/416253_2.png) [@besaidaurochs07](https://forum.freecodecamp.org/u/besaidaurochs07)\
**Post date:** [June 5, 2026, 4:57pm UTC](https://forum.freecodecamp.org/t/build-a-heritage-library-catalog-step-28/790804/1 "2026-06-05T16:57:45Z")

</div>

Error says “Strings should be wrapped in double quotes” but when I log the output, that’s what I’m getting. Thanks for your help!

```auto
const rawCatalogCards = [
  "From a Buick 8 | King, Stephen | 2002 | Shelf K7",
  "The Shining | King, Stephen | 1977 | Shelf K1",
  "The Stand | King, Stephen | 1978 | Shelf K2",
  "It | King, Stephen | 1986 | Shelf K3",
  "Misery | King, Stephen | 1987 | Shelf K4",
  "Do Androids Dream of Electric Sheep? | Dick, Philip K. | 1968 | Shelf D5",
  "I, Robot | Asimov, Isaac | 1950 | Shelf A8",
  "Foundation | Asimov, Isaac | 1951 | Shelf A9",
  "Dune | Herbert, Frank | 1965 | Shelf H3",
  "Neuromancer | Gibson, William | 1984 | Shelf G8",
  "Snow Crash | Stephenson, Neal | 1992 | Shelf S6",
  "The Martian | Weir, Andy | 2011 | Shelf W5",
  "Ender's Game | Card, Orson Scott | 1985 | Shelf C2",
  "The Hitchhiker's Guide to the Galaxy | Adams, Douglas | 1979 | Shelf A1",
  "Ready Player One | Cline, Ernest | 2011 | Shelf C7",
  "The Dark Tower: The Gunslinger | King, Stephen | 1982 | Shelf K5",
  // edge cases: missing data
  "Unknown Title | | 1975 | Shelf X1",
  "Mysterious Manuscript | Unknown Author | | Shelf Z9",
  "Ancient Scroll | Anonymous | 850 | ",
];

function parseCard(rawString) {
  const parts = rawString.split("|");
  const trimmedParts = [];
  for (let i = 0; i < parts.length; i++) {
    trimmedParts.push(parts[i].trim());
  }
  const title = trimmedParts[0];
  const author = trimmedParts[1];
  const year = trimmedParts[2];
  const location = trimmedParts[3];
  return {
    title: title || "Unknown",
    author: author || "Unknown",
    year: year ? parseInt(year) : "Unknown",
    location: location || "Unknown"
  };
}

function parseCatalog(rawCards) {
  const catalog = [];
  for (let i = 0; i < rawCards.length; i++) {
    catalog.push(parseCard(rawCards[i]));
  }
  return catalog;
}

const catalog = parseCatalog(rawCatalogCards);

function findByAuthor(catalog, author) {
  const searchTerm = author.toLowerCase();
  const results = [];
  for (let i = 0; i < catalog.length; i++) {
    if (catalog[i].author.toLowerCase().includes(searchTerm)) {
      results.push(catalog[i]);
    }
  }
  return results;
}

function groupByDecade(catalog) {
  const grouped = {};
  for (let i = 0; i < catalog.length; i++) {
    const book = catalog[i];
    if (book.year === "Unknown") {
      if (!grouped["Unknown"]) {
        grouped["Unknown"] = [];
      }
      grouped["Unknown"].push(book);
      continue;
    }
    const decade = Math.floor(book.year / 10) * 10;
    const decadeKey = `${decade}s`;
    if (!grouped[decadeKey]) {
      grouped[decadeKey] = [];
    }
    grouped[decadeKey].push(book);
  }
  return grouped;
}

const byDecade = groupByDecade(catalog);

function renderEntry(entry) {
  const title = entry.title || "Unknown";
  const author = entry.author || "Unknown";
  const year = entry.year || "Unknown";
  const location = entry.location || "Unknown";
  return `${"-".repeat(25)}
Title: ${title}
Author: ${author}
Year: ${year}
Location: ${location}
${"-".repeat(25)}`;
}

console.log(renderEntry(catalog[0]));

function validateEntry(entry) {
  let isValid = true;
  if (!("title" in entry) || !entry.title || entry.title === "Unknown") {
    isValid = false;
  }
  if (!("author" in entry) || !entry.author || entry.author === "Unknown") {
    isValid = false;
  }
  if (!("year" in entry) || !entry.year || entry.year === "Unknown") {
    isValid = false;
  }
  if (!("location" in entry) || !entry.location || entry.location === "Unknown") {
    isValid = false;
  }
  return isValid;
}

function exportToJSON(catalog) {
  return JSON.stringify(catalog, null, 2);
}

function exportToCSV(catalog) {
  const header = "Title,Author,Year,Location";
  const rows = [];
  for (let i = 0; i < catalog.length; i++) {
    const strang = `"${catalog[i].title}","${catalog[i].author}",${catalog[i].year},"${catalog[i].location}"`;
    console.log(strang);
    rows.push(strang);
  }
  return rows;
}

exportToCSV(catalog)

```

---

<div class="post-metadata">

**Author:** ![hbar1st](https://sea3.discourse-cdn.com/freecodecamp/user_avatar/forum.freecodecamp.org/hbar1st/32/372649_2.png) [@hbar1st](https://forum.freecodecamp.org/u/hbar1st)\
**Post date:** [June 5, 2026, 5:22pm UTC](https://forum.freecodecamp.org/t/build-a-heritage-library-catalog-step-28/790804/2 "2026-06-05T17:22:49Z")

</div>

If you have a question about a specific challenge as it relates to your written code for that challenge and need some help, click the **Help** button located on the challenge.

 ![image](https://global.discourse-cdn.com/freecodecamp/original/4X/9/e/5/9e5d03b4b54808931538e1c967c26d4fe89eb76a.png)

The **Help** button will create a new topic with all code you have written and include a link to the challenge also. You will still be able to ask any questions in the post before submitting it to the forum.

Thank you.

---

<div class="post-metadata">

**Author:** ![besaidaurochs07](https://sea3.discourse-cdn.com/freecodecamp/user_avatar/forum.freecodecamp.org/besaidaurochs07/32/416253_2.png) [@besaidaurochs07](https://forum.freecodecamp.org/u/besaidaurochs07)\
**Post date:** [June 5, 2026, 5:52pm UTC](https://forum.freecodecamp.org/t/build-a-heritage-library-catalog-step-28/790804/3 "2026-06-05T17:52:26Z")

</div>

### Tell us what’s happening:

I am getting the error “String must begin and end with double quotes” but the output shows that’s happening correctly. Not sure where I’m going wrong. Thanks for your help!

### Your code so far

```js
const rawCatalogCards = [
  "From a Buick 8 | King, Stephen | 2002 | Shelf K7",
  "The Shining | King, Stephen | 1977 | Shelf K1",
  "The Stand | King, Stephen | 1978 | Shelf K2",
  "It | King, Stephen | 1986 | Shelf K3",
  "Misery | King, Stephen | 1987 | Shelf K4",
  "Do Androids Dream of Electric Sheep? | Dick, Philip K. | 1968 | Shelf D5",
  "I, Robot | Asimov, Isaac | 1950 | Shelf A8",
  "Foundation | Asimov, Isaac | 1951 | Shelf A9",
  "Dune | Herbert, Frank | 1965 | Shelf H3",
  "Neuromancer | Gibson, William | 1984 | Shelf G8",
  "Snow Crash | Stephenson, Neal | 1992 | Shelf S6",
  "The Martian | Weir, Andy | 2011 | Shelf W5",
  "Ender's Game | Card, Orson Scott | 1985 | Shelf C2",
  "The Hitchhiker's Guide to the Galaxy | Adams, Douglas | 1979 | Shelf A1",
  "Ready Player One | Cline, Ernest | 2011 | Shelf C7",
  "The Dark Tower: The Gunslinger | King, Stephen | 1982 | Shelf K5",
  // edge cases: missing data
  "Unknown Title | | 1975 | Shelf X1",
  "Mysterious Manuscript | Unknown Author | | Shelf Z9",
  "Ancient Scroll | Anonymous | 850 | ",
];

function parseCard(rawString) {
  const parts = rawString.split("|");
  const trimmedParts = [];
  for (let i = 0; i < parts.length; i++) {
    trimmedParts.push(parts[i].trim());
  }
  const title = trimmedParts[0];
  const author = trimmedParts[1];
  const year = trimmedParts[2];
  const location = trimmedParts[3];
  return {
    title: title || "Unknown",
    author: author || "Unknown",
    year: year ? parseInt(year) : "Unknown",
    location: location || "Unknown"
  };
}

function parseCatalog(rawCards) {
  const catalog = [];
  for (let i = 0; i < rawCards.length; i++) {
    catalog.push(parseCard(rawCards[i]));
  }
  return catalog;
}

const catalog = parseCatalog(rawCatalogCards);

function findByAuthor(catalog, author) {
  const searchTerm = author.toLowerCase();
  const results = [];
  for (let i = 0; i < catalog.length; i++) {
    if (catalog[i].author.toLowerCase().includes(searchTerm)) {
      results.push(catalog[i]);
    }
  }
  return results;
}

function groupByDecade(catalog) {
  const grouped = {};
  for (let i = 0; i < catalog.length; i++) {
    const book = catalog[i];
    if (book.year === "Unknown") {
      if (!grouped["Unknown"]) {
        grouped["Unknown"] = [];
      }
      grouped["Unknown"].push(book);
      continue;
    }
    const decade = Math.floor(book.year / 10) * 10;
    const decadeKey = `${decade}s`;
    if (!grouped[decadeKey]) {
      grouped[decadeKey] = [];
    }
    grouped[decadeKey].push(book);
  }
  return grouped;
}

const byDecade = groupByDecade(catalog);

function renderEntry(entry) {
  const title = entry.title || "Unknown";
  const author = entry.author || "Unknown";
  const year = entry.year || "Unknown";
  const location = entry.location || "Unknown";
  return `${"-".repeat(25)}
Title: ${title}
Author: ${author}
Year: ${year}
Location: ${location}
${"-".repeat(25)}`;
}

console.log(renderEntry(catalog[0]));

function validateEntry(entry) {
  let isValid = true;
  if (!("title" in entry) || !entry.title || entry.title === "Unknown") {
    isValid = false;
  }
  if (!("author" in entry) || !entry.author || entry.author === "Unknown") {
    isValid = false;
  }
  if (!("year" in entry) || !entry.year || entry.year === "Unknown") {
    isValid = false;
  }
  if (!("location" in entry) || !entry.location || entry.location === "Unknown") {
    isValid = false;
  }
  return isValid;
}

function exportToJSON(catalog) {
  return JSON.stringify(catalog, null, 2);
}

function exportToCSV(catalog) {
  const header = "Title,Author,Year,Location";
  const rows = [];

// User Editable Region

  for (let i = 0; i < catalog.length; i++) {
    const strang = `"${catalog[i].title}","${catalog[i].author}",${catalog[i].year},"${catalog[i].location}"`;
    console.log(strang);
    rows.push(strang);
  }
  return rows;

// User Editable Region

}

exportToCSV(catalog)

```

### Your browser information:

User Agent is: `Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:151.0) Gecko/20100101 Firefox/151.0`

### Challenge Information:

Build a Heritage Library Catalog - Step 28

> **[freeCodeCamp.org](https://www.freecodecamp.org/learn/javascript-v9/workshop-heritage-library-catalog/step-28)**
>
> Learn to Code — For Free

GitHub Link: [freeCodeCamp/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/69a9de8abda299f753b1afe4.md at main · freeCodeCamp/freeCodeCamp · GitHub](https://github.com/freeCodeCamp/freeCodeCamp/blob/main/curriculum/challenges/english/blocks/workshop-heritage-library-catalog/69a9de8abda299f753b1afe4.md)

---

<div class="post-metadata">

**Author:** ![dhess](https://sea3.discourse-cdn.com/freecodecamp/user_avatar/forum.freecodecamp.org/dhess/32/406235_2.png) [@dhess](https://forum.freecodecamp.org/u/dhess)\
**Post date:** [June 5, 2026, 5:57pm UTC](https://forum.freecodecamp.org/t/build-a-heritage-library-catalog-step-28/790804/4 "2026-06-05T17:57:12Z")

</div>

Hi @besaidaurochs07,

This is from the instruction:

> Inside the loop, declare `const entry = catalog[i]`

It doesn’t look like this was done.

Also, you are not asked to log anything in this step.

Happy coding

---

<div class="post-metadata">

**Author:** ![system](https://global.discourse-cdn.com/freecodecamp/original/3X/e/1/e1c433102e34b46dfaa3e15d13ab9a514b127078.svg) [@system](https://forum.freecodecamp.org/u/system)\
**Post date:** [July 3, 2026, 5:52pm UTC](https://forum.freecodecamp.org/t/build-a-heritage-library-catalog-step-28/790804/5 "2026-07-03T17:52:56Z")

</div>

Closed on July 3, 2026, 5:52pm UTC

This topic was automatically closed 28 days after the last reply. New replies are no longer allowed.
