Quick answer: Run five JSON-to-XML checks before sending data: confirm the receiver's XML contract, detect renamed-key collisions, test collection boundaries, distinguish nil from empty and missing values, and verify escaped text. A converter returning XML text is only the start. The receiving system must recognize the intended structure and values.
A request looks readable in an XML viewer, yet the partner rejects it. The root name is wrong, a required attribute became a child element, or an empty collection disappeared. These failures need mapping tests, not another indentation setting.
Use the JSON-to-XML converter to inspect sanitized examples with its documented root and array-item controls. It creates child elements, sanitizes property names, repeats array entries, and represents nested null values with xsi:nil. It does not validate an XML Schema Definition, or XSD, or build a partner-specific envelope automatically.
For background, Wikipedia's XML overview describes the document format; our JSON versus XML guide explains broader differences. This checklist concerns outbound JSON-to-XML acceptance, not the reverse-direction parsing checks in our XML-to-JSON test guide.
The five executable examples were run from this repository's root against its conversion helper source, using the installed fast-xml-parser 5.11.2 for inspection. These relative imports require that checkout and its dependencies. They document the tested implementation, not a universal conversion standard or a complete XSD validation suite.
1. Does the output match the receiver's required XML structure?
Start with the receiving API's root element, namespaces, attribute requirements, and child structure. Generic JSON-to-XML conversion cannot infer those choices from JSON property names alone. Compare a minimal converted request with an approved receiver example, then validate the actual output using the receiver's schema and integration checks where available.
Ask for the smallest accepted request, not only a large example containing optional fields. Identify which names belong to the protocol envelope and which belong to your application data. This prevents a convenient default root from becoming an accidental contract. Put those expectations in tests next to the conversion settings.
The site's converter lets users choose a root name, but it maps every JSON property to a child element. An attribute-looking source key is not an instruction to create an attribute. This differs from libraries that deliberately support attribute prefixes, including the options documented in the fast-xml-builder reference.
import assert from 'node:assert/strict';
import { jsonToXml, xmlToJson } from './lib/json-tools/convert.mjs';
const xml = jsonToXml({ id: 'A12', '@_status': 'ready' },
{ rootName: 'request' });
const parsed = xmlToJson(xml, { typed: false });
assert.equal(parsed.request.id, 'A12');
assert.equal(parsed.request.__status, 'ready');
assert.equal(Object.hasOwn(parsed.request, '@_status'), false);
The fixture deliberately demonstrates the tool's limitation: status remains element data under a sanitized name, not an XML attribute. If a receiver demands an attribute, select a suitable transformation instead of editing the JSON until the output merely looks similar. Keep separate assertions for the required source value and its required destination location.
Namespace identity is another boundary. A colon inside a JSON key does not by itself create a namespace binding. The W3C namespace specification defines the relationship between namespace names, prefixes, and local names. Use namespace-aware construction when the contract requires it; do not infer equivalence from prefix spelling alone.
Finally, treat schema validation and delivery as separate checks. Well-formed XML can still violate required element order or omit mandatory fields. An accepted schema can still leave authentication or transport requirements unsatisfied. A formatter or converter cannot certify those independent integration properties.
2. Do sanitized element names preserve distinct JSON fields?
Test potentially colliding keys together before trusting automatic name sanitization. A converter can make each output name syntactically acceptable while mapping two different source names to the same element name. Reject collisions or define explicit destination names, and verify that every important source field remains distinguishable after conversion.
This is a data-identity test, not a spelling preference. A feed might contain both a display label called ship date and an existing machine field called ship_date. Replacing spaces with underscores makes them collide. Repeated XML elements are legal in many contexts, so a syntax check cannot prove that two independent fields stayed independent.
import assert from 'node:assert/strict';
import { jsonToXml, xmlToJson } from './lib/json-tools/convert.mjs';
const xml = jsonToXml({ 'ship date': 'display', ship_date: 'machine' });
const parsed = xmlToJson(xml, { typed: false });
assert.deepEqual(parsed.root.ship_date, ['display', 'machine']);
assert.equal(Object.hasOwn(parsed.root, 'ship date'), false);
This output is not silently overwriting a value; both texts survive as repeated elements. The problem is that their original field identities no longer survive in those names. A receiver expecting one ship_date might reject the document, choose one occurrence, or apply its own rule. Do not depend on that unspecified behavior.
Build a naming fixture from actual upstream keys, including punctuation, leading digits, non-ASCII characters, and names differing only in characters your sanitizer replaces. The converter uses a deliberately restricted ASCII naming transformation; XML itself permits a broader set of name characters. Read the W3C XML naming rules rather than treating one tool's restrictions as the full language grammar.
For a fixed integration, an explicit field map is often easier to review than a general sanitizer. Map the display field to displayShipDate and the machine field to scheduledShipDate, if those are the agreed receiver names. Test unexpected source keys so they fail clearly or enter a documented extension mechanism.
Our JSON key naming guide helps establish consistent source names. Consistency reduces surprises, but it does not prove that a transformation is one-to-one. Preserve a collision fixture even after cleaning up the naming convention, because a newly added property can reintroduce the same risk.
3. Do empty, single-item, and nested arrays keep the intended meaning?
Test collections with zero, one, and several entries, plus nested arrays if the input allows them. A repeated-element mapping needs explicit receiver rules for wrappers and empty collections. Verify element counts and nesting, not only visible values, because flattening can preserve every scalar while changing the structure that gives those values meaning.
In the site converter, a nested array repeats its property name once per entry without an extra collection wrapper. A top-level array instead uses the selected item name under the chosen root. Those are documented mapping choices, not requirements imposed by JSON or by every XML API.
import assert from 'node:assert/strict';
import { jsonToXml, xmlToJson } from './lib/json-tools/convert.mjs';
const inspect = value => xmlToJson(jsonToXml(value), { typed: false });
assert.equal(inspect({ items: [] }).root, '');
assert.equal(inspect({ items: ['A'] }).root.items, 'A');
assert.deepEqual(inspect({ items: ['A', 'B'] }).root.items, ['A', 'B']);
assert.deepEqual(inspect({ items: [['A'], ['B']] }).root.items, ['A', 'B']);
const top = xmlToJson(jsonToXml(['A', 'B'],
{ rootName: 'batch', itemName: 'entry' }), { typed: false });
assert.deepEqual(top.batch.entry, ['A', 'B']);
The empty collection produces no items element in this fixture. If the receiver distinguishes an empty collection from an omitted field, this mapping needs an adapter or a different representation. Do not interpret the lack of a parsing error as preservation of that distinction.
The nested-array assertion is equally important: two inner groups collapse into the same repeated sibling structure as a flat list. If those groups represent shipments, batches, or coordinate rows, their boundaries matter. Restructure explicitly into named group objects or reject unsupported nesting before conversion.
For the single-item fixture, remember that the inspection parser also has its own mapping conventions. A scalar inspection result does not mean XML contains a native scalar-versus-array type flag. Check the receiver's expected occurrence and wrapper rules directly, using a suitable XML-aware test or schema validator.
Keep the receiving application's iteration test alongside these fixtures. Our JSON array versus object guide explains the shape distinction on the JSON side. The integration must bridge that distinction deliberately instead of expecting the XML representation to remember the original container automatically.
4. Are nil, empty text, empty objects, and missing fields distinct?
Write separate expectations for null, empty strings, empty objects, and absent fields. An xsi:nil attribute is not a universal instruction for every XML parser to produce JSON null. Confirm that the receiving schema permits nil and that its application mapping interprets the attribute, while testing unsupported top-level values separately.
The XML Schema structures specification defines nil behavior in conjunction with element declarations. Merely adding the attribute cannot make an element nillable in a schema that disallows it. Agree with the receiver on whether null means explicitly clear a value, leave it unchanged, or reject the request.
import assert from 'node:assert/strict';
import { jsonToXml, xmlToJson } from './lib/json-tools/convert.mjs';
const xml = jsonToXml({ note: null, label: '', metadata: {} });
const root = xmlToJson(xml, { typed: false }).root;
assert.equal(root.note['@_xsi:nil'], 'true');
assert.equal(root.label, '');
assert.equal(root.metadata, '');
assert.equal(Object.hasOwn(root, 'missing'), false);
assert.equal(xmlToJson(jsonToXml(null), { typed: false }).root['#text'], 'null');
The final assertion exposes a separate top-level case: this serializer writes top-level null as text, unlike its nested null mapping. If your request contract requires an object envelope, enforce that before conversion. Do not generalize the nested-field behavior to every JSON document shape.
The inspection output also demonstrates why a reverse parse is not automatically a semantic inverse. It retains the nil attribute, while empty text and an empty object both inspect as empty strings in this configuration. Application rules must supply distinctions the chosen XML mapping no longer encodes.
These are useful acceptance failures to discover early. A patch-style integration can change behavior if an omitted field means unchanged while an explicit nil means clear. Test both with the receiver instead of substituting one simply because the resulting XML is shorter.
Document the policy in the same place as the integration's field map. Include an expected rejection when a required field is absent or nil is not permitted. Our null versus missing guide explains the source-side differences; the outbound contract must say how each state crosses the format boundary.
5. Does parsed XML recover the intended text without double escaping?
Give the converter actual string values, then verify that parsing the output recovers those values under the intended whitespace policy. Test XML-special characters, literal entity-looking text, and Unicode. Escaping markup syntax is separate from validating allowed XML characters, schema constraints, or receiver-specific text rules, so do not treat one successful fixture as complete validation.
Start with an ampersand and a less-than sign inside ordinary text. They should remain data, not become markup. Then include a string literally containing the characters ampersand-l-t-semicolon. The converter must preserve that literal text rather than interpreting it as a request to insert a less-than sign.
import assert from 'node:assert/strict';
import { jsonToXml } from './lib/json-tools/convert.mjs';
import { XMLParser, XMLValidator } from 'fast-xml-parser';
const text = 'Maya & Co <north> "quoted" — café';
const xml = jsonToXml({ text, literal: '<' });
assert.equal(XMLValidator.validate(xml), true);
const root = new XMLParser({ parseTagValue: false,
trimValues: false, ignoreDeclaration: true }).parse(xml).root;
assert.equal(root.text, text);
assert.equal(root.literal, '<');
assert(xml.includes('&lt;'));
The last assertion is intentional, not a bug: XML must encode the ampersand to preserve the literal entity-looking string. Pre-escaping ordinary source text would therefore change what the receiver reads. Keep JSON escaping, XML escaping, and HTML rendering as separate boundaries; our JSON escape-character guide covers the first of those.
Whitespace needs an explicit policy too. The reverse converter trims values by default, so the example uses a parser configured not to trim. If leading or trailing spaces matter, test them separately with the real receiving parser. Formatting indentation should not be confused with permission to alter meaningful field text.
Review forbidden control characters and malformed Unicode with an XML processor appropriate for production validation. This site's serializer escapes common markup characters but is not a complete XML-character validator. Do not advertise arbitrary JSON strings as guaranteed acceptable XML merely because these printable-character assertions pass.
Finally, run schema validation and a receiver acceptance test on the exact serialized payload. Retain versions, settings, input fixtures, and expected outcomes so a dependency or mapping change can be checked again. The goal is an understood contract—not a promise that generic conversion preserves every JSON distinction or satisfies every XML service.