typescriptintermediate

Schema Validation with Zod-like Patterns

Build a minimal schema validation library inspired by Zod using TypeScript type inference.

typescript
// Minimal Zod-like validator
type Infer<T> = T extends Validator<infer U> ? U : never;

class Validator<T> {
  constructor(private parse: (input: unknown) => T) {}

  validate(input: unknown): { success: true; data: T } | { success: false; error: string } {
    try {
      return { success: true, data: this.parse(input) };
    } catch (e: any) {
      return { success: false, error: e.message };
    }
  }

  optional(): Validator<T | undefined> {
    return new Validator((input) => {
      if (input === undefined) return undefined;
      return this.parse(input);
    });
  }
}

const v = {
  string: () => new Validator<string>((input) => {
    if (typeof input !== 'string') throw new Error(`Expected string, got ${typeof input}`);
    return input;
  }),

  number: () => new Validator<number>((input) => {
    if (typeof input !== 'number' || isNaN(input as number))
      throw new Error(`Expected number, got ${typeof input}`);
    return input as number;
  }),

  boolean: () => new Validator<boolean>((input) => {
    if (typeof input !== 'boolean') throw new Error(`Expected boolean`);
    return input;
  }),

  object: <S extends Record<string, Validator<any>>>(shape: S) => {
    type Shape = { [K in keyof S]: Infer<S[K]> };
    return new Validator<Shape>((input) => {
      if (typeof input !== 'object' || input === null) throw new Error('Expected object');
      const result = {} as any;
      for (const [key, validator] of Object.entries(shape)) {
        const val = (input as any)[key];
        result[key] = (validator as any).parse(val);
      }
      return result;
    });
  },

  array: <T>(itemValidator: Validator<T>) => new Validator<T[]>((input) => {
    if (!Array.isArray(input)) throw new Error('Expected array');
    return input.map((item, i) => {
      try { return (itemValidator as any).parse(item); }
      catch (e: any) { throw new Error(`[${i}]: ${e.message}`); }
    });
  }),
};

// Usage
const UserSchema = v.object({
  name: v.string(),
  age: v.number(),
  email: v.string(),
  active: v.boolean().optional(),
});

type User = Infer<typeof UserSchema>;

console.log(UserSchema.validate({ name: 'Alice', age: 30, email: 'a@b.com' }));
console.log(UserSchema.validate({ name: 123, age: 'bad' }));

Use Cases

  • API request validation
  • Configuration parsing
  • Form input validation

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.