What is JSON Data Modeling?
JSON data modeling is the practice of deliberately designing the shape of your JSON objects — choosing which fields to include, how to nest them, when to use arrays vs objects, and how to handle relationships between data.
A well-designed JSON schema is intuitive, consistent, and easy to extend. Use JSONKit's JSON Schema Generator to auto-generate a schema from any existing JSON object. A poorly designed schema forces consumers to work around it for every version of the API.
Principle 1: Flat is Better than Nested (Until It Isn't)
Nesting adds hierarchy and context but makes consumption harder. Start flat:
// Too flat — no grouping, hard to extend
{
"userId": 1,
"firstName": "Ravi",
"city": "Surat",
"postalCode": "395007",
"countryCode": "IN"
}// Appropriately nested — address is a logical unit
{
"id": 1,
"firstName": "Ravi",
"address": {
"city": "Surat",
"postalCode": "395007",
"countryCode": "IN"
}
}// Over-nested — unnecessary depth
{
"user": {
"personal": {
"identity": {
"name": { "first": "Ravi" }
}
}
}
}Rule: Group fields that are always used together and that logically belong together. Avoid nesting for the sake of namespace isolation.
Principle 2: Arrays of Objects for Collections
Use arrays of objects, not objects keyed by IDs, for collections:
// Avoid — object keyed by ID is hard to iterate and append
{
"users": {
"1": { "name": "Ravi" },
"2": { "name": "Priya" }
}
}// Prefer — array of objects; easy to map, filter, iterate
{
"users": [
{ "id": 1, "name": "Ravi" },
{ "id": 2, "name": "Priya" }
]
}Exceptions: use keyed objects when: - You need O(1) lookup by a key (a lookup table / dictionary) - The dataset is a fixed, small enumeration (e.g., status codes, config flags)
Principle 3: IDs for References, Not Full Embedding
Embed a related object when: - It is always needed together with the parent - It is small and does not change independently - Fetch performance outweighs duplication
Use an ID reference when: - The related object is large - It changes independently and needs its own cache entry - Multiple parent objects reference the same child
// Embedded (user always comes with their address)
{
"id": "ord_123",
"customer": {
"id": "usr_456",
"name": "Ravi",
"email": "ravi@example.com"
},
"total": 1299.00
}// ID reference (load product details on demand)
{
"id": "ord_123",
"customerId": "usr_456",
"lineItems": [
{ "productId": "prod_789", "quantity": 2, "unitPrice": 599.00 }
],
"total": 1298.00
}Principle 4: Consistent Date Format (ISO 8601)
Always use ISO 8601 UTC strings for dates and timestamps:
{
"createdAt": "2025-06-01T12:30:00Z",
"expiresAt": "2025-12-31T23:59:59Z",
"date": "2025-06-01"
}Never use Unix timestamps as numbers in public APIs — they are not human-readable and cause timezone confusion. Never use locale-formatted strings like "01/06/2025" — they are ambiguous between MM/DD and DD/MM.
Principle 5: Use null for Absent Optional Values
Return null for optional fields that have no value rather than omitting the field entirely:
// Inconsistent — sometimes middleName is absent, sometimes null
{ "name": "Ravi" }
{ "name": "Priya", "middleName": null }// Consistent — always present, null when absent
{ "name": "Ravi", "middleName": null }
{ "name": "Priya", "middleName": "Kumari" }This makes consumers' code simpler — they always check if (user.middleName !== null) rather than if ("middleName" in user).
Principle 6: Naming Conventions
Pick one convention and use it everywhere:
- camelCase (most common for JavaScript APIs):
firstName,createdAt,isActive - snake_case (common in Python, Ruby APIs):
first_name,created_at,is_active - PascalCase (avoid in JSON — too similar to class names)
- kebab-case (avoid in JSON keys — hyphens require bracket notation in JS)
Boolean fields: prefix with is, has, or can:
{
"isActive": true,
"hasSubscription": false,
"canEdit": true
}Principle 7: Envelope Responses for APIs
Wrap API responses in a consistent envelope so clients can always extract data the same way:
{
"success": true,
"data": { "id": 1, "name": "Ravi" },
"meta": {
"requestId": "req_abc123",
"timestamp": "2025-06-01T12:30:00Z"
}
}{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "The 'email' field is required.",
"field": "email"
}
}This pattern (RFC 7807 Problem Details is a formal version) lets clients write a single error handler instead of a special case per endpoint.
Principle 8: Design for Forward Compatibility
Design your JSON to be extendable without breaking existing consumers:
- Add new optional fields — old consumers ignore unknown keys by default in most languages
- Never remove or rename existing fields — this is a breaking change
- Never change a field's type (string → number) — always breaking
- Use a version field or URL versioning when you must make breaking changes:
{ "version": "2", "data": { ... } }Or use API versioning at the URL level: /api/v2/users.
Common JSON Schema Patterns
Status enum:
{ "status": "pending" }Use string enums, not integers. "status": "active" is self-documenting; "status": 2 is not.
Money:
{ "amount": 1299, "currency": "INR" }Store as integer cents/paise with an explicit currency code — never as floating-point numbers.
Pagination:
{
"data": [...],
"pagination": {
"page": 2, "limit": 20, "total": 847,
"nextCursor": "eyJpZCI6MjB9"
}
}Error response (RFC 7807):
{
"type": "https://api.example.com/errors/validation",
"title": "Validation Error",
"status": 422,
"detail": "The 'email' field must be a valid email address.",
"instance": "/api/users",
"errors": [
{ "field": "email", "message": "Invalid email format" }
]
}Generate a JSON Schema from any of these patterns using JSONKit's JSON Schema Generator at /json-schema-generator, then validate incoming data against it with the JSON Schema Validator at /json-schema-validator.