All topics
General · Learning hub

JSON notes for developers

Master JSON with a curated set of 6 developer notes — core concepts, patterns, and interview prep. Maintained by the DevRecall team.

Save this stack to your DevRecallMore General notes
JSON

JSON Syntax & Data Types

JSON Syntax & Data Types JSON (JavaScript Object Notation) is a text-based data format. It has exactly 6 value types and strict syntax rules. The 6 Value Types

JSON Syntax & Data Types

JSON (JavaScript Object Notation) is a text-based data format. It has exactly 6 value types and strict syntax rules.

The 6 Value Types

  • string — always double quotes: "hello"

  • number — integer or float, no quotes: 42, 3.14, -7, 1e10

  • boolean — lowercase only: true, false

  • null — lowercase only: null

  • object — key/value pairs: { "key": value }

  • array — ordered list: [1, "two", null]

Valid JSON Examples

// Object
{
  "name": "Alice",
  "age": 30,
  "active": true,
  "score": 9.5,
  "address": null,
  "tags": ["admin", "user"],
  "meta": { "created": "2024-01-01" }
}

// Array at root level — also valid JSON
[1, 2, 3]

// Primitive at root level — also valid
"hello"
42
true

Common Syntax Mistakes

// ❌ Trailing comma
{ "a": 1, }

// ❌ Single quotes
{ 'key': 'value' }

// ❌ Unquoted keys
{ key: "value" }

// ❌ Comments (not valid in JSON)
{ "x": 1 // this is x }

// ❌ undefined is not a JSON value
{ "x": undefined }

// ✅ Use null instead
{ "x": null }

Key Rules

  • Keys must be strings wrapped in double quotes

  • No trailing commas — JSON parsers are strict

  • No comments — use a wrapper format like JSONC if you need them

  • String values must escape: \" \\ \/ \b \f \n \r \t \uXXXX

  • Numbers must not have leading zeros: 01 is invalid

  • Whitespace (spaces, tabs, newlines) is ignored outside strings

JSON

JSON.parse & JSON.stringify

JSON.parse & JSON.stringify The two core browser/Node.js APIs for converting between JSON strings and JavaScript values. JSON.stringify — JS → string JSON.strin

JSON.parse & JSON.stringify

The two core browser/Node.js APIs for converting between JSON strings and JavaScript values.

JSON.stringify — JS → string

JSON.stringify(value, replacer?, space?)

// Basic
JSON.stringify({ a: 1, b: 'two' })
// '{"a":1,"b":"two"}'

// Pretty-print (2-space indent)
JSON.stringify({ a: 1 }, null, 2)
// '{\n  "a": 1\n}'

// Replacer: filter keys
JSON.stringify({ a: 1, b: 2, c: 3 }, ['a', 'c'])
// '{"a":1,"c":3}'

// Replacer: transform values
JSON.stringify({ a: 1, b: 2 }, (key, val) =>
  typeof val === 'number' ? val * 2 : val
)
// '{"a":2,"b":4}'

What stringify drops

const obj = {
  a: 1,
  b: undefined,      // ← dropped
  c: () => {},       // ← dropped
  d: Symbol('x'),    // ← dropped
}
JSON.stringify(obj)  // '{"a":1}'

// In arrays, dropped values become null
JSON.stringify([1, undefined, () => {}, 3])
// '[1,null,null,3]'

JSON.parse — string → JS

JSON.parse(text, reviver?)

// Basic
JSON.parse('{"a":1}')   // { a: 1 }
JSON.parse('[1,2,3]')   // [1, 2, 3]
JSON.parse('"hello"')   // 'hello'
JSON.parse('true')      // true

// Reviver: transform values on the way in
JSON.parse('{"created":"2024-01-01"}', (key, val) =>
  key === 'created' ? new Date(val) : val
)
// { created: Date object }

Safe parsing pattern

function safeParse<T>(text: string): T | null {
  try {
    return JSON.parse(text) as T
  } catch {
    return null
  }
}

// JSON.parse throws SyntaxError on invalid input
JSON.parse('not json')  // SyntaxError: Unexpected token 'o'
JSON.parse(undefined)   // TypeError

toJSON() hook

// If an object has toJSON(), stringify uses its return value
class User {
  constructor(public name: string, private password: string) {}
  toJSON() {
    return { name: this.name }  // password is never serialised
  }
}
JSON.stringify(new User('Alice', 'secret'))
// '{"name":"Alice"}'

Deep clone caveat

// JSON round-trip is a quick deep clone — but lossy
const clone = JSON.parse(JSON.stringify(obj))

// Lost: undefined, functions, Symbol, Date (becomes string),
//       Map, Set, RegExp, class instances, circular refs
// For real deep clone: structuredClone() (modern) or lodash.cloneDeep
JSON

JSON Schema & Validation

JSON Schema & Validation JSON Schema is a vocabulary for describing the structure of JSON documents. Zod and Ajv are the most common validators in JS/TS project

JSON Schema & Validation

JSON Schema is a vocabulary for describing the structure of JSON documents. Zod and Ajv are the most common validators in JS/TS projects.

JSON Schema basics

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "required": ["name", "email"],
  "properties": {
    "name": { "type": "string", "minLength": 1 },
    "email": { "type": "string", "format": "email" },
    "age": { "type": "integer", "minimum": 0, "maximum": 150 },
    "tags": {
      "type": "array",
      "items": { "type": "string" },
      "uniqueItems": true
    }
  },
  "additionalProperties": false
}

Zod (TypeScript-first)

import { z } from 'zod'

const UserSchema = z.object({
  name: z.string().min(1),
  email: z.string().email(),
  age: z.number().int().min(0).max(150).optional(),
  tags: z.array(z.string()).default([]),
})

type User = z.infer<typeof UserSchema>  // ← derive type from schema

// parse — throws ZodError on failure
const user = UserSchema.parse(rawJson)

// safeParse — returns { success, data } | { success: false, error }
const result = UserSchema.safeParse(rawJson)
if (!result.success) {
  console.error(result.error.issues)
}

Validating API responses

async function fetchUser(id: string): Promise<User> {
  const res = await fetch(`/api/users/${id}`)
  if (!res.ok) throw new Error(`HTTP ${res.status}`)

  const raw = await res.json()        // unknown — don't trust it
  return UserSchema.parse(raw)        // throws if API shape changed
}

// This pattern catches API contract breaks at runtime,
// not silently at the point of use.

Common schema keywords

  • type — "string" | "number" | "integer" | "boolean" | "null" | "object" | "array"

  • enum — restrict to a set of values: { "enum": ["red", "green", "blue"] }

  • const — single allowed value: { "const": "active" }

  • anyOf / oneOf / allOf — composing schemas

  • required — array of required keys on an object

  • additionalProperties: false — reject unknown keys

  • $ref — reference another schema definition to avoid repetition

JSON

JSON in REST APIs

JSON in REST APIs JSON is the default wire format for REST APIs. Knowing the conventions saves debugging time. Sending JSON with fetch // POST with JSON body co

JSON in REST APIs

JSON is the default wire format for REST APIs. Knowing the conventions saves debugging time.

Sending JSON with fetch

// POST with JSON body
const res = await fetch('/api/users', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ name: 'Alice', email: 'alice@example.com' }),
})

// Always check status before parsing
if (!res.ok) {
  const err = await res.json()   // API might return { error: '...' }
  throw new Error(err.message ?? res.statusText)
}

const user = await res.json()    // auto-parses response body

Content-Type header

// Request: tell the server you're sending JSON
Content-Type: application/json

// Response: server tells you it's returning JSON
Content-Type: application/json; charset=utf-8

// Missing Content-Type → server may reject with 415 Unsupported Media Type

Common API response shapes

// Single resource
{ "id": "abc", "name": "Alice", "createdAt": "2024-01-01T00:00:00Z" }

// List with pagination
{
  "data": [...],
  "meta": { "total": 150, "page": 1, "perPage": 20 }
}

// Error envelope
{
  "error": "VALIDATION_ERROR",
  "message": "email is required",
  "field": "email"
}

// Envelope pattern (some APIs)
{ "success": true, "data": { ... } }
{ "success": false, "error": "..." }

Date handling

// JSON has no Date type — use ISO 8601 strings
{ "createdAt": "2024-01-15T10:30:00Z" }

// Parse on the client
const date = new Date(obj.createdAt)

// Or use a reviver globally
const withDates = JSON.parse(text, (key, val) =>
  typeof val === 'string' && /^\d{4}-\d{2}-\d{2}T/.test(val)
    ? new Date(val)
    : val
)

Large number precision

// JS numbers are 64-bit floats — integers > 2^53 lose precision
{ "id": 9007199254740993 }   // parsed as 9007199254740992 ❌

// Solutions:
// 1. Receive as string, convert explicitly
{ "id": "9007199254740993" }

// 2. Use BigInt (requires custom reviver — JSON.parse doesn't do it natively)
JSON.parse(text, (key, val) =>
  key === 'id' ? BigInt(val) : val
)
JSON

JSON Syntax, Data Types & APIs

JSON: Syntax, Data Types & APIs JSON (JavaScript Object Notation) is a lightweight text format for data interchange. It's language-independent, human-readable,

JSON: Syntax, Data Types & APIs

JSON (JavaScript Object Notation) is a lightweight text format for data interchange. It's language-independent, human-readable, and the dominant format for REST APIs, config files, and NoSQL databases.

Valid JSON Types

{
  "string": "text value",
  "number": 42,
  "float": 3.14,
  "negative": -7,
  "scientific": 1.5e10,
  "boolean_true": true,
  "boolean_false": false,
  "null_value": null,
  "array": [1, "two", true, null, {"nested": "object"}],
  "object": {
    "nested": "values",
    "deeper": { "a": 1 }
  }
}

Common Gotchas

  • No trailing commas — {"a":1,} is invalid (unlike JavaScript objects)

  • No comments — // and /* */ are not valid JSON (use JSONC for config files with comments)

  • Keys must be double-quoted strings — {a: 1} is invalid

  • Strings must use double quotes — single quotes are invalid

  • Numbers: no leading zeros (010 is invalid), no NaN/Infinity (not valid JSON)

  • No undefined — only null for missing values

  • Integer precision: JavaScript loses precision for integers > 2^53. Use strings for large IDs.

JSON.parse & JSON.stringify

// Basic parse/stringify
const obj = JSON.parse('{"name":"Alice","age":30}');
const json = JSON.stringify({ name: 'Alice', age: 30 });

// Pretty-print with indent
const pretty = JSON.stringify(data, null, 2);

// Replacer function — filter/transform during stringify
const filtered = JSON.stringify(user, (key, value) => {
  if (key === 'password' || key === 'ssn') return undefined; // exclude sensitive
  if (value instanceof Date) return value.toISOString();
  return value;
}, 2);

// Array replacer — include only listed keys
JSON.stringify(user, ['name', 'email'], 2);

// Reviver function — transform during parse
const parsed = JSON.parse(jsonString, (key, value) => {
  if (key === 'createdAt') return new Date(value);  // string → Date
  if (key === 'price') return parseFloat(value);    // string → number
  return value;
});

// Handle errors
try {
  const data = JSON.parse(maybeInvalidJson);
} catch (err) {
  console.error('Invalid JSON:', err.message);
  // SyntaxError: Unexpected token ...
}

Working with APIs (Fetch)

// GET request
const response = await fetch('https://api.example.com/users/1');
if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
const user = await response.json();  // Parses JSON body

// POST with JSON body
const created = await fetch('https://api.example.com/users', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${token}`,
  },
  body: JSON.stringify({ name: 'Alice', email: 'alice@example.com' }),
});
const newUser = await created.json();

// Handle errors properly
async function apiRequest(url, options = {}) {
  const res = await fetch(url, {
    headers: { 'Content-Type': 'application/json' },
    ...options,
    body: options.body ? JSON.stringify(options.body) : undefined,
  });

  if (!res.ok) {
    const error = await res.json().catch(() => ({ message: res.statusText }));
    throw Object.assign(new Error(error.message), { status: res.status });
  }
  if (res.status === 204) return null;  // No content
  return res.json();
}

JSON in Node.js

import { readFileSync, writeFileSync } from 'fs';
import { readFile, writeFile } from 'fs/promises';

// Read JSON file synchronously
const config = JSON.parse(readFileSync('./config.json', 'utf8'));

// Read JSON file asynchronously
const data = JSON.parse(await readFile('./data.json', 'utf8'));

// Write JSON file
await writeFile('./output.json', JSON.stringify(result, null, 2) + '
');

// Deep clone via JSON (loses functions, Dates, undefined, Sets, Maps)
const clone = JSON.parse(JSON.stringify(original));
// Prefer structuredClone() in modern Node.js for proper deep clone

JSONL (Newline-Delimited JSON)

JSONL (JSON Lines) stores one JSON object per line. Used for log files, streaming APIs, and large datasets where you process records one at a time without loading everything into memory.

// Read JSONL file line by line
import { createReadStream } from 'fs';
import { createInterface } from 'readline';

const rl = createInterface({
  input: createReadStream('data.jsonl'),
  crlfDelay: Infinity,
});

for await (const line of rl) {
  if (line.trim()) {
    const record = JSON.parse(line);
    await processRecord(record);
  }
}

// Write JSONL
const stream = createWriteStream('output.jsonl');
for (const item of items) {
  stream.write(JSON.stringify(item) + '
');
}
stream.end();
JSON

JSON Schema, Validation & Tooling

JSON Schema, Validation & Tooling JSON Schema Basics JSON Schema is a vocabulary for annotating and validating JSON. Used in API contracts (OpenAPI), config val

JSON Schema, Validation & Tooling

JSON Schema Basics

JSON Schema is a vocabulary for annotating and validating JSON. Used in API contracts (OpenAPI), config validation, form validation, and IDE autocomplete.

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://example.com/user.schema.json",
  "title": "User",
  "type": "object",
  "required": ["id", "name", "email"],
  "additionalProperties": false,
  "properties": {
    "id": {
      "type": "integer",
      "minimum": 1
    },
    "name": {
      "type": "string",
      "minLength": 2,
      "maxLength": 100
    },
    "email": {
      "type": "string",
      "format": "email"
    },
    "role": {
      "type": "string",
      "enum": ["admin", "user", "guest"]
    },
    "age": {
      "type": "integer",
      "minimum": 0,
      "maximum": 150
    },
    "tags": {
      "type": "array",
      "items": { "type": "string" },
      "uniqueItems": true
    },
    "address": {
      "$ref": "#/$defs/Address"
    }
  },
  "$defs": {
    "Address": {
      "type": "object",
      "required": ["street", "city"],
      "properties": {
        "street": { "type": "string" },
        "city": { "type": "string" },
        "zip": { "type": "string", "pattern": "^[0-9]{5}$" }
      }
    }
  }
}

Validation with Zod (TypeScript)

import { z } from 'zod';

const UserSchema = z.object({
  id: z.number().int().positive(),
  name: z.string().min(2).max(100),
  email: z.string().email(),
  role: z.enum(['admin', 'user', 'guest']).default('user'),
  age: z.number().int().min(0).max(150).optional(),
  tags: z.array(z.string()).default([]),
  createdAt: z.string().datetime().transform(s => new Date(s)),
});

type User = z.infer<typeof UserSchema>;

// Parse (throws on invalid)
const user = UserSchema.parse(rawData);

// Safe parse (returns result object, never throws)
const result = UserSchema.safeParse(rawData);
if (result.success) {
  console.log(result.data);
} else {
  console.error(result.error.format());
  // { name: { _errors: ['String must contain at least 2 character(s)'] } }
}

// Validate API request body in Next.js
export async function POST(req: Request) {
  const body = await req.json();
  const parsed = UserSchema.safeParse(body);
  if (!parsed.success) {
    return Response.json({ errors: parsed.error.format() }, { status: 400 });
  }
  return Response.json(await createUser(parsed.data));
}

jq — Command-Line JSON Processor

# Pretty print
cat data.json | jq .
curl -s api.example.com/users | jq .

# Extract field
jq '.name' data.json
jq '.users[0].email' data.json

# Filter array
jq '.users[] | select(.age > 18)' data.json
jq '.users[] | select(.role == "admin") | .name' data.json

# Transform: build new object
jq '.users[] | {id, name, isAdmin: (.role == "admin")}' data.json

# Get array of values
jq '[.users[].name]' data.json
jq '[.users[] | select(.active)] | length' data.json

# Modify values
jq '.users[] | .name = (.name | ascii_upcase)' data.json

# Combine files
jq -s '.[0] * .[1]' base.json override.json   # merge

# Output as TSV
jq -r '.users[] | [.id, .name, .email] | @tsv' data.json

# Process API responses
curl -s "https://api.github.com/repos/vercel/next.js" | \
  jq '{name, stars: .stargazers_count, forks: .forks_count, language}'

JSON Patch (RFC 6902)

JSON Patch describes changes to a JSON document as an array of operations. Used in REST APIs for partial updates (PATCH method).

[
  { "op": "add",     "path": "/tags/-",        "value": "typescript" },
  { "op": "remove",  "path": "/deprecated"                            },
  { "op": "replace", "path": "/name",           "value": "New Name"  },
  { "op": "move",    "from": "/author/name",    "path": "/authorName"},
  { "op": "copy",    "from": "/template",       "path": "/instance"  },
  { "op": "test",    "path": "/version",        "value": "2.0"       }
]

JSON5 & JSONC

  • JSON5: superset of JSON allowing comments, trailing commas, single quotes, unquoted keys, multiline strings. Used in some config files.

  • JSONC (JSON with Comments): VS Code's format for settings.json, tsconfig.json. Comments allowed, trailing commas sometimes.

  • tsconfig.json, .eslintrc.json use JSONC — VS Code handles them with special parser.

  • For programmatic use, parse JSONC with json5 or strip-json-comments npm packages.

JSON in Databases

  • PostgreSQL: json (stored as text, validated) vs jsonb (binary, indexable, operators). Prefer jsonb.

  • PostgreSQL jsonb operators: -> (get key), ->> (get key as text), #> (path), @> (contains), ? (key exists)

  • MySQL: JSON type with JSON_EXTRACT(), JSON_SET(), JSON_CONTAINS(). Path: $.key.nested

  • MongoDB: native BSON (superset of JSON). Query nested: {"address.city": "Kyiv"}

  • SQLite: JSON1 extension with json_extract(), json_object(), json_array()

Keep your JSON knowledge sharp.

Save this stack to your personal DevRecall — add your own notes, track what you're learning, and share what you know with the community.

Get started — free forever