You could reduce the whole structure into a dictionary indexed by item name whose values are dictionaries indexed by dates:
items_dicc = array.reduce((acc, e) => {
if (!acc[e["item"]["name"]]) {
acc[e["item"]["name"]] = {
[e["date"]["name"]]: e["price"]
}
} else {
acc[e["item"]["name"]][e["date"]["name"]] = e["price"]
}
return acc
}, {})
/* items_dicc holds:
{ I1: { '202001': 100, '202002': 200 }, I2: { '202002': 300 } }
*/
Subsequently, you could take advantage of the items_dicc
structure to build the table you're looking for:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<table>
<thead>
<tr class="thead">
<th>ITEM</th>
</tr>
</thead>
<tbody class="tbody">
<tr>
<td></td>
</tr>
</tbody>
</table>
</body>
<script>
array = [
{
"id": 1,
"date": {
"id": 1,
"name": "202001"
},
"item": {
"id": 1,
"name": "I1"
},
"price": 100
},
{
"id": 2,
"date": {
"id": 2,
"name": "202002"
},
"item": {
"id": 1,
"name": "I1"
},
"price": 200
},
{
"id": 3,
"date": {
"id": 2,
"name": "202002"
},
"item": {
"id": 2,
"name": "I2"
},
"price": 300
}
]
items_dicc = array.reduce((acc, e) => {
if (!acc[e["item"]["name"]]) {
acc[e["item"]["name"]] = {
[e["date"]["name"]]: e["price"]
}
} else {
acc[e["item"]["name"]][e["date"]["name"]] = e["price"]
}
return acc
}, {})
// Compute dates that will be used as headers. Duplicates are removed after creating a set and turning it into a list again
dates = [...new Set(Object.keys(items_dicc).map(i => Object.keys(items_dicc[i])).flat())]
const thead = document.getElementsByClassName("thead")[0]
const tbody = document.getElementsByClassName("tbody")[0]
dates.forEach(date => {
thead.appendChild(
htmlToElement(`<th>${date}</th>`)
)
});
Object.keys(items_dicc).forEach(i => {
let row = `<tr><td>${i}</td>`
dates.forEach(date => {
row = `${row}<td>${items_dicc[i][date] || ''}</td>`
});
row = `${row}</tr>`
tbody.appendChild(htmlToElement(row))
})
// From https://stackoverflow.com/questions/494143/creating-a-new-dom-element-from-an-html-string-using-built-in-dom-methods-or-pro/35385518#35385518
function htmlToElement(html) {
var template = document.createElement('template');
html = html.trim();
template.innerHTML = html;
return template.content.firstChild;
}
</script>
</html>
The final result:
<table>
<thead>
<tr class="thead">
<th>ITEM</th>
<th>202001</th>
<th>202002</th>
</tr>
</thead>
<tbody class="tbody">
<tr>
<td></td>
</tr>
<tr>
<td>I1</td>
<td>100</td>
<td>200</td>
</tr>
<tr>
<td>I2</td>
<td></td>
<td>300</td>
</tr>
</tbody>
</table>
EDIT: Simplest React
version of the solution:
App.js
function App() {
const array = [
{
"id": 1,
"date": {
"id": 1,
"name": "202001"
},
"item": {
"id": 1,
"name": "I1"
},
"price": 100
},
{
"id": 2,
"date": {
"id": 2,
"name": "202002"
},
"item": {
"id": 1,
"name": "I1"
},
"price": 200
},
{
"id": 3,
"date": {
"id": 2,
"name": "202002"
},
"item": {
"id": 2,
"name": "I2"
},
"price": 300
}
]
const items_dicc = array.reduce((acc, e) => {
if (!acc[e["item"]["name"]]) {
acc[e["item"]["name"]] = {
[e["date"]["name"]]: e["price"]
}
} else {
acc[e["item"]["name"]][e["date"]["name"]] = e["price"]
}
return acc
}, {})
// Compute dates that will be used as headers. Duplicates are removed after creating a set and turning it into a list again
const dates = [...new Set(Object.keys(items_dicc).map(i => Object.keys(items_dicc[i])).flat())]
return (
<div className="App">
<table>
<thead>
<tr>
<th>ITEM</th>
{dates.map(date => <th>{date}</th>)}
</tr>
</thead>
<tbody>
{
Object.keys(items_dicc).map((item) => {
return (
<tr>
<td>{item}</td>
{dates.map((date) => <td>{items_dicc[item][date] || ''}</td>)}
</tr>
)
})
}
</tbody>
</table>
</div>
);
}
export default App;