DEV Community

Alex Chen
Alex Chen

Posted on

JSON Tips That Will Make You a Better Developer

JSON Tips That Will Make You a Better Developer

JSON is everywhere. Most developers use 10% of what it can do.

Beyond JSON.parse and JSON.stringify

1. Pretty Print

const data = { name: "Alex", age: 30, skills: ["js", "python", "go"] };

// Compact (default)
JSON.stringify(data); // '{"name":"Alex","age":30,"skills":["js","python","go"]}'

// Pretty print
JSON.stringify(data, null, 2);
// {
//   "name": "Alex",
//   "age": 30,
//   "skills": [
//     "js",
//     "python",
//     "go"
//   ]
// }
Enter fullscreen mode Exit fullscreen mode

2. Filter Keys with Replacer

const user = {
  id: 'abc123',
  name: 'Alex',
  email: 'alex@example.com',
  password: 'supersecret123',
  creditCard: '4111-1111-1111-1111',
};

// Only include safe fields
const safe = JSON.stringify(user, ['id', 'name', 'email'], 2);

// Remove sensitive fields
const clean = JSON.stringify(user, (key, value) => {
  if (['password', 'creditCard', 'ssn', 'token'].includes(key)) return undefined;
  return value;
}, 2);
Enter fullscreen mode Exit fullscreen mode

3. Deep Clone

const original = { a: 1, b: { c: 2 } };

// Deep clone using JSON (works for JSON-serializable data)
const clone = JSON.parse(JSON.stringify(original));

clone.b.c = 99;
console.log(original.b.c); // Still 2 — truly independent!
Enter fullscreen mode Exit fullscreen mode

4. Reviver: Transform While Parsing

const json = '{"name":"Alex","age":"30","active":"true","created":"2026-01-15"}';

const parsed = JSON.parse(json, (key, value) => {
  if (key === 'age') return Number(value);      // String → Number
  if (key === 'active') return Boolean(value);   // String → Boolean
  if (key === 'created') return new Date(value); // String → Date
  return value;
});

// parsed.age is a number, parsed.active is a boolean, parsed.created is a Date object
Enter fullscreen mode Exit fullscreen mode

5. Circular Reference Handling

const obj = { name: 'Alex' };
obj.self = obj; // Circular!

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

// Fix: Use a replacer that tracks seen objects
function safeStringify(obj, indent = 2) {
  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;
  }, indent);
}

console.log(safeStringify(obj));
// {"name":"Alex","self":"[Circular]"}
Enter fullscreen mode Exit fullscreen mode

6. JSON Schema Validation

// Validate JSON structure with Zod (easier than JSON Schema)
import { z } from 'zod';

const UserSchema = z.object({
  name: z.string().min(1),
  age: z.number().int().min(0).max(150),
  email: z.string().email(),
  tags: z.array(z.string()).optional(),
  metadata: z.record(z.unknown()).optional(), // Any JSON object
});

const result = UserSchema.safeParse(JSON.parse(jsonString));
if (!result.success) {
  console.error(result.error); // Field-level errors
}
Enter fullscreen mode Exit fullscreen mode

7. JSON Lines (NDJSON)

// One JSON object per line (used in logs, streaming)
// file.ndjson:
// {"level":"info","msg":"Server started","time":"2026-01-15T00:00:00Z"}
// {"level":"error","msg":"Connection failed","time":"2026-01-15T00:01:00Z"}

import { readFileSync, writeFileSync } from 'fs';

// Read
const lines = readFileSync('file.ndjson', 'utf8').trim().split('\n');
const records = lines.map(line => JSON.parse(line));

// Filter and process
const errors = records.filter(r => r.level === 'error');

// Write
const newRecords = [{ level: 'info', msg: 'Processed', time: new Date().toISOString() }];
writeFileSync('output.ndjson', newRecords.map(r => JSON.stringify(r)).join('\n') + '\n');
Enter fullscreen mode Exit fullscreen mode

8. JSON Merge (Deep)

// Shallow merge (Object.assign)
const merged = { ...defaults, ...userConfig }; // userConfig overrides defaults

// Deep merge utility
function deepMerge(target, ...sources) {
  for (const source of sources) {
    for (const key of Object.keys(source)) {
      if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
        if (!target[key]) target[key] = {};
        deepMerge(target[key], source[key]);
      } else {
        target[key] = source[key];
      }
    }
  }
  return target;
}

const config = deepMerge(
  { api: { timeout: 5000, retries: 3 }, debug: false },
  { api: { timeout: 10000 }, debug: true }
);
// { api: { timeout: 10000, retries: 3 }, debug: true }
Enter fullscreen mode Exit fullscreen mode

9. JSON to URL Query String

const params = { q: 'node.js tutorial', page: 1, limit: 20 };

const queryString = new URLSearchParams(params).toString();
// "q=node.js+tutorial&page=1&limit=20"

fetch(`/api/search?${queryString}`);
Enter fullscreen mode Exit fullscreen mode

10. JSONC — JSON with Comments

// tsconfig.json is actually JSONC (JSON with Comments)
{
  // Compiler options
  "compilerOptions": {
    "target": "ES2022", /* Target ECMAScript version */
    "strict": true,     // Enable all strict checks
  }
  /* This is a block comment */
}
Enter fullscreen mode Exit fullscreen mode
// Parse JSONC (strip comments first)
function parseJSONC(text) {
  // Remove single-line comments
  const stripped = text.replace(/\/\/.*$/gm, '');
  // Remove block comments
  const cleaned = stripped.replace(/\/\*[\s\S]*?\*\//g, '');
  return JSON.parse(cleaned);
}
Enter fullscreen mode Exit fullscreen mode

Performance Tips

// JSON.parse is fast. But for repeated parsing of the same data, cache it:
const cache = new Map();

function getParsedConfig() {
  if (!cache.has('config')) {
    cache.set('config', JSON.parse(readFileSync('config.json', 'utf8')));
  }
  return cache.get('config');
}

// For very large JSON files (>100MB), consider streaming:
import { createReadStream } from 'fs';
import { createInterface } from 'readline';

async function* parseLargeJSON(filePath) {
  const stream = createReadStream(filePath, { encoding: 'utf8' });
  const rl = createInterface({ input: stream });
  let buffer = '';
  let depth = 0;

  for await (const chunk of rl) {
    buffer += chunk;
    for (const char of buffer) {
      if (char === '{' || char === '[') depth++;
      if (char === '}' || char === ']') depth--;
      if (depth === 0 && buffer.trim()) {
        yield JSON.parse(buffer.trim());
        buffer = '';
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

What's your favorite JSON trick? Anything I missed?

Follow @armorbreak for more developer tips.

Top comments (0)