Complete guide to working with JSON in JavaScript with code examples
Developer Guide

Working with JSON in JavaScript: Complete Guide for Developers

41 min read
2468 words
Share:

Working with JSON in JavaScript: Complete Guide for Developers

You’ve called an API. You got back data. You tried to access a property. And suddenly:

TypeError: Cannot read property 'name' of undefined

Or worse:

SyntaxError: Unexpected token o in JSON at position 1

Your JSON looks fine in the browser. The API documentation says it returns JSON. But when you try to use it in JavaScript, everything breaks.

Welcome to the JSON in JavaScript maze.

The problem isn’t that JSON is complex—it’s that the relationship between JSON and JavaScript is confusing. They look identical but behave differently. JSON is a string format. JavaScript objects are data structures. Converting between them seems simple until you hit circular references, lose date precision, or encounter undefined values.

Most tutorials show you JSON.parse() and JSON.stringify() and call it a day. But real applications need error handling, type validation, performance optimization, and handling edge cases like large datasets, circular structures, and API inconsistencies.

This guide is different. You’ll learn not just how to parse and stringify, but how to handle errors gracefully, work with APIs effectively, optimize performance, handle complex data structures, and debug common JSON issues.

By the end, you’ll understand exactly when to use JSON vs JavaScript objects, how to handle every data type correctly, how to validate JSON before parsing, and how to build robust data pipelines that handle anything APIs throw at you.

Quick Answer: JSON in JavaScript Essentials

Don’t have time for 6,000 words? Here’s what you need to know:

  • What JSON is: A text format for data exchange, not the same as JavaScript objects
  • Two key methods: JSON.parse() (string → object) and JSON.stringify() (object → string)
  • Most common use: Receiving data from APIs and sending data to servers
  • Key difference: JSON uses double quotes, no functions, no undefined, strict syntax
  • Common error: Trying to parse already-parsed objects or HTML responses
  • Quick tools: Use our JSON Formatter to validate and format JSON instantly
  • Best practice: Always use try/catch when parsing JSON from external sources

Still here? Let’s master JSON in JavaScript.

What is JSON in JavaScript?

JSON (JavaScript Object Notation) is a text-based data format that was inspired by JavaScript object syntax but is not the same as JavaScript objects.

JSON vs JavaScript Objects: Key Differences

This is the most important concept to understand. They look similar but are fundamentally different.

JSON (a string):

const jsonString = '{"name":"John","age":30,"active":true}';
typeof jsonString;  // "string"

JavaScript Object:

const jsObject = {name: "John", age: 30, active: true};
typeof jsObject;  // "object"

Visual Comparison:

Feature JavaScript Object JSON
Format Data structure in memory Text string
Keys Can be unquoted Must be double-quoted
Strings Single or double quotes Only double quotes
Values Any JavaScript value String, number, boolean, array, object, null
Functions Allowed Not allowed
Undefined Allowed Not allowed
Comments Allowed Not allowed
Trailing commas Allowed (ES5+) Not allowed
Methods Can have methods No methods

Example of all differences:

// ✅ Valid JavaScript Object
const jsObj = {
  name: 'John',              // Single quotes OK
  age: 30,                   // No quotes on numbers
  hobbies: ['reading',],     // Trailing comma OK
  greet: function() {        // Functions allowed
    return 'Hello';
  },
  middle: undefined,         // undefined allowed
  // This is a comment
};

// ❌ This is NOT valid JSON
const notJson = '{"name": "John", "greet": function() {}}';

// ✅ Valid JSON (as a string)
const validJson = '{"name":"John","age":30,"hobbies":["reading"]}';

Key insight: JSON is always a string until you parse it. Even though it looks like an object, it’s text.

Why JSON is Essential for Web Development

1. Universal data exchange format

// Server sends this:
'{"userId": 123, "username": "john"}'

// JavaScript receives and parses:
const user = JSON.parse(response);
console.log(user.username);  // "john"

2. Language-independent

  • Python can read JSON
  • Java can read JSON
  • PHP can read JSON
  • Go can read JSON

Everyone speaks JSON.

3. Human-readable

{
  "product": "Laptop",
  "price": 999.99,
  "inStock": true
}

You can read and understand it without a decoder.

4. Lightweight

  • Smaller than XML
  • Faster to parse than XML
  • Native browser support

JSON’s Role in APIs and Data Exchange

Every modern API uses JSON:

// Making an API request
fetch('https://api.example.com/users/123')
  .then(response => response.json())  // Parses JSON automatically
  .then(data => {
    console.log(data.name);  // Access as JavaScript object
  });

Common JSON scenarios:

  • REST APIs - Sending and receiving data
  • Configuration files - package.json, config.json
  • LocalStorage - Storing complex data in the browser
  • WebSockets - Real-time data exchange
  • NoSQL databases - MongoDB stores JSON-like documents

Browser and Node.js Support

All modern environments support JSON natively:

Browser:

  • Supported since IE8+ (with limitations)
  • Modern browsers: Full support
  • Available globally as JSON object

Node.js:

  • Built-in support (all versions)
  • Same JSON API as browsers
  • Additional file system methods

No libraries needed! JSON.parse() and JSON.stringify() are built into JavaScript.

Tools: Validate and format JSON with our JSON Formatter and compare JSON structures with our Diff Tool.

JSON.parse(): Converting Strings to Objects

JSON.parse() transforms a JSON string into a JavaScript object you can work with.

Basic Syntax and Usage

Syntax:

JSON.parse(text, reviver)

Basic example:

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

console.log(obj.name);  // "John"
console.log(obj.age);   // 30
console.log(typeof obj);  // "object"

Parsing different types:

// Object
JSON.parse('{"key":"value"}');  // {key: "value"}

// Array
JSON.parse('[1,2,3]');  // [1, 2, 3]

// String
JSON.parse('"hello"');  // "hello"

// Number
JSON.parse('123');  // 123

// Boolean
JSON.parse('true');  // true

// Null
JSON.parse('null');  // null

Parsing API Responses

Modern fetch API:

// The .json() method automatically parses JSON
fetch('https://api.example.com/users')
  .then(response => response.json())  // Parses JSON automatically
  .then(users => {
    console.log(users[0].name);
  });

Manual parsing:

fetch('https://api.example.com/users')
  .then(response => response.text())  // Get as text first
  .then(text => {
    const users = JSON.parse(text);  // Manual parsing
    console.log(users);
  });

Async/await version:

async function getUsers() {
  const response = await fetch('https://api.example.com/users');
  const users = await response.json();
  return users;
}

XMLHttpRequest (legacy):

const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/users');
xhr.onload = function() {
  if (xhr.status === 200) {
    const users = JSON.parse(xhr.responseText);
    console.log(users);
  }
};
xhr.send();

Error Handling (Try/Catch for Invalid JSON)

Always wrap JSON.parse() in try/catch when working with external data:

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

const data = safeJsonParse(apiResponse);
if (data) {
  console.log('Success:', data);
} else {
  console.log('Invalid JSON received');
}

Better error handling with context:

function parseWithContext(jsonString, source = 'unknown') {
  try {
    return {
      success: true,
      data: JSON.parse(jsonString)
    };
  } catch (error) {
    console.error(`JSON parse error from ${source}:`, error.message);

    // Extract position from error message
    const match = error.message.match(/position (\d+)/);
    const position = match ? parseInt(match[1]) : 0;

    return {
      success: false,
      error: error.message,
      position: position,
      preview: jsonString.substring(Math.max(0, position - 20), position + 20)
    };
  }
}

const result = parseWithContext(apiResponse, 'User API');
if (result.success) {
  console.log(result.data);
} else {
  console.error('Parse failed at position', result.position);
  console.error('Context:', result.preview);
}

Validating before parsing:

function isValidJson(str) {
  try {
    JSON.parse(str);
    return true;
  } catch (e) {
    return false;
  }
}

if (isValidJson(apiResponse)) {
  const data = JSON.parse(apiResponse);
} else {
  console.error('Invalid JSON received');
}

The Reviver Function (Second Parameter)

The reviver function lets you transform values during parsing.

Syntax:

JSON.parse(text, (key, value) => {
  // Transform value here
  return value;
});

Example: Convert date strings to Date objects

const jsonString = '{"name":"John","createdAt":"2025-01-15T10:30:00Z"}';

const data = JSON.parse(jsonString, (key, value) => {
  // Check if value looks like a date
  if (typeof value === 'string' && /^\d{4}-\d{2}-\d{2}T/.test(value)) {
    return new Date(value);
  }
  return value;
});

console.log(data.createdAt instanceof Date);  // true
console.log(data.createdAt.getFullYear());  // 2025

Example: Convert string numbers to actual numbers

const jsonString = '{"price":"19.99","quantity":"5"}';

const data = JSON.parse(jsonString, (key, value) => {
  if (key === 'price' || key === 'quantity') {
    return parseFloat(value);
  }
  return value;
});

console.log(typeof data.price);  // "number"
console.log(data.price + data.quantity);  // 24.99 (not "19.995")

Example: Filter sensitive data

const jsonString = '{"name":"John","password":"secret123","email":"john@example.com"}';

const data = JSON.parse(jsonString, (key, value) => {
  if (key === 'password') {
    return '[REDACTED]';
  }
  return value;
});

console.log(data);
// {name: "John", password: "[REDACTED]", email: "john@example.com"}

Real-World Examples

Example 1: Parsing nested API response

const apiResponse = `{
  "success": true,
  "data": {
    "user": {
      "id": 123,
      "name": "John Doe",
      "email": "john@example.com"
    },
    "posts": [
      {"id": 1, "title": "First Post"},
      {"id": 2, "title": "Second Post"}
    ]
  }
}`;

try {
  const response = JSON.parse(apiResponse);

  if (response.success) {
    const user = response.data.user;
    const posts = response.data.posts;

    console.log(`User: ${user.name}`);
    posts.forEach(post => {
      console.log(`- ${post.title}`);
    });
  }
} catch (error) {
  console.error('Failed to parse API response:', error);
}

Example 2: Reading configuration file (Node.js)

const fs = require('fs');

try {
  const configFile = fs.readFileSync('config.json', 'utf8');
  const config = JSON.parse(configFile);

  console.log('Database:', config.database.host);
  console.log('Port:', config.server.port);
} catch (error) {
  if (error.code === 'ENOENT') {
    console.error('Config file not found');
  } else if (error instanceof SyntaxError) {
    console.error('Invalid JSON in config file');
  } else {
    console.error('Error reading config:', error);
  }
}

Example 3: LocalStorage retrieval

function getFromStorage(key) {
  try {
    const item = localStorage.getItem(key);

    if (item === null) {
      return null;
    }

    return JSON.parse(item);
  } catch (error) {
    console.error(`Failed to parse localStorage item "${key}":`, error);
    localStorage.removeItem(key);  // Remove corrupted data
    return null;
  }
}

const userData = getFromStorage('user');
if (userData) {
  console.log('Welcome back,', userData.name);
}

Tools: Test JSON parsing with our JSON Formatter and validate structure with JSON Schema validation.

JSON.stringify(): Converting Objects to Strings

JSON.stringify() converts JavaScript values into JSON strings for storage or transmission.

Basic Syntax and Usage

Syntax:

JSON.stringify(value, replacer, space)

Basic example:

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

const jsonString = JSON.stringify(user);
console.log(jsonString);
// {"name":"John","age":30,"active":true}

console.log(typeof jsonString);  // "string"

Stringifying different types:

// Object
JSON.stringify({name: "John"});  // '{"name":"John"}'

// Array
JSON.stringify([1, 2, 3]);  // '[1,2,3]'

// String
JSON.stringify("hello");  // '"hello"'

// Number
JSON.stringify(123);  // '123'

// Boolean
JSON.stringify(true);  // 'true'

// Null
JSON.stringify(null);  // 'null'

// Undefined
JSON.stringify(undefined);  // undefined (not a string!)

Important behavior with special values:

// undefined is omitted from objects
JSON.stringify({a: 1, b: undefined, c: 3});
// '{"a":1,"c":3}'  (b is missing!)

// undefined becomes null in arrays
JSON.stringify([1, undefined, 3]);
// '[1,null,3]'

// Functions are omitted
JSON.stringify({name: "John", greet: function() {}});
// '{"name":"John"}'

// Symbols are omitted
JSON.stringify({name: "John", id: Symbol('id')});
// '{"name":"John"}'

Formatting Output (Indent Parameter)

Pretty-print JSON with indentation:

const data = {name: "John", age: 30, hobbies: ["reading", "gaming"]};

// No formatting (compact)
JSON.stringify(data);
// '{"name":"John","age":30,"hobbies":["reading","gaming"]}'

// With 2-space indentation
JSON.stringify(data, null, 2);
/*
{
  "name": "John",
  "age": 30,
  "hobbies": [
    "reading",
    "gaming"
  ]
}
*/

// With 4-space indentation
JSON.stringify(data, null, 4);

// With tabs
JSON.stringify(data, null, '\t');

When to use formatting:

  • Development/debugging: Use indentation for readability
  • Production/APIs: Omit indentation to reduce size
  • Log files: Use indentation for easier reading
  • Configuration files: Use indentation for human editing
// Save to file with formatting
const fs = require('fs');
fs.writeFileSync('config.json', JSON.stringify(config, null, 2));

// Send to API without formatting (smaller payload)
fetch('/api/data', {
  method: 'POST',
  body: JSON.stringify(data)  // Compact
});

The Replacer Function (Filtering Properties)

The replacer lets you control what gets stringified.

Filter specific keys:

const user = {
  name: "John",
  email: "john@example.com",
  password: "secret123",
  role: "admin"
};

// Only include specific keys
const safeJson = JSON.stringify(user, ['name', 'email', 'role']);
// '{"name":"John","email":"john@example.com","role":"admin"}'

Use a function for complex filtering:

const user = {
  name: "John",
  email: "john@example.com",
  password: "secret123",
  ssn: "123-45-6789"
};

const safeJson = JSON.stringify(user, (key, value) => {
  // Hide sensitive fields
  if (key === 'password' || key === 'ssn') {
    return '[REDACTED]';
  }
  return value;
});

console.log(safeJson);
// {"name":"John","email":"john@example.com","password":"[REDACTED]","ssn":"[REDACTED]"}

Transform values during stringification:

const data = {
  name: "John",
  createdAt: new Date('2025-01-15'),
  price: 19.99
};

const json = JSON.stringify(data, (key, value) => {
  // Convert Date to ISO string
  if (value instanceof Date) {
    return value.toISOString();
  }

  // Round prices to 2 decimals
  if (key === 'price') {
    return Math.round(value * 100) / 100;
  }

  return value;
});

Remove empty values:

const data = {
  name: "John",
  email: "",
  phone: null,
  address: undefined
};

const cleanJson = JSON.stringify(data, (key, value) => {
  // Remove null, undefined, and empty strings
  if (value === null || value === undefined || value === '') {
    return undefined;  // Returning undefined omits the property
  }
  return value;
});

console.log(cleanJson);
// '{"name":"John"}'

Handling Circular References

The problem:

const obj = {name: "John"};
obj.self = obj;  // Circular reference

JSON.stringify(obj);
// ❌ TypeError: Converting circular structure to JSON

Solution 1: Remove circular references

function stringifyWithoutCircular(obj) {
  const seen = new WeakSet();

  return JSON.stringify(obj, (key, value) => {
    if (typeof value === 'object' && value !== null) {
      if (seen.has(value)) {
        return '[Circular]';
      }
      seen.add(value);
    }
    return value;
  });
}

const obj = {name: "John"};
obj.self = obj;

const json = stringifyWithoutCircular(obj);
console.log(json);
// '{"name":"John","self":"[Circular]"}'

Solution 2: Use flatted library

// npm install flatted
const { stringify, parse } = require('flatted');

const obj = {name: "John"};
obj.self = obj;

const json = stringify(obj);  // Handles circular references
const restored = parse(json);  // Restores structure

Solution 3: Remove circular properties before stringifying

function removeCircular(obj, seen = new WeakSet()) {
  if (typeof obj !== 'object' || obj === null) {
    return obj;
  }

  if (seen.has(obj)) {
    return undefined;
  }

  seen.add(obj);

  const result = Array.isArray(obj) ? [] : {};

  for (const key in obj) {
    result[key] = removeCircular(obj[key], seen);
  }

  return result;
}

const obj = {name: "John"};
obj.self = obj;

const clean = removeCircular(obj);
const json = JSON.stringify(clean);

Real-World Examples

Example 1: Sending data to API

async function createUser(userData) {
  const response = await fetch('https://api.example.com/users', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(userData)
  });

  return response.json();
}

await createUser({
  name: "John Doe",
  email: "john@example.com",
  age: 30
});

Example 2: Saving to localStorage

function saveToStorage(key, value) {
  try {
    const json = JSON.stringify(value);
    localStorage.setItem(key, json);
    return true;
  } catch (error) {
    console.error('Failed to save to localStorage:', error);
    return false;
  }
}

const userData = {
  id: 123,
  name: "John",
  preferences: {
    theme: "dark",
    notifications: true
  }
};

saveToStorage('user', userData);

Example 3: Writing to file (Node.js)

const fs = require('fs');

function saveConfig(config, filename = 'config.json') {
  try {
    const json = JSON.stringify(config, null, 2);
    fs.writeFileSync(filename, json, 'utf8');
    console.log(`Config saved to ${filename}`);
  } catch (error) {
    console.error('Failed to save config:', error);
  }
}

const config = {
  database: {
    host: "localhost",
    port: 5432
  },
  server: {
    port: 3000
  }
};

saveConfig(config);

Example 4: Deep cloning objects

function deepClone(obj) {
  try {
    return JSON.parse(JSON.stringify(obj));
  } catch (error) {
    console.error('Failed to clone object:', error);
    return null;
  }
}

const original = {
  name: "John",
  hobbies: ["reading", "gaming"]
};

const clone = deepClone(original);
clone.hobbies.push("coding");

console.log(original.hobbies);  // ["reading", "gaming"]
console.log(clone.hobbies);     // ["reading", "gaming", "coding"]

⚠️ Warning: Deep cloning with JSON has limitations:

  • Functions are removed
  • undefined values are removed
  • Dates become strings
  • Regular expressions become empty objects
  • Circular references cause errors

Tools: Format JSON output with our JSON Formatter and generate test UUIDs with our UUID Generator.

Working with JSON Data

Once you’ve parsed JSON, here’s how to manipulate it effectively.

Accessing Nested Properties

Dot notation:

const data = {
  user: {
    name: "John",
    address: {
      city: "New York",
      zipCode: "10001"
    }
  }
};

console.log(data.user.name);  // "John"
console.log(data.user.address.city);  // "New York"

Bracket notation:

console.log(data['user']['name']);  // "John"
console.log(data['user']['address']['city']);  // "New York"

// Useful for dynamic keys
const key = 'name';
console.log(data.user[key]);  // "John"

Safe access with optional chaining (ES2020+):

const data = {
  user: {
    name: "John"
  }
};

// Without optional chaining
console.log(data.user.address.city);
// ❌ TypeError: Cannot read property 'city' of undefined

// With optional chaining
console.log(data.user?.address?.city);  // undefined (no error!)

Providing defaults:

const city = data.user?.address?.city || 'Unknown';
console.log(city);  // "Unknown"

// Nullish coalescing (ES2020)
const zipCode = data.user?.address?.zipCode ?? '00000';

Deep property access helper:

function getNestedProperty(obj, path, defaultValue = undefined) {
  const keys = path.split('.');
  let result = obj;

  for (const key of keys) {
    if (result === null || result === undefined) {
      return defaultValue;
    }
    result = result[key];
  }

  return result !== undefined ? result : defaultValue;
}

const city = getNestedProperty(data, 'user.address.city', 'Unknown');
const country = getNestedProperty(data, 'user.address.country', 'USA');

Iterating Over JSON Arrays

forEach loop:

const users = [
  {id: 1, name: "John"},
  {id: 2, name: "Jane"},
  {id: 3, name: "Bob"}
];

users.forEach(user => {
  console.log(`${user.id}: ${user.name}`);
});

map (transform array):

const names = users.map(user => user.name);
console.log(names);  // ["John", "Jane", "Bob"]

const formatted = users.map(user => ({
  ...user,
  displayName: `User: ${user.name}`
}));

filter (find matching items):

const filtered = users.filter(user => user.id > 1);
console.log(filtered);  // [{id: 2, name: "Jane"}, {id: 3, name: "Bob"}]

find (get first match):

const user = users.find(u => u.name === "Jane");
console.log(user);  // {id: 2, name: "Jane"}

reduce (aggregate data):

const products = [
  {name: "Laptop", price: 999},
  {name: "Mouse", price: 29},
  {name: "Keyboard", price: 79}
];

const total = products.reduce((sum, product) => sum + product.price, 0);
console.log(total);  // 1107

Modifying JSON Objects

Add properties:

const user = {name: "John"};

user.age = 30;
user['email'] = 'john@example.com';

console.log(user);
// {name: "John", age: 30, email: "john@example.com"}

Update properties:

user.name = "John Doe";
user.age += 1;

Delete properties:

delete user.email;
console.log(user);
// {name: "John Doe", age: 31}

Merge objects:

const user = {name: "John", age: 30};
const extra = {email: "john@example.com", age: 31};

// Spread operator (ES6+)
const merged = {...user, ...extra};
console.log(merged);
// {name: "John", age: 31, email: "john@example.com"}

// Object.assign()
const merged2 = Object.assign({}, user, extra);

Update nested properties:

const data = {
  user: {
    name: "John",
    settings: {
      theme: "light"
    }
  }
};

// Update nested value
data.user.settings.theme = "dark";

// Add nested property
data.user.settings.notifications = true;

Deep Cloning Objects with JSON

Simple deep clone:

const original = {
  name: "John",
  hobbies: ["reading", "gaming"],
  address: {
    city: "New York"
  }
};

const clone = JSON.parse(JSON.stringify(original));

// Modify clone
clone.hobbies.push("coding");
clone.address.city = "Boston";

// Original unchanged
console.log(original.hobbies);  // ["reading", "gaming"]
console.log(original.address.city);  // "New York"

Limitations of JSON cloning:

const obj = {
  name: "John",
  greet: function() { return "Hello"; },  // Lost!
  birthday: new Date('1990-01-15'),      // Becomes string!
  regex: /test/i,                         // Becomes {}!
  undefined: undefined,                   // Lost!
  symbol: Symbol('id'),                   // Lost!
  infinity: Infinity                      // Becomes null!
};

const clone = JSON.parse(JSON.stringify(obj));

console.log(typeof clone.greet);        // "undefined"
console.log(clone.birthday instanceof Date);  // false (it's a string)
console.log(clone.regex);               // {}

Better deep clone with structuredClone (modern browsers):

const original = {
  name: "John",
  birthday: new Date('1990-01-15'),
  regex: /test/i
};

const clone = structuredClone(original);

console.log(clone.birthday instanceof Date);  // true
console.log(clone.regex instanceof RegExp);   // true

Merging JSON Objects

Shallow merge:

const obj1 = {a: 1, b: 2};
const obj2 = {b: 3, c: 4};

const merged = {...obj1, ...obj2};
console.log(merged);  // {a: 1, b: 3, c: 4}

Deep merge helper:

function deepMerge(target, source) {
  const result = {...target};

  for (const key in source) {
    if (source[key] instanceof Object && key in target) {
      result[key] = deepMerge(target[key], source[key]);
    } else {
      result[key] = source[key];
    }
  }

  return result;
}

const obj1 = {
  user: {
    name: "John",
    settings: {
      theme: "light",
      notifications: true
    }
  }
};

const obj2 = {
  user: {
    email: "john@example.com",
    settings: {
      theme: "dark"
    }
  }
};

const merged = deepMerge(obj1, obj2);
console.log(merged);
/*
{
  user: {
    name: "John",
    email: "john@example.com",
    settings: {
      theme: "dark",
      notifications: true
    }
  }
}
*/

Tools: Compare JSON objects with our Diff Tool and convert between data formats with our Converter.

Common JSON Operations

Essential patterns for working with JSON in real applications.

Reading JSON from Files (Node.js)

Synchronous (blocking):

const fs = require('fs');

try {
  const jsonString = fs.readFileSync('data.json', 'utf8');
  const data = JSON.parse(jsonString);
  console.log(data);
} catch (error) {
  console.error('Error reading file:', error);
}

Asynchronous (non-blocking):

const fs = require('fs').promises;

async function readJsonFile(filename) {
  try {
    const jsonString = await fs.readFile(filename, 'utf8');
    return JSON.parse(jsonString);
  } catch (error) {
    console.error('Error reading file:', error);
    return null;
  }
}

const data = await readJsonFile('data.json');

With error handling:

async function loadConfig(filename) {
  try {
    const content = await fs.readFile(filename, 'utf8');
    const config = JSON.parse(content);
    return {success: true, data: config};
  } catch (error) {
    if (error.code === 'ENOENT') {
      return {success: false, error: 'File not found'};
    } else if (error instanceof SyntaxError) {
      return {success: false, error: 'Invalid JSON'};
    }
    return {success: false, error: error.message};
  }
}

Fetching JSON from APIs (fetch, axios)

Using fetch (native):

async function fetchUsers() {
  try {
    const response = await fetch('https://api.example.com/users');

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    const users = await response.json();
    return users;
  } catch (error) {
    console.error('Failed to fetch users:', error);
    return [];
  }
}

With query parameters:

async function searchUsers(query) {
  const params = new URLSearchParams({
    q: query,
    limit: 10,
    sort: 'name'
  });

  const response = await fetch(`https://api.example.com/users?${params}`);
  return response.json();
}

Using axios (library):

const axios = require('axios');

async function fetchUsers() {
  try {
    const response = await axios.get('https://api.example.com/users');
    return response.data;  // Axios automatically parses JSON
  } catch (error) {
    if (error.response) {
      console.error('Server error:', error.response.status);
    } else if (error.request) {
      console.error('Network error');
    } else {
      console.error('Error:', error.message);
    }
    return [];
  }
}

Handling different response types:

async function fetchData(url) {
  const response = await fetch(url);
  const contentType = response.headers.get('content-type');

  if (contentType && contentType.includes('application/json')) {
    return response.json();
  } else {
    throw new Error('Response is not JSON');
  }
}

Tools: Check API responses with our HTTP Headers Checker and decode JWT tokens with our JWT Decoder.

Sending JSON in POST Requests

Basic POST request:

async function createUser(userData) {
  const response = await fetch('https://api.example.com/users', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(userData)
  });

  return response.json();
}

await createUser({
  name: "John Doe",
  email: "john@example.com"
});

With error handling:

async function createUser(userData) {
  try {
    const response = await fetch('https://api.example.com/users', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(userData)
    });

    if (!response.ok) {
      const error = await response.json();
      throw new Error(error.message || 'Request failed');
    }

    return await response.json();
  } catch (error) {
    console.error('Failed to create user:', error);
    throw error;
  }
}

With authentication:

async function apiRequest(url, data, token) {
  const response = await fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${token}`
    },
    body: JSON.stringify(data)
  });

  return response.json();
}

Storing JSON in localStorage

Save to localStorage:

function saveToStorage(key, value) {
  try {
    const json = JSON.stringify(value);
    localStorage.setItem(key, json);
    return true;
  } catch (error) {
    if (error.name === 'QuotaExceededError') {
      console.error('localStorage quota exceeded');
    } else {
      console.error('Failed to save:', error);
    }
    return false;
  }
}

saveToStorage('user', {id: 123, name: "John"});

Read from localStorage:

function getFromStorage(key, defaultValue = null) {
  try {
    const item = localStorage.getItem(key);

    if (item === null) {
      return defaultValue;
    }

    return JSON.parse(item);
  } catch (error) {
    console.error('Failed to parse localStorage item:', error);
    localStorage.removeItem(key);  // Remove corrupted data
    return defaultValue;
  }
}

const user = getFromStorage('user');

Complete localStorage wrapper:

const storage = {
  set(key, value) {
    try {
      localStorage.setItem(key, JSON.stringify(value));
      return true;
    } catch (error) {
      console.error(`Failed to save "${key}":`, error);
      return false;
    }
  },

  get(key, defaultValue = null) {
    try {
      const item = localStorage.getItem(key);
      return item ? JSON.parse(item) : defaultValue;
    } catch (error) {
      console.error(`Failed to load "${key}":`, error);
      return defaultValue;
    }
  },

  remove(key) {
    localStorage.removeItem(key);
  },

  clear() {
    localStorage.clear();
  }
};

// Usage
storage.set('user', {id: 123, name: "John"});
const user = storage.get('user');

JSON in Form Submissions

Convert form data to JSON:

function formToJson(form) {
  const formData = new FormData(form);
  const json = {};

  for (const [key, value] of formData.entries()) {
    // Handle multiple values (checkboxes, select multiple)
    if (json[key]) {
      if (!Array.isArray(json[key])) {
        json[key] = [json[key]];
      }
      json[key].push(value);
    } else {
      json[key] = value;
    }
  }

  return json;
}

// Usage
const form = document.querySelector('#myForm');
form.addEventListener('submit', async (e) => {
  e.preventDefault();

  const jsonData = formToJson(form);

  const response = await fetch('/api/submit', {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify(jsonData)
  });
});

Populate form from JSON:

function jsonToForm(form, data) {
  for (const [key, value] of Object.entries(data)) {
    const input = form.elements[key];

    if (!input) continue;

    if (input.type === 'checkbox') {
      input.checked = Boolean(value);
    } else if (input.type === 'radio') {
      const radio = form.querySelector(`input[name="${key}"][value="${value}"]`);
      if (radio) radio.checked = true;
    } else {
      input.value = value;
    }
  }
}

// Usage
const userData = {
  name: "John Doe",
  email: "john@example.com",
  subscribe: true
};

jsonToForm(document.querySelector('#userForm'), userData);

Data Type Handling

Understanding how JSON handles different JavaScript types.

Strings, Numbers, Booleans

Strings:

JSON.stringify("hello");     // '"hello"'
JSON.stringify('hello');     // '"hello"'
JSON.stringify(`hello`);     // '"hello"'

// Special characters are escaped
JSON.stringify("Line 1\nLine 2");  // '"Line 1\\nLine 2"'
JSON.stringify('He said "hi"');     // '"He said \\"hi\\""'

Numbers:

JSON.stringify(42);          // '42'
JSON.stringify(3.14);        // '3.14'
JSON.stringify(-17);         // '-17'
JSON.stringify(1e10);        // '10000000000'

// Special number values
JSON.stringify(NaN);         // 'null'
JSON.stringify(Infinity);    // 'null'
JSON.stringify(-Infinity);   // 'null'

Booleans:

JSON.stringify(true);        // 'true'
JSON.stringify(false);       // 'false'

// In objects
JSON.stringify({active: true});  // '{"active":true}'

Arrays and Nested Arrays

Simple arrays:

JSON.stringify([1, 2, 3]);
// '[1,2,3]'

JSON.stringify(["a", "b", "c"]);
// '["a","b","c"]'

Mixed-type arrays:

JSON.stringify([1, "two", true, null]);
// '[1,"two",true,null]'

Nested arrays:

const matrix = [
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9]
];

JSON.stringify(matrix);
// '[[1,2,3],[4,5,6],[7,8,9]]'

// With formatting
JSON.stringify(matrix, null, 2);
/*
[
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9]
]
*/

Arrays with objects:

const users = [
  {id: 1, name: "John"},
  {id: 2, name: "Jane"}
];

JSON.stringify(users, null, 2);
/*
[
  {
    "id": 1,
    "name": "John"
  },
  {
    "id": 2,
    "name": "Jane"
  }
]
*/

Objects and Nested Objects

Simple objects:

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

Nested objects:

const user = {
  name: "John",
  address: {
    street: "123 Main St",
    city: "New York",
    coordinates: {
      lat: 40.7128,
      lng: -74.0060
    }
  }
};

JSON.stringify(user, null, 2);

Property order:

// JSON.stringify() may reorder properties
const obj = {z: 3, a: 1, m: 2};
JSON.stringify(obj);
// Order is not guaranteed! Could be any order.

Null vs Undefined

Null:

JSON.stringify(null);        // 'null'
JSON.stringify({value: null});  // '{"value":null}'
JSON.stringify([null]);      // '[null]'

Undefined:

JSON.stringify(undefined);   // undefined (not a string!)

// In objects: property is omitted
JSON.stringify({a: 1, b: undefined, c: 3});
// '{"a":1,"c":3}'

// In arrays: becomes null
JSON.stringify([1, undefined, 3]);
// '[1,null,3]'

Handling undefined values:

// Convert undefined to null before stringifying
function sanitize(obj) {
  return JSON.parse(JSON.stringify(obj, (key, value) => {
    return value === undefined ? null : value;
  }));
}

const data = {name: "John", age: undefined};
console.log(sanitize(data));  // {name: "John", age: null}

Dates in JSON (ISO 8601 Strings)

The problem:

const data = {
  name: "John",
  birthday: new Date('1990-01-15')
};

JSON.stringify(data);
// '{"name":"John","birthday":"1990-01-15T00:00:00.000Z"}'

// Date becomes a string!
const parsed = JSON.parse(JSON.stringify(data));
console.log(parsed.birthday instanceof Date);  // false
console.log(typeof parsed.birthday);  // "string"

Solution 1: Convert dates when parsing

const jsonString = '{"name":"John","birthday":"1990-01-15T00:00:00.000Z"}';

const data = JSON.parse(jsonString, (key, value) => {
  // Detect ISO 8601 date strings
  if (typeof value === 'string' && /^\d{4}-\d{2}-\d{2}T/.test(value)) {
    return new Date(value);
  }
  return value;
});

console.log(data.birthday instanceof Date);  // true

Solution 2: Custom toJSON() method

Date.prototype.toJSON = function() {
  return {
    $date: this.toISOString()
  };
};

const data = {birthday: new Date('1990-01-15')};
JSON.stringify(data);
// '{"birthday":{"$date":"1990-01-15T00:00:00.000Z"}}'

Solution 3: Store as timestamp

const data = {
  name: "John",
  birthday: new Date('1990-01-15').getTime()  // Store as number
};

JSON.stringify(data);
// '{"name":"John","birthday":632188800000}'

// Convert back
const parsed = JSON.parse(JSON.stringify(data));
const birthday = new Date(parsed.birthday);

Tools: Format dates with our Timestamp Converter and generate ISO 8601 strings for JSON.

BigInt and Precision Issues

BigInt problem:

const data = {
  id: 1234567890123456789n  // BigInt
};

JSON.stringify(data);
// ❌ TypeError: Do not know how to serialize a BigInt

Solution: Convert to string

const data = {
  id: 1234567890123456789n
};

const json = JSON.stringify(data, (key, value) => {
  if (typeof value === 'bigint') {
    return value.toString();
  }
  return value;
});
// '{"id":"1234567890123456789"}'

Number precision issues:

// JavaScript can't accurately represent all numbers
const data = {
  id: 9007199254740993  // Beyond Number.MAX_SAFE_INTEGER
};

JSON.stringify(data);
// '{"id":9007199254740993}'

// But precision is lost!
const parsed = JSON.parse(JSON.stringify(data));
console.log(parsed.id === data.id);  // false!
console.log(parsed.id);  // 9007199254740992 (wrong!)

Solution: Store large numbers as strings

const data = {
  id: "9007199254740993",
  amount: "1234567890.123456789"
};

// Use a library like decimal.js for calculations
const Decimal = require('decimal.js');
const amount = new Decimal(data.amount);

Validation and Error Handling

Robust JSON handling requires comprehensive validation.

Checking if String is Valid JSON

Simple check:

function isValidJson(str) {
  try {
    JSON.parse(str);
    return true;
  } catch (e) {
    return false;
  }
}

console.log(isValidJson('{"name":"John"}'));  // true
console.log(isValidJson('{invalid}'));  // false

With error details:

function validateJson(str) {
  try {
    const data = JSON.parse(str);
    return {valid: true, data: data};
  } catch (error) {
    return {
      valid: false,
      error: error.message,
      position: error.message.match(/position (\d+)/)?.[1]
    };
  }
}

const result = validateJson('{"name":"John"');
if (result.valid) {
  console.log('Valid:', result.data);
} else {
  console.log('Invalid at position', result.position);
}

Try/Catch Patterns

Basic pattern:

try {
  const data = JSON.parse(jsonString);
  console.log(data);
} catch (error) {
  console.error('Parse error:', error.message);
}

With specific error handling:

try {
  const data = JSON.parse(apiResponse);
  processData(data);
} catch (error) {
  if (error instanceof SyntaxError) {
    console.error('Invalid JSON syntax');
  } else {
    console.error('Unexpected error:', error);
  }
}

Async try/catch:

async function fetchAndParse(url) {
  try {
    const response = await fetch(url);

    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`);
    }

    const data = await response.json();
    return data;
  } catch (error) {
    if (error.name === 'SyntaxError') {
      console.error('Server returned invalid JSON');
    } else if (error.message.includes('HTTP')) {
      console.error('Server error:', error.message);
    } else {
      console.error('Network error:', error);
    }
    return null;
  }
}

Schema Validation (AJV)

Install AJV:

npm install ajv ajv-formats

Basic validation:

const Ajv = require('ajv');
const addFormats = require('ajv-formats');

const ajv = new Ajv();
addFormats(ajv);

const schema = {
  type: "object",
  properties: {
    name: {type: "string", minLength: 1},
    email: {type: "string", format: "email"},
    age: {type: "integer", minimum: 0}
  },
  required: ["name", "email"]
};

const validate = ajv.compile(schema);

const data = {
  name: "John",
  email: "john@example.com",
  age: 30
};

if (validate(data)) {
  console.log('Valid!');
} else {
  console.log('Errors:', validate.errors);
}

Validate API responses:

async function fetchWithValidation(url, schema) {
  const response = await fetch(url);
  const data = await response.json();

  const validate = ajv.compile(schema);

  if (!validate(data)) {
    throw new Error(`Invalid response: ${JSON.stringify(validate.errors)}`);
  }

  return data;
}

Tools: Learn more about JSON Schema in our JSON Schema Validation Tutorial.

Type Checking Parsed Data

Runtime type checking:

function validateUser(data) {
  if (typeof data !== 'object' || data === null) {
    throw new Error('User must be an object');
  }

  if (typeof data.name !== 'string') {
    throw new Error('Name must be a string');
  }

  if (typeof data.age !== 'number' || data.age < 0) {
    throw new Error('Age must be a positive number');
  }

  if (typeof data.email !== 'string' || !data.email.includes('@')) {
    throw new Error('Email must be valid');
  }

  return true;
}

try {
  const user = JSON.parse(apiResponse);
  validateUser(user);
  console.log('User is valid');
} catch (error) {
  console.error('Validation failed:', error.message);
}

Type guard functions:

function isUser(obj) {
  return (
    typeof obj === 'object' &&
    obj !== null &&
    typeof obj.name === 'string' &&
    typeof obj.age === 'number' &&
    typeof obj.email === 'string'
  );
}

const data = JSON.parse(apiResponse);
if (isUser(data)) {
  console.log(data.name);  // TypeScript knows this is safe
}

Defensive Programming Practices

Always validate external data:

async function getUserData(userId) {
  const response = await fetch(`/api/users/${userId}`);

  // Check content type
  const contentType = response.headers.get('content-type');
  if (!contentType || !contentType.includes('application/json')) {
    throw new Error('Response is not JSON');
  }

  // Check status
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}`);
  }

  const data = await response.json();

  // Validate structure
  if (!data || typeof data !== 'object') {
    throw new Error('Invalid response structure');
  }

  return data;
}

Provide defaults:

function parseUserData(jsonString) {
  try {
    const data = JSON.parse(jsonString);

    return {
      name: data.name || 'Unknown',
      email: data.email || '',
      age: typeof data.age === 'number' ? data.age : 0,
      active: Boolean(data.active)
    };
  } catch (error) {
    console.error('Parse failed, returning defaults');
    return {
      name: 'Unknown',
      email: '',
      age: 0,
      active: false
    };
  }
}

Tools: Validate JSON structure with our JSON Formatter and debug errors with our 15 Common JSON Errors Guide.

Performance Optimization

Optimize JSON operations for large datasets and production use.

Parsing Large JSON Files

Stream large files (Node.js):

const fs = require('fs');
const JSONStream = require('JSONStream');

function streamLargeJson(filename) {
  return new Promise((resolve, reject) => {
    const stream = fs.createReadStream(filename);
    const parser = JSONStream.parse('*');
    const items = [];

    stream.pipe(parser)
      .on('data', (item) => {
        items.push(item);
      })
      .on('end', () => resolve(items))
      .on('error', reject);
  });
}

Process in chunks:

async function processLargeArray(items, chunkSize = 1000) {
  for (let i = 0; i < items.length; i += chunkSize) {
    const chunk = items.slice(i, i + chunkSize);

    // Process chunk
    await processChunk(chunk);

    // Allow event loop to breathe
    await new Promise(resolve => setImmediate(resolve));
  }
}

Lazy parsing:

// Only parse what you need
const text = await fetch('/api/large-data').then(r => r.text());

// Extract just the part you need
const match = text.match(/"users":\s*\[([^\]]+)\]/);
if (match) {
  const users = JSON.parse(`[${match[1]}]`);
}

Streaming JSON Data

Using JSON.parse() with streaming (Node.js):

const { pipeline } = require('stream');
const JSONStream = require('JSONStream');

async function streamProcessJson(inputFile, outputFile) {
  const readStream = fs.createReadStream(inputFile);
  const parseStream = JSONStream.parse('items.*');
  const writeStream = fs.createWriteStream(outputFile);

  const transformStream = new Transform({
    objectMode: true,
    transform(item, encoding, callback) {
      // Process each item
      const processed = {
        ...item,
        processed: true,
        timestamp: Date.now()
      };

      callback(null, JSON.stringify(processed) + '\n');
    }
  });

  await pipeline(
    readStream,
    parseStream,
    transformStream,
    writeStream
  );
}

Memory Management

Avoid memory leaks:

// ❌ BAD - Creates huge string in memory
const data = {
  items: new Array(1000000).fill({...})
};
const json = JSON.stringify(data);  // May crash!

// ✅ GOOD - Process in batches
const batches = [];
for (let i = 0; i < data.items.length; i += 1000) {
  const batch = data.items.slice(i, i + 1000);
  batches.push(JSON.stringify(batch));
}

Release references:

async function processLargeFile(filename) {
  let data = JSON.parse(await fs.readFile(filename, 'utf8'));

  await processData(data);

  data = null;  // Release memory
  if (global.gc) global.gc();  // Force GC (if --expose-gc flag)
}

When to Use JSON vs Alternatives

Use JSON when:

  • ✅ Data needs to be human-readable
  • ✅ Working with web APIs
  • ✅ Data size is reasonable (< 10MB)
  • ✅ Cross-language compatibility needed

Use alternatives when:

  • ❌ Data is > 100MB (use streaming formats)
  • ❌ Need faster parsing (use Protocol Buffers, MessagePack)
  • ❌ Binary data (use ArrayBuffer, Blob)
  • ❌ Need compression (use BSON, CBOR)

Alternatives:

Format Use Case Speed Size
JSON General purpose Medium Large
MessagePack Fast binary Fast Small
Protocol Buffers Typed binary Very Fast Very Small
BSON MongoDB Medium Medium
CBOR IoT, embedded Fast Small

Benchmarks and Best Practices

Benchmark JSON operations:

const { performance } = require('perf_hooks');

function benchmark(fn, iterations = 10000) {
  const start = performance.now();

  for (let i = 0; i < iterations; i++) {
    fn();
  }

  const end = performance.now();
  return end - start;
}

const data = {name: "John", age: 30, hobbies: ["reading", "gaming"]};

console.log('stringify:', benchmark(() => JSON.stringify(data)), 'ms');
console.log('parse:', benchmark(() => JSON.parse(JSON.stringify(data))), 'ms');

Best practices:

  1. Compile validators once, reuse many times
  2. Use streaming for large files
  3. Cache parsed results when possible
  4. Validate in development, skip in production (if data is trusted)
  5. Use workers for heavy JSON processing
// Use Web Workers for heavy JSON parsing
const worker = new Worker('json-worker.js');

worker.postMessage(largeJsonString);

worker.onmessage = (e) => {
  const parsed = e.data;
  console.log('Parsed in worker:', parsed);
};

Common Mistakes and How to Fix Them

Let’s debug the most frequent JSON errors in JavaScript.

SyntaxError: Unexpected Token

Error:

const json = '{"name": "John", "age": 30,}';  // Trailing comma
JSON.parse(json);
// ❌ SyntaxError: Unexpected token } in JSON

Fix:

// Remove trailing comma
const json = '{"name": "John", "age": 30}';
JSON.parse(json);  // ✅ Works

Tools: Debug syntax errors with our JSON Formatter.

Undefined is Not Valid JSON

Error:

const data = {
  name: "John",
  age: undefined
};

JSON.stringify(data);
// '{"name":"John"}'  - age is silently removed!

Fix:

// Use null instead of undefined
const data = {
  name: "John",
  age: null
};

JSON.stringify(data);
// '{"name":"John","age":null}'

Losing Data Precision

Problem:

const data = {
  id: 9007199254740993  // Larger than Number.MAX_SAFE_INTEGER
};

const json = JSON.stringify(data);
const parsed = JSON.parse(json);

console.log(parsed.id === data.id);  // false! Precision lost

Fix:

// Store large numbers as strings
const data = {
  id: "9007199254740993"
};

// Or use a library like decimal.js or bignumber.js

Circular Structure Errors

Error:

const obj = {name: "John"};
obj.self = obj;

JSON.stringify(obj);
// ❌ TypeError: Converting circular structure to JSON

Fix: See the “Handling Circular References” section above.

Character Encoding Issues

Problem:

// UTF-8 characters may break
const data = {message: "Hello 世界"};
const json = JSON.stringify(data);  // Works in modern browsers

// But reading from files...
const content = fs.readFileSync('data.json', 'latin1');  // ❌ Wrong encoding
JSON.parse(content);  // Garbled characters

Fix:

// Always use UTF-8
const content = fs.readFileSync('data.json', 'utf8');
JSON.parse(content);  // ✅ Correct

Trailing Commas Breaking JSON

See Error #4 in our JSON Debugging Guide.

Advanced Techniques

Power user patterns for complex JSON scenarios.

Custom toJSON() Method

Define how objects serialize:

class User {
  constructor(name, password) {
    this.name = name;
    this.password = password;
  }

  toJSON() {
    // Don't include password in JSON
    return {
      name: this.name,
      type: 'User'
    };
  }
}

const user = new User('John', 'secret123');
JSON.stringify(user);
// '{"name":"John","type":"User"}'

Handle dates custom:

Date.prototype.toJSON = function() {
  return {
    $date: this.getTime()
  };
};

JSON with Functions (Workarounds)

Problem: Functions are lost:

const obj = {
  name: "Calculator",
  add: function(a, b) { return a + b; }
};

JSON.stringify(obj);
// '{"name":"Calculator"}'  - function is gone!

Workaround 1: Store as string, eval later (dangerous!):

const obj = {
  name: "Calculator",
  add: "function(a, b) { return a + b; }"
};

const json = JSON.stringify(obj);
const parsed = JSON.parse(json);

// Restore function (unsafe!)
parsed.add = eval(`(${parsed.add})`);

Workaround 2: Use Function constructor (still risky):

parsed.add = new Function('a', 'b', 'return a + b');

Better: Don’t store functions, store configuration:

const obj = {
  name: "Calculator",
  operation: "add"  // Store operation name
};

// Reconstruct behavior on client
const operations = {
  add: (a, b) => a + b,
  subtract: (a, b) => a - b
};

const parsed = JSON.parse(json);
const fn = operations[parsed.operation];

JSON Patches (RFC 6902)

Apply changes to JSON documents:

const jsonpatch = require('fast-json-patch');

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

const modified = {
  name: "John Doe",
  age: 30,
  email: "john@example.com"
};

// Generate patch
const patch = jsonpatch.compare(original, modified);
console.log(patch);
/*
[
  { op: 'replace', path: '/name', value: 'John Doe' },
  { op: 'add', path: '/email', value: 'john@example.com' }
]
*/

// Apply patch
const result = jsonpatch.applyPatch(original, patch).newDocument;

Working with JSON Lines (.jsonl)

JSON Lines format: One JSON object per line

{"name":"John","age":30}
{"name":"Jane","age":25}
{"name":"Bob","age":35}

Reading JSON Lines:

const fs = require('fs');

function readJsonLines(filename) {
  const content = fs.readFileSync(filename, 'utf8');
  const lines = content.trim().split('\n');

  return lines.map(line => JSON.parse(line));
}

const data = readJsonLines('data.jsonl');

Writing JSON Lines:

function writeJsonLines(filename, items) {
  const lines = items.map(item => JSON.stringify(item)).join('\n');
  fs.writeFileSync(filename, lines, 'utf8');
}

Streaming JSON Lines:

const readline = require('readline');

async function streamJsonLines(filename) {
  const fileStream = fs.createReadStream(filename);

  const rl = readline.createInterface({
    input: fileStream,
    crlfDelay: Infinity
  });

  for await (const line of rl) {
    const item = JSON.parse(line);
    console.log(item);
  }
}

Real-World Examples

Complete, production-ready patterns.

Fetching User Data from API

async function getUserProfile(userId) {
  try {
    const response = await fetch(`https://api.example.com/users/${userId}`);

    if (!response.ok) {
      if (response.status === 404) {
        throw new Error('User not found');
      }
      throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    }

    const contentType = response.headers.get('content-type');
    if (!contentType || !contentType.includes('application/json')) {
      throw new Error('Response is not JSON');
    }

    const user = await response.json();

    // Validate response structure
    if (!user.id || !user.name) {
      throw new Error('Invalid user data structure');
    }

    return {
      success: true,
      data: user
    };
  } catch (error) {
    console.error('Failed to fetch user:', error);

    return {
      success: false,
      error: error.message
    };
  }
}

// Usage
const result = await getUserProfile(123);
if (result.success) {
  console.log('User:', result.data.name);
} else {
  console.error('Error:', result.error);
}

Form Data to JSON

class FormSerializer {
  constructor(form) {
    this.form = form;
  }

  toJSON() {
    const formData = new FormData(this.form);
    const json = {};

    for (const [key, value] of formData.entries()) {
      this.addValue(json, key, value);
    }

    return json;
  }

  addValue(obj, key, value) {
    // Handle array notation: name[]
    if (key.endsWith('[]')) {
      const arrayKey = key.slice(0, -2);
      if (!obj[arrayKey]) {
        obj[arrayKey] = [];
      }
      obj[arrayKey].push(value);
    }
    // Handle nested notation: user[name]
    else if (key.includes('[')) {
      this.addNestedValue(obj, key, value);
    }
    // Simple key
    else {
      if (obj[key]) {
        // Multiple values with same key
        if (!Array.isArray(obj[key])) {
          obj[key] = [obj[key]];
        }
        obj[key].push(value);
      } else {
        obj[key] = value;
      }
    }
  }

  addNestedValue(obj, key, value) {
    const parts = key.split(/\[|\]/).filter(Boolean);
    let current = obj;

    for (let i = 0; i < parts.length - 1; i++) {
      if (!current[parts[i]]) {
        current[parts[i]] = {};
      }
      current = current[parts[i]];
    }

    current[parts[parts.length - 1]] = value;
  }
}

// Usage
const form = document.querySelector('#userForm');
const serializer = new FormSerializer(form);
const json = serializer.toJSON();

fetch('/api/users', {
  method: 'POST',
  headers: {'Content-Type': 'application/json'},
  body: JSON.stringify(json)
});

LocalStorage Cache Implementation

class JSONCache {
  constructor(prefix = 'cache_') {
    this.prefix = prefix;
  }

  set(key, value, ttl = 3600000) {  // Default 1 hour
    const item = {
      value: value,
      expires: Date.now() + ttl
    };

    try {
      localStorage.setItem(this.prefix + key, JSON.stringify(item));
      return true;
    } catch (error) {
      console.error('Cache set failed:', error);
      return false;
    }
  }

  get(key) {
    try {
      const itemStr = localStorage.getItem(this.prefix + key);

      if (!itemStr) {
        return null;
      }

      const item = JSON.parse(itemStr);

      // Check expiration
      if (Date.now() > item.expires) {
        this.delete(key);
        return null;
      }

      return item.value;
    } catch (error) {
      console.error('Cache get failed:', error);
      this.delete(key);
      return null;
    }
  }

  delete(key) {
    localStorage.removeItem(this.prefix + key);
  }

  clear() {
    const keys = Object.keys(localStorage);
    for (const key of keys) {
      if (key.startsWith(this.prefix)) {
        localStorage.removeItem(key);
      }
    }
  }
}

// Usage
const cache = new JSONCache();

cache.set('user_123', {name: "John", email: "john@example.com"}, 600000);

const user = cache.get('user_123');
if (user) {
  console.log('From cache:', user);
} else {
  // Fetch fresh data
}

Building a JSON API Client

class APIClient {
  constructor(baseURL, options = {}) {
    this.baseURL = baseURL;
    this.defaultHeaders = {
      'Content-Type': 'application/json',
      ...options.headers
    };
    this.timeout = options.timeout || 30000;
  }

  async request(endpoint, options = {}) {
    const url = `${this.baseURL}${endpoint}`;
    const config = {
      ...options,
      headers: {
        ...this.defaultHeaders,
        ...options.headers
      }
    };

    // Add timeout
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), this.timeout);
    config.signal = controller.signal;

    try {
      const response = await fetch(url, config);
      clearTimeout(timeoutId);

      // Parse JSON response
      const contentType = response.headers.get('content-type');
      let data;

      if (contentType && contentType.includes('application/json')) {
        data = await response.json();
      } else {
        data = await response.text();
      }

      if (!response.ok) {
        throw new APIError(response.status, data.message || 'Request failed', data);
      }

      return data;
    } catch (error) {
      clearTimeout(timeoutId);

      if (error.name === 'AbortError') {
        throw new Error('Request timeout');
      }

      throw error;
    }
  }

  async get(endpoint, params = {}) {
    const queryString = new URLSearchParams(params).toString();
    const url = queryString ? `${endpoint}?${queryString}` : endpoint;

    return this.request(url, {method: 'GET'});
  }

  async post(endpoint, data) {
    return this.request(endpoint, {
      method: 'POST',
      body: JSON.stringify(data)
    });
  }

  async put(endpoint, data) {
    return this.request(endpoint, {
      method: 'PUT',
      body: JSON.stringify(data)
    });
  }

  async delete(endpoint) {
    return this.request(endpoint, {method: 'DELETE'});
  }
}

class APIError extends Error {
  constructor(status, message, data) {
    super(message);
    this.status = status;
    this.data = data;
  }
}

// Usage
const api = new APIClient('https://api.example.com', {
  headers: {
    'Authorization': 'Bearer token123'
  },
  timeout: 10000
});

try {
  const users = await api.get('/users', {limit: 10, sort: 'name'});
  const newUser = await api.post('/users', {name: "John", email: "john@example.com"});
} catch (error) {
  if (error instanceof APIError) {
    console.error(`API Error ${error.status}:`, error.message);
  } else {
    console.error('Request failed:', error);
  }
}

Frequently Asked Questions

What’s the difference between JSON and JavaScript objects?

JSON is a text format (a string). JavaScript objects are data structures in memory.

// JSON (string)
const json = '{"name":"John"}';  // This is text

// JavaScript object
const obj = {name: "John"};  // This is a data structure

Key differences:

  • JSON uses double quotes only
  • JSON has no functions
  • JSON has no undefined
  • JSON is language-independent

Why does JSON.stringify() return undefined?

JSON.stringify() returns undefined in these cases:

JSON.stringify(undefined);  // undefined (not a string!)
JSON.stringify(function() {});  // undefined
JSON.stringify(Symbol('id'));  // undefined

For objects:

JSON.stringify({a: undefined});  // '{}' (property omitted)

Solution: Check the input before stringifying.

How do I handle dates in JSON?

Problem: Dates become strings:

const data = {date: new Date()};
JSON.stringify(data);  // Date is now a string!

Solution: Use a reviver when parsing:

JSON.parse(jsonString, (key, value) => {
  if (typeof value === 'string' && /^\d{4}-\d{2}-\d{2}T/.test(value)) {
    return new Date(value);
  }
  return value;
});

Or store as timestamp:

const data = {date: Date.now()};  // Store as number

Can I use comments in JSON?

No. JSON does not support comments.

// ❌ Invalid JSON
{
  // This is a comment
  "name": "John"
}

Workarounds:

  • Use a separate documentation file
  • Add a "_comment" property:
{
  "_comment": "This is user configuration",
  "name": "John"
}
  • Use JSON5 (superset that allows comments)

How do I parse JSON with duplicate keys?

const json = '{"name":"John","age":30,"name":"Jane"}';
const obj = JSON.parse(json);
console.log(obj.name);  // "Jane" (last value wins)

JSON.parse() silently uses the last value. To detect duplicates, parse manually or use a strict validator.

Tools: Learn about JSON Schema validation in our complete tutorial.

What’s the maximum JSON size I can parse?

Limits vary by environment:

  • Browsers: Typically 100MB-500MB
  • Node.js: Limited by heap size (default ~1.5GB)
  • Mobile: Much smaller (50MB-100MB)

Best practices:

  • Keep JSON under 10MB for web apps
  • Use streaming for larger files
  • Consider pagination for large datasets

Conclusion: Master JSON in JavaScript

You’ve now learned everything you need to work with JSON in JavaScript effectively. Let’s recap:

Core concepts:

  1. JSON vs JavaScript objects - JSON is a string format, not a data structure
  2. JSON.parse() - Convert JSON strings to JavaScript objects
  3. JSON.stringify() - Convert JavaScript to JSON strings
  4. Error handling - Always use try/catch with external data
  5. Type handling - Understand how undefined, dates, and special values work
  6. Performance - Stream large files, cache parsed results
  7. Validation - Check data before using it

Key takeaways:

  • JSON is always a string until parsed
  • Functions and undefined are not valid in JSON
  • Always validate external JSON data
  • Use proper error handling
  • Understand the limitations (circular references, precision, dates)

Your JSON JavaScript Checklist:

Before parsing:

  • ✅ Verify content-type is application/json
  • ✅ Wrap JSON.parse() in try/catch
  • ✅ Validate response status codes
  • ✅ Check for empty or invalid responses

When stringifying:

  • ✅ Handle circular references
  • ✅ Convert undefined to null
  • ✅ Format dates consistently
  • ✅ Remove sensitive data

In production:

  • ✅ Use schema validation
  • ✅ Implement proper error handling
  • ✅ Cache parsed results when possible
  • ✅ Stream large JSON files
  • ✅ Monitor for parsing errors

Your JSON Toolkit

Essential tools for JSON work:

Validation & Debugging:

Error Fixing:

Development Tools:

API Tools:

View all tools: Developer Tools

Next Steps

  1. Practice with our JSON Formatter
  2. Implement validation in your projects
  3. Add error handling to all JSON operations
  4. Learn JSON Schema with our complete guide
  5. Share this guide with your team

Additional Resources

Official Documentation:

Libraries:

Related Articles:


Found this helpful? Share it with your team and bookmark our developer tools for your next project.

Have questions about JSON? Contact us or check our FAQ for more help.


Last Updated: November 1, 2025
Reading Time: 19 minutes
Author: Orbit2x Team

Share This Guide

Related Articles

Continue learning with these related posts

Found This Guide Helpful?

Try our free developer tools that power your workflow. No signup required, instant results.

Share This Article

Help others discover this guide

Share:

Stay Updated

Get notified about new guides and tools