JSON Schema for Polyglot Engineers

Table of Contents

1. JSON Schema Through the Lens of Type Systems

1.1. Overview

JSON Schema can be understood through familiar type system concepts from various languages:

Language Concept JSON Schema Equivalent
Haskell ADTs oneOf, anyOf
TypeScript Union Types oneOf
Clojure Spec properties, patterns
Python Type Hints type definitions
Scheme Contracts validation keywords

1.2. Type System Parallels

// TypeScript union type
type Status = "active" | "inactive" | "pending"
{
  "type": "string",
  "enum": ["active", "inactive", "pending"]
}

2. Practical Examples

2.1. Record Types (Haskell → JSON Schema)

data User = User
  { userId :: Int
  , userName :: String
  , userEmail :: Maybe String
  }
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "userId": { "type": "integer" },
    "userName": { "type": "string" },
    "userEmail": { 
      "oneOf": [
        { "type": "string", "format": "email" },
        { "type": "null" }
      ]
    }
  },
  "required": ["userId", "userName"]
}

2.2. Clojure Spec Translation

(require '[clojure.spec.alpha :as s])

(s/def ::age (s/and int? #(>= % 0)))
(s/def ::name string?)
(s/def ::person (s/keys :req-un [::name ::age]))
{
  "type": "object",
  "properties": {
    "age": {
      "type": "integer",
      "minimum": 0
    },
    "name": {
      "type": "string"
    }
  },
  "required": ["name", "age"]
}

2.3. TypeScript Interface Mapping

interface ApiResponse<T> {
  status: 'success' | 'error';
  data?: T;
  error?: {
    code: number;
    message: string;
  };
}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "status": {
      "type": "string",
      "enum": ["success", "error"]
    },
    "data": {
      "$comment": "Generic type parameter T",
      "description": "Any valid JSON value"
    },
    "error": {
      "type": "object",
      "properties": {
        "code": { "type": "integer" },
        "message": { "type": "string" }
      },
      "required": ["code", "message"]
    }
  },
  "required": ["status"],
  "allOf": [
    {
      "if": {
        "properties": { "status": { "const": "success" } }
      },
      "then": {
        "required": ["data"]
      }
    },
    {
      "if": {
        "properties": { "status": { "const": "error" } }
      },
      "then": {
        "required": ["error"]
      }
    }
  ]
}

3. Advanced Pattern Matching

3.1. Haskell-style Pattern Matching

data Shape
  = Circle Double
  | Rectangle Double Double
  | Triangle Double Double Double
{
  "type": "object",
  "oneOf": [
    {
      "type": "object",
      "properties": {
        "type": { "const": "circle" },
        "radius": { "type": "number" }
      },
      "required": ["type", "radius"]
    },
    {
      "type": "object",
      "properties": {
        "type": { "const": "rectangle" },
        "width": { "type": "number" },
        "height": { "type": "number" }
      },
      "required": ["type", "width", "height"]
    },
    {
      "type": "object",
      "properties": {
        "type": { "const": "triangle" },
        "a": { "type": "number" },
        "b": { "type": "number" },
        "c": { "type": "number" }
      },
      "required": ["type", "a", "b", "c"]
    }
  ]
}

4. Validation Functions

4.1. Python Type Validation

from typing import TypedDict, Literal, Union
from dataclasses import dataclass

class Success(TypedDict):
    status: Literal['success']
    data: dict

class Error(TypedDict):
    status: Literal['error']
    message: str

Response = Union[Success, Error]
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "oneOf": [
    {
      "type": "object",
      "properties": {
        "status": { "const": "success" },
        "data": { "type": "object" }
      },
      "required": ["status", "data"]
    },
    {
      "type": "object",
      "properties": {
        "status": { "const": "error" },
        "message": { "type": "string" }
      },
      "required": ["status", "message"]
    }
  ]
}

5. Recursive Types

5.1. Scheme-like List Structure

(define-record-type <tree-node>
  (make-node value left right)
  node?
  (value node-value)
  (left node-left)
  (right node-right))
ice-9/boot-9.scm:1676:22: In procedure raise-exception:
Unbound variable: define-record-type

Entering a new prompt.  Type `,bt' for a backtrace or `,q' to continue.
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "Binary Tree Node",
  "$defs": {
    "node": {
      "type": "object",
      "properties": {
        "value": { "type": "number" },
        "left": { 
          "oneOf": [
            { "$ref": "#/$defs/node" },
            { "type": "null" }
          ]
        },
        "right": {
          "oneOf": [
            { "$ref": "#/$defs/node" },
            { "type": "null" }
          ]
        }
      },
      "required": ["value"]
    }
  },
  "$ref": "#/$defs/node"
}

6. Testing and Validation

6.1. Property-Based Testing (QuickCheck Style)

import Test.QuickCheck

prop_validJson :: User -> Property
prop_validJson user = 
  validateSchema userSchema (toJSON user) === Right True
from hypothesis import given
from hypothesis.strategies import builds, text, integers

@given(builds(User, 
       user_id=integers(min_value=0),
       user_name=text()))
def test_user_schema(user):
    assert validate_schema(user_schema, user.dict())

7. Practical Applications

7.1. API Contract Definition

// TypeScript API definition
type APIEndpoint = {
  path: string;
  method: 'GET' | 'POST' | 'PUT' | 'DELETE';
  requestBody?: unknown;
  response: unknown;
}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "path": { 
      "type": "string",
      "pattern": "^/"
    },
    "method": {
      "type": "string",
      "enum": ["GET", "POST", "PUT", "DELETE"]
    },
    "requestBody": {
      "$comment": "Schema for request body"
    },
    "response": {
      "$comment": "Schema for response body"
    }
  },
  "required": ["path", "method", "response"]
}

8. Code Generation Examples

8.1. Generate TypeScript from Schema

// Generated from JSON Schema
interface User {
  userId: number;
  userName: string;
  userEmail?: string | null;
}

// Usage with type safety
const user: User = {
  userId: 1,
  userName: "test",
  userEmail: null
};

8.2. Generate Python Dataclasses

# Generated from JSON Schema
@dataclass
class User:
    user_id: int
    user_name: str
    user_email: Optional[str] = None

# Usage with type hints
user = User(user_id=1, user_name="test")

9. Common Patterns and Best Practices

9.1. Algebraic Data Types

data Either a b = Left a | Right b
{
  "oneOf": [
    {
      "type": "object",
      "properties": {
        "type": { "const": "Left" },
        "value": { "$ref": "#/$defs/a" }
      },
      "required": ["type", "value"]
    },
    {
      "type": "object",
      "properties": {
        "type": { "const": "Right" },
        "value": { "$ref": "#/$defs/b" }
      },
      "required": ["type", "value"]
    }
  ]
}

10. Babel Code Blocks for Testing

import json
from jsonschema import validate

schema = {
    "type": "object",
    "properties": {
        "name": {"type": "string"},
        "age": {"type": "integer", "minimum": 0}
    },
    "required": ["name"]
}

# Valid
validate({"name": "John", "age": 30}, schema)
print("Valid schema validated successfully")

# This would raise ValidationError
# validate({"age": "invalid"}, schema)

11. Integration Examples

11.1. Express.js Middleware

import { validate } from 'jsonschema';

const validateMiddleware = (schema: any) => (
  req: Request,
  res: Response,
  next: NextFunction
) => {
  const result = validate(req.body, schema);
  if (result.valid) {
    next();
  } else {
    res.status(400).json({ errors: result.errors });
  }
};

12. Resources and Further Reading

Author: Tutorial Series

jasonwalsh@jasons-mbp.lan

Last Updated: 2024-11-08 08:00:04