js2xmlparser
js2xmlparser converts a JavaScript value into a complete XML document string. Plain object keys become element names, arrays become repeated sibling elements, and reserved keys represent attributes, bare text, or a renamed element. It also handles Map and Set values, XML declarations, DTDs, CDATA, pretty-printing, custom type conversion, and insertion into an existing xmlcreate element. It is a serializer, not an XML reader or schema mapper.
A good fit when your XML is a direct projection of JSON-shaped data and you can standardize its reserved-key convention. Choose a builder or bidirectional parser once namespaces, streaming, schema validation, or exact mixed-content control become central.
Use it if
- You need a compact Node.js utility that turns JSON-shaped data into a complete XML string with a named root
- Your XML format maps cleanly to object keys, repeated array elements, attributes under @, and text under #
- You need TypeScript declarations, CDATA controls, DTD output, or custom handlers for Date and other JavaScript types
- You already use xmlcreate and want to append object-shaped data to an XmlElement with parseToExistingElement
- You need to parse XML, validate against XSD, execute XPath, or round-trip arbitrary documents: this package only serializes JavaScript values and its validation is basic XML construction validation
- You need streaming output for very large documents: parse builds an xmlcreate document and returns one complete string, so the object tree and resulting XML coexist in memory
- Your data legitimately uses keys beginning with @ or #, or the exact key =: those strings have structural meaning by default and require option changes or input remapping
- You need a fluent XML builder for namespaces, comments, processing instructions, or carefully interleaved mixed content: the object convention becomes less clear than using xmlbuilder2 or xmlcreate directly
- You expect JSON round-trip semantics: undefined and null are stringified unless a type handler suppresses them, Date uses its ordinary string conversion by default, and arrays emit repeated elements rather than an enclosing list
Setup reality
Install `js2xmlparser` and import its `parse` function; the package is CommonJS but ships declarations through its `typings` entry. Its only runtime dependency is `xmlcreate`, and there are no peer dependencies, native builds, credentials, or config files. The work is designing an object convention that matches the XML your consumer expects. By default `@` introduces attributes, `#` introduces bare text, and `=` renames the current element. A prefix match is used for attribute and value keys, so keys such as `@meta` and `#2` are structural too. Arrays and Sets produce repeated elements named after the property; they do not add a container unless a `wrapHandlers` function tells the serializer what to call each item. Pretty-printing and an XML declaration are enabled by default, single quotes are used for attributes, empty values become self-closing tags, and validation is enabled. Those defaults can break byte-for-byte fixtures or partners that demand a specific declaration, indentation, quote style, or explicit closing tag. Native objects fall back to `toString()`, which makes Date output dependent on the runtime's local timezone and format unless you install a type handler. Null and undefined are not automatically omitted; return `Absent.instance` from a matching handler when omission is required. CDATA is opt-in, while normal text is escaped. `replaceInvalidChars` substitutes U+FFFD rather than rejecting invalid input, so use it only when lossy cleanup is acceptable. Finally, the result is one in-memory string, not a stream, and XML schema validation remains a separate step.
Patterns
Convert an object to an XML documentserialize-object
const { parse } = require('js2xmlparser');
const xml = parse('user', {
name: 'Ada',
active: true,
});
console.log(xml);The first argument is the required root element name. An XML declaration and pretty formatting are included by default.
Put attributes on an elementadd-attributes
const xml = parse('product', {
'@': { id: 'sku-42', currency: 'USD' },
name: 'Keyboard',
price: 99,
});Any object key beginning with the default @ marker is treated as an attribute collection, not as a child element.
Combine element text with attributesmix-text-and-attributes
const xml = parse('message', {
'@': { lang: 'en' },
'#': 'Hello & welcome',
});The # property becomes bare text and special characters are escaped. Use a named child property when you actually want a nested element.
Emit repeated sibling elements from an arrayrepeat-elements
const xml = parse('catalog', {
item: [
{ '@': { id: 'a' }, '#': 'Alpha' },
{ '@': { id: 'b' }, '#': 'Beta' },
],
});An array under item produces multiple item elements directly. It does not create a separate array wrapper.
Add a container around array itemswrap-array-items
const xml = parse('order', { lines: ['A', 'B'] }, {
wrapHandlers: {
lines: () => 'line',
},
});This emits a lines container containing line elements. Returning null from the handler keeps the default unwrapped repeated elements.
Choose an element name from datarename-element
const xml = parse('root', {
entry: {
'=': 'warning',
'#': 'Disk space low',
},
});The default = alias changes the current element name. Type handlers are not applied to alias values.
Preserve order with Map and numbered text keyspreserve-mixed-order
const content = new Map([
['#1', 'Read '],
['link', { '@': { href: '/docs' }, '#': 'the docs' }],
['#2', ' first.'],
]);
const xml = parse('p', content);Map iteration order controls placement. Distinct # keys allow text to appear on both sides of a child element.
Use CDATA for selected elementsemit-cdata
const xml = parse('article', {
title: 'XML tips',
source: 'if (a < b && b > 0) {}',
}, {
cdataKeys: ['source'],
});Only matching element names use CDATA. Embedded ]]> sequences are split safely rather than copied into an invalid CDATA section.
Control declaration and whitespaceformat-output
const xml = parse('response', { ok: true }, {
declaration: {
include: true,
encoding: 'UTF-8',
standalone: 'yes',
},
format: {
pretty: true,
indent: ' ',
newline: '\n',
doubleQuotes: true,
},
});Formatting choices affect serialized bytes. Match the receiving system's fixtures if signatures or strict comparisons are involved.
Serialize dates deterministically and omit nullsnormalize-types
const { parse, Absent } = require('js2xmlparser');
const xml = parse('event', {
at: new Date('2026-08-08T12:00:00Z'),
note: null,
}, {
typeHandlers: {
'[object Date]': (value) => value.toISOString(),
'[object Null]': () => Absent.instance,
},
});Without handlers, native values use normal string conversion. Returning Absent.instance suppresses the element entirely.
Add a system DTD declarationinclude-dtd
const xml = parse('invoice', { total: 125 }, {
dtd: {
include: true,
name: 'invoice',
sysId: 'invoice.dtd',
},
});With validation enabled, dtd.name is required when the DTD is included; a public identifier also requires a system identifier.
Append object data to an xmlcreate elementappend-existing-element
const { XmlDocument } = require('xmlcreate');
const { parseToExistingElement } = require('js2xmlparser');
const doc = new XmlDocument();
const root = doc.element({ name: 'feed' });
parseToExistingElement(root, { item: ['one', 'two'] });
const xml = doc.toString();parseToExistingElement adds no root, declaration, or DTD. Those document-level concerns belong to the existing xmlcreate document.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| xmlbuilder2 | npm | You need a fluent builder, namespaces, document manipulation, converters, or callback-based output control |
| fast-xml-parser | npm | You need both XML parsing and object-to-XML building, with configurable attribute and text-node conventions |
| xml-js | npm | You need bidirectional XML and JavaScript conversion with compact and non-compact representations |