JSON data in a variable is only useful if people can see it. This lesson turns JSON into things on a web page: a sentence, a list, a table, and a page that loads its own data. There is no separate HTML section in this course, so the few page tools you need are explained right here.
A web page is made of elements: paragraphs, lists, table rows. JavaScript can find an element and change what is inside it. Three tools cover almost everything in this lesson:
| Tool | Does |
|---|---|
document.getElementById("out") | finds the element whose id is out |
element.textContent = "..." | replaces what is inside it with plain text |
document.createElement("li") | makes a new element, not yet on the page |
parent.appendChild(child) | puts the new element on the page, inside the parent |
element.innerHTML = "..." | replaces what is inside with text that is read as HTML markup |
The buttons in this lesson open a small page in the editor (the Result pane shows what the page looks like). The output boxes show the text that page displays.
<p id="out"></p>
<script>
const data = JSON.parse('{"name":"Ava","grade":10}');
document.getElementById("out").textContent = data.name + " is in grade " + data.grade;
</script>
For an array, make one new element per item and add each to the page:
<ul id="list"></ul>
<script>
const students = JSON.parse('[{"name":"Ava","grade":10},{"name":"Ben","grade":11}]');
const list = document.getElementById("list");
for (const s of students) {
const li = document.createElement("li");
li.textContent = s.name + " (grade " + s.grade + ")";
list.appendChild(li);
}
</script>
For something with many pieces, such as table rows, it is often easier to build the markup as one string with map and join, then set innerHTML once:
<table id="t" border="1" cellpadding="4"></table>
<script>
const rows = JSON.parse('[{"name":"Pen","price":2.5},{"name":"Notebook","price":4.75}]');
const html = rows.map(r => "<tr><td>" + r.name + "</td><td>$" + r.price.toFixed(2) + "</td></tr>").join("");
document.getElementById("t").innerHTML = html;
</script>
The two behave differently when the data contains angle brackets. textContent always treats it as plain text. innerHTML reads it as markup:
<p id="a"></p>
<p id="b"></p>
<script>
const data = JSON.parse('{"comment":"<b>hi</b>"}');
document.getElementById("a").textContent = data.comment;
document.getElementById("b").innerHTML = data.comment;
</script>
innerHTML would let a user slip their own HTML and scripts onto your page. That is a real attack. Use textContent, or build elements with createElement, for any data you did not write yourself. Save innerHTML for markup you control.
Put it together: fetch the JSON, then fill in the page. Start with a "Loading..." message so the page is never blank, and replace it when the data arrives:
<h3>Active students</h3>
<ul id="list"><li>Loading...</li></ul>
<script>
fetch("students.json")
.then(response => response.json())
.then(students => {
const list = document.getElementById("list");
list.innerHTML = "";
for (const s of students.filter(s => s.active)) {
const average = s.scores.reduce((a, b) => a + b, 0) / s.scores.length;
const li = document.createElement("li");
li.textContent = s.name + ": " + average.toFixed(1);
list.appendChild(li);
}
});
</script>
Real pages must handle the case where the data does not arrive. Check response.ok, and write a friendly message into the page instead of leaving "Loading..." forever:
<p id="status">Loading...</p>
<script>
fetch("missing.json")
.then(response => {
if (!response.ok) {
throw new Error("Could not load data (" + response.status + ")");
}
return response.json();
})
.then(data => {
document.getElementById("status").textContent = "Loaded";
})
.catch(error => {
document.getElementById("status").textContent = error.message;
});
</script>
Loading...
id must come after that element in the page, or the element does not exist yet when the script runs. That is why every example here puts the <script> last.
Which is safest for showing text that a user typed?
element.innerHTML = textelement.textContent = textdocument.write(text)textContent treats everything as plain text, so any HTML in it cannot run.Why start a data-driven page with a "Loading..." message?
What is wrong?
const list = document.getElementById("list");
const li = document.createElement("li");
li.textContent = "Ava";
li is never added to the page. It needs list.appendChild(li); as well.textContent with innerHTML and put <b> tags in a name. What changes?