How xml2js Converts XML Trees to JavaScript Objects
The xml2js module converts XML markup into JavaScript
object graphs by leveraging an event-driven SAX parser combined with an
internal state stack. Instead of building a resource-heavy Document
Object Model (DOM) in memory, xml2js streams the XML string
sequentially, identifies structural boundaries, and dynamically
constructs nested JavaScript objects, arrays, and primitive values based
on predefined parsing rules.
The SAX-Based Parsing Pipeline
At its core, xml2js relies on sax-js, a
pure JavaScript SAX (Simple API for XML) parser. The conversion process
is stream-oriented and event-driven:
- Tokenization: As the parser reads the input XML string, it breaks the text into distinct lexical tokens (such as element opening tags, attributes, text nodes, and closing tags).
- Event Emission: When the tokenizer detects these
boundaries, it emits specific events, primarily
opentag,text,cdata, andclosetag. - Listener Processing: The
xml2jsparser listens to these events to assemble the target JavaScript structure incrementally.
Managing Hierarchy with the Stack Model
To convert flat streaming events into a multi-dimensional object
tree, xml2js maintains an execution stack:
- Opening Tags (
opentag): When a new element starts,xml2jscreates a new JavaScript object to represent that element. It pushes this object onto its internal stack, designating it as the current active context. - Text and CDATA (
text,cdata): Any character data found inside an element is captured and assigned to the active object on top of the stack. - Closing Tags (
closetag): When the closing tag is encountered, the element is completed, popped off the stack, and attached as a property to its parent element (the new top of the stack).
Property Mapping and Array Normalization
Because XML elements can repeat under the same parent while
JavaScript object keys must be unique, xml2js applies
specific structural rules:
- Array Boxing: By default (with
explicitArray: true), child elements are stored in arrays. This guarantees consistent data structures whether a parent contains one child or multiple sibling elements sharing the same tag name. - Attribute Handling: Attributes are extracted and
stored inside a nested object under the
$key by default, preventing name collisions between attributes and child tags of the same name. - Text Node Keying: When an element contains both
attributes and text content, the text content is assigned to the
_key to separate raw character data from metadata.
Final Graph Generation
Once the root element’s closing tag is processed and the stack is empty, the parser finishes building the object tree. The resulting native JavaScript graph mirrors the original XML hierarchy and is returned asynchronously via a Promise or a callback function.