Tools: The Complete Guide to JSON Formatting for API Development

Tools: The Complete Guide to JSON Formatting for API Development

Source: Dev.to

The Complete Guide to JSON Formatting for API Development ## The Real Cost of Messy JSON ## Quick Wins: JSON Formatting in 30 Seconds ## Browser-Based Formatting ## Command Line Formatting ## IDE Integration ## JSON Validation: Beyond Syntax Checking ## Common JSON Gotchas ## Schema Validation ## Working with Large JSON Responses ## jq: The Swiss Army Knife ## Navigation Techniques ## Performance Considerations ## JSON in Testing ## Generating Test Data ## JSON Diff for API Testing ## JSON Security Considerations ## Never Trust Client JSON ## Prototype Pollution ## Use Client-Side Tools for Sensitive Data ## Putting It All Together If you work with APIs, you work with JSON. That's just the reality of modern web development. And yet, I see developers — senior ones — waste an embarrassing amount of time on JSON formatting issues that should take seconds to fix. This isn't a "what is JSON" tutorial. You know what JSON is. This is the practical guide to working with JSON efficiently: formatting it, validating it, debugging it, and not losing your mind when an API returns a 2MB nested response. Let's quantify this. In a typical API development workflow, you might: If each incident costs you 2-5 minutes of squinting at minified text, that's easily 2-3 hours per week lost to JSON friction. Over a year, that's roughly 130 hours — more than three full work weeks. The fix isn't complicated. It's just having the right tools and habits. For quick one-off formatting, a browser tool is fastest. JSONFormat.co is my go-to because: When to use it: Inspecting API responses, formatting JSON for documentation, quick validation checks. For scripted workflows: When to use it: CI/CD pipelines, automated testing, processing JSON files in bulk. Most modern editors handle JSON well: When to use it: Working with JSON config files, API mocks, test fixtures. Syntax validation catches missing commas and unclosed brackets. But real-world JSON bugs are sneakier. JavaScript tolerates trailing commas in objects. JSON does not. This is the #1 cause of "I swear this JSON is valid" bugs. Python developers hit this constantly. Python's str() uses single quotes by default. Always use json.dumps(), not str(). JavaScript's JSON.stringify() silently converts NaN to null. But if you're hand-constructing JSON strings, this bites. The JSON spec says behavior for duplicate keys is undefined. Most parsers take the last value, but some take the first. This is a silent bug waiting to happen. For production APIs, syntax validation isn't enough. You need JSON Schema: Tools like ajv (JavaScript) or jsonschema (Python) validate API payloads against schemas at runtime. When an API returns megabytes of nested JSON, you need strategies beyond "paste it in a formatter." When exploring unfamiliar API responses: For API testing, you need realistic JSON payloads. Here's a practical approach: When testing API changes, comparing JSON responses catches regressions: Always validate and sanitize JSON from external sources: Be careful with Object.assign() and spread operators on parsed JSON: If you're formatting JSON that contains API keys, tokens, or PII, use a client-side tool like JSONFormat.co instead of a server-based formatter. Your data shouldn't leave your machine just to add some whitespace. Here's my recommended JSON workflow for API development: The goal isn't to use all of these — it's to have the right tool ready when you need it, so you're never wasting time on something that should be automatic. Part of the Developer Tools Deep Dives series. Follow for more practical guides to the tools and techniques that make API development less painful. Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse COMMAND_BLOCK: # Pretty-print with jq curl -s https://api.example.com/data | jq '.' # Compact format (minify) cat data.json | jq -c '.' # Python one-liner echo '{"key":"value"}' | python3 -m json.tool # Node.js one-liner echo '{"key":"value"}' | node -e "process.stdin.on('data',d=>console.log(JSON.stringify(JSON.parse(d),null,2)))" Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: # Pretty-print with jq curl -s https://api.example.com/data | jq '.' # Compact format (minify) cat data.json | jq -c '.' # Python one-liner echo '{"key":"value"}' | python3 -m json.tool # Node.js one-liner echo '{"key":"value"}' | node -e "process.stdin.on('data',d=>console.log(JSON.stringify(JSON.parse(d),null,2)))" COMMAND_BLOCK: # Pretty-print with jq curl -s https://api.example.com/data | jq '.' # Compact format (minify) cat data.json | jq -c '.' # Python one-liner echo '{"key":"value"}' | python3 -m json.tool # Node.js one-liner echo '{"key":"value"}' | node -e "process.stdin.on('data',d=>console.log(JSON.stringify(JSON.parse(d),null,2)))" CODE_BLOCK: { "name": "test", "value": 42, // ← This comma will break strict parsers } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: { "name": "test", "value": 42, // ← This comma will break strict parsers } CODE_BLOCK: { "name": "test", "value": 42, // ← This comma will break strict parsers } CODE_BLOCK: {'name': 'test'} // ← Not valid JSON {"name": "test"} // ← Valid JSON Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: {'name': 'test'} // ← Not valid JSON {"name": "test"} // ← Valid JSON CODE_BLOCK: {'name': 'test'} // ← Not valid JSON {"name": "test"} // ← Valid JSON CODE_BLOCK: {name: "test"} // ← JavaScript object literal, not JSON {"name": "test"} // ← Valid JSON Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: {name: "test"} // ← JavaScript object literal, not JSON {"name": "test"} // ← Valid JSON CODE_BLOCK: {name: "test"} // ← JavaScript object literal, not JSON {"name": "test"} // ← Valid JSON CODE_BLOCK: {"value": NaN} // ← Not valid JSON {"value": Infinity} // ← Not valid JSON {"value": null} // ← Use null instead Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: {"value": NaN} // ← Not valid JSON {"value": Infinity} // ← Not valid JSON {"value": null} // ← Use null instead CODE_BLOCK: {"value": NaN} // ← Not valid JSON {"value": Infinity} // ← Not valid JSON {"value": null} // ← Use null instead CODE_BLOCK: { "id": 1, "name": "first", "id": 2 } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: { "id": 1, "name": "first", "id": 2 } CODE_BLOCK: { "id": 1, "name": "first", "id": 2 } CODE_BLOCK: { "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "required": ["id", "name", "email"], "properties": { "id": { "type": "integer", "minimum": 1 }, "name": { "type": "string", "minLength": 1 }, "email": { "type": "string", "format": "email" } } } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: { "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "required": ["id", "name", "email"], "properties": { "id": { "type": "integer", "minimum": 1 }, "name": { "type": "string", "minLength": 1 }, "email": { "type": "string", "format": "email" } } } CODE_BLOCK: { "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "required": ["id", "name", "email"], "properties": { "id": { "type": "integer", "minimum": 1 }, "name": { "type": "string", "minLength": 1 }, "email": { "type": "string", "format": "email" } } } COMMAND_BLOCK: # Extract specific fields curl -s api.example.com/users | jq '.[].name' # Filter by condition curl -s api.example.com/products | jq '.[] | select(.price > 100)' # Reshape data curl -s api.example.com/orders | jq '[.[] | {id: .order_id, total: .amount}]' # Count items curl -s api.example.com/items | jq 'length' # Get unique values curl -s api.example.com/logs | jq '[.[].level] | unique' Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: # Extract specific fields curl -s api.example.com/users | jq '.[].name' # Filter by condition curl -s api.example.com/products | jq '.[] | select(.price > 100)' # Reshape data curl -s api.example.com/orders | jq '[.[] | {id: .order_id, total: .amount}]' # Count items curl -s api.example.com/items | jq 'length' # Get unique values curl -s api.example.com/logs | jq '[.[].level] | unique' COMMAND_BLOCK: # Extract specific fields curl -s api.example.com/users | jq '.[].name' # Filter by condition curl -s api.example.com/products | jq '.[] | select(.price > 100)' # Reshape data curl -s api.example.com/orders | jq '[.[] | {id: .order_id, total: .amount}]' # Count items curl -s api.example.com/items | jq 'length' # Get unique values curl -s api.example.com/logs | jq '[.[].level] | unique' COMMAND_BLOCK: # Compare two JSON files (ignoring key order) diff <(jq -S '.' old.json) <(jq -S '.' new.json) # Using jd for semantic diff jd old.json new.json Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: # Compare two JSON files (ignoring key order) diff <(jq -S '.' old.json) <(jq -S '.' new.json) # Using jd for semantic diff jd old.json new.json COMMAND_BLOCK: # Compare two JSON files (ignoring key order) diff <(jq -S '.' old.json) <(jq -S '.' new.json) # Using jd for semantic diff jd old.json new.json CODE_BLOCK: // Bad: Direct parse without error handling const data = JSON.parse(userInput); // Good: Safe parse with validation try { const data = JSON.parse(userInput); if (!data || typeof data !== 'object') throw new Error('Invalid'); // Validate against schema... } catch (e) { return res.status(400).json({ error: 'Invalid JSON' }); } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: // Bad: Direct parse without error handling const data = JSON.parse(userInput); // Good: Safe parse with validation try { const data = JSON.parse(userInput); if (!data || typeof data !== 'object') throw new Error('Invalid'); // Validate against schema... } catch (e) { return res.status(400).json({ error: 'Invalid JSON' }); } CODE_BLOCK: // Bad: Direct parse without error handling const data = JSON.parse(userInput); // Good: Safe parse with validation try { const data = JSON.parse(userInput); if (!data || typeof data !== 'object') throw new Error('Invalid'); // Validate against schema... } catch (e) { return res.status(400).json({ error: 'Invalid JSON' }); } CODE_BLOCK: // Dangerous if userInput contains "__proto__" keys const config = { ...defaults, ...JSON.parse(userInput) }; // Safer: use Object.create(null) or a validation library Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: // Dangerous if userInput contains "__proto__" keys const config = { ...defaults, ...JSON.parse(userInput) }; // Safer: use Object.create(null) or a validation library CODE_BLOCK: // Dangerous if userInput contains "__proto__" keys const config = { ...defaults, ...JSON.parse(userInput) }; // Safer: use Object.create(null) or a validation library - Inspect 20-50 API responses per day - Debug 3-5 malformed JSON payloads per week - Manually format JSON for documentation or tests - Paste → formatted. No clicking "Format" buttons. - Syntax errors are highlighted with line numbers - Collapse/expand nested objects to navigate large payloads - 100% client-side — your API responses never leave your browser - VS Code: Shift+Alt+F formats JSON files. Install "Prettier" for opinionated formatting. - JetBrains IDEs: Ctrl+Alt+L auto-formats. JSON schema validation built-in. - Vim/Neovim: :%!jq '.' pipes the buffer through jq. - Start with the shape: jq 'keys' or jq 'type' to understand top-level structure - Check array lengths: jq '.items | length' before trying to display 10,000 records - Sample first: jq '.items[:3]' to see the first 3 items - Use a visual tool: JSONFormat.co lets you collapse/expand nodes, which is faster than scrolling through formatted text - Don't log full JSON responses in production. Log the status code, headers, and a truncated body. - Stream large JSON with libraries like json-stream instead of loading everything into memory. - Compress in transit. Enable gzip/brotli compression on your API responses. A 2MB JSON response typically compresses to ~200KB. - Capture real responses from your API during development - Anonymize sensitive data — replace real emails, names, and IDs - Use generators for specific fields: UUIDs → CreateUUID.com IBANs → RandomIBAN.co Card numbers → Namso.io (test numbers only) - UUIDs → CreateUUID.com - IBANs → RandomIBAN.co - Card numbers → Namso.io (test numbers only) - Store as fixtures in your test suite - UUIDs → CreateUUID.com - IBANs → RandomIBAN.co - Card numbers → Namso.io (test numbers only)