Advanced JavaScript (ES6+)

Working with JSON

13 min Lesson 18 of 40

Working with JSON

JSON (JavaScript Object Notation) is the standard format for exchanging data between web applications. JavaScript provides built-in methods to convert between JavaScript objects and JSON strings, making it essential for modern web development.

Understanding JSON

JSON is a text-based format that looks similar to JavaScript objects but with stricter rules:

// Valid JSON { "name": "John Doe", "age": 30, "email": "john@example.com", "isActive": true, "hobbies": ["reading", "coding", "gaming"], "address": { "city": "New York", "zipCode": "10001" } } // JSON Rules: // ✓ Property names MUST be in double quotes // ✓ Strings MUST use double quotes (no single quotes) // ✓ No trailing commas // ✓ No comments allowed // ✓ No undefined, functions, or symbols // ✓ Numbers, booleans, null, strings, arrays, and objects only
Key Difference: JavaScript objects are more flexible than JSON. JSON is a subset of JavaScript with stricter syntax rules designed for data interchange.

JSON.stringify() - Converting to JSON

JSON.stringify() converts JavaScript values to JSON strings:

const user = { name: "John Doe", age: 30, email: "john@example.com", hobbies: ["reading", "coding"] }; // Basic conversion const jsonString = JSON.stringify(user); console.log(jsonString); // {"name":"John Doe","age":30,"email":"john@example.com","hobbies":["reading","coding"]} // Pretty printing with indentation (2 spaces) const prettyJson = JSON.stringify(user, null, 2); console.log(prettyJson); /* { "name": "John Doe", "age": 30, "email": "john@example.com", "hobbies": [ "reading", "coding" ] } */ // Using tabs for indentation const tabbedJson = JSON.stringify(user, null, "\t");

JSON.parse() - Parsing JSON Strings

JSON.parse() converts JSON strings back to JavaScript objects:

const jsonString = '{"name":"John","age":30,"isActive":true}'; // Parse JSON string const user = JSON.parse(jsonString); console.log(user.name); // "John" console.log(user.age); // 30 console.log(user.isActive); // true // Parse array JSON const arrayJson = '["apple", "banana", "orange"]'; const fruits = JSON.parse(arrayJson); console.log(fruits[0]); // "apple"
Common Error: JSON.parse() throws a SyntaxError if the string is not valid JSON. Always use try/catch when parsing JSON from external sources!

Handling JSON Errors

Always wrap JSON.parse() in try/catch to handle invalid JSON gracefully:

function safeJsonParse(jsonString, fallback = null) { try { return JSON.parse(jsonString); } catch (error) { console.error("JSON parse error:", error.message); return fallback; } } // Usage const validJson = '{"name":"John"}'; const invalidJson = '{name: "John"}'; // Invalid: no quotes around key const user1 = safeJsonParse(validJson); console.log(user1); // { name: "John" } const user2 = safeJsonParse(invalidJson, { name: "Default" }); console.log(user2); // { name: "Default" } // More detailed error handling function parseJsonWithDetails(jsonString) { try { return { success: true, data: JSON.parse(jsonString), error: null }; } catch (error) { return { success: false, data: null, error: error.message }; } } const result = parseJsonWithDetails(invalidJson); if (result.success) { console.log("Data:", result.data); } else { console.log("Error:", result.error); }

The Replacer Parameter

JSON.stringify() accepts a replacer function or array to control what gets included:

const user = { name: "John", password: "secret123", email: "john@example.com", age: 30 }; // Using array to select specific properties const limitedJson = JSON.stringify(user, ["name", "email"]); console.log(limitedJson); // {"name":"John","email":"john@example.com"} // Using function to filter/transform values const filteredJson = JSON.stringify(user, (key, value) => { // Exclude password field if (key === "password") { return undefined; } // Convert age to string if (key === "age") { return String(value); } return value; }); console.log(filteredJson); // {"name":"John","email":"john@example.com","age":"30"}

The Reviver Parameter

JSON.parse() accepts a reviver function to transform parsed values:

const jsonString = '{ "name": "John", "birthDate": "1990-01-15", "lastLogin": "2024-01-20T10:30:00.000Z" }'; // Parse with reviver to convert date strings to Date objects const user = JSON.parse(jsonString, (key, value) => { // Check if value looks like a date string if (typeof value === "string") { const datePattern = /^\d{4}-\d{2}-\d{2}/; if (datePattern.test(value)) { return new Date(value); } } return value; }); console.log(user.birthDate instanceof Date); // true console.log(user.birthDate.getFullYear()); // 1990 console.log(user.lastLogin instanceof Date); // true

JSON with Special Types

Some JavaScript types need special handling when converting to/from JSON:

// Dates const data1 = { date: new Date() }; const json1 = JSON.stringify(data1); console.log(json1); // {"date":"2024-01-20T10:30:00.000Z"} const parsed1 = JSON.parse(json1); console.log(typeof parsed1.date); // "string" (not Date!) console.log(new Date(parsed1.date)); // Convert back to Date // undefined values are omitted const data2 = { name: "John", age: undefined, email: "john@example.com" }; console.log(JSON.stringify(data2)); // {"name":"John","email":"john@example.com"} // Functions are omitted const data3 = { name: "John", greet: function() { return "Hi"; } }; console.log(JSON.stringify(data3)); // {"name":"John"} // NaN and Infinity become null const data4 = { value: NaN, infinite: Infinity }; console.log(JSON.stringify(data4)); // {"value":null,"infinite":null} // Arrays keep undefined as null const arr = [1, undefined, 3]; console.log(JSON.stringify(arr)); // [1,null,3]
Best Practice: When working with dates in JSON, always store them as ISO 8601 strings and convert them back to Date objects after parsing using a reviver function.

Deep Cloning with JSON

JSON can be used to create deep copies of objects (with limitations):

const original = { name: "John", hobbies: ["reading", "coding"], address: { city: "New York" } }; // Deep clone using JSON const clone = JSON.parse(JSON.stringify(original)); // Modify clone clone.hobbies.push("gaming"); clone.address.city = "Boston"; console.log(original.hobbies); // ["reading", "coding"] - unchanged! console.log(original.address.city); // "New York" - unchanged! console.log(clone.hobbies); // ["reading", "coding", "gaming"] console.log(clone.address.city); // "Boston" // ⚠️ Limitations: const complexObject = { date: new Date(), regex: /test/, func: () => {}, undef: undefined, symbol: Symbol("test"), circularRef: null }; complexObject.circularRef = complexObject; // This loses date, regex, function, undefined, symbol // And throws error on circular reference! // const cloneComplex = JSON.parse(JSON.stringify(complexObject)); // Error!
Warning: JSON cloning only works for simple data types (strings, numbers, booleans, null, plain objects, and arrays). It loses functions, dates (converts to strings), undefined, symbols, and throws errors on circular references.

Real-World JSON Patterns

// 1. API Response handling async function fetchUserData(userId) { try { const response = await fetch(`/api/users/${userId}`); const jsonText = await response.text(); // Get as text first // Check if response is valid JSON if (!jsonText) { throw new Error("Empty response"); } const userData = JSON.parse(jsonText); return userData; } catch (error) { console.error("Failed to fetch user:", error); throw error; } } // 2. LocalStorage with JSON const storage = { set(key, value) { try { const jsonString = JSON.stringify(value); localStorage.setItem(key, jsonString); } catch (error) { console.error("Failed to save to localStorage:", error); } }, get(key, fallback = null) { try { const jsonString = localStorage.getItem(key); return jsonString ? JSON.parse(jsonString) : fallback; } catch (error) { console.error("Failed to read from localStorage:", error); return fallback; } }, remove(key) { localStorage.removeItem(key); } }; // Usage storage.set("user", { name: "John", age: 30 }); const user = storage.get("user"); console.log(user); // { name: "John", age: 30 } // 3. Form data to JSON function formToJson(formElement) { const formData = new FormData(formElement); const jsonObject = {}; formData.forEach((value, key) => { // Handle multiple values (like checkboxes) if (jsonObject[key]) { if (!Array.isArray(jsonObject[key])) { jsonObject[key] = [jsonObject[key]]; } jsonObject[key].push(value); } else { jsonObject[key] = value; } }); return jsonObject; } // Usage const form = document.querySelector("#myForm"); const formJson = JSON.stringify(formToJson(form)); console.log(formJson);

JSON Validation

// Check if string is valid JSON function isValidJson(str) { try { JSON.parse(str); return true; } catch { return false; } } console.log(isValidJson('{"name":"John"}')); // true console.log(isValidJson('{name: "John"}')); // false // Validate JSON structure function validateUserJson(jsonString) { try { const user = JSON.parse(jsonString); // Check required fields if (!user.name || typeof user.name !== "string") { throw new Error("Invalid or missing name"); } if (!user.email || !user.email.includes("@")) { throw new Error("Invalid or missing email"); } if (user.age && (typeof user.age !== "number" || user.age < 0)) { throw new Error("Invalid age"); } return { valid: true, data: user }; } catch (error) { return { valid: false, error: error.message }; } } // Usage const result1 = validateUserJson('{"name":"John","email":"john@example.com","age":30}'); console.log(result1); // { valid: true, data: {...} } const result2 = validateUserJson('{"name":"John","email":"invalid"}'); console.log(result2); // { valid: false, error: "Invalid or missing email" }

Practice Exercise:

Task: Create a utility class for safely working with JSON that includes error handling and type conversion.

// Your task: Implement this class class JsonUtil { static stringify(value, options = {}) { // Convert to JSON with error handling // Support pretty printing option } static parse(jsonString, options = {}) { // Parse JSON with error handling // Support reviver for date conversion // Support default fallback value } static isValid(str) { // Check if string is valid JSON } static clone(obj) { // Deep clone object using JSON // Handle errors } }

Solution:

class JsonUtil { static stringify(value, options = {}) { const { pretty = false, space = 2 } = options; try { return JSON.stringify(value, null, pretty ? space : 0); } catch (error) { console.error("JSON stringify error:", error.message); return null; } } static parse(jsonString, options = {}) { const { convertDates = false, fallback = null } = options; try { const reviver = convertDates ? (key, value) => { if (typeof value === "string") { const datePattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/; if (datePattern.test(value)) { return new Date(value); } } return value; } : undefined; return JSON.parse(jsonString, reviver); } catch (error) { console.error("JSON parse error:", error.message); return fallback; } } static isValid(str) { if (typeof str !== "string") return false; try { JSON.parse(str); return true; } catch { return false; } } static clone(obj) { try { return JSON.parse(JSON.stringify(obj)); } catch (error) { console.error("JSON clone error:", error.message); return null; } } } // Usage const obj = { name: "John", date: new Date().toISOString() }; const json = JsonUtil.stringify(obj, { pretty: true }); console.log(json); const parsed = JsonUtil.parse(json, { convertDates: true }); console.log(parsed.date instanceof Date); // true console.log(JsonUtil.isValid('{"name":"John"}')); // true console.log(JsonUtil.isValid('{invalid}')); // false const cloned = JsonUtil.clone(obj); console.log(cloned);

Summary

In this lesson, you learned:

  • JSON is a text format for data interchange with strict syntax rules
  • JSON.stringify() converts JavaScript to JSON strings
  • JSON.parse() converts JSON strings to JavaScript objects
  • Always use try/catch when parsing JSON from external sources
  • Replacer and reviver functions provide transformation control
  • Special types (dates, functions, undefined) need careful handling
  • JSON can create deep clones but has limitations
  • Validate JSON structure for robust applications
Next Up: In the next lesson, we'll explore advanced async patterns including async iteration, generators, and sophisticated error handling strategies!