且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

JSON到CSV转换(JavaScript):如何正确设置CSV转换格式

更新时间:2023-11-29 12:37:28

是它了(必须运行代码段以查看结果)作为HTML表演示-不知道如何将其直接放在此处)?



 功能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(ar中的var i)if(arr [i] .toString()。indexOf( [object)== 0 || Array.isArray(arr [i])){if(isNaN (i))在[0] + ='_'+ i下; readDown(under [0],arr [i],outRows);如果(arr.length === undefined)返回; //空关联(技能)delete arr [i ++]; var next = false; //是否有以下要分组的数组? while(i&& arr [i-1] === undefined& i< arr.length&&(arr [i] .toString()。indexOf( [object)== 0 || Array.isArray(arr [i]))){//对整个最低数组进行分组next = true; var idx = outRows.length-1如果(!Array.isArray(outRows [idx] [1])){outRows [idx] [1] = [outRows [idx] [1],arr [i ++]]; } else outRows [idx] [1] .push(arr [i ++]);删除arr [i-1]; } if(next&& i == arr.length){arr.length = 0; //充满未定义成员的数组,固定长度,以防返回null; //然后返回-我们在这里完成了}}}其他{//如果(hasArray& isNaN(i))低于[0] + =‘_’+ i; under.push(arr [i]);打破; }}} else {if(arr.length === undefined){// Work_skills [0],friends [0] under.push(arr); outRows.push(under); } else {//标签,问候if(!arr.length)返回; //不要在.push(arr [i]);下为(var i in arr)保留空的问候。 outRows.push(under);返回null; // //所有数组都返回此处} //无法访问的下一个}}函数JSON2CSV(arr,inRows,outRows){var row = []; var hasArray = false; for(var a in arr){试试{if(arr [a] == null){//将null更改为字符串arr [a] = null; }如果(Array.isArray(arr [a])|| arr [a] .toString()。indexOf( [object)== 0){readDown(a,arr [a],outRows);继续; } else {if(isNaN(a)){outRows.push([a,arr [a]]); } else {outRows.push(['#’,arr [a]]); }}} catch(e){return e; }}} function convert(json){var JSONtxt;尝试{JSONtxt = JSON.parse(json); } catch(e){console.log(e);返回; } var columnsArray = []; JSON2CSV(JSONtxt,[],columnsArray); var maxDepth = 1; //如果(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(在ColumnArray [c] [1] [r]中的var nd){csv.push([columnsArray [c] [0] +'_'+ nd]);; //顶部标头var col = csv.length-1; for(; r< columnsArray [c] [1] .length; r ++){csv [col] .push(columnsArray [c] [1] [r] [nd]); //键的值删除columnArray [c] [1] [r] [nd]; } r = 0; }} else {csv.push(columnsArray [c]); } while(csv [c] .length&maxDepth)csv [c] .push(null); } var csvTxt =; //制表符分隔的值(复制/粘贴2 XL吗?)var htmlTab =< TABLE border = \ 1\ width = \ 1500\>; for(var r = 0; r< maxDepth; r ++){htmlTab + =< TR>; for(csv中的var c){htmlTab + = r == 0?< TH:< TD; //如果(c!= 0)csvTxt + =‘\t’,也要注意第一个标题行;如果(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,俄勒冈州,5491,标签:[结果, reprehenderit, amet],工作:{技能:[{ id:0,名称:编程} ,{ id:1,名称:业务}]}},朋友:[{ id:0,名称: Stafford Hernandez},{ id:1,名称: Colleen Christensen},{ id:2, name: Barker Keith}}, greeting:[], favoriteFruit: banana}'';  

  table {border-spacing:0px; / *小技巧2使圆桌简单或* /} th {text-align:left; / *居中看起来很难看* /} td.empty {background-color:lightgray; / *标记空单元格* /}  

 < body onload = document.body.innerHTML = convert(json)>< / body>  



未对代码段进行整洁修改的原始代码:

  function readDown(headName,arr,outRows){ 
var under = [headName];
while(arr.toString()。indexOf( [object)== 0
|| Array.isArray(arr)){
var hasArray = false;
for(var in 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 ===未定义)
返回; //空关联(技能)
delete arr [i ++];
var next = false; //是否有以下要分组的数组?
while(i&& arr [i-1] ===未定义&& i< arr.length&&&(amp; arr [i] .toString()。indexOf( [ )== 0
|| Array.isArray(arr [i]))){//将整个最低数组
分组= 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; //充满未定义成员的数组,如果
返回null,则固定长度; //然后返回-我们在这里完成了
}
} else {//如果(hasArray&& isNaN(i))低于[0],== '_'+我;
under.push(arr [i]);
休息时间;
}
} else {
if(arr.length === undefined){//工作技能[0],朋友[0]
under.push(arr);
outRows.push(under);
} else {//标签,问候
if(!arr.length)返回; //不要为.push(arr [i]);下的(var i in arr)保留空的问候

outRows.push(under);
}
返回null; //所有数组都返回此处
}
//无法访问的下一个
}
}
函数JSON2CSV(arr,inRows,outRows){
var row = [];
var hasArray = false;
for(var a in arr){
try {
if(arr [a] == null){//将null更改为字符串
arr [a] = null ;
}
if(Array.isArray(arr [a])|| arr [a] .toString()。indexOf( [object)== 0){
readDown(a ,arr [a],outRows);
继续;
} else {
if(isNaN(a)){
outRows.push([a,arr [a]]);
} else {
outRows.push([’#’,arr [a]]);
}
}
}抓住(e){
return e;
}
}
}
function convert(json){
var JSONtxt;
try {
JSONtxt = JSON.parse(json);
} catch(e){
console.log(e);
的回报;
}
var columnsArray = [];
JSON2CSV(JSONtxt,[],columnsArray);
var maxDepth = 1; //找到(Array.isArray(columnsArray [i] [1])$ ​​b $ b&& maxDepth< columnsArray [i]的最长的最低数组大小
] [1] .length)
maxDepth = columnsArray [i] [1] .length;
maxDepth ++;
var csv = [];
for(vars in columnsArray){
if(Array.isArray(columnsArray [c] [1])){{b $ b var r = 0;
for(vars in columnsArray [c] [1] [r]){
csv.push([columnsArray [c] [0] + __ + nd]);; //顶部标头
var col = csv.length-1;
for(; r< columnsArray [c] [1] .length; r ++){
csv [col] .push(columnsArray [c] [1] [r] [nd]); //键的值
delete columnsArray [c] [1] [r] [nd];
}
r = 0;
}

} else {
csv.push(columnsArray [c]);
}
而(csv [c] .length< maxDepth)csv [c] .push(null);
}
var csvTxt =; //制表符分隔的值(复制/粘贴2 XL吗?)
var htmlTab =< TABLE border = \ 1\>;
for(var r = 0; r< maxDepth; r ++){
htmlTab + =< TR>;
for(csv中的var c){
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,标签:[结果, reprehenderit, amet],工作:{技能:[{ id:0,名称 :正在编程},{ id:1,名称:业务}]}},朋友:[{ id:0,名称:斯塔福德·埃尔南德斯},{ id :1, name: Colleen Christensen},{ id:2, name: Barker Keith}], greeting:[], favoriteFruit: banana}';;
convert(json);

甚至是我原来的 JSONoperations.hta CSV导出看起来并不糟糕,但是有点旋转并且包括无用的1级编号,无论如何,如果我从这里开始;-)



  table {border-spacing:0px; } th {text-align:left; } td.empty {background-color:lightgray;}  

  < TABLE border = 1 width = 900>< TR>< TD>标签< / TD>< TD> 0< / TD>< TD> 1< / TD>< TD> 2< / TD> / TR< TR>< TD class = empty< / TD< TD" / TD< TD> preprehenderit< / TD< TD> ; amet / TD< TR> TR< TD> Work< / TD< TD>技能// TD< TD>#< / TD< TD> id< / TD< TD>名称< / TD< / TR>< TR>< TD class = empty< / TD>< TD class = empty< / TD> < TD> 1 / TD< TD> 0< / TD>< TD> / TD< / TR>< TR>< TD class = empty< / TD> ;< TD class = empty< / TD>< TD> 2< TD< TD> 1< / TD>< TD>< TD>< / TR>< TR< TD> /< TD>#< / TD< TD> id< / TD< TD>名称< / TD>< ; / TR>< TR>< TD class = empty< / TD>< TD> 1< / TD> TD< 0> / TD< TD>斯塔福德·赫尔南德斯< / TD> ;< / TR>< TD class = empty< / TD>< TD> 2< TD< TD>< TD< 1> / TD< TD< Colleen Christensen< / TD< / TR>< TD class = empty< / TD< TD< 3> / TD< TD> 2< / TD< TD>巴克Keith< TD> / TR> TR< TD> _id< TD< TD>名称< / TD< TD>电话< / TD>< TD>地址< / TD> < TD>最喜欢的水果/ TD< / TR> TR< TD> 5cfe7d3c6deeeef08ce0444b< TD> TD> Debra Milligain< TD>< 182> -1(906) / TD< TD> 676 Merit Court,俄勒冈州Steinhatchee,5491< / TD< TD"香蕉< / TD< / TR< / TABLE>  


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 proper header. The problem I have is structuring the data that corresponds to each row. For example lets say I have these columns called id, tags, and friends_id. Taking the case for id = 1, tags = [car, plane] friends_id = [0,1,2]. It should kind of look like this in a csv table format.

+-------+-------+------------+
| id    | tags  | friends_id |
+-------+-------+------------+
| 1     | car   | 0          |
+-------+-------+------------+
| empty | plane | 1          |
+-------+-------+------------+
| empty | empty | 2          |
+-------+-------+------------+
| 2     | ...   | ...        |
+-------+-------+------------+

Here's some test data that I'm working on.

{
    "_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"
}

The following code (Which is a solution to an earlier problem I had, found here is what I use to recursively generate headers into a map or hash table and append their corresponding values.

var arrayOfHeaders = {};
var headerDirectory = "";
var rootLevel = true;
var temp = ""
var firstWalkthrough = true;
function traverseJSON(obj){
    for (var o in obj) {
        if (typeof obj[o] == "object") {
            //console.log("Before traversal ", o)
            //console.log("Traversing the object: ", obj[o])
            if(!isNaN(o)){
                //console.log("Current position is a number ", o)
            }else{
                console.log("Adding to directory... " , o)
                headerDirectory += (headerDirectory == "") ? o : "_" + o;
            }
            rootLevel = false;
            if (firstWalkthrough){
                firstWalkthrough = false;
                //if (o == 0) 
                rootLevel = true;
            }

            traverseJSON(obj[o]);
            rootLevel = true;
            temp = headerDirectory;
            headerDirectory = "";
        } else {
            if (rootLevel) {
                if(isNaN(o)){ 
                    headerDirectory = "";
                    //console.log("Has value and is root ", o, "and temp ", temp)
                    arrayOfHeaders[o] +=  ",\"" + obj[o] + "\"";
                }else{
                    arrayOfHeaders[headerDirectory+"_"+o] +=  ",\"" + obj[o] + "\"";
                }

            }
            else {
                //console.log("Has value and is not root ", o)
                //console.log("Current Header Directory " + headerDirectory)
                //console.log("Saved temp : ", temp)
                if(isNaN(o)){ 
                    if(headerDirectory == "") headerDirectory = temp; 
                    //arrayOfHeaders.push(headerDirectory + "_" + o)
                    arrayOfHeaders[headerDirectory + "_" + o] += ",\"" + obj[o] + "\"";
                }              
            }
        }
    }
    // console.log("Array of Headers : ", arrayOfHeaders)
}

This is the actual response from arrayofHeaders:

{ _id: 'undefined,"5cfe7d3c6deeeef08ce0444b"',
  name: 'undefined,"Debra Milligain"',
  phone: 'undefined,"+1 (906) 432-2182"',
  address: 'undefined,"676 Merit Court, Steinhatchee, Oregon, 5491"',
  tags_0: 'undefined,"consequat"',
  tags_1: 'undefined,"reprehenderit"',
  tags_2: 'undefined,"amet"',
  'Work_skills_id-skill': 'undefined,"0","Business"',
  'Work_skills_name-skill': 'undefined,"Programming"',
  'friends_id-friends': 'undefined,"0","1","2"',
  'friends_name-friends':
   'undefined,"Stafford Hernandez","Colleen Christensen","Barker Keith"',
  favoriteFruit: 'undefined,"banana"' }

I want to structure the data in such a way that it would be easy to loop through and produce a csv like this. The issue is that I'm not sure how to make the leap from whats above to whats below.

+--------------------------+-----------------+-------------------+---------------------------------------------+---------------+------------------------+--------------------------+---------------------+-----------------------+---------------+
| _id                      | name            | phone             | address                                     | tags          | Work__skills__id-skill | Work__skills__name-skill | friends__id-friends | friends__name-friends | favoriteFruit |
+--------------------------+-----------------+-------------------+---------------------------------------------+---------------+------------------------+--------------------------+---------------------+-----------------------+---------------+
| 5cfe7d3c6deeeef08ce0444b | Debra Milligain | +1 (906) 432-2182 | 676 Merit Court, Steinhatchee, Oregon, 5491 | consequat     | 0                      | Programming              | 0                   | Stafford Hernandez    | banana        |
+--------------------------+-----------------+-------------------+---------------------------------------------+---------------+------------------------+--------------------------+---------------------+-----------------------+---------------+
|                          |                 |                   |                                             | reprehenderit | Business               |                          | 1                   | Colleen Christensen   |               |
+--------------------------+-----------------+-------------------+---------------------------------------------+---------------+------------------------+--------------------------+---------------------+-----------------------+---------------+
|                          |                 |                   |                                             | amet          |                        |                          | 2                   | Barker Keith          |               |
+--------------------------+-----------------+-------------------+---------------------------------------------+---------------+------------------------+--------------------------+---------------------+-----------------------+---------------+

EDIT: El Tom - possible fix of your code

var arrayOfHeaders = {};
var headerDirectory = "";
var rootLevel = true;
var temp = ""
var firstWalkthrough = true;
traverseJSON(
    JSON.parse('{"_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"}')
)
function traverseJSON(obj) {
    for (var o in obj) {
        if (typeof obj[o] == "object") {
            //console.log("Before traversal ", o)
            //console.log("Traversing the object: ", obj[o])
            if (!isNaN(o)) {
                //console.log("Current position is a number ", o)
            } else {
                console.log("Adding to directory... ", o)
                headerDirectory += (headerDirectory == "") ? o : "_" + o;
            }
            rootLevel = false;
            if (firstWalkthrough) {
                firstWalkthrough = false;
                //if (o == 0) 
                rootLevel = true;
            }

            traverseJSON(obj[o]);
            rootLevel = true;
            temp = headerDirectory;
            headerDirectory = "";
        } else {
            if (rootLevel) {
                if (isNaN(o)) {
                    headerDirectory = "";
                    //console.log("Has value and is root ", o, "and temp ", temp)
                    if (arrayOfHeaders[o] !== undefined) {
                        arrayOfHeaders[o].push(obj[o]);
                    } else {
                        arrayOfHeaders[o] = [obj[o]];
                    }
                } else {
                    if (arrayOfHeaders[headerDirectory + "_" + o] !== undefined) {
                        arrayOfHeaders[headerDirectory + "_" + o].push(obj[o]);
                    } else {
                        arrayOfHeaders[headerDirectory + "_" + o] = [obj[o]];
                    }
                }

            }
            else {
                //console.log("Has value and is not root ", o)
                //console.log("Current Header Directory " + headerDirectory)
                //console.log("Saved temp : ", temp)
                if (isNaN(o)) {
                    if (headerDirectory == "") headerDirectory = temp;
                    //arrayOfHeaders.push(headerDirectory + "_" + o)
                    if (arrayOfHeaders[headerDirectory + "_" + o] !== undefined) {
                        arrayOfHeaders[headerDirectory + "_" + o].push(obj[o]);
                    } else {
                        arrayOfHeaders[headerDirectory + "_" + o] = [obj[o]];
                    }
                }
            }
        }
    }
}
var res = JSON.stringify(arrayOfHeaders).replace(/,"/g, ',\n"')
console.log("Easier Arrays in Array printed by JSON.stringify:\n", res)
res = {
    "_id": ["5cfe7d3c6deeeef08ce0444b"],
    "name": ["Debra Milligain"],
    "phone": ["+1 (906) 432-2182"],
    "address": ["676 Merit Court, Steinhatchee, Oregon, 5491"],
    "tags_0": ["consequat"],
    "tags_1": ["reprehenderit"],
    "tags_2": ["amet"],
    "Work_skills_id": [0, 1],
    "Work_skills_name": ["Programming",
        "Business"],
    "friends_id": [0, 1, 2],
    "friends_name": ["Stafford Hernandez",
        "Colleen Christensen",
        "Barker Keith"],
    "favoriteFruit": ["banana"]
};
var yourResult = "";
for(var i in arrayOfHeaders) {
    if(arrayOfHeaders[i].length > 1) {
       arrayOfHeaders[i] = '"' + arrayOfHeaders[i].join('","') + '"';
    } else {
        arrayOfHeaders[i] = '"' + arrayOfHeaders[i] + '"';
    }
    yourResult += i + ':' + arrayOfHeaders[i] + '\n';
}
console.log("\nProbably result you wanted to collect but invalid JSON format:\n", yourResult);

But if you compare them with my answer, printed structure is the same, but not as your manualy generated table (probably some errors).

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>