frontend

Serialize DOM Tree to JSON in JavaScript

January 24, 2026

Serialize DOM Tree to JSON in JavaScript

Overview

Serializing a DOM tree to JSON means converting the HTML structure into a JSON representation that preserves the element hierarchy, attributes, and text content. This is useful for storing DOM structures, sending them over networks, or manipulating them as data.

Basic Implementation

// Convert HTML DOM tree nodes into JSON format
function serializeDOMTree(node) {
  /** Create the json structure for the json data */
  let nodeData = {
    tag: node.tagName.toLowerCase(),
    attributes: {},
    children: [],
  };

  /** Store all the attributes of the element (e.g. class, id, data-cy etc) */
  for (let i = 0; i < node.attributes.length; i++) {
    const attr = node.attributes[i];
    nodeData.attributes[attr.name] = attr.value;
  }

  /** Loop through the child nodes */
  for (let i = 0; i < node.childNodes.length; i++) {
    const childNode = node.childNodes[i];

    /** If element node found then make recursive call for further children */
    if (childNode.nodeType === Node.ELEMENT_NODE) {
      nodeData.children.push(serializeDOMTree(childNode));
    } else if (childNode.nodeType === Node.TEXT_NODE) {
      /** If text node then store the value as child */
      const text = childNode.textContent.trim();
      if (text) {
        nodeData.children.push(text);
      }
    }
  }

  return nodeData;
}

console.log(serializeDOMTree(document.body));

Enhanced Implementation

With Options

function serializeDOMTree(node, options = {}) {
  const {
    includeTextNodes = true,
    includeComments = false,
    includeWhitespace = false,
    attributeFilter = null
  } = options;
  
  const nodeData = {
    tag: node.tagName ? node.tagName.toLowerCase() : null,
    attributes: {},
    children: []
  };
  
  // Handle attributes
  if (node.attributes) {
    for (let attr of node.attributes) {
      if (!attributeFilter || attributeFilter.includes(attr.name)) {
        nodeData.attributes[attr.name] = attr.value;
      }
    }
  }
  
  // Handle children
  for (let child of node.childNodes) {
    if (child.nodeType === Node.ELEMENT_NODE) {
      nodeData.children.push(serializeDOMTree(child, options));
    } else if (child.nodeType === Node.TEXT_NODE) {
      const text = includeWhitespace ? child.textContent : child.textContent.trim();
      if (text && includeTextNodes) {
        nodeData.children.push(text);
      }
    } else if (child.nodeType === Node.COMMENT_NODE && includeComments) {
      nodeData.children.push({
        type: 'comment',
        text: child.textContent
      });
    }
  }
  
  return nodeData;
}

Use Cases

1. DOM Snapshot

const snapshot = serializeDOMTree(document.body);
localStorage.setItem('domSnapshot', JSON.stringify(snapshot));

2. Server Communication

const domJSON = serializeDOMTree(formElement);
fetch('/api/save-form', {
  method: 'POST',
  body: JSON.stringify(domJSON)
});

3. DOM Comparison

const before = serializeDOMTree(element);
// Make changes
const after = serializeDOMTree(element);
// Compare structures

Best Practices

  1. Handle Edge Cases: Text nodes, comments, empty elements
  2. Filter Attributes: Only include needed attributes
  3. Preserve Structure: Maintain hierarchy accurately
  4. Performance: Consider for small DOM trees
  5. Validation: Validate JSON structure