Readme

@skmtc/gen-zod

Coverage

OpenAPI to Zod schema generator for Skmtc.

Supported Features

  • Primitive types: string, number, integer, boolean, void, unknown
  • Complex types: object, array, union (oneOf/anyOf), references ($ref)
  • Modifiers: nullable (.nullable()), optional (.optional())
  • Enums: Single values (z.literal()) and multiple values (z.enum())
  • Integer validation: Uses z.number().int() for integer types
  • Objects: Nested objects, properties with special characters
  • Additional properties: Record types (z.record()), mixed with regular properties using .and()
  • Arrays: Typed arrays, nested arrays, arrays of objects
  • Unions: Simple unions and discriminated unions (z.discriminatedUnion())
  • References: Schema references including recursive types with z.lazy()
  • Name transformations: kebab-case and snake_case to PascalCase

Getting started

Install Skmtc

deno install -g -A --unstable-worker-options jsr:@skmtc/cli -n skmtc -f

Skmtc runs on Deno. You can install it using

  • curl -fsSL https://deno.land/install.sh | sh on MacOS/Linux
  • irm https://deno.land/install.ps1 | iex on Windows

Create project and generate artifacts using TUI

skmtc

Create project and generate artifacts using CLI

# Create project
skmtc init <project name>

# Install Zod generator
skmtc install @skmtc/gen-zod <project name>

# Bundle generator code
skmtc bundle <project name>

# Generate artifacts from OpenAPI schema
skmtc generate <project name> <path or url to openapi schema>

Usage Examples

Basic Primitive Types

Input (OpenAPI Schema)Zod Schema
{
  "components": {
    "schemas": {
      "User": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "age": { "type": "number" },
          "score": { "type": "integer" },
          "isActive": { "type": "boolean" }
        },
        "required": ["id", "age", "score", "isActive"]
      }
    }
  }
}
import { z } from "zod";

export const user = z.object({
  id: z.string(),
  age: z.number(),
  score: z.number().int(),
  isActive: z.boolean(),
});

Optional Properties

Properties not in the required array become optional:

Input (OpenAPI Schema)Zod Schema
{
  "Profile": {
    "type": "object",
    "properties": {
      "username": { "type": "string" },
      "bio": { "type": "string" },
      "website": { "type": "string" }
    },
    "required": ["username"]
  }
}
export const profile = z.object({
  username: z.string(),
  bio: z.string().optional(),
  website: z.string().optional(),
});

Enums and Literals

Single enum values become literals, multiple values become z.enum:

Input (OpenAPI Schema)Zod Schema
{
  "Status": {
    "type": "string",
    "enum": ["active", "inactive", "pending"]
  },
  "Role": {
    "type": "string",
    "enum": ["admin"]
  }
}
export const status = z.enum(["active", "inactive", "pending"]);
export const role = z.literal("admin");

Arrays

Input (OpenAPI Schema)Zod Schema
{
  "Tags": {
    "type": "array",
    "items": { "type": "string" }
  },
  "Matrix": {
    "type": "array",
    "items": {
      "type": "array",
      "items": { "type": "number" }
    }
  }
}
export const tags = z.array(z.string());
export const matrix = z.array(z.array(z.number()));

Nested Objects

Input (OpenAPI Schema)Zod Schema
{
  "Company": {
    "type": "object",
    "properties": {
      "name": { "type": "string" },
      "address": {
        "type": "object",
        "properties": {
          "street": { "type": "string" },
          "city": { "type": "string" }
        },
        "required": ["street", "city"]
      }
    },
    "required": ["name", "address"]
  }
}
export const company = z.object({
  name: z.string(),
  address: z.object({
    street: z.string(),
    city: z.string(),
  }),
});

Nullable Types

Input (OpenAPI Schema)Zod Schema
{
  "Article": {
    "type": "object",
    "properties": {
      "title": { "type": "string" },
      "publishedAt": {
        "type": "string",
        "nullable": true
      }
    },
    "required": ["title", "publishedAt"]
  }
}
export const article = z.object({
  title: z.string(),
  publishedAt: z.string().nullable(),
});

Union Types

Input (OpenAPI Schema)Zod Schema
{
  "StringOrNumber": {
    "anyOf": [
      { "type": "string" },
      { "type": "number" }
    ]
  },
  "Pet": {
    "oneOf": [
      {
        "type": "object",
        "properties": {
          "type": { "type": "string", "enum": ["cat"] },
          "meow": { "type": "boolean" }
        },
        "required": ["type", "meow"]
      },
      {
        "type": "object",
        "properties": {
          "type": { "type": "string", "enum": ["dog"] },
          "bark": { "type": "boolean" }
        },
        "required": ["type", "bark"]
      }
    ]
  }
}
export const stringOrNumber = z.union([z.string(), z.number()]);

export const pet = z.discriminatedUnion("type", [
  z.object({
    type: z.literal("cat"),
    meow: z.boolean(),
  }),
  z.object({
    type: z.literal("dog"),
    bark: z.boolean(),
  }),
]);

Record Types (Additional Properties)

Input (OpenAPI Schema)Zod Schema
{
  "Metadata": {
    "type": "object",
    "additionalProperties": { "type": "string" }
  },
  "Config": {
    "type": "object",
    "properties": {
      "id": { "type": "string" }
    },
    "required": ["id"],
    "additionalProperties": { "type": "number" }
  }
}
export const metadata = z.record(z.string(), z.string());

export const config = z.object({ id: z.string() }).and(
  z.record(z.string(), z.number()),
);

References and Recursive Types

Input (OpenAPI Schema)Zod Schema
{
  "Category": {
    "type": "object",
    "properties": {
      "name": { "type": "string" },
      "parent": { "$ref": "#/components/schemas/Category" }
    },
    "required": ["name"]
  }
}
export const category = z.object({
  name: z.string(),
  parent: z.lazy(() => Category).optional(),
});

Type Name Transformations

Schema names are automatically converted to PascalCase:

Input (OpenAPI Schema)Zod Schema
{
  "user-profile": { "type": "string" },
  "api_response": { "type": "number" },
  "MyType": { "type": "boolean" }
}
export const userProfile = z.string();
export const apiResponse = z.number();
export const myType = z.boolean();

Testing

Run tests for this generator:

cd gen-zod && deno task test

Or with coverage:

deno task test:coverage

Generate HTML coverage report:

deno task coverage:html

Support

License

MIT.