npm package: @speria-jp/json-schema-validator-gen
Description: A lightweight JSON Schema validator generator that creates minimal TypeScript code and type definitions
Author: Speria Inc.
Latest version: 0.3.2

# json-schema-validator-gen

A lightweight JSON Schema validator generator that creates minimal TypeScript code and type definitions.

## Features

- 🚀 **Zero runtime dependencies** - Generated validators are standalone
- 🔍 **Type-safe** - Full TypeScript support with generated type definitions
- ⚡ **Fast validation** - No runtime schema parsing, direct code execution
- 🌐 **Universal** - Works in Node.js and browsers
- 📋 **Detailed errors** - Rich error information with path, expected/received values

## Installation

```bash
npm install -D @speria-jp/json-schema-validator-gen
# or
yarn add -D @speria-jp/json-schema-validator-gen
# or
pnpm add -D @speria-jp/json-schema-validator-gen
# or
bun add -D @speria-jp/json-schema-validator-gen
```

## Usage

### CLI

Basic usage:

```bash
# Generate from root schema (type name derived from file name)
npx @speria-jp/json-schema-validator-gen -s user-schema.json -o validator.ts

# Generate with custom type name
npx @speria-jp/json-schema-validator-gen -s schema.json -o validator.ts \
  -t 'path=#,name=User'

# Generate from specific definition
npx @speria-jp/json-schema-validator-gen -s schema.json -o validator.ts \
  -t '#/$defs/User'

# Generate multiple types with custom names
npx @speria-jp/json-schema-validator-gen -s schema.json -o types.ts \
  -t 'path=#/$defs/User,name=AppUser' \
  -t 'path=#/$defs/Post,name=BlogPost'
```

If installed locally, you can also use your package manager directly:

```bash
# npm
npx json-schema-validator-gen -s schema.json -o validator.ts

# yarn
yarn json-schema-validator-gen -s schema.json -o validator.ts

# pnpm
pnpm json-schema-validator-gen -s schema.json -o validator.ts

# bun
bun json-schema-validator-gen -s schema.json -o validator.ts
```

Options:
- `-s, --schema` - Path to JSON Schema file (required)
- `-o, --output` - Output path for generated code (required)
- `-t, --target` - JSON Schema target path (e.g., `#/$defs/User`). Can be specified multiple times to generate multiple types. Supports custom type names with `path=...,name=...` format. Defaults to `#` (root schema)
- `-h, --help` - Show help message

### Programmatic API

```typescript
import { generate } from '@speria-jp/json-schema-validator-gen';

// Generate from root schema (default, type name derived from file name)
const result = await generate({
  schemaPath: './user-schema.json',
  outputPath: './validator.ts'
});

// Generate from root with custom type name
const result = await generate({
  schemaPath: './schema.json',
  outputPath: './validator.ts',
  targets: ['path=#,name=User']
});

// Generate from specific targets
const results = await generate({
  schemaPath: './schema.json',
  outputPath: './types.ts',
  targets: ['#/$defs/User', '#/$defs/Post']
});

// Generate with custom type names
const results = await generate({
  schemaPath: './schema.json',
  outputPath: './types.ts',
  targets: [
    'path=#/$defs/User,name=AppUser',
    'path=#/$defs/Post,name=BlogPost'
  ]
});
```

### Example

Given a JSON Schema:

```json
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "title": "User",
  "required": ["id", "name", "email"],
  "properties": {
    "id": {
      "type": "integer",
      "minimum": 1
    },
    "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 100
    },
    "email": {
      "type": "string",
      "format": "email",
      "pattern": "^[\\w.-]+@[\\w.-]+\\.[a-z]{2,}$"
    },
    "age": {
      "type": "integer",
      "minimum": 0,
      "maximum": 150
    },
    "tags": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "role": {
      "type": "string",
      "enum": ["admin", "user", "guest"]
    }
  },
  "additionalProperties": false
}
```

The generator creates:

```typescript
export type User = {
    id: number;
    name: string;
    email: string;
    age?: number;
    tags?: string[];
    role?: "admin" | "user" | "guest";
};

// Returns ValidationResult with detailed error information
export function validateUser(value: unknown, options?: ValidationOptions): ValidationResult<User> {
    // ... validation logic
}

// Throws an error if validation fails, returns the validated value otherwise
export function unsafeValidateUser(value: unknown): User {
    // ... validation logic
}
```

Usage:

```typescript
import { validateUser, unsafeValidateUser, type User } from './validator';

// Using validateUser - returns ValidationResult
const result = validateUser(data);
if (result.success) {
    console.log('Valid user:', result.data);
} else {
    console.log('Validation errors:', result.issues);
    // Each issue contains: code, path, message, expected, received
}

// Using unsafeValidateUser - throws on invalid input
try {
    const user = unsafeValidateUser(data);
    console.log('Valid user:', user);
} catch (error) {
    console.log('Validation failed:', error.message);
}

// Abort early option - stop on first error
const result = validateUser(data, { abortEarly: true });
```

### Multiple Types Example

You can generate multiple types from a single JSON Schema file using the `--target` option:

```bash
# Generate User and Post types from $defs (type names derived from path)
npx json-schema-validator-gen -s schema.json -o types.ts \
  -t '#/$defs/User' -t '#/$defs/Post'
```

Given a JSON Schema with `$defs`:

```json
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$defs": {
    "Address": {
      "type": "object",
      "properties": {
        "street": { "type": "string" },
        "city": { "type": "string" },
        "zipCode": { "type": "string" }
      },
      "required": ["street", "city"]
    },
    "User": {
      "type": "object",
      "properties": {
        "id": { "type": "string" },
        "name": { "type": "string" },
        "email": { "type": "string" },
        "address": { "$ref": "#/$defs/Address" }
      },
      "required": ["id", "name", "email"]
    },
    "Post": {
      "type": "object",
      "properties": {
        "id": { "type": "string" },
        "title": { "type": "string" },
        "authorId": { "type": "string" }
      },
      "required": ["id", "title", "authorId"]
    }
  }
}
```

The generator automatically collects dependencies and creates all necessary types in a single file. Note that `Address` is also generated because it's referenced by `User`, but it's not exported since it wasn't specified in `--target`:

```typescript
// Address type (generated as dependency, not exported)
type Address = {
    street: string;
    city: string;
    zipCode?: string;
};

// User type uses type reference instead of inline expansion
export type User = {
    id: string;
    name: string;
    email: string;
    address?: Address;
};

export type Post = {
    id: string;
    title: string;
    authorId: string;
};

// Dependency validator (not exported)
function validateAddress(value: unknown): ValidationResult<Address> { /* ... */ }

// Exported validators for User and Post
export function validateUser(value: unknown): ValidationResult<User> { /* ... */ }
export function unsafeValidateUser(value: unknown): User { /* ... */ }

export function validatePost(value: unknown): ValidationResult<Post> { /* ... */ }
export function unsafeValidatePost(value: unknown): Post { /* ... */ }
```

## Validation Result

Generated validators return a `ValidationResult<T>` discriminated union:

```typescript
type ValidationResult<T> =
  | { success: true; data: T }
  | { success: false; issues: ValidationIssue[] };

interface ValidationIssue {
  code: ValidationIssueCode;
  path: (string | number)[];  // e.g., ["address", "zipCode"] or ["items", 0]
  message: string;
  expected: string;
  received: string;
}
```

### Error Codes

| Code | Description | Example |
|------|-------------|---------|
| `invalid_type` | Value has wrong type | Expected `string`, received `number` |
| `missing_key` | Required property is missing | Missing `email` property |
| `too_small` | Value below minimum | Number < minimum, string length < minLength |
| `too_big` | Value above maximum | Number > maximum, string length > maxLength |
| `invalid_string` | String doesn't match pattern | Email pattern mismatch |
| `invalid_value` | Value not in allowed set | Enum value not in list |
| `not_integer` | Number is not an integer | `25.5` instead of `25` |
| `not_unique` | Array has duplicate items | `["a", "a"]` with `uniqueItems: true` |
| `unrecognized_key` | Unknown property | Extra property with `additionalProperties: false` |

## Supported JSON Schema Features

- **Basic types**: string, number, integer, boolean, null, array, object
- **Type constraints**: 
  - Numbers: minimum, maximum
  - Strings: minLength, maxLength, pattern, format
  - Arrays: items validation
  - Integers: proper integer validation
- **Object validation**: required properties, additionalProperties
- **Array validation**: items schema validation
- **Enums and const values**: Full enum support
- **Union types**: Full support for oneOf and anyOf with validation
- **References**: $ref support for local definitions (#/definitions/ and #/$defs/)
- **Optional properties**: Proper handling of required vs optional fields

## Security Considerations

For general security considerations when working with JSON Schema validation, please refer to the [Ajv Security Guidelines](https://github.com/ajv-validator/ajv/blob/master/docs/security.md). While this tool generates code at build time rather than runtime, many of the same principles apply regarding input validation and schema design.

## Development

```bash
# Install dependencies
bun install

# Run development CLI
bun run dev

# Build the project
bun run build

# Run tests
bun test

# Type checking
bun run typecheck

# Linting
bun run lint
bun run lint:fix
```

## License

MIT