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

Before conventions, let’s be precise about what JSON supports. JSON has exactly six data types:

TypeExample
String"hello world"
Number42, 3.14, -7, 1.5e10
Booleantrue, false
Nullnull
Array[1, 2, 3]
Object{"key": "value"}

JSON does NOT support:

  • Comments (// ... or /* */)
  • Trailing commas ({"a": 1,})
  • Single-quoted strings ('value')
  • undefined
  • NaN or Infinity
  • 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.

{
  "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)

  1. Be consistent — never mix camelCase and snake_case in the same object
  2. Use full wordsfirstName not fn, createdAt not ca
  3. Boolean names should be readable as statementsisActive, hasPermission, canEdit
  4. Date fields should have a suffixcreatedAt, updatedAt, deletedAt, expiresAt
  5. ID fields should be explicituserId not just id in 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": "[email protected]"
}

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-DD for 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": "[email protected]"
  },
  "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 data key for success responses
  • Always have a top-level error key 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 code alongside a human-readable message

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

ConventionRecommendation
Key namingcamelCase (JS) or snake_case (Python) — be consistent
DatesISO 8601 strings: "2026-04-14T10:00:00Z"
MoneyStrings or integer cents — never float
Booleanstrue/false — never "true", 1, or 0
Large IDsStrings — avoid JS precision loss above 2^53
Empty collections[] — never null
EnumsLowercase strings: "active", "pending"
API errorsTop-level "error" key with "code" and "message"

Format and validate your JSON with the free Genbox JSON Formatter.