I want to convert JSON responses to CSV format. I was able to figure out how to properly generate the headers in a way where parents of a key are appended together to generate a
Is that it (have to run the snippet to see results as HTML table demo - do not know how to put it here directly) ?
function readDown(headName, arr, outRows) {
var under = [headName];
while (arr.toString().indexOf("[object") == 0
|| Array.isArray(arr)) {
var hasArray = false;
for (var i in arr) {
if (arr[i].toString().indexOf("[object") == 0
|| Array.isArray(arr[i])) hasArray = true;
}
if (hasArray) {
for (var i in arr)
if (arr[i].toString().indexOf("[object") == 0
|| Array.isArray(arr[i])) {
if (isNaN(i)) under[0] += '_' + i;
readDown(under[0], arr[i], outRows);
if (arr.length === undefined)
return; // empty associative (skills)
delete arr[i++];
var next = false; // are there following arrays to be groupped ?
while (i && arr[i - 1] === undefined && i < arr.length && (arr[i].toString().indexOf("[object") == 0
|| Array.isArray(arr[i]))) { // group whole lowest array
next = true;
var idx = outRows.length - 1
if (!Array.isArray(outRows[idx][1])) {
outRows[idx][1] = [outRows[idx][1], arr[i++]];
} else outRows[idx][1].push(arr[i++]);
delete arr[i - 1];
}
if (next && i == arr.length) {
arr.length = 0; // array full of undefined members, fix length in case
return null; // and return - we are done here
}
} else { // funny never get there now
if (hasArray && isNaN(i)) under[0] += '_' + i;
under.push(arr[i]);
break;
}
} else {
if (arr.length === undefined) { // Work_skills[0], friends[0]
under.push(arr);
outRows.push(under);
} else { // tags, greetings
if(!arr.length) return; // do not keep empty greetings
for (var i in arr) under.push(arr[i]);
outRows.push(under);
}
return null; // all arrays return here
}
// unreachable next
}
}
function JSON2CSV(arr, inRows, outRows) {
var row = [];
var hasArray = false;
for (var a in arr) {
try {
if (arr[a] == null) { // Change null's to string
arr[a] = "null";
}
if (Array.isArray(arr[a]) || arr[a].toString().indexOf("[object") == 0) {
readDown(a, arr[a], outRows);
continue;
} else {
if (isNaN(a)) {
outRows.push([a, arr[a]]);
} else {
outRows.push(['#', arr[a]]);
}
}
} catch (e) {
return e;
}
}
}
function convert(json) {
var JSONtxt;
try {
JSONtxt = JSON.parse(json);
} catch (e) {
console.log(e);
return;
}
var columnsArray = [];
JSON2CSV(JSONtxt, [], columnsArray);
var maxDepth = 1; // find longest lowest array size
for (var i in columnsArray)
if (Array.isArray(columnsArray[i][1])
&& maxDepth < columnsArray[i][1].length)
maxDepth = columnsArray[i][1].length;
maxDepth++;
var csv = [];
for (var c in columnsArray) {
if (Array.isArray(columnsArray[c][1])) {
var r = 0;
for (var nd in columnsArray[c][1][r]) {
csv.push([columnsArray[c][0] + '_' + nd]); // top header
var col = csv.length - 1;
for (; r < columnsArray[c][1].length; r++) {
csv[col].push(columnsArray[c][1][r][nd]); // key's value
delete columnsArray[c][1][r][nd];
}
r = 0;
}
} else {
csv.push(columnsArray[c]);
}
while (csv[c].length < maxDepth) csv[c].push(null);
}
var csvTxt = ""; // tab separated values (copy/paste 2 XL ?)
var htmlTab = "<TABLE border=\"1\" width=\"1500\">";
for (var r = 0; r < maxDepth; r++) {
htmlTab += "<TR>";
for (var c in csv) {
htmlTab += r==0?"<TH":"<TD"; // mind 1st header row too
if (c != 0) csvTxt += '\t';
if (csv[c][r] != null) {
csvTxt += csv[c][r];
htmlTab += ">" + csv[c][r];
} else htmlTab += " class=empty>"
htmlTab += "</TD>";
}
csvTxt += '\r\n';
htmlTab += r==0?"</TH>":"</TR>";
}
htmlTab += "</TABLE>";
return htmlTab;
}
var json = '{"_id": "5cfe7d3c6deeeef08ce0444b","name": "Debra Milligain","phone": "+1 (906) 432-2182","address": "676 Merit Court, Steinhatchee, Oregon, 5491","tags": ["consequat","reprehenderit","amet"],"Work": {"skills": [{"id": 0,"name": "Programming"},{"id": 1,"name": "Business"}]},"friends": [{"id": 0,"name": "Stafford Hernandez"},{"id": 1,"name": "Colleen Christensen"},{"id": 2,"name": "Barker Keith"}],"greeting": [],"favoriteFruit": "banana"}';
table {
border-spacing: 0px; /* small tricks 2 make rounded table simply or */
}
th {
text-align:left; /* centered looks ugly */
}
td.empty {
background-color:lightgray; /* mark null cells */
}
<body onload="document.body.innerHTML=convert(json)"></body>
Original code without tidy modifications for the snippet:
function readDown(headName, arr, outRows) {
var under = [headName];
while (arr.toString().indexOf("[object") == 0
|| Array.isArray(arr)) {
var hasArray = false;
for (var i in arr) {
if (arr[i].toString().indexOf("[object") == 0
|| Array.isArray(arr[i])) hasArray = true;
}
if (hasArray) {
for (var i in arr)
if (arr[i].toString().indexOf("[object") == 0
|| Array.isArray(arr[i])) {
if (isNaN(i)) under[0] += '_' + i;
readDown(under[0], arr[i], outRows);
if (arr.length === undefined)
return; // empty associative (skills)
delete arr[i++];
var next = false; // are there following arrays to be groupped ?
while (i && arr[i - 1] === undefined && i < arr.length && (arr[i].toString().indexOf("[object") == 0
|| Array.isArray(arr[i]))) { // group whole lowest array
next = true;
var idx = outRows.length - 1
if (!Array.isArray(outRows[idx][1])) {
outRows[idx][1] = [outRows[idx][1], arr[i++]];
} else outRows[idx][1].push(arr[i++]);
delete arr[i - 1];
}
if (next && i == arr.length) {
arr.length = 0; // array full of undefined members, fix length in case
return null; // and return - we are done here
}
} else { // funny never get there now
if (hasArray && isNaN(i)) under[0] += '_' + i;
under.push(arr[i]);
break;
}
} else {
if (arr.length === undefined) { // Work_skills[0], friends[0]
under.push(arr);
outRows.push(under);
} else { // tags, greetings
if(!arr.length) return; // do not keep empty greetings
for (var i in arr) under.push(arr[i]);
outRows.push(under);
}
return null; // all arrays return here
}
// unreachable next
}
}
function JSON2CSV(arr, inRows, outRows) {
var row = [];
var hasArray = false;
for (var a in arr) {
try {
if (arr[a] == null) { // Change null's to string
arr[a] = "null";
}
if (Array.isArray(arr[a]) || arr[a].toString().indexOf("[object") == 0) {
readDown(a, arr[a], outRows);
continue;
} else {
if (isNaN(a)) {
outRows.push([a, arr[a]]);
} else {
outRows.push(['#', arr[a]]);
}
}
} catch (e) {
return e;
}
}
}
function convert(json) {
var JSONtxt;
try {
JSONtxt = JSON.parse(json);
} catch (e) {
console.log(e);
return;
}
var columnsArray = [];
JSON2CSV(JSONtxt, [], columnsArray);
var maxDepth = 1; // find longest lowest array size
for (var i in columnsArray)
if (Array.isArray(columnsArray[i][1])
&& maxDepth < columnsArray[i][1].length)
maxDepth = columnsArray[i][1].length;
maxDepth++;
var csv = [];
for (var c in columnsArray) {
if (Array.isArray(columnsArray[c][1])) {
var r = 0;
for (var nd in columnsArray[c][1][r]) {
csv.push([columnsArray[c][0] + '_' + nd]); // top header
var col = csv.length - 1;
for (; r < columnsArray[c][1].length; r++) {
csv[col].push(columnsArray[c][1][r][nd]); // key's value
delete columnsArray[c][1][r][nd];
}
r = 0;
}
} else {
csv.push(columnsArray[c]);
}
while (csv[c].length < maxDepth) csv[c].push(null);
}
var csvTxt = ""; // tab separated values (copy/paste 2 XL ?)
var htmlTab = "<TABLE border=\"1\">";
for (var r = 0; r < maxDepth; r++) {
htmlTab += "<TR>";
for (var c in csv) {
htmlTab += "<TD>";
if (c != 0) csvTxt += '\t';
if (csv[c][r] != null) {
csvTxt += csv[c][r];
htmlTab += csv[c][r];
}
htmlTab += "</TD>";
}
csvTxt += '\r\n';
htmlTab += "</TR>";
}
htmlTab += "</TABLE>";
console.log(csvTxt);
console.log(htmlTab);
}
var json = '{"_id": "5cfe7d3c6deeeef08ce0444b","name": "Debra Milligain","phone": "+1 (906) 432-2182","address": "676 Merit Court, Steinhatchee, Oregon, 5491","tags": ["consequat","reprehenderit","amet"],"Work": {"skills": [{"id": 0,"name": "Programming"},{"id": 1,"name": "Business"}]},"friends": [{"id": 0,"name": "Stafford Hernandez"},{"id": 1,"name": "Colleen Christensen"},{"id": 2,"name": "Barker Keith"}],"greeting": [],"favoriteFruit": "banana"}';
convert(json);
And even my original JSONoperations.hta CSV export does not look so bad, but a bit rotated and includes useless 1-level numbering, anyway if I would start here ;-)
table { border-spacing: 0px; }
th { text-align:left; }
td.empty { background-color:lightgray;}
<TABLE border="1" width="900"><TR><TD>tags</TD><TD>0</TD><TD>1</TD><TD>2</TD></TR><TR><TD class="empty"></TD><TD>consequat</TD><TD>reprehenderit</TD><TD>amet</TD></TR><TR><TD>Work</TD><TD>skills</TD><TD>#</TD><TD>id</TD><TD>name</TD></TR><TR><TD class="empty"></TD><TD class="empty"></TD><TD>1</TD><TD>0</TD><TD>Programming</TD></TR><TR><TD class="empty"></TD><TD class="empty"></TD><TD>2</TD><TD>1</TD><TD>Business</TD></TR><TR><TD>friends</TD><TD>#</TD><TD>id</TD><TD>name</TD></TR><TR><TD class="empty"></TD><TD>1</TD><TD>0</TD><TD>Stafford Hernandez</TD></TR><TR><TD class="empty"></TD><TD>2</TD><TD>1</TD><TD>Colleen Christensen</TD></TR><TR><TD class="empty"></TD><TD>3</TD><TD>2</TD><TD>Barker Keith</TD></TR><TR><TD>_id</TD><TD>name</TD><TD>phone</TD><TD>address</TD><TD>favoriteFruit</TD></TR><TR><TD>5cfe7d3c6deeeef08ce0444b</TD><TD>Debra Milligain</TD><TD>+1 (906) 432-2182</TD><TD>676 Merit Court, Steinhatchee, Oregon, 5491</TD><TD>banana</TD></TR></TABLE>