Home → JSON Best Practices

JSON Best Practices: Design, Naming, and Structure Guide (2026)

📅 Updated April 2026 ⏱ 13 min read 🛠 API design guide

The JSON format itself is simple — it's just text with a handful of rules. The hard part is designing the data structures inside it. A poorly designed JSON API creates friction for every developer who integrates with it, forces frequent breaking changes, and is a constant source of subtle bugs. A well-designed one feels obvious and is a pleasure to work with.

This guide collects the practical conventions and patterns that consistently produce stable, developer-friendly JSON APIs. Each section draws from real production API experience — the kind of decisions that seem minor at the time but matter enormously at scale or when you need to add a feature six months later.

Format and validate your JSON instantly

Free JSON formatter and validator — check your API responses and request bodies in seconds.

Open JSON Formatter →

1. Why JSON Structure Matters

API stability is one of the most important properties a service can have. Every time you change the shape of a JSON response in a backward-incompatible way, you risk breaking the clients that depend on it. The cost of a breaking change compounds quickly: you need to version your API, maintain multiple versions in parallel, communicate the change to all consumers, and wait for them to migrate.

Good JSON design minimizes the need for breaking changes by making the structure extensible from the start. The core principle is this: it is safe to add new fields to a JSON object, and it is almost always a breaking change to remove or rename existing fields. Design with that asymmetry in mind from day one.

Developer experience is the second major reason structure matters. Developers who consume your API will spend hours reading your JSON responses in logs, debuggers, and API explorers. Clear, consistent naming and predictable structure dramatically reduce the cognitive load of integration. An API that takes 10 minutes to understand is used correctly far more often than one that takes a day to decipher.

2. Naming Conventions

The two most common naming conventions for JSON properties are camelCase and snake_case. Both are valid — the critical rule is pick one and use it everywhere.

camelCase (common in JS/Node)
{
  "userId": 42,
  "firstName": "Alice",
  "createdAt": "2026-04-05T14:00:00Z",
  "isActive": true
}
snake_case (common in Python/Ruby)
{
  "user_id": 42,
  "first_name": "Alice",
  "created_at": "2026-04-05T14:00:00Z",
  "is_active": true
}

For boolean fields, use a verb prefix that makes the true/false meaning unambiguous: isActive, hasSubscription, canEdit, requiresApproval. Avoid bare nouns like active or subscription for booleans — a developer scanning the response cannot immediately tell if these are boolean flags or nested objects.

Singular vs plural names

Use plural names for arrays and singular names for objects and scalar values:

{
  "user": { "id": 1, "name": "Alice" },
  "tags": ["admin", "editor"],
  "permissions": ["read", "write"],
  "totalCount": 42
}

This convention lets a reader immediately identify whether a field is a collection or a scalar without looking at the type. When you see tags, you know it's a list. When you see user, you know it's a single object.

Avoid abbreviations

Prefer description over desc, quantity over qty, timestamp over ts. Abbreviations save a few bytes but cost clarity for every developer who reads the data. Modern JSON is gzip-compressed in transit anyway, so abbreviating for size savings is rarely worthwhile.

3. Data Types and Type Safety

JSON has six data types: string, number, boolean, null, array, and object. Use the most specific type that accurately represents your data — not the most convenient one to produce.

Booleans should be actual booleans

Do not use "1"/"0", "true"/"false" strings, or 1/0 integers to represent boolean values. Use true and false literals. Type coercion is fragile, language-dependent, and a frequent source of bugs when different clients handle the same field differently.

Numbers should be actual numbers

Amounts, counts, prices, and identifiers should be JSON numbers, not strings. The exception is large integers that exceed JavaScript's safe integer range (2^53 - 1 = 9007199254740991). For example, Twitter-style 64-bit integer IDs are commonly returned as both a number and a string to protect JavaScript clients:

{
  "id": 1764230847593201664,
  "id_str": "1764230847593201664"
}

For currency amounts, the common recommendation is to store and transmit values in the smallest currency unit as an integer — cents for USD, pence for GBP, and so on. This avoids floating-point precision issues entirely. "amount": 1999 means $19.99, and there is no ambiguity.

Enums as strings

For fields with a fixed set of allowed values, use lowercase strings rather than magic integers: "status": "active" is far more readable than "status": 2. Document the allowed values clearly in your schema or API specification. If you use JSON Schema, list them with the enum keyword.

4. Handling Dates and Times

Date and time handling is one of the most common sources of API bugs. The best format for JSON APIs in 2026 is ISO 8601 with explicit UTC timezone:

{
  "createdAt": "2026-04-05T14:30:00Z",
  "updatedAt": "2026-04-05T15:45:22.123Z",
  "publishedDate": "2026-04-05",
  "scheduledTime": "14:30:00"
}

The Z suffix means UTC (equivalent to +00:00). Always use UTC for datetime fields stored in your API. If you need to preserve the client's local timezone (for example, to display "3pm in New York"), store the timezone information separately as an IANA timezone name ("America/New_York") and reconstruct the localized display time client-side.

Unix timestamps

Unix timestamps (seconds or milliseconds since epoch) are machine-efficient and sortable, but they are opaque to humans reading logs or API responses directly. Reserve them for high-frequency, low-latency use cases like analytics events. For general API responses, ISO 8601 strings are preferred because they are self-documenting — a developer can read "2026-04-05T14:30:00Z" without a timestamp converter.

Consistency tip: Use the same precision everywhere. If your API sometimes returns "2026-04-05T14:30:00Z" and sometimes "2026-04-05T14:30:00.000Z", some clients will fail to parse one of the two formats. Standardize on millisecond precision (.000Z) or no fractional seconds — just pick one.

5. Null vs Missing Fields

The distinction between an explicit null value and a missing field is subtle but critically important, especially for PATCH APIs and partial updates.

// PATCH /users/42
// This sets middleName to null (clears it)
{ "middleName": null }

// This leaves middleName unchanged
{ "firstName": "Alice" }

For GET responses, establish a consistent policy. Options include:

Whatever policy you choose, document it explicitly and apply it uniformly. The worst outcome is inconsistency where some null fields are included and others are omitted with no clear rule.

6. Array vs Single Object Responses

Never return a bare JSON array at the root of a response. Always wrap collections in an envelope object:

Do not return bare arrays: [{"id":1}, {"id":2}] — this makes it impossible to add metadata later without a breaking change.

Return wrapped objects: {"data": [{"id":1}, {"id":2}], "meta": {"total": 2}}

The envelope pattern costs almost nothing — a few extra bytes per response — but gives you the ability to add pagination metadata, total counts, cursor tokens, links, and warnings without ever changing the fundamental structure of the response.

Single object responses can use a consistent top-level key too, though many APIs return the object directly. The tradeoff is the same: a bare object is convenient now but harder to extend.

7. Pagination Patterns

For any collection endpoint, you need a pagination strategy from day one. The three main approaches are:

Offset-based pagination

// Request: GET /articles?offset=20&limit=10
{
  "data": [...],
  "meta": {
    "total": 450,
    "offset": 20,
    "limit": 10
  },
  "links": {
    "self":  "/articles?offset=20&limit=10",
    "next":  "/articles?offset=30&limit=10",
    "prev":  "/articles?offset=10&limit=10",
    "first": "/articles?offset=0&limit=10",
    "last":  "/articles?offset=440&limit=10"
  }
}

Offset-based pagination is easy to implement and supports random access (jump to page 5 directly). Its weakness is that it becomes inconsistent when records are inserted or deleted while the user is paginating — items can be skipped or duplicated.

Cursor-based pagination

// Request: GET /events?cursor=eyJpZCI6MTAwfQ&limit=25
{
  "data": [...],
  "meta": {
    "limit": 25,
    "hasNextPage": true,
    "hasPreviousPage": true
  },
  "cursors": {
    "next": "eyJpZCI6MTI1fQ",
    "prev": "eyJpZCI6MTAxfQ"
  }
}

Cursor-based pagination is stable under mutations — it uses an opaque token derived from the last item's position. It is the right choice for feeds, activity streams, and any list that changes frequently. The downside is you cannot jump to arbitrary pages; you can only go forward or backward from a known position.

Link header pagination

GitHub's API uses the HTTP Link header for pagination URLs rather than putting them in the response body. This keeps the response body focused on data. However, for most JSON APIs, keeping pagination metadata in the response body is more ergonomic for clients that parse JSON but do not inspect headers.

8. Error Response Format

Inconsistent error formats are one of the most frustrating aspects of working with third-party APIs. The IETF published RFC 7807 "Problem Details for HTTP APIs" as a standard error format — using it (or a close derivative) makes your API immediately familiar to developers who know the standard.

// HTTP 422 Unprocessable Entity
{
  "type": "https://jsonwebtools.com/errors/validation-failed",
  "title": "Validation Failed",
  "status": 422,
  "detail": "The request body contains invalid fields.",
  "instance": "/requests/a5f3e291",
  "errors": [
    {
      "field": "email",
      "code": "INVALID_FORMAT",
      "message": "Must be a valid email address."
    },
    {
      "field": "age",
      "code": "OUT_OF_RANGE",
      "message": "Must be between 0 and 150."
    }
  ]
}

Key principles for error responses:

Security note: Never expose internal error details, stack traces, database error messages, or file paths in error responses. Log them server-side and return only a sanitized, user-facing message to the client.

9. Versioning JSON APIs

At some point, you will need to make a breaking change. Having a versioning strategy before you need it prevents the painful scramble of bolting one on after the fact.

URL versioning

GET /v1/users/42
GET /v2/users/42

URL versioning is the most common approach and the most visible. It is easy to reason about in browser URLs, server logs, and API documentation. The downside is it violates the REST principle that a URI should identify a resource, not a version of a resource.

Header versioning

GET /users/42
Accept: application/json; version=2
# or
API-Version: 2026-04-01

Header versioning keeps URLs clean and is semantically more correct for REST purists. Stripe uses date-based header versioning effectively. The downside is it is invisible in browser URLs and harder to test without tooling.

Deprecation strategy

Regardless of your versioning approach, plan for deprecation. Best practices include:

10. Nested vs Flat Structures

Deep nesting makes JSON harder to work with at every level: harder to read, harder to query with tools like JSONPath, harder to partially update with PATCH, and harder to index in databases. The practical guideline is to stay within three levels of nesting for most data structures.

Avoid unnecessary depth: deeply nested objects are hard to consume and patch.

// Overly nested — hard to work with
{
  "order": {
    "customer": {
      "contact": {
        "address": {
          "shipping": {
            "street": "123 Main St"
          }
        }
      }
    }
  }
}

Prefer flat structures with clear naming:

// Flat and clear
{
  "orderId": "ord_123",
  "customerId": "cus_456",
  "shippingStreet": "123 Main St",
  "shippingCity": "Springfield",
  "shippingCountry": "US"
}

If you must model hierarchical data, consider whether a two-level structure (data + a nested object for a coherent sub-entity) can replace deeper nesting. Objects representing a well-defined sub-entity — like an address or a coordinates object — are fine at two levels deep. The problem arises when nesting reflects lazy database structure rather than intentional design.

11. Handling Large Payloads

As APIs mature, responses often balloon with fields that most clients never use. Large payloads increase latency, bandwidth costs, and memory usage on both server and client.

Sparse fieldsets (field projection)

Let clients request only the fields they need:

// Only return id, name, and email
GET /users?fields=id,name,email

This pattern (common in GraphQL and JSON:API) can dramatically reduce response size for clients that only need a few fields from a large object. Implement it for your most frequently called endpoints where response size is a known issue.

Lazy loading related resources

Avoid automatically embedding large related objects in every response. Instead, return an ID or a URL for related resources and let clients request them explicitly if needed:

{
  "orderId": "ord_123",
  "customerId": "cus_456",
  "customerUrl": "/customers/cus_456"
}

Provide an expand or include parameter to let clients opt into embedding related data when they need it, avoiding the N+1 problem while keeping default responses lean.

12. Security Considerations

JSON structure decisions have security implications that are easy to overlook during API design.

Mass assignment vulnerabilities

Never deserialize a client's JSON payload directly into a database model or ORM entity without explicitly whitelisting the fields that are allowed to be set. A client who knows your schema can inject fields like isAdmin: true or creditBalance: 99999 and have them persisted if you blindly trust the payload. Always use an explicit allowlist of writable fields.

Sensitive field exposure

Audit your JSON responses for fields that should never be exposed to clients: password hashes, API keys, internal IDs from third-party systems, private notes, billing details. These fields often end up in responses because the server-side object was serialized lazily. Define explicit response schemas and serialize to them — never serialize raw database rows.

JSON injection and prototype pollution

In JavaScript environments, be aware of prototype pollution — a malicious JSON payload containing __proto__ or constructor keys can modify Object.prototype in older Node.js code. Use JSON Schema validation to reject payloads with unexpected keys, and keep your Node.js and library versions current. In server-side languages, sanitize user-supplied key names before using them as property names or map keys.

13. Performance Tips

The single most impactful performance improvement for JSON in transit is compression. Always enable gzip or Brotli compression on your API server. For typical JSON API responses, gzip reduces payload size by 60–85%. This is especially important for list responses with many repeated field names.

Minification in transit

When compression is enabled, pretty-printing (adding whitespace and newlines) has negligible impact on payload size — gzip eliminates redundant whitespace effectively. In development, pretty-print JSON responses for readability. In production, use minified JSON if compression is not available (some CDN configurations), otherwise the prettiness is free.

Streaming large responses

For very large datasets, consider NDJSON (Newline Delimited JSON) streaming instead of wrapping everything in a single large array. NDJSON sends one JSON object per line, allowing the client to start processing immediately without waiting for the entire response. This is the format used by many log pipelines, database export tools, and event streams.

{"id":1,"name":"Alice","email":"alice@example.com"}
{"id":2,"name":"Bob","email":"bob@example.com"}
{"id":3,"name":"Carol","email":"carol@example.com"}

Response caching

Use HTTP caching headers (Cache-Control, ETag, Last-Modified) aggressively for read-heavy endpoints. A cached response has zero server processing cost and zero serialization cost. For APIs with deterministic responses (like lookup tables or configuration data), even a 60-second cache can eliminate most of your read traffic.

14. JSON:API Format and Standardized Envelopes

If you are building a public API or an API consumed by many different clients, consider adopting the JSON:API specification (jsonapi.org). JSON:API defines a comprehensive envelope format for resources, relationships, errors, and pagination:

{
  "data": [
    {
      "type": "articles",
      "id": "1",
      "attributes": {
        "title": "JSON Best Practices",
        "slug": "json-best-practices"
      },
      "relationships": {
        "author": {
          "data": { "type": "people", "id": "9" }
        }
      }
    }
  ],
  "included": [
    {
      "type": "people",
      "id": "9",
      "attributes": {
        "name": "Alice"
      }
    }
  ],
  "meta": {
    "total": 1
  }
}

JSON:API is verbose compared to a custom envelope, but it provides out-of-the-box solutions for compound documents (avoiding N+1 request problems), relationship handling, sparse fieldsets, and pagination links. Client libraries exist for every major language, which means your API consumers get a head start on integration.

For simpler internal APIs or APIs with well-defined, narrow scope, the overhead of JSON:API is not worthwhile. Use it when your API models complex relationships between many resource types, or when you want the benefits of a rich client library ecosystem.

15. Frequently Asked Questions

Should JSON APIs use camelCase or snake_case? +
Both are valid, but camelCase (e.g., firstName, createdAt) is the most common convention in JavaScript-centric ecosystems, while snake_case (e.g., first_name, created_at) is more common in Python and Ruby APIs. The most important rule is consistency: choose one convention and use it everywhere in your API. Mixing the two in the same API forces clients to handle both styles and is the worst possible outcome.
What is the best format for dates in JSON APIs? +
Use ISO 8601 formatted strings with explicit UTC timezone: "2026-04-05T14:30:00Z". This format is human-readable, lexicographically sortable, and supported by date parsers in every major programming language. Avoid Unix timestamps for fields that represent calendar dates, as they are opaque to humans reading logs and lose timezone context. If you must use timestamps for performance reasons, use milliseconds (not seconds) and document it clearly.
How should JSON APIs handle errors? +
Follow RFC 7807 Problem Details format: return a JSON object with "type" (a URI identifying the error type), "title" (a short human-readable summary), "status" (the HTTP status code), "detail" (a longer explanation), and optionally "instance" (a URI for this specific occurrence). Always use appropriate HTTP status codes — never return 200 OK with an error in the body. For validation errors, return all failed fields at once rather than one at a time.
Should JSON API responses wrap arrays in an envelope object? +
Yes, always wrap array responses in an envelope object. Returning a bare array makes it impossible to add metadata like pagination info, total count, or cursor tokens without a breaking change. Return {"data": [...], "meta": {"total": 100}} instead. The overhead is negligible but the future flexibility is enormous. This is one of the most common mistakes in early-stage API design that becomes very painful to fix later.
What is the difference between null and omitting a field in JSON? +
Explicit null means the field is present but has no value — you are intentionally communicating absence. Omitting the field means it is absent from the payload entirely. For PATCH APIs, the difference is critical: null means "set this field to null", while omitting means "do not change this field". For GET responses, define a consistent policy (always include all fields with null, or omit null fields) and document it clearly. Never mix both behaviors without a clear rule.

16. Related Tools

JSON Formatter
Pretty-print and format JSON
JSON Validator
Validate JSON syntax instantly
JSON Schema Validator
Validate JSON against a schema
JSON Minifier
Minify JSON for production use
JSON Diff
Compare two JSON documents
Mock Generator
Generate mock API response data