Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions concurrency-test/bench.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { XMLParser } from "fast-xml-parser";
import { performance } from "node:perf_hooks";

// Generate a bigger XML payload (tune SIZE if needed)
const ITEMS = 50000; // try 5k, 20k, 50k if needed
const xml =
`<root>` +
Array.from({ length: ITEMS }, (_, i) => `<item><id>${i}</id><value>test</value></item>`).join("") +
`</root>`;

function parseWithNewParser() {
const parser = new XMLParser();
return parser.parse(xml);
}

const sharedParser = new XMLParser();
function parseWithSharedParser() {
return sharedParser.parse(xml);
}

async function runScenario({ label, iterations, concurrency, shared }) {
// Create tasks to run in batches of `concurrency`
const parseFn = shared ? parseWithSharedParser : parseWithNewParser;

const t0 = performance.now();

for (let i = 0; i < iterations; i += concurrency) {
const batchSize = Math.min(concurrency, iterations - i);
await Promise.all(
Array.from({ length: batchSize }, () =>
Promise.resolve().then(() => parseFn())
)
);
}

const t1 = performance.now();
return { label, ms: t1 - t0 };
}

function fmt(ms) {
return `${(ms / 1000).toFixed(3)}s`;
}

async function main() {
const rounds = 3; // average a few runs
const iterations = 50; // increase if results are too noisy
const concurrencies = [1, 2, 5, 10, 25, 50];

console.log(`Node: ${process.version}`);
console.log(`ITEMS: ${ITEMS}, iterations: ${iterations}, rounds: ${rounds}\n`);

for (const shared of [true, false]) {
console.log(shared ? "=== Shared parser instance ===" : "=== New parser per task ===");

for (const c of concurrencies) {
const results = [];
for (let r = 0; r < rounds; r++) {
const out = await runScenario({
label: `c=${c}`,
iterations,
concurrency: c,
shared,
});
results.push(out.ms);
}
const avg = results.reduce((a, b) => a + b, 0) / results.length;
console.log(`concurrency ${String(c).padStart(2)} avg: ${fmt(avg)} (runs: ${results.map(fmt).join(", ")})`);
}
console.log("");
}
}

main();
33 changes: 33 additions & 0 deletions concurrency-test/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { XMLParser } from "fast-xml-parser";

const parser = new XMLParser();

const xml = `
<root>
${Array.from({ length: 1000 })
.map((_, i) => `<item><id>${i}</id><value>test</value></item>`)
.join("")}
</root>
`;

function syncParse() {
parser.parse(xml);
}

function asyncParse() {
return Promise.resolve().then(() => parser.parse(xml));
}

async function runTest() {
console.time("sync");
for (let i = 0; i < 1000; i++) syncParse();
console.timeEnd("sync");

console.time("promiseAll");
await Promise.all(
Array.from({ length: 1000 }, () => asyncParse())
);
console.timeEnd("promiseAll");
}

runTest();
34 changes: 34 additions & 0 deletions docs/v4/3.XMLBuilder.md
Original file line number Diff line number Diff line change
Expand Up @@ -545,3 +545,37 @@ Output


[> Next: XmlValidator](./4.XMLValidator.md)

## Round-tripping XML → JSON → XML (XML declaration)

If your input XML contains a declaration like `<?xml version="1.0"?>`, the parser may include it in the output JSON as a `"?xml"` property (unless disabled).
When converting the JSON back to XML, you may want to omit this node to avoid invalid output (e.g., “XML declaration allowed only at the start of the document”).

### Recommended approaches

**Option A (recommended): ignore the declaration while parsing**
```js
import { XMLParser, XMLBuilder } from "fast-xml-parser";

const parser = new XMLParser({ ignoreDeclaration: true });
const builder = new XMLBuilder();

const obj = parser.parse(xmlInput);
const xmlOutput = builder.build(obj);

**Option B: Remove the XML declaration node before building**

If you need to preserve the XML declaration during parsing but want to prevent it from being re-emitted during JSON → XML conversion, you can remove the `"?xml"` node before building:

```js
import { XMLParser, XMLBuilder } from "fast-xml-parser";

const parser = new XMLParser();
const builder = new XMLBuilder();

const obj = parser.parse(xmlInput);

// Remove XML declaration before building
delete obj["?xml"];

const xmlOutput = builder.build(obj);