I spent 30 minutes debugging an API integration because a database ID was being silently corrupted. The API returned a 64-bit integer ID in a JSON response.JSON.parse() rounded it to the nearest representable float. The stored ID was wrong by 1. Every lookup with that ID failed silently. The fix was a two-line API response change. This guide covers the practical JSON operations that trip up developers β not just the happy path.
Format, validate, and diff JSON β locally.
Pretty-print minified JSON, check structure, compare files. All in your browser.
Open JSON Formatter βFormatting: Pretty-Print and Minify
// Pretty-print with JSON.stringify
const data = { name: "Alice", scores: [95, 87, 92], active: true };
// Compact (default API response format)
JSON.stringify(data);
// β {"name":"Alice","scores":[95,87,92],"active":true}
// Indented (human-readable)
JSON.stringify(data, null, 2);
// β {
// "name": "Alice",
// "scores": [95, 87, 92],
// "active": true
// }
// Sorted keys (for stable diffs and comparisons)
function sortedStringify(obj: unknown, indent = 2): string {
return JSON.stringify(obj, (_, value) => {
if (typeof value === 'object' && !Array.isArray(value) && value !== null) {
return Object.fromEntries(
Object.entries(value as Record<string, unknown>).sort()
);
}
return value;
}, indent);
}
sortedStringify({ z: 3, a: 1, m: 2 });
// β {"a":1,"m":2,"z":3} (alphabetical key order)Validating JSON Structure with Ajv
Ajv (Another JSON Schema Validator) compiles JSON Schema into a JavaScript validation function. It is 2β10Γ faster than other validators because it generates native JavaScript code rather than interpreting the schema at runtime:
import Ajv from 'ajv';
const ajv = new Ajv({ allErrors: true }); // Report all errors, not just the first
// Define the schema
const userSchema = {
type: 'object',
required: ['id', 'email', 'role'],
properties: {
id: { type: 'integer', minimum: 1 },
email: { type: 'string', format: 'email' },
name: { type: 'string', maxLength: 100 },
role: { type: 'string', enum: ['admin', 'editor', 'viewer'] },
createdAt: { type: 'string', format: 'date-time' },
},
additionalProperties: false, // Reject unknown fields
};
const validate = ajv.compile(userSchema);
// Valid data
const user = { id: 1, email: '[email protected]', role: 'admin' };
validate(user); // β true
// Invalid data
const bad = { id: 'not-a-number', email: 'not-an-email' };
validate(bad); // β false
validate.errors;
// β [
// { instancePath: '/id', message: 'must be integer' },
// { instancePath: '/email', message: 'must match format "email"' },
// { instancePath: '', message: 'must have required property "role"' }
// ]Querying JSON with jq (Command-Line)
jq is the standard tool for querying and transforming JSON at the command line. It is to JSON what awk is to text:
# Input: a JSON API response
# {"users": [{"id": 1, "name": "Alice", "active": true}, {"id": 2, "name": "Bob", "active": false}]}
# Extract all names
cat data.json | jq '.users[].name'
# β "Alice"
# "Bob"
# Filter only active users and extract their IDs
cat data.json | jq '[.users[] | select(.active == true) | .id]'
# β [1]
# Transform structure: create an idβname map
cat data.json | jq '.users | map({(.id|tostring): .name}) | add'
# β {"1":"Alice","2":"Bob"}
# Count users per status
cat data.json | jq '.users | group_by(.active) | map({active: .[0].active, count: length})'
# Prettify a minified JSON file
cat minified.json | jq '.'
# In Node.js β jq-style querying with jsonpath-plus
import { JSONPath } from 'jsonpath-plus';
const names = JSONPath({ path: '$.users[*].name', json: data });
// β ['Alice', 'Bob']Converting JSON to CSV
// Flat JSON array β CSV string
function jsonToCSV(rows: Record<string, unknown>[]): string {
if (rows.length === 0) return '';
// Use the keys from the first row as headers
const headers = Object.keys(rows[0]);
const csvRows = rows.map(row =>
headers.map(header => {
const value = row[header];
if (value === null || value === undefined) return '';
const str = String(value);
// Quote if contains comma, newline, or double quote
if (/[,"
]/.test(str)) {
return '"' + str.replace(/"/g, '""') + '"';
}
return str;
}).join(',')
);
// UTF-8 BOM for Excel compatibility
return 'ο»Ώ' + [headers.join(','), ...csvRows].join('
');
}
// Download as CSV file
function downloadCSV(json: Record<string, unknown>[], filename: string): void {
const csv = jsonToCSV(json);
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
link.click();
URL.revokeObjectURL(url);
}JSON Edge Cases That Bite Developers
Large Numbers Lose Precision
// The dangerous behaviour
const response = '{"id": 9007199254740993}'; // 64-bit integer ID from PostgreSQL
const parsed = JSON.parse(response);
parsed.id; // β 9007199254740992 (WRONG! Lost the last digit)
// Fix 1: return IDs as strings from your API (simplest)
const safeResponse = '{"id": "9007199254740993"}';
const safe = JSON.parse(safeResponse);
safe.id; // β "9007199254740993" (correct, keep as string)
// Fix 2: use a BigInt-aware JSON parser
// npm install json-bigint
import JSONbig from 'json-bigint';
const withBigInt = JSONbig.parse(response);
withBigInt.id; // β 9007199254740993n (BigInt β correct)JSON Does Not Have undefined
// Undefined values are silently dropped by JSON.stringify
const obj = { name: 'Alice', score: undefined, active: true };
JSON.stringify(obj);
// β {"name":"Alice","active":true} β score is gone!
// Null is preserved β use null for "no value"
const safe = { name: 'Alice', score: null, active: true };
JSON.stringify(safe);
// β {"name":"Alice","score":null,"active":true}
// undefined in arrays becomes null
JSON.stringify([1, undefined, 3]);
// β "[1,null,3]" β undefined β null in array positionsDates Serialise to Strings (and Back to Strings)
// Date objects are serialised to ISO strings
JSON.stringify(new Date('2026-07-02'));
// β '"2026-07-02T00:00:00.000Z"'
// But JSON.parse gives you a STRING back, not a Date
const parsed = JSON.parse('"2026-07-02T00:00:00.000Z"');
typeof parsed; // β "string" (NOT a Date!)
// You must convert back manually
const date = new Date(parsed); // β Date object
// Or use a reviver function in JSON.parse
const dateReviver = (_key: string, value: unknown) => {
if (typeof value === 'string' && /^d{4}-d{2}-d{2}T/.test(value)) {
return new Date(value);
}
return value;
};
const data = JSON.parse(jsonString, dateReviver);
// Now date fields are automatically converted back to Date objectsQuick Reference
| Task | Tool |
|---|---|
| Format/pretty-print | JSON.stringify(data, null, 2) |
| Validate structure | Ajv with JSON Schema |
| Query large JSON | jq (CLI) or jsonpath-plus (JS) |
| Handle 64-bit integers | json-bigint or return as string |
| Parse large JSON files | Oboe.js (streaming) or chunked read |
| Convert to CSV | Custom jsonToCSV or our converter tool |
For converting JSON to other structured formats, see our XML vs JSON comparison for the API design perspective, or our JSON vs YAML guide for configuration file format decisions.
Format and validate JSON locally.
Pretty-print minified JSON, check syntax, and compare files β all in your browser.
Open JSON Formatter β