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 βFormat
pretty-print / minify
Validate
Ajv + JSON Schema
Query
jq / jsonpath-plus
Transform
JSON β CSV
Formatting: Pretty-Print and Minify
1// Pretty-print with JSON.stringify2const data = { name: "Alice", scores: [95, 87, 92], active: true };3Β 4// Compact (default API response format)5JSON.stringify(data);6// β {"name":"Alice","scores":[95,87,92],"active":true}7Β 8// Indented (human-readable)9JSON.stringify(data, null, 2);10// β {11// "name": "Alice",12// "scores": [95, 87, 92],13// "active": true14// }15Β 16// Sorted keys (for stable diffs and comparisons)17function sortedStringify(obj: unknown, indent = 2): string {18 return JSON.stringify(obj, (_, value) => {19 if (typeof value === 'object' && !Array.isArray(value) && value !== null) {20 return Object.fromEntries(21 Object.entries(value as Record<string, unknown>).sort()22 );23 }24 return value;25 }, indent);26}27Β 28sortedStringify({ z: 3, a: 1, m: 2 });29// β {"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:
1import Ajv from 'ajv';2Β 3const ajv = new Ajv({ allErrors: true }); // Report all errors, not just the first4Β 5// Define the schema6const userSchema = {7 type: 'object',8 required: ['id', 'email', 'role'],9 properties: {10 id: { type: 'integer', minimum: 1 },11 email: { type: 'string', format: 'email' },12 name: { type: 'string', maxLength: 100 },13 role: { type: 'string', enum: ['admin', 'editor', 'viewer'] },14 createdAt: { type: 'string', format: 'date-time' },15 },16 additionalProperties: false, // Reject unknown fields17};18Β 19const validate = ajv.compile(userSchema);20Β 21// Valid data22const user = { id: 1, email: 'alice@example.com', role: 'admin' };23validate(user); // β true24Β 25// Invalid data26const bad = { id: 'not-a-number', email: 'not-an-email' };27validate(bad); // β false28validate.errors;29// β [30// { instancePath: '/id', message: 'must be integer' },31// { instancePath: '/email', message: 'must match format "email"' },32// { instancePath: '', message: 'must have required property "role"' }33// ]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:
1# Input: a JSON API response2# {"users": [{"id": 1, "name": "Alice", "active": true}, {"id": 2, "name": "Bob", "active": false}]}3Β 4# Extract all names5cat data.json | jq '.users[].name'6# β "Alice"7# "Bob"8Β 9# Filter only active users and extract their IDs10cat data.json | jq '[.users[] | select(.active == true) | .id]'11# β [1]12Β 13# Transform structure: create an idβname map14cat data.json | jq '.users | map({(.id|tostring): .name}) | add'15# β {"1":"Alice","2":"Bob"}16Β 17# Count users per status18cat data.json | jq '.users | group_by(.active) | map({active: .[0].active, count: length})'19Β 20# Prettify a minified JSON file21cat minified.json | jq '.'22Β 23# In Node.js β jq-style querying with jsonpath-plus24import { JSONPath } from 'jsonpath-plus';25const names = JSONPath({ path: '$.users[*].name', json: data });26// β ['Alice', 'Bob']Converting JSON to CSV
1// Flat JSON array β CSV string2function jsonToCSV(rows: Record<string, unknown>[]): string {3 if (rows.length === 0) return '';4Β 5 // Use the keys from the first row as headers6 const headers = Object.keys(rows[0]);7Β 8 const csvRows = rows.map(row =>9 headers.map(header => {10 const value = row[header];11 if (value === null || value === undefined) return '';12Β 13 const str = String(value);14 // Quote if contains comma, newline, or double quote15 if (/[,"16
]/.test(str)) {17 return '"' + str.replace(/"/g, '""') + '"';18 }19 return str;20 }).join(',')21 );22Β 23 // UTF-8 BOM for Excel compatibility24 return 'ο»Ώ' + [headers.join(','), ...csvRows].join('
25');26}27Β 28// Download as CSV file29function downloadCSV(json: Record<string, unknown>[], filename: string): void {30 const csv = jsonToCSV(json);31 const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });32 const url = URL.createObjectURL(blob);33 const link = document.createElement('a');34 link.href = url;35 link.download = filename;36 link.click();37 URL.revokeObjectURL(url);38}JSON Edge Cases That Bite Developers
Large Numbers Lose Precision
1// The dangerous behaviour2const response = '{"id": 9007199254740993}'; // 64-bit integer ID from PostgreSQL3const parsed = JSON.parse(response);4parsed.id; // β 9007199254740992 (WRONG! Lost the last digit)5Β 6// Fix 1: return IDs as strings from your API (simplest)7const safeResponse = '{"id": "9007199254740993"}';8const safe = JSON.parse(safeResponse);9safe.id; // β "9007199254740993" (correct, keep as string)10Β 11// Fix 2: use a BigInt-aware JSON parser12// npm install json-bigint13import JSONbig from 'json-bigint';14const withBigInt = JSONbig.parse(response);15withBigInt.id; // β 9007199254740993n (BigInt β correct)JSON Does Not Have undefined
1// Undefined values are silently dropped by JSON.stringify2const obj = { name: 'Alice', score: undefined, active: true };3JSON.stringify(obj);4// β {"name":"Alice","active":true} β score is gone!5Β 6// Null is preserved β use null for "no value"7const safe = { name: 'Alice', score: null, active: true };8JSON.stringify(safe);9// β {"name":"Alice","score":null,"active":true}10Β 11// undefined in arrays becomes null12JSON.stringify([1, undefined, 3]);13// β "[1,null,3]" β undefined β null in array positionsDates Serialise to Strings (and Back to Strings)
1// Date objects are serialised to ISO strings2JSON.stringify(new Date('2026-07-02'));3// β '"2026-07-02T00:00:00.000Z"'4Β 5// But JSON.parse gives you a STRING back, not a Date6const parsed = JSON.parse('"2026-07-02T00:00:00.000Z"');7typeof parsed; // β "string" (NOT a Date!)8Β 9// You must convert back manually10const date = new Date(parsed); // β Date object11Β 12// Or use a reviver function in JSON.parse13const dateReviver = (_key: string, value: unknown) => {14 if (typeof value === 'string' && /^d{4}-d{2}-d{2}T/.test(value)) {15 return new Date(value);16 }17 return value;18};19const data = JSON.parse(jsonString, dateReviver);20// 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 β