Skip to main content
CodeRocket
JavaScriptmediumquality

Parse JSON safely with error handling

rule · json-safety

JSON.parse() is a common source of uncaught exceptions when data comes from external, user-controlled, or cached sources.

Code Example

JavaScript
// ❌ Throws SyntaxError if input is invalid
const data = JSON.parse(input)
 
// ✅ Always wrap in try/catch
function safeParse(json, fallback = null) {
  try {
    return JSON.parse(json)
  } catch {
    return fallback
  }
}
 
const config = safeParse(localStorage.getItem('config'), {})

Why It Matters

JSON.parse() on invalid input throws a SyntaxError that will crash your application if uncaught. API responses, localStorage values, and user-provided data can all be malformed. Safe parsing with validation catches these errors at the point of parsing, not deep in your application logic when a missing property causes an unexpected error.

Validating the Shape

JavaScript
// Just parsing isn't enough — the shape might be wrong
const raw = JSON.parse(apiResponse)
const name = raw.user.profile.name // TypeError if any property is missing!
 
// ✅ Validate before use
function parseUserResponse(json) {
  try {
    const data = JSON.parse(json)
    if (typeof data?.user?.profile?.name !== 'string') {
      throw new Error('Invalid user response shape')
    }
    return data
  } catch (error) {
    console.error('Failed to parse user response:', error)
    return null
  }
}

Using Zod for Schema Validation

JavaScript
import { z } from 'zod'
 
const UserSchema = z.object({
  id: z.number(),
  name: z.string(),
  email: z.string().email(),
  role: z.enum(['admin', 'user', 'moderator'])
})
 
function parseUser(json) {
  try {
    const raw = JSON.parse(json)
    return UserSchema.parse(raw) // Throws ZodError if shape is wrong
  } catch (error) {
    console.error('User parsing failed:', error)
    return null
  }
}
 
// Or use safeParse which returns { success, data, error }
const result = UserSchema.safeParse(raw)
if (result.success) {
  processUser(result.data) // Fully typed!
}

JSON.stringify Edge Cases

JavaScript
// Some values become undefined in JSON.stringify:
JSON.stringify(undefined)           // undefined (not a string!)
JSON.stringify({ a: undefined })    // '{}' — property dropped!
JSON.stringify({ fn: () => {} })    // '{}' — functions dropped!
JSON.stringify(new Date())          // '"2024-01-15T..."' — serialized as string
JSON.stringify(new Map([[1, 2]]))   // '{}' — Maps don't serialize!
 
// Safe serialization
function safeStringify(value, fallback = '{}') {
  try {
    const result = JSON.stringify(value)
    return result ?? fallback
  } catch {
    return fallback
  }
}

Parsing API Responses

JavaScript
async function fetchData(url) {
  const response = await fetch(url)
  if (!response.ok) {
    throw new Error(`HTTP error: ${response.status}`)
  }
 
  // response.json() already does JSON.parse() + try/catch
  // But it throws on non-JSON content types
  const contentType = response.headers.get('content-type')
  if (!contentType?.includes('application/json')) {
    throw new Error('Response is not JSON')
  }
 
  return response.json()
}

Standards

  • Use MDN: JavaScript Guide as the standard for how this JavaScript pattern should behave in production, not just in a small local example.
  • Use web.dev: Learn JavaScript as the standard for how this JavaScript pattern should behave in production, not just in a small local example.

Verification

Automated Checks

  • Verify the behavior in the browser after the code change, not only in static analysis.
  • Inspect DevTools Network or Performance panels when the rule affects loading or execution order.
  • Test the primary user flow and one edge case triggered by the changed script path.

Manual Checks

  • Confirm the code still behaves correctly when the feature is delayed, lazy-loaded, or fails.