JSON (JavaScript Object Notation) is the universal language of data exchange. It’s in every REST API, configuration file, database export, and inter-service message on the internet. Yet most developers write it inconsistently, making codebases harder to maintain and APIs harder to integrate.
This guide covers the conventions and tooling that make JSON clean, predictable, and a pleasure to work with.
Quick formatting: Paste your JSON into the Genbox JSON Formatter for instant formatting and validation.
Table of Contents
- JSON Syntax Fundamentals
- Naming Conventions
- Data Type Best Practices
- Structure and Nesting
- API Response Conventions
- Common Mistakes
- Tooling
JSON Syntax Fundamentals
Before conventions, let’s be precise about what JSON supports. JSON has exactly six data types:
| Type | Example |
|---|---|
| String | "hello world" |
| Number | 42, 3.14, -7, 1.5e10 |
| Boolean | true, false |
| Null | null |
| Array | [1, 2, 3] |
| Object | {"key": "value"} |
JSON does NOT support:
- Comments (
// ...or/* */) - Trailing commas (
{"a": 1,}) - Single-quoted strings (
'value') undefinedNaNorInfinity- Dates (use ISO 8601 strings)
- Functions or methods
These restrictions are features, not bugs — they guarantee that any JSON you write can be parsed by any conformant parser in any language.
Naming Conventions
There is no official JSON naming convention, but three styles dominate in practice. Pick one and stick to it throughout your entire API or codebase.
camelCase (Recommended for JavaScript/TypeScript APIs)
{
"userId": 12345,
"firstName": "Ada",
"lastName": "Lovelace",
"createdAt": "2026-04-14T10:00:00Z",
"isActive": true
}
When to use: Any API primarily consumed by JavaScript/TypeScript clients. Aligns with JS variable naming, making destructuring and dot-access natural.
snake_case (Common in Python APIs)
{
"user_id": 12345,
"first_name": "Ada",
"last_name": "Lovelace",
"created_at": "2026-04-14T10:00:00Z",
"is_active": true
}
When to use: Backends written in Python (Django REST Framework, FastAPI), Ruby on Rails, or when your primary consumers are non-JS clients.
PascalCase (Less common, seen in .NET APIs)
{
"UserId": 12345,
"FirstName": "Ada"
}
When to use: Rarely, unless following .NET/C# conventions where it maps directly to class properties.
Key Rules (Regardless of Case Style)
- Be consistent — never mix
camelCaseandsnake_casein the same object - Use full words —
firstNamenotfn,createdAtnotca - Boolean names should be readable as statements —
isActive,hasPermission,canEdit - Date fields should have a suffix —
createdAt,updatedAt,deletedAt,expiresAt - ID fields should be explicit —
userIdnot justidin nested objects
Data Type Best Practices
Strings
Use strings for text, identifiers, and anything that needs to remain exactly as-is:
{
"id": "usr_01HXYZ",
"name": "Ada Lovelace",
"email": "ada@example.com"
}
Avoid stringly-typed booleans and numbers:
// Bad
{ "isActive": "true", "score": "42" }
// Good
{ "isActive": true, "score": 42 }
Numbers
JSON numbers have no size limit in the spec, but parsers vary. For large integers (IDs over 2^53), use strings to avoid precision loss in JavaScript:
{
"id": "9007199254740993",
"smallId": 12345
}
Never use numbers for money. Use strings with a fixed decimal, or represent as integer cents:
// Bad
{ "price": 29.99 }
// Good (string)
{ "price": "29.99", "currency": "USD" }
// Also good (integer cents)
{ "priceInCents": 2999, "currency": "USD" }
Floating-point arithmetic is notoriously imprecise (0.1 + 0.2 !== 0.3), and financial calculations must be exact.
Dates and Times
JSON has no native date type. Always use ISO 8601 strings:
{
"createdAt": "2026-04-14T10:30:00Z",
"expiresAt": "2026-12-31T23:59:59Z",
"birthDate": "1995-06-15"
}
- Use UTC (suffix
Z) for timestamps - Use
YYYY-MM-DDfor date-only values - Never use Unix timestamps in JSON (they’re ambiguous and unreadable)
Booleans
Use actual booleans, never 1/0 or "true"/"false":
{
"isVerified": true,
"hasSubscription": false,
"canDelete": true
}
Null vs Absent
The distinction between null and a missing key matters:
null— the field exists but has no value (intentional absence)- missing key — the field is not applicable or not returned
// User has a profile, but hasn't set a bio yet
{ "userId": 1, "bio": null }
// User is a guest — bio field doesn't apply
{ "userId": null, "guestToken": "abc123" }
Avoid returning null for arrays — return an empty array [] instead:
// Bad
{ "items": null }
// Good
{ "items": [] }
Structure and Nesting
Keep Nesting Shallow
Each level of nesting adds cognitive load. As a rule of thumb, keep objects to 3 levels deep maximum:
// Too deep
{
"user": {
"profile": {
"address": {
"location": {
"coordinates": { "lat": 59.33, "lng": 18.06 }
}
}
}
}
}
// Better
{
"user": {
"lat": 59.33,
"lng": 18.06
}
}
Arrays of Objects
Prefer arrays of objects over nested objects keyed by ID when the collection is iterable:
// Harder to iterate
{
"users": {
"u1": { "name": "Ada" },
"u2": { "name": "Grace" }
}
}
// Easier to iterate
{
"users": [
{ "id": "u1", "name": "Ada" },
{ "id": "u2", "name": "Grace" }
]
}
Enumerations
Use lowercase strings for enum values (not integers):
{
"status": "active",
"role": "admin",
"priority": "high"
}
String enums are self-documenting. Integer enums require a lookup table and are opaque in logs and debugging.
API Response Conventions
Consistent API response envelopes make clients more predictable.
Successful Response
{
"data": {
"id": "usr_01HXYZ",
"name": "Ada Lovelace",
"email": "ada@example.com"
},
"meta": {
"requestId": "req_abc123",
"timestamp": "2026-04-14T10:30:00Z"
}
}
Paginated List Response
{
"data": [
{ "id": "1", "name": "Item 1" },
{ "id": "2", "name": "Item 2" }
],
"pagination": {
"total": 150,
"page": 1,
"perPage": 20,
"nextCursor": "cursor_abc123"
}
}
Error Response
{
"error": {
"code": "VALIDATION_ERROR",
"message": "The request body is invalid.",
"details": [
{ "field": "email", "message": "Must be a valid email address" },
{ "field": "age", "message": "Must be a positive integer" }
]
}
}
Key principles:
- Always have a top-level
datakey for success responses - Always have a top-level
errorkey for error responses - Never return raw arrays as the top-level response (makes adding metadata later a breaking change)
- Use consistent error codes (SCREAMING_SNAKE_CASE)
- Include a machine-readable
codealongside a human-readablemessage
Common Mistakes
1. Trailing Commas
// Invalid JSON!
{
"name": "Ada",
"age": 31,
}
Many editors and configs allow trailing commas (JSONC), but standard JSON parsers reject them. Remove them before sending over HTTP.
2. Comments
// Invalid JSON!
{
// This is a user object
"name": "Ada"
}
Use JSONC or JSON5 in configuration files that support it (VS Code settings, TypeScript config), but never in API responses.
3. Inconsistent ID Types
// Inconsistent — avoid
{ "userId": 42, "postId": "post_xyz" }
// Consistent
{ "userId": "usr_42", "postId": "post_xyz" }
4. Using Numbers as Object Keys
// Technically valid but confusing
{ "1": "one", "2": "two" }
// All JSON keys are strings anyway — be explicit about what you mean
{ "items": ["one", "two"] }
5. Very Large Payloads
Split large responses with pagination. JSON parsing is synchronous in most environments — a 50MB JSON payload will block the thread.
Tooling
Validation
- Genbox JSON Formatter — browser-based, instant, no data sent to server
jq— command-line JSON processor:echo '{"a":1}' | jq .- VS Code — built-in JSON validation and formatting
Schema Validation
For validating the structure of JSON (not just syntax), use JSON Schema:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["name", "email"],
"properties": {
"name": { "type": "string", "minLength": 1 },
"email": { "type": "string", "format": "email" },
"age": { "type": "integer", "minimum": 0 }
}
}
Pair JSON Schema with ajv (JavaScript) or jsonschema (Python) for runtime validation.
Formatting in Code
JavaScript/TypeScript:
// Format with 2-space indent
JSON.stringify(data, null, 2);
// Compact/minified
JSON.stringify(data);
Python:
import json
# Format
json.dumps(data, indent=2)
# Compact
json.dumps(data, separators=(',', ':'))
Quick Reference
| Convention | Recommendation |
|---|---|
| Key naming | camelCase (JS) or snake_case (Python) — be consistent |
| Dates | ISO 8601 strings: "2026-04-14T10:00:00Z" |
| Money | Strings or integer cents — never float |
| Booleans | true/false — never "true", 1, or 0 |
| Large IDs | Strings — avoid JS precision loss above 2^53 |
| Empty collections | [] — never null |
| Enums | Lowercase strings: "active", "pending" |
| API errors | Top-level "error" key with "code" and "message" |
Format and validate your JSON with the free Genbox JSON Formatter.