<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged ajv</title>
<link>https://coderlegion.com/tag/ajv</link>
<description>Powered by Question2Answer</description>
<item>
<title>🧩 **Engineering a Machine-Validatable Contributor Evidence Workflow**</title>
<link>https://coderlegion.com/30305/engineering-a-machine-validatable-contributor-evidence-workflow</link>
<description>&lt;p&gt;🧩 &lt;strong&gt;Engineering a Machine-Validatable Contributor Evidence Workflow&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I just merged a new infrastructure milestone into MyZubster:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;PR #1572 — standardized contributor interoperability checkpoints&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The problem I wanted to solve was not just “how do we document a successful contributor test?”&lt;/p&gt;
&lt;p&gt;The real problem was:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How do we make technical evidence portable across contributors, technologies and verification paths without losing provenance or inflating claims?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Until now, MyZubster already had reproducible contributor checkpoints, but the structure was mostly expressed through Markdown, PR discussions and manually reviewed evidence.&lt;/p&gt;
&lt;p&gt;That works at small scale.&lt;/p&gt;
&lt;p&gt;It does not scale well when multiple contributors are producing different kinds of evidence.&lt;/p&gt;
&lt;p&gt;So I introduced a common machine-readable format:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;myzubster.interoperability-checkpoint.v1&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Each checkpoint now describes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;contributor identity&lt;/li&gt;
&lt;li&gt;repository / PR&lt;/li&gt;
&lt;li&gt;immutable commit reference&lt;/li&gt;
&lt;li&gt;technical bridge type&lt;/li&gt;
&lt;li&gt;exact scope&lt;/li&gt;
&lt;li&gt;test environment&lt;/li&gt;
&lt;li&gt;reproduction procedure&lt;/li&gt;
&lt;li&gt;observed output&lt;/li&gt;
&lt;li&gt;evidence state&lt;/li&gt;
&lt;li&gt;limitations&lt;/li&gt;
&lt;li&gt;canonical provenance&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;why-the-limitations-field-matters&quot;&gt;Why the limitations field matters&lt;/h4&gt;
&lt;p&gt;One of the most important design decisions was making limitations mandatory.&lt;/p&gt;
&lt;p&gt;A technical checkpoint should not silently become a broader claim.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;p&gt;A successful broker-to-agent test does not automatically mean:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;full decentralization&lt;/li&gt;
&lt;li&gt;direct P2P networking&lt;/li&gt;
&lt;li&gt;security certification&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A research Knowledge Card marked &lt;code&gt;SUPPORTED&lt;/code&gt; should not become “scientifically validated.”&lt;/p&gt;
&lt;p&gt;A signed webhook regression should not become “payment settlement verified.”&lt;/p&gt;
&lt;p&gt;The schema is intentionally designed to preserve those boundaries.&lt;/p&gt;
&lt;h4 id=&quot;evidence-state-is-constrained-by-observation&quot;&gt;Evidence state is constrained by observation&lt;/h4&gt;
&lt;p&gt;The validator enforces an important rule:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;TESTED&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;requires:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;observation.result = PASS&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;That sounds obvious, but encoding it into the schema means it is no longer just a documentation convention.&lt;/p&gt;
&lt;p&gt;It becomes something CI can reject.&lt;/p&gt;
&lt;h4 id=&quot;ajv-ci-integration&quot;&gt;AJV + CI integration&lt;/h4&gt;
&lt;p&gt;I added an AJV-based validator and connected it to the existing &lt;strong&gt;Continuous Evidence Gate&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;So the pipeline now looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Contributor work
        ↓
Scoped reproducible test
        ↓
Machine-readable checkpoint
        ↓
JSON Schema validation
        ↓
Continuous Evidence Gate
        ↓
Human review
        ↓
Canonical merge
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The important distinction:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;automation validates evidence structure; it does not replace engineering judgment.&lt;/strong&gt;&lt;/p&gt;
&lt;h4 id=&quot;testing-the-format-across-different-contribution-types&quot;&gt;Testing the format across different contribution types&lt;/h4&gt;
&lt;p&gt;I did not want the first schema version to be designed around one specific contributor.&lt;/p&gt;
&lt;p&gt;So the initial dataset already includes multiple technical paths.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;N4K48 / Nicola&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Runtime interoperability:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;MyZubster VPS
→ authenticated HTTPS broker
→ contributor-controlled agent
→ local catalog
→ result returned to Bridge
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;wasim-builds&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Security regression:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;unconfigured auth → rejected
missing credential → rejected
wrong credential → rejected
correct credential → accepted
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;khongten124 / Open Period Care&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Semantic evidence bridge:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;public research evidence
→ structured ingestion
→ contributor-scoped retrieval
→ Knowledge Cards returned
→ original SUPPORTED state preserved
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Aming9303&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Signed webhook regression:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;HMAC signing
→ stable delivery identity
→ unsigned request rejection
→ replay protection
→ stale-event rejection
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These are very different systems.&lt;/p&gt;
&lt;p&gt;That is exactly why using the same evidence structure is useful.&lt;/p&gt;
&lt;h4 id=&quot;current-maturity&quot;&gt;Current maturity&lt;/h4&gt;
&lt;p&gt;I would describe the workflow as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;repeatable
→ auditable
→ multi-contributor
→ machine-validatable
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The next layer will be:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;automated ingestion
→ Contributor Passport linkage
→ Knowledge Graph integration
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;while preserving the original evidence state and provenance.&lt;/p&gt;
&lt;h4 id=&quot;merge-result&quot;&gt;Merge result&lt;/h4&gt;
&lt;p&gt;PR #1572 passed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CI – Test &amp;amp; Lint&lt;/li&gt;
&lt;li&gt;Security Audit&lt;/li&gt;
&lt;li&gt;Continuous Evidence Gate&lt;/li&gt;
&lt;li&gt;policy checks&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Canonical merge:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;2be06b7e34f22744ecf81a9daef12f7ba11864be&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;What I like about this milestone is that it is not visually impressive.&lt;/p&gt;
&lt;p&gt;There is no shiny UI involved.&lt;/p&gt;
&lt;p&gt;But it improves something much more fundamental:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;the trust model between contributors and the platform.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Instead of:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;“This was tested.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;the system can increasingly answer:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Who tested it?&lt;br&gt;
Against which commit?&lt;br&gt;
In which environment?&lt;br&gt;
Using which procedure?&lt;br&gt;
What was observed?&lt;br&gt;
What does the evidence not prove?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That is the direction I want to keep pushing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Evidence should be inspectable data, not just prose.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;coderlegion-softwareengineering-jsonschema-ajv-ci-devops-systemdesign-opensource-reproducibility-testing-developertools&quot;&gt;CoderLegion #SoftwareEngineering #JSONSchema #AJV #CI #DevOps #SystemDesign #OpenSource #Reproducibility #Testing #DeveloperTools&lt;/h2&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/30305/engineering-a-machine-validatable-contributor-evidence-workflow</guid>
<pubDate>Thu, 08 Oct 2026 13:50:59 +0000</pubDate>
</item>
<item>
<title>JSON Schema in the Wild: Real World Applications &amp; HAL</title>
<link>https://coderlegion.com/6206/json-schema-in-the-wild-real-world-applications-hal</link>
<description>&lt;p&gt;&lt;em&gt;Part 3 of 3: Where Theory Meets Production&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Welcome to the final part of our JSON Schema journey! We've covered the theory and implementation – now let's see how JSON Schema solves real problems in production environments. Plus, we'll dive into HAL (Hypermedia Application Language) and how it plays beautifully with JSON Schema.&lt;/p&gt;
&lt;h3 id=&quot;1-api-validation-middleware-your-apis-bodyguard-&quot;&gt;1. API Validation Middleware (Your APIs' Bodyguard)&lt;/h3&gt;
&lt;p&gt;Let's build bulletproof API validation that catches bad data before it causes havoc:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;import express from 'express';
import Ajv from 'ajv';
import addFormats from 'ajv-formats';

const ajv = new Ajv({ allErrors: true });
addFormats(ajv);

// Generic validation middleware
function validateRequest(schema: any, target: 'body' | 'query' | 'params' = 'body') {
  const validate = ajv.compile(schema);
  
  return (req: any, res: any, next: any) =&amp;gt; {
    const dataToValidate = req[target];
    
    if (!validate(dataToValidate)) {
      return res.status(400).json({
        error: 'Validation failed',
        details: validate.errors?.map(err =&amp;gt; ({
          field: err.instancePath || err.params?.missingProperty,
          message: err.message,
          value: err.data
        }))
      });
    }
    
    // Store validated data (with defaults applied!)
    req.validatedData = dataToValidate;
    next();
  };
}

// Define your schemas
const createUserSchema = {
  type: &quot;object&quot;,
  properties: {
    name: { type: &quot;string&quot;, minLength: 1, maxLength: 100 },
    email: { type: &quot;string&quot;, format: &quot;email&quot; },
    age: { type: &quot;number&quot;, minimum: 13, maximum: 120 },
    role: { enum: [&quot;user&quot;, &quot;admin&quot;], default: &quot;user&quot; }
  },
  required: [&quot;name&quot;, &quot;email&quot;, &quot;age&quot;],
  additionalProperties: false
};

const updateUserSchema = {
  type: &quot;object&quot;,
  properties: {
    name: { type: &quot;string&quot;, minLength: 1, maxLength: 100 },
    email: { type: &quot;string&quot;, format: &quot;email&quot; },
    age: { type: &quot;number&quot;, minimum: 13, maximum: 120 }
  },
  minProperties: 1, // At least one field must be provided
  additionalProperties: false
};

// Use in your routes
const app = express();
app.use(express.json());

app.post('/users', 
  validateRequest(createUserSchema, 'body'),
  (req, res) =&amp;gt; {
    // req.validatedData contains clean, validated data
    const user = createUser(req.validatedData);
    res.json(user);
  }
);

app.patch('/users/:id', 
  validateRequest(updateUserSchema, 'body'),
  (req, res) =&amp;gt; {
    const userId = req.params.id;
    const updates = req.validatedData;
    const user = updateUser(userId, updates);
    res.json(user);
  }
);

// Query parameter validation too!
const searchSchema = {
  type: &quot;object&quot;,
  properties: {
    q: { type: &quot;string&quot;, minLength: 1 },
    page: { type: &quot;string&quot;, pattern: &quot;^[1-9]\\d*$&quot;, default: &quot;1&quot; },
    limit: { type: &quot;string&quot;, pattern: &quot;^(10|25|50|100)$&quot;, default: &quot;25&quot; }
  },
  required: [&quot;q&quot;],
  additionalProperties: false
};

app.get('/search', 
  validateRequest(searchSchema, 'query'),
  (req, res) =&amp;gt; {
    const { q, page, limit } = req.validatedData;
    const results = searchUsers(q, parseInt(page), parseInt(limit));
    res.json(results);
  }
);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;2-data-pipeline-validation-etl-with-confidence-&quot;&gt;2. Data Pipeline Validation (ETL with Confidence)&lt;/h3&gt;
&lt;p&gt;When processing large datasets, catching bad data early saves hours of debugging:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// data-pipeline.ts
import Ajv from 'ajv';
import addFormats from 'ajv-formats';

const ajv = new Ajv({ allErrors: true });
addFormats(ajv);

// Schema for incoming data records
const dataRecordSchema = {
  type: &quot;object&quot;,
  properties: {
    id: { 
      type: &quot;string&quot;, 
      pattern: &quot;^[A-Z]{2}[0-9]{6}$&quot; // e.g., &quot;AB123456&quot;
    },
    timestamp: { 
      type: &quot;string&quot;, 
      format: &quot;date-time&quot; 
    },
    value: { 
      type: &quot;number&quot;,
      minimum: 0
    },
    category: { 
      enum: [&quot;sales&quot;, &quot;marketing&quot;, &quot;support&quot;, &quot;development&quot;] 
    },
    metadata: {
      type: &quot;object&quot;,
      properties: {
        source: { type: &quot;string&quot; },
        confidence: { type: &quot;number&quot;, minimum: 0, maximum: 1 }
      },
      required: [&quot;source&quot;],
      additionalProperties: true // Allow extra metadata
    }
  },
  required: [&quot;id&quot;, &quot;timestamp&quot;, &quot;value&quot;, &quot;category&quot;],
  additionalProperties: false
};

const validateRecord = ajv.compile(dataRecordSchema);

interface ProcessingResult {
  validRecords: any[];
  invalidRecords: Array&amp;lt;{
    record: any;
    errors: string[];
  }&amp;gt;;
  stats: {
    total: number;
    valid: number;
    invalid: number;
    validationRate: number;
  };
}

export function processDataBatch(records: any[]): ProcessingResult {
  const validRecords: any[] = [];
  const invalidRecords: any[] = [];
  
  records.forEach((record, index) =&amp;gt; {
    if (validateRecord(record)) {
      validRecords.push(record);
    } else {
      invalidRecords.push({
        record,
        errors: validateRecord.errors?.map(err =&amp;gt; 
          `${err.instancePath || 'root'}: ${err.message}`
        ) || []
      });
    }
  });
  
  const total = records.length;
  const valid = validRecords.length;
  const invalid = invalidRecords.length;
  
  return {
    validRecords,
    invalidRecords,
    stats: {
      total,
      valid,
      invalid,
      validationRate: (valid / total) * 100
    }
  };
}

// Usage in your ETL process
async function runETL(inputFile: string) {
  const rawData = await loadDataFromFile(inputFile);
  const result = processDataBatch(rawData);
  
  console.log(`Processed ${result.stats.total} records:`);
  console.log(`✅ Valid: ${result.stats.valid} (${result.stats.validationRate.toFixed(1)}%)`);
  console.log(`❌ Invalid: ${result.stats.invalid}`);
  
  if (result.invalidRecords.length &amp;gt; 0) {
    console.log('\nInvalid records:');
    result.invalidRecords.slice(0, 5).forEach((invalid, i) =&amp;gt; {
      console.log(`Record ${i + 1}:`, invalid.errors.join(', '));
    });
  }
  
  // Process only valid records
  await saveToDatabase(result.validRecords);
  
  // Log invalid records for investigation
  if (result.invalidRecords.length &amp;gt; 0) {
    await saveInvalidRecords(result.invalidRecords);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;3-hal-hypermedia-application-language-schemas&quot;&gt;3. HAL (Hypermedia Application Language) Schemas&lt;/h3&gt;
&lt;p&gt;HAL makes your APIs self-describing with built-in navigation. Think JSON with links that tell clients what actions they can take next.&lt;/p&gt;
&lt;h4 id=&quot;basic-hal-structure-amp-schema&quot;&gt;Basic HAL Structure &amp;amp; Schema&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// HAL link schema
const halLinkSchema = {
  type: &quot;object&quot;,
  properties: {
    href: { type: &quot;string&quot;, format: &quot;uri-reference&quot; },
    templated: { type: &quot;boolean&quot; },
    type: { type: &quot;string&quot; }
  },
  required: [&quot;href&quot;],
  additionalProperties: false
};

// Complete HAL resource schema
const halUserSchema = {
  type: &quot;object&quot;,
  properties: {
    // Resource data
    id: { type: &quot;number&quot; },
    name: { type: &quot;string&quot; },
    email: { type: &quot;string&quot;, format: &quot;email&quot; },
    
    // HAL links (required)
    _links: {
      type: &quot;object&quot;,
      properties: {
        self: halLinkSchema,
        edit: halLinkSchema,
        delete: halLinkSchema
      },
      required: [&quot;self&quot;],
      additionalProperties: halLinkSchema
    },
    
    // HAL embedded resources (optional)
    _embedded: {
      type: &quot;object&quot;,
      properties: {
        posts: {
          type: &quot;array&quot;,
          items: {
            type: &quot;object&quot;,
            properties: {
              id: { type: &quot;number&quot; },
              title: { type: &quot;string&quot; },
              _links: {
                type: &quot;object&quot;,
                properties: { self: halLinkSchema },
                required: [&quot;self&quot;]
              }
            },
            required: [&quot;id&quot;, &quot;title&quot;, &quot;_links&quot;]
          }
        }
      }
    }
  },
  required: [&quot;id&quot;, &quot;name&quot;, &quot;_links&quot;],
  additionalProperties: false
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;hal-response-builder&quot;&gt;HAL Response Builder&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;import Ajv from 'ajv';
const ajv = new Ajv();
const validateHalUser = ajv.compile(halUserSchema);

class HalBuilder {
  static user(user: any, baseUrl: string) {
    const halUser = {
      ...user,
      _links: {
        self: { href: `${baseUrl}/users/${user.id}` },
        edit: { href: `${baseUrl}/users/${user.id}` },
        posts: { href: `${baseUrl}/users/${user.id}/posts` }
      }
    };
    
    // Add embedded posts if available
    if (user.posts?.length) {
      halUser._embedded = {
        posts: user.posts.map(post =&amp;gt; ({
          ...post,
          _links: { self: { href: `${baseUrl}/posts/${post.id}` } }
        }))
      };
    }
    
    if (!validateHalUser(halUser)) {
      throw new Error('Invalid HAL resource');
    }
    
    return halUser;
  }
}

// Usage in Express
app.get('/users/:id', async (req, res) =&amp;gt; {
  const user = await getUserById(req.params.id);
  const baseUrl = `${req.protocol}://${req.get('host')}`;
  
  res.json(HalBuilder.user(user, baseUrl));
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;4-testing-your-schemas-because-bugs-hide-in-edge-cases-&quot;&gt;4. Testing Your Schemas (Because Bugs Hide in Edge Cases)&lt;/h3&gt;
&lt;p&gt;Don't just validate production data – test your schemas themselves:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// schema-tests.ts
import { describe, it, expect } from 'vitest';
import Ajv from 'ajv';
import addFormats from 'ajv-formats';
import { halUserSchema, userRegistrationSchema } from './schemas';

const ajv = new Ajv({ allErrors: true });
addFormats(ajv);

describe('User Registration Schema', () =&amp;gt; {
  const validate = ajv.compile(userRegistrationSchema);
  
  it('should accept valid user data', () =&amp;gt; {
    const validUser = {
      username: &quot;johndoe123&quot;,
      email: &quot;*Emails are not allowed*&quot;,
      password: &quot;SecurePass123!&quot;,
      age: 25
    };
    
    expect(validate(validUser)).toBe(true);
  });
  
  it('should reject invalid email', () =&amp;gt; {
    const invalidUser = {
      username: &quot;johndoe123&quot;,
      email: &quot;not-an-email&quot;,
      password: &quot;SecurePass123!&quot;,
      age: 25
    };
    
    expect(validate(invalidUser)).toBe(false);
    expect(validate.errors).toContainEqual(
      expect.objectContaining({
        instancePath: '/email',
        message: 'must match format &quot;email&quot;'
      })
    );
  });
  
  it('should reject weak password', () =&amp;gt; {
    const invalidUser = {
      username: &quot;johndoe123&quot;,
      email: &quot;*Emails are not allowed*&quot;,
      password: &quot;weak&quot;,
      age: 25
    };
    
    expect(validate(invalidUser)).toBe(false);
  });
  
  it('should apply defaults', () =&amp;gt; {
    const userData = {
      username: &quot;johndoe123&quot;,
      email: &quot;*Emails are not allowed*&quot;,
      password: &quot;SecurePass123!&quot;,
      age: 25
    };
    
    validate(userData);
    expect(userData.preferences?.newsletter).toBe(false);
    expect(userData.preferences?.theme).toBe('light');
  });
});

describe('HAL User Schema', () =&amp;gt; {
  const validate = ajv.compile(halUserSchema);
  
  it('should accept valid HAL user', () =&amp;gt; {
    const halUser = {
      id: 123,
      name: &quot;Alice&quot;,
      email: &quot;*Emails are not allowed*&quot;,
      createdAt: &quot;2023-01-01T00:00:00.000Z&quot;,
      _links: {
        self: { href: &quot;/users/123&quot; },
        edit: { href: &quot;/users/123&quot; }
      }
    };
    
    expect(validate(halUser)).toBe(true);
  });
  
  it('should require _links.self', () =&amp;gt; {
    const invalidHalUser = {
      id: 123,
      name: &quot;Alice&quot;,
      email: &quot;*Emails are not allowed*&quot;,
      _links: {
        edit: { href: &quot;/users/123&quot; }
      }
    };
    
    expect(validate(invalidHalUser)).toBe(false);
    expect(validate.errors).toContainEqual(
      expect.objectContaining({
        instancePath: '/_links',
        message: &quot;must have required property 'self'&quot;
      })
    );
  });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;5-pro-tips-for-production-success&quot;&gt;5. Pro Tips for Production Success&lt;/h3&gt;
&lt;h4 id=&quot;1-schema-versioning-strategy&quot;&gt;1. Schema Versioning Strategy&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// Keep schemas versioned for backward compatibility
const schemas = {
  'v1': {
    user: userSchemaV1,
    post: postSchemaV1
  },
  'v2': {
    user: userSchemaV2,
    post: postSchemaV2
  }
};

function getValidator(resource: string, version: string = 'v2') {
  const schema = schemas[version]?.[resource];
  if (!schema) {
    throw new Error(`Schema not found: ${resource} v${version}`);
  }
  return ajv.compile(schema);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;2-performance-monitoring&quot;&gt;2. Performance Monitoring&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// Monitor validation performance
function createTimedValidator(schema: any, name: string) {
  const validate = ajv.compile(schema);
  
  return (data: any) =&amp;gt; {
    const start = process.hrtime.bigint();
    const result = validate(data);
    const end = process.hrtime.bigint();
    
    const duration = Number(end - start) / 1000000; // Convert to milliseconds
    console.log(`Validation ${name}: ${duration.toFixed(2)}ms`);
    
    return result;
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;3-schema-documentation-generation&quot;&gt;3. Schema Documentation Generation&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// Generate documentation from your schemas
function generateSchemaDoc(schema: any, title: string) {
  const doc = {
    title,
    properties: {},
    required: schema.required || []
  };
  
  for (const [prop, definition] of Object.entries(schema.properties || {})) {
    doc.properties[prop] = {
      type: definition.type,
      description: definition.description || `${prop} field`,
      required: schema.required?.includes(prop) || false,
      example: generateExample(definition)
    };
  }
  
  return doc;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;wrapping-up-the-series&quot;&gt;Wrapping Up the Series&lt;/h3&gt;
&lt;p&gt;Congratulations! You've journeyed from JSON Schema basics to production-ready implementations. Here's what you've gained:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Part 1&lt;/strong&gt;: Understanding JSON Schema fundamentals and YAML compatibility&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Part 2&lt;/strong&gt;: Mastering AJV implementation with advanced features and TypeScript integration&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Part 3&lt;/strong&gt;: Real-world patterns including API validation, ETL pipelines, and HAL hypermedia APIs&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;your-next-steps&quot;&gt;Your Next Steps&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Start Small&lt;/strong&gt;: Pick one use case (maybe API validation) and implement it&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Build Your Schema Library&lt;/strong&gt;: Create reusable schemas for your common data structures&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Test Everything&lt;/strong&gt;: Write tests for both your schemas and your validation logic&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Monitor Performance&lt;/strong&gt;: Keep an eye on validation performance in production&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Document Your Schemas&lt;/strong&gt;: Good schemas are self-documenting, but examples help&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;em&gt;You're now equipped to bring order to the chaos of unvalidated data. Go forth and validate with confidence!&lt;/em&gt; :rocket:&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Series Navigation:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/5669/json-schema-your-datas-new-best-friend&quot;&gt;Part 1: Understanding the Basics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/5928/json-schema-with-ajv-implementation-deep-dive&quot;&gt;Part 2: Implementation with AJV&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Part 3: Real-World Applications &amp;amp; HAL&lt;/strong&gt; ← You are here&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/6206/json-schema-in-the-wild-real-world-applications-hal</guid>
<pubDate>Wed, 08 Oct 2025 13:05:06 +0000</pubDate>
</item>
<item>
<title>JSON Schema with AJV: Implementation Deep Dive ⚡</title>
<link>https://coderlegion.com/5928/json-schema-with-ajv-implementation-deep-dive</link>
<description>&lt;p&gt;&lt;em&gt;Part 2 of 3: Getting Your Hands Dirty&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Welcome back! In &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/5669/json-schema-your-datas-new-best-friend&quot;&gt;Part 1&lt;/a&gt;, we covered the basics of JSON Schema and why it's awesome. Now it's time to roll up our sleeves and implement some real validation with AJV (Another JSON Schema Validator) – the Swiss Army knife of JSON validation.&lt;/p&gt;
&lt;h3 id=&quot;why-ajv-rules-the-validation-world&quot;&gt;Why AJV Rules the Validation World&lt;/h3&gt;
&lt;p&gt;AJV isn't just another validation library – it's THE validation library. Here's why developers love it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Blazingly Fast&lt;/strong&gt;: Compiles schemas to optimized JavaScript functions&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fully Compliant&lt;/strong&gt;: Supports JSON Schema draft-07 and draft 2019-09&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Extensible&lt;/strong&gt;: Custom keywords, formats, and error messages&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TypeScript Ready&lt;/strong&gt;: Excellent TypeScript support out of the box&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;getting-started-installation-and-basic-setup&quot;&gt;Getting Started: Installation and Basic Setup&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// The essentials
npm install ajv

// Optional but recommended additions
npm install ajv-formats ajv-errors ajv-keywords
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;import Ajv from 'ajv';
import addFormats from 'ajv-formats';

// Create your AJV instance
const ajv = new Ajv({ 
  allErrors: true,        // Collect all errors, not just the first
  removeAdditional: true, // Remove additional properties
  useDefaults: true       // Apply default values
});

// Add common formats (email, date, uri, etc.)
addFormats(ajv);

// Now you're ready to validate!
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;your-first-real-validation&quot;&gt;Your First Real Validation&lt;/h3&gt;
&lt;p&gt;Let's build something practical – a user registration validator:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;const userRegistrationSchema = {
  type: &quot;object&quot;,
  properties: {
    username: {
      type: &quot;string&quot;,
      minLength: 3,
      maxLength: 20,
      pattern: &quot;^[a-zA-Z0-9_]+$&quot;
    },
    email: {
      type: &quot;string&quot;,
      format: &quot;email&quot;
    },
    password: {
      type: &quot;string&quot;,
      minLength: 8,
      pattern: &quot;^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&amp;amp;])[A-Za-z\\d@$!%*?&amp;amp;]&quot;
    },
    age: {
      type: &quot;number&quot;,
      minimum: 13,
      maximum: 120
    },
    preferences: {
      type: &quot;object&quot;,
      properties: {
        newsletter: { type: &quot;boolean&quot;, default: false },
        theme: { enum: [&quot;light&quot;, &quot;dark&quot;], default: &quot;light&quot; }
      },
      additionalProperties: false
    }
  },
  required: [&quot;username&quot;, &quot;email&quot;, &quot;password&quot;, &quot;age&quot;],
  additionalProperties: false
};

// Compile the schema (do this once, reuse many times)
const validateUser = ajv.compile(userRegistrationSchema);

// Test it out
function registerUser(userData: any) {
  const valid = validateUser(userData);
  
  if (!valid) {
    console.log('Validation failed:');
    validateUser.errors?.forEach(error =&amp;gt; {
      console.log(`- ${error.instancePath}: ${error.message}`);
    });
    return null;
  }
  
  console.log('✅ User data is valid!');
  return userData; // Now with defaults applied!
}

// Try it
const newUser = {
  username: &quot;coolguy123&quot;,
  email: &quot;*Emails are not allowed*&quot;,
  password: &quot;SuperSecret123!&quot;,
  age: 25
};

registerUser(newUser);
// ✅ User data is valid!
// Note: preferences.newsletter and preferences.theme get default values
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;advanced-ajv-features-that-ll-blow-your-mind&quot;&gt;Advanced AJV Features That'll Blow Your Mind&lt;/h3&gt;
&lt;h4 id=&quot;1-custom-keywords&quot;&gt;1. Custom Keywords&lt;/h4&gt;
&lt;p&gt;Sometimes the built-in validation isn't enough. Let's create a custom keyword:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// Add a custom &quot;isAdult&quot; keyword
ajv.addKeyword({
  keyword: 'isAdult',
  type: 'number',
  schemaType: 'boolean',
  compile(schemaVal) {
    return function validate(data) {
      if (schemaVal) {
        return data &amp;gt;= 18;
      }
      return true;
    }
  },
  errors: false,
  metaSchema: {
    type: &quot;boolean&quot;
  }
});

// Use it in a schema
const adultSchema = {
  type: &quot;object&quot;,
  properties: {
    age: { 
      type: &quot;number&quot;, 
      isAdult: true 
    }
  }
};

const validateAdult = ajv.compile(adultSchema);
console.log(validateAdult({ age: 17 })); // false
console.log(validateAdult({ age: 21 })); // true
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;2-conditional-validation&quot;&gt;2. Conditional Validation&lt;/h4&gt;
&lt;p&gt;Real-world data often has complex relationships. AJV handles this beautifully:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;const conditionalSchema = {
  type: &quot;object&quot;,
  properties: {
    userType: { enum: [&quot;admin&quot;, &quot;user&quot;, &quot;guest&quot;] },
    permissions: { 
      type: &quot;array&quot;,
      items: { type: &quot;string&quot; }
    },
    adminKey: { type: &quot;string&quot; }
  },
  required: [&quot;userType&quot;],
  
  // If user is admin, they need permissions and adminKey
  if: { 
    properties: { 
      userType: { const: &quot;admin&quot; } 
    } 
  },
  then: { 
    required: [&quot;permissions&quot;, &quot;adminKey&quot;],
    properties: {
      permissions: { minItems: 1 }
    }
  },
  
  // If user is guest, no additional requirements
  else: {
    if: { 
      properties: { 
        userType: { const: &quot;guest&quot; } 
      } 
    },
    then: {
      // Guests can't have permissions
      not: {
        required: [&quot;permissions&quot;]
      }
    }
  }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;3-schema-composition-with-ref&quot;&gt;3. Schema Composition with $ref&lt;/h4&gt;
&lt;p&gt;Keep your schemas DRY and maintainable:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;const schemas = {
  // Base address schema
  address: {
    $id: &quot;https://example.com/schemas/address.json&quot;,
    type: &quot;object&quot;,
    properties: {
      street: { type: &quot;string&quot; },
      city: { type: &quot;string&quot; },
      zipCode: { 
        type: &quot;string&quot;, 
        pattern: &quot;^[0-9]{5}(-[0-9]{4})?$&quot; 
      }
    },
    required: [&quot;street&quot;, &quot;city&quot;, &quot;zipCode&quot;]
  },
  
  // Person schema using address
  person: {
    $id: &quot;https://example.com/schemas/person.json&quot;,
    type: &quot;object&quot;,
    properties: {
      name: { type: &quot;string&quot; },
      homeAddress: { $ref: &quot;address.json&quot; },
      workAddress: { $ref: &quot;address.json&quot; }
    }
  }
};

// Add schemas to AJV
ajv.addSchema(schemas.address);
ajv.addSchema(schemas.person);

const validatePerson = ajv.getSchema(&quot;https://example.com/schemas/person.json&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;error-handling-like-a-pro&quot;&gt;Error Handling Like a Pro&lt;/h3&gt;
&lt;p&gt;Default AJV errors are... functional. But we can make them much better:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;import addErrors from 'ajv-errors';
addErrors(ajv);

const betterUserSchema = {
  type: &quot;object&quot;,
  properties: {
    username: {
      type: &quot;string&quot;,
      minLength: 3,
      maxLength: 20,
      pattern: &quot;^[a-zA-Z0-9_]+$&quot;
    },
    password: {
      type: &quot;string&quot;,
      minLength: 8,
      pattern: &quot;^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)&quot;
    }
  },
  required: [&quot;username&quot;, &quot;password&quot;],
  
  // Custom error messages
  errorMessage: {
    properties: {
      username: &quot;Username must be 3-20 characters long and contain only letters, numbers, and underscores&quot;,
      password: &quot;Password must be at least 8 characters with uppercase, lowercase, and a number&quot;
    },
    required: {
      username: &quot;Username is required&quot;,
      password: &quot;Password is required&quot;
    }
  }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;performance-tips-that-actually-matter&quot;&gt;Performance Tips That Actually Matter&lt;/h3&gt;
&lt;h4 id=&quot;1-compile-once-use-many-times&quot;&gt;1. Compile Once, Use Many Times&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// ❌ DON'T do this
function validateUserData(data: any) {
  const validate = ajv.compile(userSchema); // Compiling every time!
  return validate(data);
}

// ✅ DO this instead
const validateUserData = ajv.compile(userSchema); // Compile once

function checkUser(data: any) {
  return validateUserData(data); // Reuse compiled function
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;2-use-removeadditional-for-large-objects&quot;&gt;2. Use removeAdditional for Large Objects&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;const ajv = new Ajv({ 
  removeAdditional: &quot;all&quot; // Strip unknown properties automatically
});

// Input: { name: &quot;Bob&quot;, age: 25, secretHackerData: &quot;evil&quot; }
// After validation: { name: &quot;Bob&quot;, age: 25 }
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;3-optimize-for-your-use-case&quot;&gt;3. Optimize for Your Use Case&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// For APIs - fast validation, detailed errors
const apiAjv = new Ajv({ 
  allErrors: true,
  verbose: true 
});

// For data processing - fastest validation
const processingAjv = new Ajv({ 
  allErrors: false,
  verbose: false,
  validateSchema: false
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;typescript-integration-the-cherry-on-top-&quot;&gt;TypeScript Integration (The Cherry on Top)&lt;/h3&gt;
&lt;p&gt;AJV plays beautifully with TypeScript:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;import { JSONSchemaType } from 'ajv';

// Define your TypeScript interface
interface User {
  id: string;
  name: string;
  age: number;
  email?: string;
}

// Create a typed schema
const userSchema: JSONSchemaType&amp;lt;User&amp;gt; = {
  type: &quot;object&quot;,
  properties: {
    id: { type: &quot;string&quot; },
    name: { type: &quot;string&quot; },
    age: { type: &quot;number&quot; },
    email: { type: &quot;string&quot;, nullable: true }
  },
  required: [&quot;id&quot;, &quot;name&quot;, &quot;age&quot;],
  additionalProperties: false
};

// Compile with type safety
const validateUser = ajv.compile(userSchema);

// TypeScript knows the validated data structure!
function processUser(userData: unknown): User | null {
  if (validateUser(userData)) {
    // userData is now typed as User!
    return userData;
  }
  return null;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;common-gotchas-learn-from-my-mistakes-&quot;&gt;Common Gotchas (Learn From My Mistakes)&lt;/h3&gt;
&lt;h4 id=&quot;1-format-validation-needs-ajv-formats&quot;&gt;1. Format Validation Needs ajv-formats&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// This won't work without ajv-formats
const schema = {
  type: &quot;string&quot;,
  format: &quot;email&quot; // Silently ignored!
};

// Fix it
import addFormats from 'ajv-formats';
addFormats(ajv);
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;2-regexp-patterns-need-double-escaping&quot;&gt;2. RegExp Patterns Need Double Escaping&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// ❌ Wrong
pattern: &quot;^\d{3}-\d{3}-\d{4}$&quot;

// ✅ Correct
pattern: &quot;^\\d{3}-\\d{3}-\\d{4}$&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;3-additionalproperties-vs-properties&quot;&gt;3. additionalProperties vs Properties&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// This allows ANY additional properties
const schema1 = {
  type: &quot;object&quot;,
  properties: { name: { type: &quot;string&quot; } }
  // additionalProperties defaults to true
};

// This blocks additional properties
const schema2 = {
  type: &quot;object&quot;,
  properties: { name: { type: &quot;string&quot; } },
  additionalProperties: false
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;wrapping-up-part-2&quot;&gt;Wrapping Up Part 2&lt;/h3&gt;
&lt;p&gt;You now have the power to implement robust, fast, and maintainable validation in your applications! AJV transforms JSON Schema from a simple concept into a validation powerhouse.&lt;/p&gt;
&lt;h3 id=&quot;coming-up-in-part-3-&quot;&gt;Coming Up in Part 3...&lt;/h3&gt;
&lt;p&gt;In the final part of our series, we'll explore real-world applications:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Building API validation middleware&lt;/li&gt;
&lt;li&gt;Data pipeline validation patterns&lt;/li&gt;
&lt;li&gt;HAL (Hypermedia) schemas for self-describing APIs&lt;/li&gt;
&lt;li&gt;Testing strategies for schema validation&lt;/li&gt;
&lt;li&gt;Production ready tips&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Ready to put this knowledge to work in production? Part 3 is where the magic happens!&lt;/em&gt; &lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Series Navigation:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/5669/json-schema-your-datas-new-best-friend&quot;&gt;Part 1: Understanding the Basics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Part 2: Implementation with AJV&lt;/strong&gt; ← You are here  &lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/6206/json-schema-in-the-wild-real-world-applications-hal&quot;&gt;Part 3: Real-World Applications &amp;amp; HAL&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/5928/json-schema-with-ajv-implementation-deep-dive</guid>
<pubDate>Fri, 03 Oct 2025 04:08:27 +0000</pubDate>
</item>
<item>
<title>JSON Schema: Your Data's New Best Friend ️</title>
<link>https://coderlegion.com/5669/json-schema-your-datas-new-best-friend</link>
<description>&lt;p&gt;&lt;em&gt;Part 1 of 3: Understanding the Basics&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Ever had that moment where your API returns &lt;code&gt;{ age: &quot;twenty-five&quot; }&lt;/code&gt; instead of &lt;code&gt;{ age: 25 }&lt;/code&gt; and your whole app breaks? Yeah, we've all been there. Let's talk about JSON Schema – your new best friend in the fight against unpredictable data.&lt;/p&gt;
&lt;h3 id=&quot;what-the-heck-is-json-schema-anyway-&quot;&gt;What the Heck is JSON Schema Anyway?&lt;/h3&gt;
&lt;p&gt;JSON Schema is basically a contract for your JSON data. Think of it like TypeScript's type definitions, but for runtime validation. It tells your data: &quot;Hey, you better look exactly like this, or we're gonna have problems.&quot;&lt;/p&gt;
&lt;p&gt;The concept emerged around 2009-2010 when developers got tired of writing custom validation logic for every single API endpoint. Someone smart said, &quot;What if we could describe our data structure using... more JSON?&quot; And boom – JSON Schema was born.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// Instead of this mess:
if (!data.name || typeof data.name !== 'string') {
  throw new Error('Name is required and must be a string');
}
if (!data.age || typeof data.age !== 'number' || data.age &amp;lt; 0) {
  throw new Error('Age must be a positive number');
}
// ... 50 more lines of validation

// You get this beauty:
const schema = {
  type: &quot;object&quot;,
  properties: {
    name: { type: &quot;string&quot; },
    age: { type: &quot;number&quot;, minimum: 0 }
  },
  required: [&quot;name&quot;, &quot;age&quot;]
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;the-anatomy-of-a-json-schema&quot;&gt;The Anatomy of a JSON Schema&lt;/h3&gt;
&lt;p&gt;Let's break down what makes a schema tick:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const userSchema = {
  // Root type - what kind of data are we expecting?
  type: &quot;object&quot;,
  
  // Define the shape of our object
  properties: {
    name: { 
      type: &quot;string&quot;, 
      minLength: 1,
      maxLength: 100 
    },
    age: { 
      type: &quot;number&quot;, 
      minimum: 0, 
      maximum: 150 
    },
    email: { 
      type: &quot;string&quot;, 
      format: &quot;email&quot;  // Built-in format validation!
    },
    hobbies: {
      type: &quot;array&quot;,
      items: { type: &quot;string&quot; },
      maxItems: 10,
      uniqueItems: true
    },
    isActive: { 
      type: &quot;boolean&quot; 
    }
  },
  
  // Which fields are mandatory?
  required: [&quot;name&quot;, &quot;age&quot;],
  
  // Should we allow extra properties?
  additionalProperties: false
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;common-schema-patterns-you-ll-love&quot;&gt;Common Schema Patterns You'll Love&lt;/h3&gt;
&lt;h4 id=&quot;1-enums-because-options-are-limited-&quot;&gt;1. Enums (Because Options Are Limited)&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const statusSchema = {
  type: &quot;object&quot;,
  properties: {
    status: { 
      enum: [&quot;active&quot;, &quot;inactive&quot;, &quot;pending&quot;, &quot;banned&quot;] 
    },
    priority: {
      type: &quot;string&quot;,
      enum: [&quot;low&quot;, &quot;medium&quot;, &quot;high&quot;, &quot;urgent&quot;]
    }
  }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;2-nested-objects-objects-all-the-way-down-&quot;&gt;2. Nested Objects (Objects All the Way Down)&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const addressSchema = {
  type: &quot;object&quot;,
  properties: {
    street: { type: &quot;string&quot; },
    city: { type: &quot;string&quot; },
    country: { type: &quot;string&quot; },
    zipCode: { 
      type: &quot;string&quot;, 
      pattern: &quot;^[1-9][0-9]{5}$&quot; // India ZIP code pattern
    }
  },
  required: [&quot;street&quot;, &quot;city&quot;, &quot;country&quot;]
};

const personSchema = {
  type: &quot;object&quot;,
  properties: {
    name: { type: &quot;string&quot; },
    homeAddress: addressSchema,  // Nested schema
    workAddress: addressSchema   // Reused!
  }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;3-arrays-with-specific-item-types&quot;&gt;3. Arrays with Specific Item Types&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const orderSchema = {
  type: &quot;object&quot;,
  properties: {
    orderId: { type: &quot;string&quot; },
    items: {
      type: &quot;array&quot;,
      minItems: 1,
      items: {
        type: &quot;object&quot;,
        properties: {
          productId: { type: &quot;string&quot; },
          quantity: { type: &quot;number&quot;, minimum: 1 },
          price: { type: &quot;number&quot;, minimum: 0 }
        },
        required: [&quot;productId&quot;, &quot;quantity&quot;, &quot;price&quot;]
      }
    }
  }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;yaml-schema-json-schema-s-chill-cousin&quot;&gt;YAML Schema: JSON Schema's Chill Cousin&lt;/h3&gt;
&lt;p&gt;Here's a cool secret – JSON Schema works perfectly with YAML too! Since YAML is essentially JSON's more readable sibling, you can use the same schema definitions.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# Your YAML data
name: &quot;Alice&quot;
age: 30
email: &quot;*Emails are not allowed*&quot;
hobbies:
  - &quot;coding&quot;
  - &quot;coffee&quot;
  - &quot;cats&quot;

# Same schema validates both JSON and YAML!
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The validation logic doesn't care if your data started as JSON or YAML – it all becomes the same object structure in memory anyway. This means you can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use JSON Schema to validate YAML config files&lt;/li&gt;
&lt;li&gt;Accept both JSON and YAML in your APIs&lt;/li&gt;
&lt;li&gt;Keep one schema definition for multiple data formats&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;why-should-you-care-&quot;&gt;Why Should You Care?&lt;/h3&gt;
&lt;h4 id=&quot;1-catch-errors-early&quot;&gt;1. Catch Errors Early&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// This will blow up at runtime somewhere deep in your code
const user = { name: &quot;Bob&quot;, age: &quot;thirty-five&quot; };
calculateInsurance(user.age * 1.2); // NaN strikes again!

// This gets caught immediately with schema validation
// Error: /age: should be number
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;2-self-documenting-apis&quot;&gt;2. Self-Documenting APIs&lt;/h4&gt;
&lt;p&gt;Your schema IS your documentation. No more outdated API docs that say &lt;code&gt;age&lt;/code&gt; is a number when it's actually a string.&lt;/p&gt;
&lt;h4 id=&quot;3-frontend-backend-agreement&quot;&gt;3. Frontend/Backend Agreement&lt;/h4&gt;
&lt;p&gt;When both sides validate against the same schema, miscommunication becomes impossible. It's like having a peace treaty for your data structures.&lt;/p&gt;
&lt;h4 id=&quot;4-better-error-messages&quot;&gt;4. Better Error Messages&lt;/h4&gt;
&lt;p&gt;Instead of &quot;Something went wrong,&quot; you get &quot;Property 'email' should match format 'email'.&quot; Your users (and your future self) will thank you.&lt;/p&gt;
&lt;h3 id=&quot;coming-up-next-&quot;&gt;Coming Up Next...&lt;/h3&gt;
&lt;p&gt;In &lt;strong&gt;Part 2&lt;/strong&gt;, we'll dive deep into implementing JSON Schema validation with AJV, including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Setting up AJV in your project&lt;/li&gt;
&lt;li&gt;Advanced validation features&lt;/li&gt;
&lt;li&gt;Custom keywords and error messages&lt;/li&gt;
&lt;li&gt;Performance optimization tips&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Ready to never debug mysterious data issues at 2 AM again? Let's make it happen!&lt;/em&gt; &lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Series Navigation:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Part 1: Understanding the Basics&lt;/strong&gt; ← You are here&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/5928/json-schema-with-ajv-implementation-deep-dive&quot;&gt;Part 2: Implementation with AJV&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/6206/json-schema-in-the-wild-real-world-applications-hal&quot;&gt;Part 3: Real-World Applications &amp;amp; HAL&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/5669/json-schema-your-datas-new-best-friend</guid>
<pubDate>Sat, 27 Sep 2025 11:49:08 +0000</pubDate>
</item>
</channel>
</rss>