14 August 2018
Converting JSON from and to XML in Javascript
Checkout the website: JSON2XML or the full example.
As it turns out, finding a good xml to json javascript library is not as easy as it sounds. I did find this implementation does the job but sometimes the output can be a bit confusing:
xmlToJson((new DOMParser).parseFromString('<root root-attr="value"> <child child-attr="value"> text \n </child>\n</root>', 'application/xml'));
will output:
{
"root": {
"@attributes": {
"root-attr": "value"
},
"#text": [
" ",
"\n"
],
"child": {
"@attributes": {
"child-attr": "value"
},
"#text": " text \n "
}
}
}
Borrowing from David Walsh’s implementation, let’s implement the obj2xml function:
function xml2obj(xml, opt) {
if (typeof xml === 'string') {
var dom = (new DOMParser).parseFromString(xml, 'application/xml');
xml = dom.childNodes[dom.childNodes.length - 1];
}
opt = opt || {};
var attrPrefix = opt.attrPrefix || '';
function toObj(xml) {
var n, o = {};
if (xml.nodeType == 1) {
if (xml.attributes.length) {
for (var i = 0; i < xml.attributes.length; i++) {
o[attrPrefix + xml.attributes[i].nodeName] = (xml.attributes[i].nodeValue || '').toString();
}
}
if (xml.firstChild) {
var textChild = 0, cdataChild = 0, hasElementChild = false;
for (n = xml.firstChild; n; n = n.nextSibling) {
if (n.nodeType == 1) {
hasElementChild = true;
} else {
if (n.nodeType == 3 && n.nodeValue.match(/[^ \f\n\r\t\v]/)) {
textChild++;
} else {
if (n.nodeType == 4) {
cdataChild++;
}
}
}
}
if (hasElementChild) {
if (textChild < 2 && cdataChild < 2) {
removeWhite(xml);
for (n = xml.firstChild; n; n = n.nextSibling) {
if (n.nodeType == 3) {
o['#text'] = escape(n.nodeValue);
} else {
if (n.nodeType == 4) {
o['#cdata'] = escape(n.nodeValue);
} else {
if (o[n.nodeName]) {
if (o[n.nodeName] instanceof Array) {
o[n.nodeName][o[n.nodeName].length] = toObj(n);
} else {
o[n.nodeName] = [o[n.nodeName], toObj(n)];
}
} else {
o[n.nodeName] = toObj(n);
}
}
}
}
} else {
if (!xml.attributes.length) {
o = escape(innerXml(xml));
} else {
o['#text'] = escape(innerXml(xml));
}
}
} else {
if (textChild) {
if (!xml.attributes.length) {
o = escape(innerXml(xml));
} else {
o['#text'] = escape(innerXml(xml));
}
} else {
if (cdataChild) {
if (cdataChild > 1) {
o = escape(innerXml(xml));
} else {
for (n = xml.firstChild; n; n = n.nextSibling) {
o['#cdata'] = escape(n.nodeValue);
}
}
}
}
}
}
if (!xml.attributes.length && !xml.firstChild) {
o = null;
}
} else {
if (xml.nodeType == 9) {
o = toObj(xml.documentElement);
} else {
alert('unhandled node type: ' + xml.nodeType);
}
}
return o;
}
function innerXml(node) {
var s = '';
if ('innerHTML' in node) {
s = node.innerHTML;
} else {
var asXml = function (n) {
var s = '';
if (n.nodeType == 1) {
s += '<' + n.nodeName;
for (var i = 0; i < n.attributes.length; i++) {
s += ' ' + n.attributes[i].nodeName + '="' + (n.attributes[i].nodeValue || '').toString() + '"';
}
if (n.firstChild) {
s += '>';
for (var c = n.firstChild; c; c = c.nextSibling) {
s += asXml(c);
}
s += '</' + n.nodeName + '>';
} else {
s += '/>';
}
} else {
if (n.nodeType == 3) {
s += n.nodeValue;
} else {
if (n.nodeType == 4) {
s += '<![CDATA[' + n.nodeValue + ']]\x3e';
}
}
}
return s;
};
for (var c = node.firstChild; c; c = c.nextSibling) {
s += asXml(c);
}
}
return s;
}
function escape(txt) {
return txt.replace(/[\\]/g, '\\\\').replace(/["]/g, '\\"').replace(/[\n]/g, '\\n').replace(/[\r]/g, '\\r');
}
function removeWhite(e) {
e.normalize();
for (var n = e.firstChild; n;) {
if (n.nodeType == 3) {
if (!n.nodeValue.match(/[^ \f\n\r\t\v]/)) {
var nxt = n.nextSibling;
e.removeChild(n);
n = nxt;
} else {
n = n.nextSibling;
}
} else {
if (n.nodeType == 1) {
removeWhite(n);
n = n.nextSibling;
} else {
n = n.nextSibling;
}
}
}
return e;
}
if (xml.nodeType == 9) {
xml = xml.documentElement;
}
var obj = toObj(removeWhite(xml));
return obj;
};
This will return the desired output. The function takes in an xml string or an XMLDocument
.
xml2obj('<root root-attr="value"> <child child-attr="value"> text \n </child>\n</root>', );
For example this xml string will become:
{
"root-attr": "value",
"child": {
"child-attr": "value",
"#text": " text \\n "
}
}
You can also pass in { attrPrefix: '@' }
as a second argument to prevent name collisions between attributes and child elements:
xml2obj('<root samename="value"> <samename child-attr="value"> text \n </samename>\n</root>', { attrPrefix: '@' });
Will become:
{
"@samename": "value",
"samename": {
"@child-attr": "value",
"#text": " text \\n "
}
}
To convert a json object to xml, you can use the following function.:
var endScopeObj = {};
window.obj2xml = function (obj, opt) {
if (!opt) opt = {};
var rootName = opt.rootName || 'root';
var declaration = opt.declaration === 'auto' ? '<?xml version="1.0" encoding="utf-8"?>' : opt.declaration;
var indentation = opt.indentation || 0;
var generateDtd = (opt.doctype === 'auto' || opt.doctype === 'generate') && declaration;
var useAttributes = opt.attributes === false ? false : true;
var scope_indent = 0;
if (generateDtd) {
var dtdAttr = {};
var dtdElem = {};
}
var ret = [];
var tagContent, isArr, curs, _t, _ti, key, innerKey, name, queue = [obj, rootName];
while (queue.length > 0) {
name = queue.pop();
curs = queue.pop();
if (generateDtd)
dtdElem[name] = dtdElem[name] || {};
if (curs === endScopeObj) {
scope_indent--;
if (indentation > 0) ret.push('\n', ' '.repeat(indentation * scope_indent));
ret.push('</', name, '>');
continue;
}
if (typeof curs === 'object') {
queue.push(endScopeObj);
queue.push(name);
tagContent = [name];
isArr = Array.isArray(curs);
if (isArr && generateDtd) {
dtdElem[name][name + 'Item*'] = true;
}
for (key in curs) {
if (curs.hasOwnProperty(key)) {
if (isArr) {
queue.push(curs[key]);
queue.push(name + 'Item');
} else if (typeof curs[key] == 'object' || useAttributes === false) {
queue.push(curs[key]);
queue.push(key);
if (generateDtd)
dtdElem[name][key] = true;
} else {
if (generateDtd) {
dtdAttr[name] = dtdAttr[name] || {};
dtdAttr[name][key] = true;
}
tagContent.push(key + '=' + '"' + curs[key] + '"');
}
}
}
if (indentation > 0) ret.push('\n', ' '.repeat(indentation * scope_indent));
ret.push('<', tagContent.join(' '), '>');
scope_indent++;
} else {
if (generateDtd)
dtdElem[name]['#PCDATA'] = true;
if (indentation > 0) ret.push('\n', ' '.repeat(indentation * scope_indent));
ret.push('<');
ret.push(name);
ret.push('>');
ret.push(curs);
ret.push('</');
ret.push(name);
ret.push('>');
}
}
if (generateDtd) {
var dtd = ['<!DOCTYPE ', rootName, ' ['];
for (key in dtdAttr) {
if (dtdAttr.hasOwnProperty(key)) {
for (innerKey in dtdAttr[key]) {
if (dtdAttr[key].hasOwnProperty(innerKey)) {
if (indentation > 0) dtd.push('\n');
dtd.push('<!ATTLIST ', key, ' ', innerKey, ' CDATA #IMPLIED>');
}
}
}
}
for (key in dtdElem) {
if (dtdElem.hasOwnProperty(key)) {
innerKey = null;
_t = ['<!ELEMENT ', key, ' ('];
_ti = [];
for (innerKey in dtdElem[key]) {
if (dtdElem[key].hasOwnProperty(innerKey)) {
_ti.push(innerKey);
}
}
if (indentation > 0) dtd.push('\n');
if (innerKey === null) // no children
dtd.push('<!ELEMENT ', key, ' EMPTY>');
else {
_t.push(_ti.join(', '));
_t.push(')>');
dtd.push.apply(dtd, _t);
}
}
}
dtd.push(']>');
ret.unshift.apply(ret, dtd);
} else if (declaration)
ret.unshift(opt.doctype ? opt.doctype : '<!DOCTYPE ' + rootName + '>');
if (declaration) ret.unshift(declaration);
return ret.join('');
};
Which turns:
obj2xml({ child: { key: 'value' } });
into:
<root><child key="value"></child></root>
The function obj2xml(obj, {...options})
takes the following options:
{
rootName: 'name of the root', // defaults to 'root'
declaration: 'auto' || '<a custom declaration>', // defaults to undefined
indentation: 'number', // defaults to 0
doctype: 'auto', // defaults to undefined
attributes: false || true // defaults to true
}
I used the code in this example to build a json2xml/xml2json converter which can be found here.
Full Example:
Resources:
InformIT eBook Store has a large number of ebooks on a wide range of topics. I would definitely recommend them.
Great courses:
Quickstart offers a large amount of (online) courses on web development (Use Code LSOFF50 to get 50% off ;p)