- Compare the namespace name and local name, not the prefix alone.
- A default namespace applies to unprefixed element names, not directly to unprefixed attributes.
- MarkupSprig preserves qualified-name spelling; it does not normalize different prefixes into one JSON key.
On this page
An XML namespace identifies which vocabulary a name belongs to. The namespace gets a name — a URI (Uniform Resource Identifier) such as urn:demo or a web-address-shaped value — and the document refers to it through a short prefix or through a default declaration. When you read namespaced XML before converting it to JSON, four things matter: the namespace name, the prefix, the local name, and the combination of the first and third, which is called the expanded name. Confusing them is how you end up surprised that <a:item> and <b:item> can mean the same element — when both prefixes are bound to the same namespace name — or that an attribute named id sits outside the namespace you assumed it had.
This guide walks through small documents and shows the JSON that MarkupSprig’s xml-json-v1 mapping produces for each one, so you can predict what your own payload looks like after conversion — or decide it should not be converted blindly at all. Each JSON block below is an expected output derived from the documented mapping rules, not a captured live run.
Read an xmlns declaration
Here is a small XML document with one namespace declaration:
<r xmlns:a="urn:demo"><a:item id="001">7</a:item></r>
The markup is built from two kinds of names. <a:item>...</a:item> is an element, and id="001" is an attribute of that element — elements carry the document’s structure, attributes carry name-and-value details attached to them. Both element names and attribute names are what namespaces disambiguate.
The declaration xmlns:a="urn:demo" has three parts worth separating:
urn:demois the namespace name: a URI that identifies a vocabulary. A URI is not necessarily a URL — a URL (Uniform Resource Locator) identifies a resource by its location, while a namespace name identifies a vocabulary. The recommendation states: “It is not a goal that it be directly usable for retrieval of a schema (if any exists).” A namespace declaration does not itself require an application to fetch a schema; what an application retrieves is a separate question.ais a prefix — a short local label for that namespace name.itemin<a:item>is the local name.
The spelled name in the markup — a:item, prefix and local name together — is called a qualified name (QName). What actually identifies the element is the expanded name: the namespace name plus the local name, urn:demo + item — the value the qualified name resolves to once the prefix is read against the declarations in scope. The prefix itself is only a placeholder for the namespace name, which is why different documents can spell the same vocabulary with different labels. If an order feed and a shipping feed use the local name item for different concepts, namespace names let a combined document distinguish those vocabularies. Different prefixes bound to different namespace names make that distinction visible in the markup.
A declaration also has scope. xmlns:a applies to the element it is written on and to everything inside that element; a deeper element can redeclare the same prefix and bind it to a different namespace name for its own subtree. So the prefix a: means “look up the nearest declaration in scope”, not “look for a global definition of a”.
Converted with the xml-json-v1 mapping, the document above maps to:
{"r":{"@xmlns:a":"urn:demo","a:item":{"@id":"001","#text":"7"}}}
Reading the keys: the declaration is kept as @xmlns:a with the namespace name as its string value; the attribute becomes @id; the element key is the qualified name exactly as spelled, a:item; and the text and attribute values in it are strings — "7" is text, not a number. The text lands under #text because the element also carries an attribute; a childless element with no attributes would just be the string itself.
A prefix is not the namespace identity
Two prefixes can point at the same namespace name:
<r xmlns:a="urn:demo" xmlns:b="urn:demo">
<a:item>1</a:item>
<b:item>2</b:item>
</r>
Both item elements have the same expanded name:
| Element | Prefix | Local name | Namespace name |
|---|---|---|---|
a:item |
a |
item |
urn:demo |
b:item |
b |
item |
urn:demo |
Read as XML, these are two occurrences of one name in one vocabulary. The expected JSON nevertheless keeps two keys, because the mapping preserves the spelling from the document:
{"r":{"@xmlns:a":"urn:demo","@xmlns:b":"urn:demo","a:item":"1","b:item":"2"}}
Two details in that output. The two elements stay separate keys — the mapping does not normalize expanded names into one key, and it does not merge the values into an array, because the spelled qualified names differ. And "1" and "2" are strings; character data is never turned into a number. (The line breaks and spaces in the input are indentation between elements, and the mapping drops those.)
What makes the identity check exact is how namespace names compare: as case-sensitive strings, with no lowercasing and no percent-decoding. urn:demo and urn:DEMO are two different namespace names, and a web-address-shaped name is not normalized into a canonical spelling before comparison (Namespaces in XML 1.0). In the example above, a and b are two aliases for one name, so both elements share one identity. Had b been bound to urn:DEMO, the two item elements would live in different vocabularies even though every name in the document would still look almost the same.
Spelling does decide one shape change. When repeated siblings share one qualified-name spelling, they collapse into a single array:
<r xmlns:a="urn:demo"><a:item>1</a:item><a:item>2</a:item></r>
Expected JSON:
{"r":{"@xmlns:a":"urn:demo","a:item":["1","2"]}}
One key, a:item, holds both values in document order. Side by side, the mapping’s rule reads plainly: identical spellings become one array; different spellings stay separate keys — even when both spellings mean the same expanded name in XML.
That two-keys outcome is a decision of this mapping, not a rule from Namespaces in XML 1.0. The recommendation defines what the names mean inside XML; it does not define a JSON representation for them. In this two-line example the retained declarations make the grouping readable: both spellings visibly bind urn:demo. Longer documents are not this considerate. Declarations are scoped, a descendant can rebind the same prefix, and the JSON carries no per-key namespace annotation — so grouping elements by vocabulary means resolving every key against the bindings in scope where it appears. That is work the JSON does not do for you. If per-vocabulary grouping matters downstream, pick a namespace-aware representation or processing strategy before conversion instead of rebuilding one from JSON keys after the fact.
Default namespaces and unprefixed attributes
A declaration without a prefix — plain xmlns="..." — sets a default namespace:
<r xmlns="urn:demo" id="001"><item>7</item></r>
<r> is the root element — the one element that contains the whole document — so its declaration covers every unprefixed element name below it. Here the names resolve like this:
| Name | Namespace name | Local name |
|---|---|---|
r |
urn:demo |
r |
item |
urn:demo |
item |
id (attribute) |
(no namespace name) | id |
r and item are in the urn:demo namespace even though nothing in the markup marks them locally. The unprefixed attribute is the counterexample: it looks like it should inherit the default namespace, and it does not. Per the namespace recommendation’s defaulting rules, a default namespace applies to unprefixed element names, not directly to unprefixed attribute names — an unprefixed attribute has no namespace name at all, not the default one.
The expected JSON keeps both facts in their own way:
{"r":{"@xmlns":"urn:demo","@id":"001","item":"7"}}
The default declaration is stored as the key @xmlns, the attribute as @id, and the element as the plain spelling item. Notice that item carries no namespace marker anywhere in the JSON — the only place urn:demo is recorded is the declaration value under @xmlns. If the namespace a key belonged to matters to you after conversion, that declaration is what you read.
A default namespace can also be cleared. The declaration xmlns="" means that no default namespace is in effect within its scope; it does not bind a namespace whose name is an empty string. Per the recommendation, this applies to the element carrying the declaration and its descendants, unless another declaration overrides it:
<r xmlns="urn:demo"><item>1</item><other xmlns=""><item>2</item></other></r>
Reading the names: r is the root element, and its declaration puts r and the first item in the urn:demo namespace. other carries its own xmlns="", which clears the default namespace for other and everything inside it — so other and the inner item have no namespace name at all. The attribute rule does not change: no attribute in this document gets a namespace name from any default declaration.
Expected JSON under the mapping:
{"r":{"@xmlns":"urn:demo","item":"1","other":{"@xmlns":"","item":"2"}}}
The cancelled declaration is still recorded, as "@xmlns": "" — an empty string is a stored value like any other, and it is exactly the fact that tells you the inner item is not in urn:demo, even though its JSON key spelling item looks identical to its cousin one level up.
What happens when XML becomes JSON
XML name semantics and a converter’s JSON keys are two separate layers, and keeping them separate is what makes converter output predictable. The namespace recommendation says what a qualified name means in XML; what ends up as a JSON key is a mapping decision. For xml-json-v1 the decisions are:
- Element keys keep the qualified-name spelling used in the document:
a:itemstaysa:item. - Attributes — including
xmlnsdeclarations — become keys with an@prefix, and their values are strings. - Character data stays a string:
"001"stays"001"; nothing is coerced to a number. (Strings here means the text and attribute values; container values are still JSON objects and arrays.) - An element with no attributes and no children becomes a plain string value.
- An element with attributes, no child elements, and some text keeps that text under
#text. An element with children never gets a#textkey: its whitespace-only text is dropped as indentation, and significant text there is refused as mixed content. - Repeated siblings with the same qualified-name spelling become one array, in document order. Same spelling, not same expanded name — the two-prefix example above stayed two keys, and the same-spelling example collapsed into one.
- Whitespace-only text between elements (indentation) is dropped; character data inside a leaf element is kept as content, whitespace included. Kept means kept as parsed character data — XML parsing normalizes line endings before any mapping runs — not a byte-for-byte copy of the source file.
Some documents are refused instead of converted: a document with a DTD — a Document Type Definition — or a DOCTYPE, one containing processing instructions other than the XML declaration, and an element that mixes significant text with child elements. The XML declaration at the top of a file is allowed — the documented limits single it out from general processing instructions. The mapping also enforces size and depth budgets, so oversized input is refused rather than truncated.
The other thing that sits outside this mapping is schema validation. An XML Schema (XSD) is a separate document that spells out what a vocabulary allows — which elements and attributes may appear where, and with which values — and a validating XML processor checks a document against those rules. Namespace declarations do a different job: they say which vocabulary a name belongs to, not whether the document follows that vocabulary’s rules. Namespace-aware parsing and XSD validation are therefore separate jobs: this tool parses XML namespace-aware — qualified names and xmlns declarations are read as such and preserved as written — but it performs no XSD validation and does not normalize expanded names; the JSON keeps the document’s spellings. And the mapping is not a standard JSON form of XML — not a W3C-defined namespace normalization, and not a promise that converting JSON back returns your original XML.
Avoid silent namespace loss
Removing declarations or rewriting names without checking their in-scope bindings can change expanded names or leave a prefix unbound. A consistent prefix rename can preserve expanded names when it keeps the same namespace name and local names, and updates the declaration and its uses together in each affected scope.
The mapping groups sibling elements by qualified-name spelling, not by expanded name. Bindings can change in nested scopes, so a spelling alone is not a reliable namespace identity. Retained declarations still have to be interpreted in scope. If vocabulary identity matters downstream, choose a namespace-aware representation or processing strategy before conversion; do not rely on a spelling-only grouping of JSON keys.
If you want to see this mapping on your own payload, MarkupSprig’s Convert XML to JSON tool applies the xml-json-v1 rules described above: well-formed XML 1.0 documents supplied as text, with attributes and namespace declarations kept as @-prefixed string keys and qualified-name spellings preserved. Its limits: DTD/DOCTYPE, processing instructions other than the XML declaration and mixed content are refused, size and depth budgets apply, and there is no XSD validation, no XPath and no reverse conversion from JSON back to XML.