Advanced JSON Structures and Design Patterns
Master advanced JSON design patterns. Learn normalization, polymorphism, versioning, and API response patterns for scalable applications.
Sarah Chen
• Senior Software EngineerSarah is a full-stack software engineer with 8 years of experience in API development, TypeScript, and data engineering. She has designed and maintained large-scale JSON processing pipelines and contributes in-depth technical guides on performance optimisation, schema design, Python data workflows, and backend integration patterns.
Nested vs Flat Structures
Balanced Approach (Recommended)
{
"id": 1,
"name": "John",
"address": {
"street": "123 Main St",
"city": "NYC"
},
"contact": {
"email": "john@example.com",
"phone": "+1234567890"
}
}
Avoid excessive nesting (>3-4 levels).
Normalization
Denormalized (Embedded)
{
"orders": [
{
"id": 1,
"customer": {
"id": 101,
"name": "Alice"
}
}
]
}
Use when data is read frequently, updated rarely.
Normalized (References)
{
"orders": [
{ "id": 1, "customerId": 101 }
],
"customers": {
"101": { "id": 101, "name": "Alice" }
}
}
Use when data changes frequently.
API Response Patterns
Envelope Pattern
{
"success": true,
"data": { ... },
"meta": {
"total": 100,
"page": 1
}
}
Error Response
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid input",
"details": [
{
"field": "email",
"message": "Invalid format"
}
]
}
}
Pagination Patterns
Offset-Based Pagination
{
"data": [
{"id": 21, "name": "Item 21"},
{"id": 22, "name": "Item 22"}
],
"pagination": {
"page": 2,
"perPage": 20,
"total": 156,
"totalPages": 8
},
"links": {
"first": "/api/items?page=1",
"prev": "/api/items?page=1",
"next": "/api/items?page=3",
"last": "/api/items?page=8"
}
}
Pros: Easy to implement, jump to specific page
Cons: Performance degrades with deep pagination, inconsistent results if data changes
Cursor-Based Pagination (Recommended for APIs)
{
"data": [{"id": 100, "name": "Item 100", "created_at": "2026-01-15T10:30:00Z"}],
"pagination": {
"cursor": "eyJpZCI6MTAwLCJjcmVhdGVkX2F0IjoiMjAyNi0wMS0xNVQxMDozMDowMFoifQ==",
"hasMore": true
},
"links": {
"next": "/api/items?cursor=eyJpZCI6MTAwLCJjcmVhdGVkX2F0IjoiMjAyNi0wMS0xNVQxMDozMDowMFoifQ=="
}
}
Pros: Consistent results, O(1) performance, handles real-time data
Cons: Can't jump to specific page
Implementation Example
// Express.js cursor pagination
app.get('/api/items', async (req, res) => {
const { cursor, limit = 20 } = req.query;
let query = db('items').orderBy('created_at', 'desc').limit(limit + 1);
if (cursor) {
const decoded = JSON.parse(Buffer.from(cursor, 'base64').toString());
query = query.where('created_at', '<', decoded.created_at)
.orWhere(function() {
this.where('created_at', '=', decoded.created_at)
.where('id', '<', decoded.id);
});
}
const items = await query;
const hasMore = items.length > limit;
const data = items.slice(0, limit);
const nextCursor = hasMore
? Buffer.from(JSON.stringify({
id: data[data.length - 1].id,
created_at: data[data.length - 1].created_at
})).toString('base64')
: null;
res.json({
data,
pagination: { cursor: nextCursor, hasMore }
});
});
Polymorphic Data
Discriminator Pattern
{
"notifications": [
{
"type": "email",
"recipient": "user@example.com",
"subject": "Welcome!",
"body": "Thanks for signing up"
},
{
"type": "sms",
"phoneNumber": "+1234567890",
"message": "Your verification code is 123456"
},
{
"type": "push",
"deviceToken": "abc123",
"title": "New message",
"body": "You have 3 unread messages"
}
]
}
TypeScript Type Guards
type EmailNotification = {
type: 'email';
recipient: string;
subject: string;
body: string;
};
type SmsNotification = {
type: 'sms';
phoneNumber: string;
message: string;
};
type Notification = EmailNotification | SmsNotification;
function sendNotification(notification: Notification) {
switch (notification.type) {
case 'email':
// TypeScript knows this is EmailNotification
sendEmail(notification.recipient, notification.subject);
break;
case 'sms':
// TypeScript knows this is SmsNotification
sendSms(notification.phoneNumber, notification.message);
break;
}
}
Versioning Strategies
URL Versioning
GET /api/v1/users
GET /api/v2/users # Breaking changes
Header Versioning
GET /api/users
Accept: application/vnd.myapi.v2+json
Content Versioning (In Payload)
{
"version": "2.0",
"data": {
"id": 1,
"fullName": "John Doe", // v1 had firstName/lastName
"email": "john@example.com"
}
}
Backward Compatibility Example
// API handler supports both v1 and v2
function formatUserResponse(user, version = '2.0') {
if (version === '1.0') {
return {
id: user.id,
firstName: user.fullName.split(' ')[0],
lastName: user.fullName.split(' ')[1] || '',
email: user.email
};
}
return {
version: '2.0',
id: user.id,
fullName: user.fullName,
email: user.email
};
}
Event/Message Patterns
CloudEvents Standard
{
"specversion": "1.0",
"type": "com.example.user.created",
"source": "https://api.example.com/users",
"id": "evt_abc123",
"time": "2026-01-15T10:30:00Z",
"datacontenttype": "application/json",
"data": {
"userId": 123,
"email": "newuser@example.com"
}
}
Message Queue Pattern
{
"messageId": "msg_xyz789",
"timestamp": "2026-01-15T10:30:00Z",
"eventType": "order.placed",
"version": "1.0",
"payload": {
"orderId": 12345,
"customerId": 67890,
"total": 99.99
},
"metadata": {
"source": "web-checkout",
"userId": 67890,
"sessionId": "sess_abc123"
}
}
Best Practices
Naming Conventions
Be consistent across your API:
// camelCase (JavaScript/TypeScript)
{"userId": 1, "firstName": "John", "createdAt": "2026-01-15"}
// snake_case (Python/Ruby)
{"user_id": 1, "first_name": "John", "created_at": "2026-01-15"}
Date/Time (ISO 8601)
{
"createdAt": "2026-01-15T10:30:00Z", // UTC
"updatedAt": "2026-01-15T10:30:00-05:00", // With timezone
"date": "2026-01-15" // Date only
}
Money/Currency (Avoid Floats)
{
"price": {
"amount": 2999, // Store in cents/smallest unit
"currency": "USD"
},
"total": {
"amount": 15495,
"currency": "EUR"
}
}
Boolean Flags
{
"isActive": true,
"hasPermission": false,
"isDeleted": false
}
Avoid: "status": "active" when a boolean suffices.
HATEOAS (Hypermedia)
{
"id": 1,
"name": "Laptop",
"price": 999,
"_links": {
"self": {
"href": "/api/products/1"
},
"update": {
"href": "/api/products/1",
"method": "PUT"
},
"delete": {
"href": "/api/products/1",
"method": "DELETE"
},
"reviews": {
"href": "/api/products/1/reviews"
}
}
}
Bulk Operations
{
"operations": [
{
"op": "create",
"type": "user",
"data": {"name": "Alice", "email": "alice@example.com"}
},
{
"op": "update",
"type": "user",
"id": 123,
"data": {"name": "Alice Smith"}
},
{
"op": "delete",
"type": "user",
"id": 456
}
]
}
Response Format
{
"results": [
{"op": "create", "success": true, "id": 789},
{"op": "update", "success": true},
{"op": "delete", "success": false, "error": "User not found"}
],
"summary": {
"total": 3,
"succeeded": 2,
"failed": 1
}
}
Sparse Fieldsets (JSON:API Style)
Reduce payload size by requesting only needed fields:
GET /api/users?fields[user]=id,name,email
{
"data": [
{"id": 1, "name": "Alice", "email": "alice@example.com"},
{"id": 2, "name": "Bob", "email": "bob@example.com"}
]
}
Vs. full response (40% bandwidth savings).
GraphQL-Style Nested Queries
{
"query": {
"user": {
"id": 1,
"include": ["posts", "comments"],
"posts": {
"limit": 5,
"include": ["author"]
}
}
}
}
Production Design Checklist
☑️ Use cursor pagination for infinite scroll
☑️ Include version field for schema evolution
☑️ Use discriminator (type field) for polymorphic data
☑️ Store money in cents/smallest unit
☑️ Use ISO 8601 for dates with timezone
☑️ Include error codes in error responses
☑️ Provide HATEOAS links for discoverability
☑️ Support sparse fieldsets to reduce bandwidth
☑️ Use envelope pattern for consistent response structure
☑️ Add request IDs for tracing and debugging
Conclusion
Advanced JSON design patterns enable scalable, maintainable APIs. Use cursor pagination for performance, discriminator pattern for polymorphism, ISO 8601 dates for consistency, and cents for currency to avoid floating-point errors. Always include version fields and error codes to support evolution and debugging in production systems.
{ "id": 1, "name": "Alice", "email": "alice@example.com" }
]
}
```
Conclusion
Good JSON structure design:
Well-structured JSON makes APIs easier to use and maintain!
Related Articles
What is JSON? Complete Guide for Beginners 2026
Learn what JSON is, its syntax, data types, and use cases. A comprehensive beginner-friendly guide to understanding JavaScript Object Notation.
Understanding JSON Schema: Complete Validation Guide
Master JSON Schema for data validation. Learn schema syntax, validation techniques, and implementation across different programming languages.
JSON APIs and REST Services: Complete Development Guide
Learn to build and consume JSON-based REST APIs. Covers HTTP methods, authentication, best practices, and real-world implementation examples.