// data types
function nullV() { return 'null'; }
function boolV(val) { return '' + html(val) + ''; }
function numV(val) { return '' + html(val) + ''; }
function stringV(val) { return '"' + jstr(val) + '"'; }
function linkV(val) { return '"' + jstr(val) + '"'; }
function propK(key) { return '"' + jstr(key) + '"'; }
function wrapJSONP(callback, json, suffix) {
return `${callback}(${json})${suffix}`;
}
function errorPage(error, json) {
return '
Error parsing JSON:
' + error + '
Content:
' + html(json) + '
';
}
// helper
function indent(nl, level) { return nl ? nl + ' '.repeat(level) : ''; }
function jstr(s) { return html(JSON.stringify(s).slice(1, -1)); }
function html(s) {
return (s + '').replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>');
}
// formatter entry point
function init() {
document.body.innerHTML = parse(document.getElementById('json').textContent);
}
function parse(raw) {
let data = raw, callback = '', suffix = '';
// matches "callback({json});" ... original had \s -> [\s\u200B\uFEFF]
let match = /^\s*([\w$\[\]\.]+)\s*\(\s*([\[{][\s\S]*[\]}])\s*\)([\s;]*)$/m.exec(raw);
if (match && match.length === 4) {
callback = match[1];
data = match[2];
suffix = match[3].replace(/[^;]+/g, '');
}
try {
let root = nested(JSON.parse(data), 0, '
');
if (callback)
return wrapJSONP(callback, root, suffix);
return root;
} catch (e) {
return errorPage(e, raw);
}
}
function nested(val, level, nl) {
if (null === val)
return nullV();
switch (typeof val) {
case 'boolean': return boolV(val);
case 'number': return numV(val);
case 'string':
return (/^(\w+):\/\/[^\s]+$/i.test(val)) ? linkV(val) : stringV(val);
case 'object':
return (Array.isArray(val))
? array(val, level + 1, nl)
: dict(val, level + 1, nl);
}
return '<-unsupported-type->';
}
function dict(dict, level, nl) {
let output = '';
for (let key in dict) {
if (output)
output += indent(',
', level);
output += propK(key) + ': ' + nested(dict[key], level, '
');
}
if (!output)
return '{}';
return '{' + foldableContent(output, level, nl) + '}';
}
function array(arr, level, nl) {
let output = '';
for (let i = 0; i < arr.length; i++) {
if (i > 0)
output += indent(',
', level);
output += nested(arr[i], level, '
');
}
if (!output)
return '[]';
return '[' + foldableContent(output, level, nl) + ']';
}
// foldable content
function foldableContent(output, level, nl) {
let collapsible = '';
return collapsible + '' + indent(nl, level) + output + indent('
', level - 1) + '' + collapsible
}
function fold(sender) {
let folder = sender.parentNode;
folder.classList.toggle('closed');
}
function highlight(sender, show) {
let folder = sender.parentNode;
show ? folder.classList.add('highlight') : folder.classList.remove('highlight');
}