Working with JSON in JavaScript: A Complete Guide

JSON and JavaScript go hand in hand. Whether you're fetching data from APIs, storing user preferences, or configuring applications, knowing how to work with JSON in JavaScript is essential for every web developer.

Topics Covered
• JSON.parse()
• JSON.stringify()
• Fetch API & JSON
• Error Handling
• localStorage & JSON
• Advanced Techniques

JSON.parse() - String to Object

JSON.parse() converts a JSON string into a JavaScript object. It's the primary method for consuming JSON data in your application.

Basic Usage
const jsonString = '{"name": "John", "age": 30}';
const user = JSON.parse(jsonString);

console.log(user.name); // "John"
console.log(user.age);  // 30 (number, not string)

The Reviver Function

The second parameter is a "reviver" function that lets you transform values during parsing:

const data = '{"date": "2026-01-29T12:00:00Z"}';

const result = JSON.parse(data, (key, value) => {
  if (key === 'date') {
    return new Date(value);
  }
  return value;
});

console.log(result.date instanceof Date); // true

JSON.stringify() - Object to String

JSON.stringify() converts a JavaScript value to a JSON string. It's used when you need to store or transmit data.

const user = { name: "John", age: 30 };

// Compact JSON
JSON.stringify(user);
// '{"name":"John","age":30}'

// Pretty-printed with 2-space indent
JSON.stringify(user, null, 2);
// {
//   "name": "John",
//   "age": 30
// }

The Replacer Parameter

The second parameter controls which properties are included:

const user = {
  name: "John",
  password: "secret123",
  email: "[email protected]"
};

// Array: include only these keys
JSON.stringify(user, ['name', 'email']);
// '{"name":"John","email":"[email protected]"}'

// Function: transform values
JSON.stringify(user, (key, value) => {
  if (key === 'password') return undefined; // omit
  return value;
});
// '{"name":"John","email":"[email protected]"}'

Fetching JSON from APIs

The Fetch API is the modern way to make HTTP requests in JavaScript. It works seamlessly with JSON:

GET Request
// Using async/await (recommended)
async function getUser(id) {
  const response = await fetch(`/api/users/${id}`);
  
  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }
  
  const user = await response.json(); // Parses JSON automatically
  return user;
}

// Usage
const user = await getUser(123);
console.log(user.name);
POST Request
async function createUser(userData) {
  const response = await fetch('/api/users', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(userData),
  });
  
  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }
  
  return response.json();
}

// Usage
const newUser = await createUser({
  name: 'Jane',
  email: '[email protected]'
});

Error Handling

Always wrap JSON operations in try-catch blocks to handle parsing errors gracefully:

function safeJsonParse(jsonString, defaultValue = null) {
  try {
    return JSON.parse(jsonString);
  } catch (error) {
    console.error('JSON parse error:', error.message);
    return defaultValue;
  }
}

// Usage
const data = safeJsonParse('{"valid": true}');     // { valid: true }
const fallback = safeJsonParse('not json', {});    // {}
const invalid = safeJsonParse('{"broken"');        // null

Working with localStorage

localStorage only stores strings, so JSON is essential for storing complex data:

// Helper functions for localStorage + JSON
const storage = {
  get(key, defaultValue = null) {
    const item = localStorage.getItem(key);
    if (item === null) return defaultValue;
    try {
      return JSON.parse(item);
    } catch {
      return defaultValue;
    }
  },
  
  set(key, value) {
    localStorage.setItem(key, JSON.stringify(value));
  },
  
  remove(key) {
    localStorage.removeItem(key);
  }
};

// Usage
storage.set('user', { name: 'John', theme: 'dark' });
const user = storage.get('user');          // { name: 'John', theme: 'dark' }
const missing = storage.get('unknown', {}); // {}

Advanced Techniques

Deep Cloning Objects

// Quick deep clone (works for JSON-safe values)
const clone = JSON.parse(JSON.stringify(original));

// Note: This doesn't work with:
// - Functions
// - undefined
// - Symbol
// - Circular references
// - Date objects (become strings)
// - Map, Set, RegExp

Custom toJSON Method

class User {
  constructor(name, password) {
    this.name = name;
    this.password = password;
  }
  
  // Custom serialization
  toJSON() {
    return {
      name: this.name,
      // password is NOT included
    };
  }
}

const user = new User('John', 'secret');
JSON.stringify(user);
// '{"name":"John"}' - password excluded!

BigInt Handling

// BigInt throws an error by default
// JSON.stringify({ big: 123n }); // TypeError!

// Solution: custom replacer
JSON.stringify({ big: 123n }, (key, value) =>
  typeof value === 'bigint' ? value.toString() : value
);
// '{"big":"123"}'

Common Pitfalls

⚠️
Dates Become Strings

Date objects are converted to ISO strings. Parse them back with a reviver or new Date().

⚠️
undefined is Omitted

Properties with undefined values are removed from the output.

⚠️
NaN and Infinity Become null

Special number values are converted to null in JSON.

⚠️
Circular References Throw

Objects referencing themselves will cause a TypeError.

Quick Reference

MethodPurposeExample
JSON.parse(str)String → ObjectJSON.parse('{"a":1}')
JSON.stringify(obj)Object → StringJSON.stringify({a:1})
res.json()Parse fetch responseawait res.json()
structuredClone()Deep clone (modern)structuredClone(obj)