Heading items to tree structure with missing items

I am writing a function that queries all heading elements that are direct children of a div and stores them as an item in an array (data) where a heading with a lower tag name is an item of the heading above it. In other words, creating a tree structure for the headings. Each heading is in the data array is an object with the following properties:

text : a string with the inner text of the heading.

items: an array with possible subheading items.

number: a string that represents the position of the heading in the document.

id: a string that is composed of the text of the element and it’s parents texts, separated by two underscores “__”.

Getting the names and numbers right, I had to store the last ones for each heading level in an array, to access them while traversing through the items. This is what I came up with:

const contentContainer = document.getElementById('content');
// Query headings that are direct children of the content container. 
const headings = contentContainer.querySelectorAll(":scope > h1,:scope > h2,:scope > h3,:scope > h4,:scope > h5,:scope > h6");

// String helper function
function kebabCase(string) {
  return (string
    .replace(/([a-z])([A-Z])/g, '$1-$2').replace(/[s_]+/g, '-').toLowerCase()
  )
}

// Contains the list of headings and their items.
const data = [];
// Array that stores the last depth of each of the last headings at each depth.
const indexHelper = new Array(6).fill(-1);
// Array that stores the last name of each of the last headings at each depth.
const nameHelper = new Array(6).fill('');

// Generates a datalist of headings and sets an id for each heading in the DOM.
function processTag(depth, element) {
  const listItem = {
    text: element.innerText,
    items: []
  };
  const name = kebabCase(element.innerText);
  // Increment the indexHelper at depth position and set the current name.
  indexHelper[depth]++;
  nameHelper[depth] = name;
  // Reset rest of array that is bigger than current depth.
  indexHelper.fill(-1, depth + 1);
  // Push the element to the right place in the array.
  let dataPushString = 'data';
  for (let i = 0; i < depth; i++) {
    dataPushString += "[indexHelper[".concat(i, "]]?.items?");
    i < depth - 1 && (dataPushString += '.');
  }
  dataPushString += '.push(listItem)';
  eval(dataPushString);
  // Generate an id based on the names of the element and its parents.
  let id = '';
  for (let i = 0; i < depth; i++) {
    id += nameHelper[i] + "__";
  }
  let number = '';
  for (let i = 0; i < depth; i++) {
    number += "(indexHelper[".concat(i + 1, "] +1) + "." ");
    i < depth - 1 && (number += ' + ');
  }

  listItem.id = id + name;
  listItem.number = eval(number) && eval(number);
}

function processData() {
  for (let i = 0; i < headings.length; i++) {
    const element = headings[i];
    const depth = Number(element.tagName.substring(1) - 1);
    processTag(depth, element);
  }
}

processData();

const pre = document.getElementById('pre');
pre.innerHTML = JSON.stringify(data, null, 2);
<pre id="pre"></pre>

<div id="content">
  <h1>Title</h1>
      <h3>This heading is problematic</h3>
    <h2>H2</h2>
    <h2>Another h2</h2>
      <h3>Depth h3 </h3>
        <h4>Fourth level</h4>
      <h3>Back to three</h3>
    <h2>Back to two</h2>
</div>

As you can see in the example above, the malformatted <h3>This heading is problematic</h3> not added to data. The reason being, I am looping over the item and try to add it to it’s parent that does not exist. This is done by creating concatenating a string to the right “depth” using eval(), which also is a bad idea.

My question is twofold: how can I make sure the problematic heading is added to data even though it’s (direct) parent heading does not exist? And how do I get around not using eval()?

The result I am hoping to get is:

[
  {
    "text": "Title",
    "items": [
      {
        "text": "",
        "items": [
           {
            "text": "This heading is problematic",
            "items": [],
            "id": "title____this-heading-is-problematic",
            "number": "2.1.1"
           },
       ],
        "id": "title__",
        "number": "1."
      },
      {
        "text": "H2",
        "items": [],
        "id": "title__h2",
        "number": "2."
      },
      {
        "text": "Another h2",
        "items": [
               //...etc...