Skip to content

JSON

Language Overview

JSON (JavaScript Object Notation) is a lightweight data-interchange format that is easy for humans to read and write and easy for machines to parse and generate. This guide covers JSON standards for configuration files, API responses, and data exchange.

Key Characteristics

  • Paradigm: Data serialization, configuration
  • File Extension: .json
  • Primary Use: API responses, configuration files, data storage, package manifests
  • Indentation: 2 spaces (consistent across all JSON files)

Quick Reference

Category Convention Example Notes
Syntax
Indentation 2 spaces "key": "value" Consistent 2-space indentation
Key Names camelCase or snake_case "userName" or "user_name" Be consistent project-wide
Quotes Double quotes only "key": "value" Strings must use double quotes
Trailing Commas Not allowed {"a": 1, "b": 2} No comma after last element
Data Types
String "text" "hello world" Double-quoted text
Number Numeric 42, 3.14, -10 Integer or float
Boolean true / false "active": true Lowercase only
Null null "value": null Explicit null value
Array [...] [1, 2, 3] Ordered collection
Object {...} {"key": "value"} Key-value pairs
Formatting
Arrays (short) Single line [1, 2, 3] If fits on one line
Arrays (long) Multi-line [\n "item1",\n "item2"\n] One item per line
Objects (short) Single line {"id": 1} If fits on one line
Objects (long) Multi-line {\n "key": "value"\n} One property per line
Best Practices
Validation Use JSON Schema Define structure and constraints Validate with schema
Comments Not supported Use description fields JSON doesn't allow comments
File Size Keep reasonable Consider NDJSON for large data Split large files
Files
Extension .json config.json, package.json Always .json
Encoding UTF-8 UTF-8 without BOM Standard encoding

Basic Syntax

Objects

{
  "name": "my-application",
  "version": "1.0.0",
  "description": "A sample application"
}

Arrays

{
  "fruits": ["apple", "banana", "orange"],
  "numbers": [1, 2, 3, 4, 5]
}

Nested Structures

{
  "application": {
    "name": "my-app",
    "version": "1.0.0",
    "config": {
      "database": {
        "host": "localhost",
        "port": 5432
      },
      "cache": {
        "type": "redis",
        "ttl": 3600
      }
    }
  }
}

Data Types

Strings

{
  "name": "John Doe",
  "description": "A string with \"escaped quotes\"",
  "path": "C:\\Windows\\System32",
  "unicode": "Hello \u4e16\u754c",
  "url": "https://example.com/path?query=value"
}

Numbers

{
  "integer": 42,
  "float": 3.14159,
  "negative": -100,
  "exponential": 1.23e-4,
  "zero": 0
}

Booleans

{
  "enabled": true,
  "disabled": false
}

Null

{
  "value": null,
  "optional_field": null
}

Array Types

{
  "empty_array": [],
  "numbers": [1, 2, 3],
  "mixed_types": [1, "two", true, null],
  "nested_arrays": [
    [1, 2],
    [3, 4]
  ],
  "objects": [
    { "id": 1, "name": "Alice" },
    { "id": 2, "name": "Bob" }
  ]
}

Package Configuration

package.json (Node.js)

{
  "name": "my-application",
  "version": "1.0.0",
  "description": "A Node.js application",
  "main": "index.js",
  "scripts": {
    "start": "node index.js",
    "test": "jest",
    "lint": "eslint src/",
    "build": "webpack --mode production"
  },
  "keywords": ["nodejs", "application"],
  "author": "Tyler Dukes <tyler@example.com>",
  "license": "MIT",
  "dependencies": {
    "express": "^4.18.0",
    "dotenv": "^16.0.0"
  },
  "devDependencies": {
    "jest": "^29.0.0",
    "eslint": "^8.0.0"
  },
  "engines": {
    "node": ">=18.0.0",
    "npm": ">=9.0.0"
  }
}

tsconfig.json (TypeScript)

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "commonjs",
    "lib": ["ES2022"],
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist", "**/*.spec.ts"]
}

.eslintrc.json (ESLint)

{
  "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "ecmaVersion": 2022,
    "sourceType": "module"
  },
  "plugins": ["@typescript-eslint"],
  "rules": {
    "no-console": "warn",
    "no-unused-vars": "error",
    "quotes": ["error", "single"],
    "semi": ["error", "always"]
  },
  "env": {
    "node": true,
    "es2022": true
  }
}

API Response Format

Success Response

{
  "status": "success",
  "data": {
    "id": 123,
    "name": "John Doe",
    "email": "john.doe@example.com",
    "created_at": "2025-01-15T10:30:00Z"
  },
  "metadata": {
    "timestamp": "2025-01-15T10:30:00Z",
    "version": "1.0.0"
  }
}

Error Response

{
  "status": "error",
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid email address",
    "details": {
      "field": "email",
      "value": "invalid-email",
      "constraint": "Must be a valid email address"
    }
  },
  "metadata": {
    "timestamp": "2025-01-15T10:30:00Z",
    "request_id": "req_abc123"
  }
}

Paginated Response

{
  "status": "success",
  "data": [
    { "id": 1, "name": "Item 1" },
    { "id": 2, "name": "Item 2" },
    { "id": 3, "name": "Item 3" }
  ],
  "pagination": {
    "total": 100,
    "page": 1,
    "per_page": 10,
    "total_pages": 10,
    "has_next": true,
    "has_prev": false
  }
}

JSON Schema

Defining a Schema

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "User",
  "type": "object",
  "properties": {
    "id": {
      "type": "integer",
      "minimum": 1
    },
    "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 100
    },
    "email": {
      "type": "string",
      "format": "email"
    },
    "age": {
      "type": "integer",
      "minimum": 0,
      "maximum": 150
    },
    "roles": {
      "type": "array",
      "items": {
        "type": "string",
        "enum": ["admin", "user", "guest"]
      },
      "minItems": 1,
      "uniqueItems": true
    }
  },
  "required": ["id", "name", "email"]
}

Comments in JSON

JSON does not support comments. Use these alternatives:

JSON5 (with comments)

{
  // This is a comment
  "name": "my-app",
  /* Multi-line
     comment */
  "version": "1.0.0"
}

JSONC (VSCode configuration)

{
  // VSCode settings
  "editor.tabSize": 2,
  "editor.insertSpaces": true
}

Standard JSON with Comment Keys

{
  "_comment": "This is a workaround for comments",
  "name": "my-app",
  "version": "1.0.0"
}

Formatting

Indentation

Always use 2 spaces:

{
  "level1": {
    "level2": {
      "level3": "value"
    }
  }
}

Array Formatting

{
  "short_array": [1, 2, 3],
  "long_array": [
    "item1",
    "item2",
    "item3",
    "item4"
  ]
}

Object Formatting

{
  "small_object": { "key": "value" },
  "large_object": {
    "key1": "value1",
    "key2": "value2",
    "key3": "value3"
  }
}

Testing

Schema Validation

Use JSON Schema to validate JSON files:

## schema/config.schema.json
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "required": ["name", "version", "environment"],
  "properties": {
    "name": {
      "type": "string",
      "minLength": 1
    },
    "version": {
      "type": "string",
      "pattern": "^\\d+\\.\\d+\\.\\d+$"
    },
    "environment": {
      "type": "string",
      "enum": ["development", "staging", "production"]
    },
    "port": {
      "type": "integer",
      "minimum": 1024,
      "maximum": 65535
    }
  }
}

Validating with ajv

## Install ajv-cli
npm install -g ajv-cli

## Validate JSON against schema
ajv validate -s schema/config.schema.json -d config.json

## Validate multiple files
ajv validate -s schema/config.schema.json -d "configs/*.json"

Automated Validation in CI/CD

## .github/workflows/validate-json.yml
name: Validate JSON

on: [push, pull_request]

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install ajv-cli
        run: npm install -g ajv-cli

      - name: Validate JSON files
        run: |
          for file in **/*.json; do
            echo "Validating $file"
            ajv validate -s schema/config.schema.json -d "$file"
          done

Linting JSON

## Install jsonlint
npm install -g jsonlint

## Lint JSON file
jsonlint config.json

## Lint with quiet mode
jsonlint -q config.json

## Lint multiple files
find . -name "*.json" -exec jsonlint {} \;

Testing with jq

Validate JSON structure and content:

## Check if file is valid JSON
jq empty config.json

## Validate specific fields exist
jq -e '.name' config.json
jq -e '.version' config.json

## Test field values
if [ "$(jq -r '.environment' config.json)" != "production" ]; then
  echo "Invalid environment"
  exit 1
fi

## Validate array length
if [ "$(jq '.servers | length' config.json)" -lt 2 ]; then
  echo "Must have at least 2 servers"
  exit 1
fi

Testing JSON API Responses

## Test API response structure
response=$(curl -s https://api.example.com/users/1)

## Validate response is valid JSON
echo "$response" | jq empty

## Validate required fields
echo "$response" | jq -e '.id, .name, .email' > /dev/null

## Test specific values
user_id=$(echo "$response" | jq -r '.id')
if [ "$user_id" != "1" ]; then
  echo "Unexpected user ID"
  exit 1
fi

JSON Diff Testing

Compare JSON files:

## Install json-diff
npm install -g json-diff

## Compare two JSON files
json-diff config-old.json config-new.json

## Colorized output
json-diff --color config-old.json config-new.json

## Keys only
json-diff --keys-only config-old.json config-new.json

Testing in Scripts

## test/json-validation.test.js
const Ajv = require('ajv');
const fs = require('fs');

describe('JSON Configuration Tests', () => {
  let ajv;
  let schema;

  beforeAll(() => {
    ajv = new Ajv();
    schema = JSON.parse(fs.readFileSync('schema/config.schema.json', 'utf8'));
  });

  test('config.json should be valid', () => {
    const config = JSON.parse(fs.readFileSync('config.json', 'utf8'));
    const validate = ajv.compile(schema);
    const valid = validate(config);

    expect(valid).toBe(true);
    if (!valid) {
      console.error(validate.errors);
    }
  });

  test('production config should have required security settings', () => {
    const config = JSON.parse(fs.readFileSync('config.production.json', 'utf8'));

    expect(config.ssl.enabled).toBe(true);
    expect(config.auth.required).toBe(true);
  });
});

Pre-commit Hook for JSON Validation

## .pre-commit-config.yaml
repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.5.0
    hooks:
      - id: check-json
      - id: pretty-format-json
        args: ['--autofix', '--indent=2', '--no-sort-keys']

  - repo: https://github.com/python-jsonschema/check-jsonschema
    rev: 0.27.0
    hooks:
      - id: check-jsonschema
        name: Validate JSON configs
        files: "config.*\\.json$"
        args: ["--schemafile", "schema/config.schema.json"]

Performance Testing JSON Processing

## Test JSON file size
size=$(stat -f%z config.json 2>/dev/null || stat -c%s config.json)
max_size=$((1024 * 1024))  # 1MB

if [ "$size" -gt "$max_size" ]; then
  echo "JSON file too large: $(($size / 1024))KB"
  exit 1
fi

## Test parsing performance
time jq '.' large-file.json > /dev/null

Security Best Practices

Never Store Secrets in JSON

JSON files are often committed to version control - never store sensitive data:

// Bad - Secrets in JSON (especially in version control)
{
  "database": {
    "host": "db.example.com",
    "password": "MySecretPassword123",  // ❌ Exposed!
    "apiKey": "sk-1234567890abcdef"     // ❌ Hardcoded!
  }
}

// Good - Use placeholders for environment variables
{
  "database": {
    "host": "${DB_HOST}",
    "password": "${DB_PASSWORD}",  // ✅ From environment
    "apiKey": "${API_KEY}"
  }
}

// Good - Reference external secure storage
{
  "database": {
    "host": "db.example.com",
    "password": "vault://secrets/db/password",
    "apiKey": "ssm:///myapp/api-key"
  }
}

Key Points:

  • Never commit secrets to version control
  • Use environment variables for sensitive data
  • Reference secret management systems (Vault, AWS Secrets Manager)
  • Use .env files (gitignored) for local development
  • Scan repositories for accidentally committed secrets

Validate JSON Schema

Always validate JSON against a schema to prevent injection and data corruption:

// Good - Validate with JSON Schema
import Ajv from 'ajv';

const schema = {
  type: 'object',
  properties: {
    username: { type: 'string', pattern: '^[a-zA-Z0-9_-]+$' },
    email: { type: 'string', format: 'email' },
    age: { type: 'integer', minimum: 0, maximum: 150 }
  },
  required: ['username', 'email'],
  additionalProperties: false  // ✅ Prevent unexpected properties
};

const ajv = new Ajv();
const validate = ajv.compile(schema);

function processUserData(data: unknown) {
  if (!validate(data)) {
    throw new Error(`Invalid data: ${ajv.errorsText(validate.errors)}`);
  }
  // Safe to use validated data
  return data;
}

Key Points:

  • Define JSON schemas for all data structures
  • Validate all external JSON input
  • Use additionalProperties: false to prevent unexpected fields
  • Enforce format constraints (email, URL, date)
  • Fail fast on invalid data

Prevent JSON Injection

Sanitize data before embedding in JSON:

// Bad - String concatenation (injection risk)
const userInput = '", "isAdmin": true, "fake": "';
const json = `{"username": "${userInput}"}`;  // ❌ Injected admin field!
// Result: {"username": "", "isAdmin": true, "fake": ""}

// Good - Use JSON.stringify (automatic escaping)
const userInput = '"; DROP TABLE users; --';
const safeJson = JSON.stringify({ username: userInput });  // ✅ Properly escaped

// Good - Validate before parsing
function safeJSONParse(text: string): unknown {
  try {
    const parsed = JSON.parse(text);
    // Validate against schema here
    return parsed;
  } catch (error) {
    throw new Error('Invalid JSON');
  }
}

Key Points:

  • Always use JSON.stringify() and JSON.parse()
  • Never build JSON with string concatenation
  • Validate after parsing
  • Sanitize user inputs before JSON encoding
  • Use TypeScript for type safety

Limit JSON Size

Prevent denial of service from large JSON payloads:

// Good - Limit JSON payload size
import express from 'express';

const app = express();

app.use(express.json({
  limit: '100kb',  // ✅ Limit payload size
  strict: true,    // Only accept objects and arrays
}));

// Good - Streaming parser for large files
import { parser } from 'stream-json';
import { streamArray } from 'stream-json/streamers/StreamArray';

const pipeline = fs.createReadStream('large-file.json')
  .pipe(parser())
  .pipe(streamArray())
  .on('data', ({ value }) => {
    // Process each item individually
    processItem(value);
  });

Key Points:

  • Set maximum payload size limits
  • Use streaming parsers for large files
  • Implement timeouts for JSON parsing
  • Monitor memory usage
  • Reject deeply nested structures

Sanitize Output

Prevent Cross-Site Scripting (XSS) when displaying JSON in HTML:

// Bad - Directly embedding JSON in HTML
const data = { name: '<script>alert("XSS")</script>' };
const html = `<div>${JSON.stringify(data)}</div>`;  // ❌ XSS vulnerability!

// Good - Properly escape for HTML context
function escapeHTML(str: string): string {
  return str
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#x27;');
}

const safeHTML = `<div>${escapeHTML(JSON.stringify(data))}</div>`;  // ✅ Safe

Key Points:

  • Escape JSON before embedding in HTML
  • Use Content Security Policy (CSP) headers
  • Avoid innerHTML with user-controlled JSON
  • Use safe templating libraries
  • Sanitize before display

File Access Control

Protect JSON configuration files with appropriate permissions:

## Good - Restrictive file permissions
# Configuration files (readable by application)
chmod 640 config.json
chown app:app config.json

# Secrets files (readable only by application)
chmod 600 secrets.json
chown app:app secrets.json

# Public configuration
chmod 644 public-config.json

Key Points:

  • Set restrictive file permissions (600 or 640)
  • Use appropriate file ownership
  • Never make secrets world-readable
  • Audit file access regularly
  • Encrypt sensitive JSON files at rest

Common Pitfalls

Trailing Commas Breaking Parsers

Issue: Adding trailing commas (common in JavaScript) causes JSON parsers to fail.

Example:

{
  "name": "John",
  "age": 30,
  "email": "john@example.com",
}

Solution: Remove all trailing commas.

{
  "name": "John",
  "age": 30,
  "email": "john@example.com"
}

Key Points:

  • JSON specification forbids trailing commas
  • Most JSON parsers will reject trailing commas
  • Use JSON linter to catch trailing commas
  • JavaScript allows trailing commas, JSON does not

Number Precision Loss

Issue: Large integers lose precision when parsed as JavaScript numbers due to IEEE 754 limits.

Example:

{
  "user_id": 9007199254740993,
  "transaction_id": 12345678901234567890
}

Solution: Use strings for large integers or IDs.

{
  "user_id": "9007199254740993",
  "transaction_id": "12345678901234567890",
  "amount_cents": 1299,
  "precision_decimal": "123.456789012345"
}

Key Points:

  • JavaScript max safe integer: 2^53 - 1 (9,007,199,254,740,991)
  • Database IDs often exceed this limit
  • Use strings for IDs, UUIDs, and high-precision numbers
  • Keep numeric types only for actual calculations

Comment Attempts

Issue: Trying to add comments using // or /* */ breaks JSON parsing.

Example:

{
  // This is a comment
  "name": "John",
  /* Multi-line
     comment */
  "age": 30
}

Solution: Use a designated key for comments or switch to JSON5/JSONC if comments are needed.

{
  "_comment": "User configuration",
  "name": "John",
  "age": 30,
  "_note_age": "Age is optional for legacy users"
}

Key Points:

  • Standard JSON does not support comments
  • Use _comment, _note, or similar keys for documentation
  • Consider JSON5 or JSONC for config files needing comments
  • Remove comment keys before production if needed

String Escaping Confusion

Issue: Incorrectly escaping special characters or forgetting to escape quotes.

Example:

{
  "path": "C:\Users\John\Documents",
  "message": "She said "hello"",
  "regex": "\d+"
}

Solution: Properly escape backslashes and quotes.

{
  "path": "C:\\Users\\John\\Documents",
  "message": "She said \"hello\"",
  "regex": "\\d+",
  "newline": "Line 1\\nLine 2"
}

Key Points:

  • Always escape backslashes: \ becomes \\
  • Escape double quotes inside strings: " becomes \"
  • Common escapes: \n (newline), \t (tab), \r (carriage return)
  • JSON does not support single-quoted strings

Type Inconsistency

Issue: Mixing data types for the same field causes parsing and validation issues.

Example:

[
  {
    "user_id": 123,
    "active": true
  },
  {
    "user_id": "456",
    "active": "yes"
  }
]

Solution: Maintain consistent types across all instances.

[
  {
    "user_id": 123,
    "active": true
  },
  {
    "user_id": 456,
    "active": true
  }
]

Key Points:

  • Keep field types consistent across all objects
  • Define and enforce a schema
  • Boolean values: true or false, not "true" or 1
  • Numbers: 123, not "123" (unless it's an ID)

Anti-Patterns

❌ Avoid: Trailing Commas

// Bad - Trailing comma (invalid JSON)
{
  "name": "my-app",
  "version": "1.0.0",
}

// Good - No trailing comma
{
  "name": "my-app",
  "version": "1.0.0"
}

❌ Avoid: Single Quotes

// Bad - Single quotes (invalid JSON)
{
  'name': 'my-app'
}

// Good - Double quotes
{
  "name": "my-app"
}

❌ Avoid: Unquoted Keys

// Bad - Unquoted keys (invalid JSON)
{
  name: "my-app"
}

// Good - Quoted keys
{
  "name": "my-app"
}

❌ Avoid: Comments in Standard JSON

// Bad - Comments in standard JSON (invalid)
{
  // This is a comment
  "name": "my-app"
}

// Good - No comments (use JSONC or JSON5 if needed)
{
  "name": "my-app"
}

❌ Avoid: Deep Nesting

// Bad - Deeply nested structure (hard to maintain)
{
  "app": {
    "config": {
      "database": {
        "connections": {
          "primary": {
            "settings": {
              "host": "localhost",
              "port": 5432
            }
          }
        }
      }
    }
  }
}

// Good - Flatter structure
{
  "app_database_host": "localhost",
  "app_database_port": 5432
}

// Or use references
{
  "database_settings": {
    "host": "localhost",
    "port": 5432
  },
  "app_config": {
    "database": "$ref:database_settings"
  }
}

❌ Avoid: Inconsistent Naming Conventions

// Bad - Mixed naming styles
{
  "firstName": "John",
  "last_name": "Doe",
  "EmailAddress": "john@example.com",
  "phone-number": "555-1234"
}

// Good - Consistent camelCase (or snake_case throughout)
{
  "firstName": "John",
  "lastName": "Doe",
  "emailAddress": "john@example.com",
  "phoneNumber": "555-1234"
}

❌ Avoid: Storing Sensitive Data

// Bad - Sensitive data in JSON (especially in version control)
{
  "database": {
    "password": "MySecretPassword123",
    "apiKey": "sk-1234567890abcdef"
  }
}

// Good - Use environment variables or secure vaults
{
  "database": {
    "password": "${DB_PASSWORD}",
    "apiKey": "${API_KEY}"
  }
}

// Or reference external secure storage
{
  "database": {
    "password": "vault://secrets/db/password",
    "apiKey": "vault://secrets/api/key"
  }
}

JSON Validation

Using jq

## Validate JSON file
jq empty config.json

## Pretty print
jq . config.json

## Extract specific field
jq '.name' package.json

## Filter array
jq '.users[] | select(.age > 18)' users.json

Using jsonlint

## Validate JSON
jsonlint config.json

## Format JSON
jsonlint -i config.json

Using Python

import json

## Validate JSON
with open('config.json') as f:
    try:
        data = json.load(f)
        print("Valid JSON")
    except json.JSONDecodeError as e:
        print(f"Invalid JSON: {e}")

## Pretty print
print(json.dumps(data, indent=2))

Tool Configurations

VSCode settings.json

{
  "json.schemas": [
    {
      "fileMatch": ["package.json"],
      "url": "https://json.schemastore.org/package.json"
    },
    {
      "fileMatch": ["tsconfig.json"],
      "url": "https://json.schemastore.org/tsconfig.json"
    }
  ],
  "json.format.enable": true,
  "[json]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode",
    "editor.formatOnSave": true,
    "editor.tabSize": 2
  },
  "[jsonc]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  }
}

.prettierrc (Prettier)

{
  "semi": true,
  "singleQuote": false,
  "tabWidth": 2,
  "trailingComma": "none",
  "printWidth": 100,
  "arrowParens": "always"
}

Best Practices

Use Schema Validation

Define and validate JSON structure with JSON Schema:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "User",
  "type": "object",
  "required": ["id", "email"],
  "properties": {
    "id": {
      "type": "integer",
      "minimum": 1
    },
    "email": {
      "type": "string",
      "format": "email"
    },
    "age": {
      "type": "integer",
      "minimum": 0,
      "maximum": 150
    }
  }
}

Validate Before Parsing

Always validate JSON before parsing to prevent errors:

import json
from jsonschema import validate, ValidationError

try:
    data = json.loads(json_string)
    validate(instance=data, schema=user_schema)
except json.JSONDecodeError as e:
    print(f"Invalid JSON: {e}")
except ValidationError as e:
    print(f"Schema validation failed: {e}")

Use Consistent Casing

Choose one casing style and stick to it:

// Good - camelCase (JavaScript/TypeScript projects)
{
  "userId": 123,
  "firstName": "John",
  "createdAt": "2024-01-01T00:00:00Z"
}

// Good - snake_case (Python/Ruby projects)
{
  "user_id": 123,
  "first_name": "John",
  "created_at": "2024-01-01T00:00:00Z"
}

Avoid Deep Nesting

Keep nesting levels reasonable (max 3-4 levels):

// Bad - Too deeply nested
{
  "user": {
    "profile": {
      "address": {
        "location": {
          "coordinates": {
            "lat": 40.7128,
            "lng": -74.0060
          }
        }
      }
    }
  }
}

// Good - Flattened structure
{
  "userId": 123,
  "addressLat": 40.7128,
  "addressLng": -74.0060
}

Use Arrays for Lists

Always use arrays for lists, even with one item:

// Good - Consistent array usage
{
  "users": [
    {"id": 1, "name": "John"}
  ]
}

// Bad - Inconsistent (object when multiple, single value when one)
{
  "user": {"id": 1, "name": "John"}
}

Include Metadata

Add version and timestamp metadata for API responses:

{
  "meta": {
    "version": "1.0",
    "timestamp": "2024-01-15T10:30:00Z",
    "requestId": "abc-123"
  },
  "data": {
    "users": [...]
  }
}

Handle Null Values Consistently

Be explicit about null handling:

// Good - Explicit null
{
  "name": "John",
  "middleName": null,
  "phone": "+1234567890"
}

// Consider omitting null fields entirely
{
  "name": "John",
  "phone": "+1234567890"
}

Use ISO 8601 for Dates

Always use ISO 8601 format for dates:

{
  "createdAt": "2024-01-15T10:30:00Z",
  "updatedAt": "2024-01-15T14:45:30.123Z",
  "date": "2024-01-15"
}

Minify for Production

Minify JSON in production, pretty-print for development:

# Development - pretty print
cat data.json | jq '.'

# Production - minified
cat data.json | jq -c '.'

Version Your APIs

Include API version in JSON responses:

{
  "apiVersion": "2.0",
  "data": {
    "users": [...]
  },
  "links": {
    "self": "/api/v2/users",
    "docs": "/api/v2/docs"
  }
}

References

Official Documentation

Tools

Schema Repositories


Status: Active