data = {
"Q1": [1,2,3,2],
"Q2": [5,6,7,6],
"Q3": [9,10,11,10],
'Q4': [], // test case 1: no data
'Q5': [1], // test case 2: only answer field
'Q6': ['A', 'B', 'C'],// test case 3: for string
'Q7': ['TEST'] // test case 4: for answer(string) only
};
// For Q4, Q5, you can added some extra codes to uses default values intead, or ignore this row.
// formmat the data first, add some validators if neccessary
function formatAdapter (data) {
return Object.entries(data).map((item) => {
let newItem = {}
newItem[item[0]] = {'options': item[1].slice(0, item[1].length -1),
'answer': item[-1]} // assuming the last element is the answer, [0:last] is the options
return [item[0], item[1].slice(0, item[1].length -1), item[1].slice(-1)[0]]
})
}
let formmatedData = formatAdapter(data)
var tbody = document.getElementById('tbody');
// generate the header
tbody.innerHTML += "<tr>" +
"<th id='q'>" + "Question" + "</th>" +
"<th id='o'>" + "Options" + "</th>" +
"<th id='ca'>" + "Correct Answer" + "</th>" +
"</tr>"
// generate the rows(Html) for each questions
let rowHtmls = formmatedData.map((item) => {
let row = '<tr><td rowspan="'+(item[1].length || 1) +'">'+item[0]+'</td>'
+ '<td>'+item[1][0]+'</td>'
+ '<td rowspan="'+(item[1].length || 1)+'">'+item[2]+'</td></tr>'
row += item[1].slice(1).reduce((pre, cur) => {
return pre + '<tr><td>'+cur+'</td></tr>'
}, '')
return row
})
// combine header(html) and rows(html)
tbody.innerHTML += rowHtmls.reduce((pre, cur) => {
return pre+cur
}, '')
table tbody tr td {
border: 1px solid black
}
<table class="table">
<tbody id="tbody"></tbody>
</table>