In previous exercises we learned to fetch data from API servers using Fetch and to create advanced HTTP clients for our Jurassic Park. Now it is time to learn how to efficiently process, transform, and manage the complex JSON data structures we receive from our systems.
JSON (JavaScript Object Notation) is the standard data exchange format — lightweight, human-readable, and easy for machines to process. In Jurassic Park we use JSON to represent everything — from dinosaur data to security system statuses and visitor records.
JSON in JavaScript is represented by two main structure types:
Example JSON structure representing a dinosaur in our park:
1const trex = {
2 "id": "dino-001",
3 "name": "Rexy",
4 "species": "Tyrannosaurus Rex",
5 "age": 25,
6 "stats": {
7 "weight": 8000,
8 "length": 12.3,
9 "height": 3.6
10 },
11 "diet": ["meat", "carrion"],
12 "threatLevel": 5,
13 "location": {
14 "enclosure": "T-Rex Kingdom",
15 "coordinates": {
16 "x": 34.5,
17 "y": 67.2
18 }
19 },
20 "behaviors": [
21 {
22 "type": "hunting",
23 "frequency": "high",
24 "times": ["morning", "evening"]
25 },
26 {
27 "type": "resting",
28 "frequency": "medium",
29 "times": ["midday"]
30 }
31 ],
32 "addedOn": "2023-06-15T08:30:00Z",
33 "active": true
34}JavaScript offers two fundamental methods for working with JSON:
JSON.parse() — converts a JSON string to a JavaScript objectJSON.stringify() — converts a JavaScript object to a JSON string1// Serialize object to JSON string
2const trexString = JSON.stringify(trex);
3console.log("Serialized T-Rex:", trexString);
4
5// Parse JSON string back to object
6const parsedTrex = JSON.parse(trexString);
7console.log("Parsed T-Rex:", parsedTrex);JSON.stringify() has additional parameters that can be useful:1// Format JSON with indentation for readability
2const prettyTrex = JSON.stringify(trex, null, 2);
3console.log("Formatted T-Rex:");
4console.log(prettyTrex);
5
6// Selectively serialize only chosen fields
7const simpleTrex = JSON.stringify(trex, ['id', 'name', 'species', 'age']);
8console.log("Simplified T-Rex:", simpleTrex);
9
10// Custom replacer function
11const customTrex = JSON.stringify(trex, (key, value) => {
12 // Hide exact coordinates for security reasons
13 if (key === 'coordinates') return "HIDDEN";
14 // Convert threat level codes to text description
15 if (key === 'threatLevel') {
16 const descriptions = ["harmless", "low", "moderate", "high", "extreme"];
17 return descriptions[value - 1] || value;
18 }
19 return value;
20});
21console.log("T-Rex with custom serialization:", customTrex);Let's assume we received a complex JSON structure containing data about all dinosaurs in the park. How can we efficiently process such data?
Filtering is one of the most common operations when working with data collections:
1// Assume we have an array of dinosaurs
2const dinosaurs = await api.get('/dinosaurs');
3
4// Filter only predators
5const predators = dinosaurs.filter(dino => dino.diet.includes('meat'));
6
7// Filter dinosaurs in a specific enclosure
8const dinosInEnclosure = dinosaurs.filter(dino =>
9 dino.location.enclosure === 'T-Rex Kingdom'
10);
11
12// Filter dangerous dinosaurs (threat level > 3)
13const dangerousDinos = dinosaurs.filter(dino => dino.threatLevel > 3);
14
15// Complex filtering with multiple conditions
16const dangerousPredatorsInZoneA = dinosaurs.filter(dino =>
17 dino.threatLevel > 3 &&
18 dino.diet.includes('meat') &&
19 dino.location.enclosure === 'Zone A'
20);We often need to transform data from one format to another, for example to display in a user interface:
1// Create simplified dinosaur list for UI
2const simpleDinos = dinosaurs.map(dino => ({
3 id: dino.id,
4 name: dino.name,
5 species: dino.species,
6 threatLevel: dino.threatLevel
7}));
8
9// Add new fields based on existing data
10const dinosWithExtras = dinosaurs.map(dino => ({
11 ...dino, // copy all existing fields
12 ageYears: dino.age, // rename field
13 threatDescription: ['harmless', 'low', 'moderate', 'high', 'extreme'][dino.threatLevel - 1],
14 weightTons: dino.stats.weight / 1000,
15 needsGuard: dino.threatLevel > 3
16}));
17
18// Transform to specific format for a chart
19const chartData = dinosaurs.map(dino => ({
20 x: dino.stats.length,
21 y: dino.stats.weight,
22 r: dino.threatLevel * 5, // point size proportional to threat
23 label: dino.name,
24 color: dino.diet.includes('meat') ? 'red' : 'green'
25}));Reduction allows aggregating data to obtain summaries and statistics:
1// Calculate average weight of all dinosaurs
2const avgWeight = dinosaurs.reduce((sum, dino) => sum + dino.stats.weight, 0) / dinosaurs.length;
3
4// Find the longest dinosaur
5const longest = dinosaurs.reduce((longest, dino) =>
6 dino.stats.length > longest.stats.length ? dino : longest
7, dinosaurs[0]);
8
9// Group dinosaurs by species
10const dinosBySpecies = dinosaurs.reduce((groups, dino) => {
11 // If the group for this species does not exist, create it
12 if (!groups[dino.species]) {
13 groups[dino.species] = [];
14 }
15 // Add dinosaur to the appropriate group
16 groups[dino.species].push(dino);
17 return groups;
18}, {});
19
20// Count dinosaurs by threat level
21const countByThreat = dinosaurs.reduce((counter, dino) => {
22 counter[dino.threatLevel] = (counter[dino.threatLevel] || 0) + 1;
23 return counter;
24}, {});Array methods can be chained, allowing concise and readable code:
1// Find the 3 most dangerous dinosaurs in Zone A
2const top3DangerousInZoneA = dinosaurs
3 .filter(dino => dino.location.enclosure === 'Zone A')
4 .sort((a, b) => b.threatLevel - a.threatLevel) // sort descending
5 .slice(0, 3) // take only first 3
6 .map(dino => ({
7 name: dino.name,
8 species: dino.species,
9 threatLevel: dino.threatLevel
10 }));
11
12// Calculate average weight per species
13const avgWeightBySpecies = Object.entries(
14 dinosaurs.reduce((groups, dino) => {
15 if (!groups[dino.species]) {
16 groups[dino.species] = { sum: 0, count: 0 };
17 }
18 groups[dino.species].sum += dino.stats.weight;
19 groups[dino.species].count += 1;
20 return groups;
21 }, {})
22).map(([species, { sum, count }]) => ({
23 species,
24 avgWeight: sum / count
25}));In complex structures we often need to find or update deeply nested data:
1// Find dinosaur based on deeply nested condition
2const dinoNearCoordinates = dinosaurs.find(dino => {
3 const { x, y } = dino.location.coordinates;
4 // Find dinosaur within radius 5 units from point (30, 70)
5 return Math.sqrt(Math.pow(x - 30, 2) + Math.pow(y - 70, 2)) < 5;
6});
7
8// Update deeply nested property (immutability)
9function updateDinoLocation(dino, newX, newY) {
10 return {
11 ...dino,
12 location: {
13 ...dino.location,
14 coordinates: {
15 ...dino.location.coordinates,
16 x: newX,
17 y: newY
18 }
19 }
20 };
21}
22
23// Usage
24const updatedDino = updateDinoLocation(trex, 35.0, 68.0);Some data structures are hierarchical or have unknown nesting depth. Recursive processing is useful in such cases:
1// Example hierarchical dinosaur category structure
2const dinosaurCategories = {
3 "name": "Dinosaurs",
4 "subcategories": [
5 {
6 "name": "Theropods",
7 "subcategories": [
8 {
9 "name": "Tyrannosauroids",
10 "dinosaurs": [
11 { "id": "dino-001", "name": "Rexy" },
12 { "id": "dino-002", "name": "Tara" }
13 ]
14 },
15 {
16 "name": "Deinonychosaurs",
17 "dinosaurs": [
18 { "id": "dino-003", "name": "Blue" },
19 { "id": "dino-004", "name": "Delta" }
20 ]
21 }
22 ]
23 },
24 {
25 "name": "Sauropods",
26 "dinosaurs": [
27 { "id": "dino-005", "name": "Long" },
28 { "id": "dino-006", "name": "Tall" }
29 ]
30 }
31 ]
32};
33
34// Recursive function to find dinosaur by ID in the hierarchy
35function findDinosaurById(node, id) {
36 // Check if the current node has a dinosaurs array
37 if (node.dinosaurs) {
38 const found = node.dinosaurs.find(dino => dino.id === id);
39 if (found) return found;
40 }
41
42 // If the node has subcategories, search them recursively
43 if (node.subcategories) {
44 for (const subcategory of node.subcategories) {
45 const found = findDinosaurById(subcategory, id);
46 if (found) return found;
47 }
48 }
49
50 return null;
51}
52
53// Recursive function to flatten the hierarchy into a list of all dinosaurs
54function getAllDinosaurs(node, list = []) {
55 if (node.dinosaurs) {
56 list.push(...node.dinosaurs);
57 }
58
59 if (node.subcategories) {
60 for (const subcategory of node.subcategories) {
61 getAllDinosaurs(subcategory, list);
62 }
63 }
64
65 return list;
66}
67
68// Usage
69const blue = findDinosaurById(dinosaurCategories, "dino-003");
70console.log("Found dinosaur:", blue);
71
72const allDinos = getAllDinosaurs(dinosaurCategories);
73console.log("All dinosaurs:", allDinos);Now let's combine all the discussed techniques in one practical example. Let's say we are building a dashboard for the Jurassic Park manager that shows the status of all dinosaurs in the park.
1// Function to fetch data from API
2async function fetchDinosaurData() {
3 try {
4 const response = await fetch('https://api.jurassic-park.com/dinosaurs');
5 if (!response.ok) throw new Error(`HTTP Error: ${response.status}`);
6 return await response.json();
7 } catch (error) {
8 console.error('Error fetching data:', error);
9 return { dinosaurs: [] };
10 }
11}
12
13// Function to process and prepare data for display
14function processDataForDisplay(data) {
15 const { dinosaurs } = data;
16
17 // Aggregate statistics
18 const stats = {
19 count: dinosaurs.length,
20 speciesCount: new Set(dinosaurs.map(d => d.species)).size,
21 avgWeight: dinosaurs.reduce((sum, d) => sum + d.stats.weight, 0) / dinosaurs.length,
22 maxThreat: Math.max(...dinosaurs.map(d => d.threatLevel))
23 };
24
25 // Group by enclosure
26 const dinosByEnclosure = dinosaurs.reduce((groups, dino) => {
27 const enclosure = dino.location.enclosure;
28 if (!groups[enclosure]) groups[enclosure] = [];
29 groups[enclosure].push(dino);
30 return groups;
31 }, {});
32
33 // Count by threat level
34 const countByThreat = [1, 2, 3, 4, 5].map(level => ({
35 level,
36 count: dinosaurs.filter(d => d.threatLevel === level).length,
37 percent: (dinosaurs.filter(d => d.threatLevel === level).length / dinosaurs.length) * 100
38 }));
39
40 // Identify potential threats
41 const threats = dinosaurs
42 .filter(d => d.threatLevel >= 4)
43 .map(d => ({
44 id: d.id,
45 name: d.name,
46 species: d.species,
47 enclosure: d.location.enclosure,
48 threatLevel: d.threatLevel,
49 lastBehavior: d.behaviors[0]?.type || 'unknown',
50 coordinates: d.location.coordinates
51 }))
52 .sort((a, b) => b.threatLevel - a.threatLevel);
53
54 // Prepare data for weight chart
55 const weightChartData = dinosaurs
56 .filter(d => d.stats.weight > 0)
57 .map(d => ({
58 id: d.id,
59 name: d.name,
60 species: d.species,
61 weight: d.stats.weight,
62 predator: d.diet.includes('meat'),
63 pointSize: Math.sqrt(d.stats.weight) / 5
64 }));
65
66 // Recent activities (sorted by ID)
67 const recentActivities = dinosaurs
68 .filter(d => d.behaviors && d.behaviors.length > 0)
69 .flatMap(d => d.behaviors.map(b => ({
70 dinoId: d.id,
71 dinoName: d.name,
72 type: b.type,
73 frequency: b.frequency,
74 times: b.times
75 })))
76 .sort((a, b) => a.dinoId.localeCompare(b.dinoId));
77
78 return {
79 stats,
80 dinosByEnclosure,
81 countByThreat,
82 threats,
83 weightChartData,
84 recentActivities,
85 raw: dinosaurs // Original data if needed
86 };
87}
88
89// Function to render data to the user interface (example)
90function renderDashboard(processedData) {
91 console.log("Processed data for dashboard:", processedData);
92
93 // Example: render threats table
94 const threatsTable = document.getElementById('threats-table');
95 if (threatsTable) {
96 threatsTable.innerHTML = processedData.threats
97 .map(t => `
98 <tr>
99 <td>${t.name}</td>
100 <td>${t.species}</td>
101 <td>${t.enclosure}</td>
102 <td class="threat-${t.threatLevel}">${t.threatLevel}</td>
103 <td>${t.lastBehavior}</td>
104 <td>
105 <button onclick="showOnMap(${t.coordinates.x}, ${t.coordinates.y})">
106 Show on map
107 </button>
108 </td>
109 </tr>
110 `)
111 .join('');
112 }
113}
114
115// Main update function
116async function updateDashboard() {
117 const data = await fetchDinosaurData();
118 const processedData = processDataForDisplay(data);
119 renderDashboard(processedData);
120}
121
122// Run update every 5 minutes
123updateDashboard();
124setInterval(updateDashboard, 5 * 60 * 1000);In this exercise we learned:
JSON serialization and deserialization basics — how to convert data between text format and JavaScript objects
Advanced data processing techniques:
.filter().map().reduce()Practical application of these techniques in the context of a dinosaur park management system
The ability to efficiently process and analyze JSON data is crucial for every modern JavaScript project — from simple websites to complex applications like the Jurassic Park Management System. Thanks to the techniques you have learned, you will be able to transform raw data into useful information that helps keep the park running smoothly and safely... at least until a raptor finds a gap in the system!