Stack Exchange
Issue with recursive function converting dom element to json
I have a recursivce function that takes a dom tree and converts it to JSON.
However I want to exclude any nodes that have a specific data attribute data-exclude
const htmlToJSON = node => {
  const exclude = node.attributes?.getNamedItem('data-exclude');
  if (!exclude) {
    let obj = {
      nodeType: node.nodeType
    };
    if (node.tagName) {
      obj.tagName = node.tagName.toLowerCase();
    } else if (node.nodeName) {
      obj.nodeName = node.nodeName;
    }
    if (node.nodeValue) {
      obj.nodeValue = node.nodeValue;
    }
    let attrs = node.attributes;
    if (attrs) {
      length = attrs.length;
      const arr = (obj.attributes = new Array(length));
      for (let i = 0; i < length; i++) {
        const attr = attrs[i];
        arr[i] = [attr.nodeName, attr.nodeValue];
      }
    }

    let childNodes = node.childNodes;
    if (childNodes && childNodes.length) {
      let arr = (obj.childNodes = []);
      for (let i = 0; i < childNodes.length; i++) {
        arr[i] = htmlToJSON(childNodes[i]);
      }
    }
    return obj;
  }
};

const parser = new DOMParser();
const { body } = parser.parseFromString(page, 'text/html');

let jsonOutput = htmlToJSON(body);
console.log(jsonOutput);

I am clearly missing something with the way I am excluding because when I log the results it is returning undefined instead of just excluding it.


You might try if(!exclude || typeof exclude == "undefined")

It's most likely because you're not returning anything from htmlToJSON in the case of "exclude == true". Notice how your lambda function doesn't have a "return " in that case. So the function will by default return "undefined."
And if you fill an array element with "undefined" it becomes a sparse array. So those elements in the array with "undefined" values become interpreted as "empty" slots by console.log() when printing the contents of any array to the console.
Update: I tried your code and, yup, my explanation above is correct. However, if you don't care about implicitly returning undefined from your htmlToJSON(), then you can just modify your inner for loop:
    for (let i = 0; i < childNodes.length; i++) {
      let json = htmlToJSON(childNodes[i]);
      json && arr.push(json);
    }

This way, only if json is truthy, will it add an element to the arr array.
I tried this code with your original function, and also with a modified version that returns null if exclude == true, and both ways worked.
Here's a Codepen example.

I thought it was this so had it return false or null and all it does it replace undefined with false or null respectively

Yeah, I forgot he needed to add a check for null or undefined in his htmlToJSON(childNodes[i]) loop, and NOT add to the array in this case. I've since updated my answer with the correct for() loop code which worked in all cases in my javascript environment (tried both browser and plain node on the command line [used some mock HTML and the @xmldom/xmldom package as DOMParser for the node version]).

Yep that works, so what is the difference between arr[i] = htmlToJson(childNodes[i]) and saving it to a new variable json and pushing it json && arr.push(json) that solves this?

Nevermind it became obvious as asked you. :) It is first checking if there is any JSON before pushing it so it does not return undefined.

Did not execute the code. As far as I can see htmlToJSON will return obj or undefined. If exclude is truthy, the function will return undef, thats what you are seeing.
Change your for loop:
for (let i = 0, temp; i < childNodes.length; i++) {
   temp = htmlToJSON(childNodes[i]);
   temp && (arr[i] = temp);
}

that way you make sure if temp is defined you assign, otherwise not.
Another option is to use Array.prototype.filter on the resultant array.

This did not work unfortunately. Adding it to the array with json && arr[i] = temp does not work but json && arr.push(json) does.

If you create a minimal reproducible example, it will be easier to debug. You don't even have a variable called json at your example.

Another answer similar to yours uses/adds a json variable I was referring to. In your example you called it temp but same concept applies. I did try create a reproducible example but the stackexchange code editor didn't work for some reason
