superstruct の使い方

A simple and composable way to validate data in JavaScript (and TypeScript).

v2.0.23.9M/週MITバリデーション
AI生成コンテンツ

この記事はAIによって生成されました。内容の正確性は保証されません。最新の情報は公式ドキュメントをご確認ください。

superstruct の使い方 — JavaScriptとTypeScriptのためのデータバリデーションライブラリ

一言でいうと

superstruct は、シンプルで合成可能(composable)なAPIを通じて、JavaScriptおよびTypeScriptのデータ構造をバリデーションするライブラリです。スキーマ定義がそのままTypeScriptの型として機能するため、ランタイムバリデーションと型安全性を同時に実現できます。


どんな時に使う?

  1. APIのリクエスト/レスポンスのバリデーション — 外部から受け取るJSONデータが期待する構造を持っているか検証したいとき
  2. フォーム入力値の検証 — ユーザー入力がスキーマに合致するかをランタイムでチェックし、型安全に扱いたいとき
  3. 設定ファイルや環境変数のパース — アプリケーション起動時に設定値が正しい型・形式であることを保証したいとき

インストール

# npm
npm install superstruct

# yarn
yarn add superstruct

# pnpm
pnpm add superstruct

以下の解説は superstruct v2.x 系を前提としています。v1.x 以前とはAPIに差異がある場合があります。


基本的な使い方

最も基本的なパターンは、object でスキーマを定義し、assert または is でバリデーションを行うことです。

import { object, string, number, assert, is, validate } from 'superstruct';

// スキーマ定義
const User = object({
  name: string(),
  age: number(),
  email: string(),
});

// --- パターン1: assert(不正なら例外をスロー) ---
const input: unknown = { name: '田中太郎', age: 30, email: 'tanaka@example.com' };

assert(input, User);
// ここを通過すれば input は { name: string; age: number; email: string } として型が絞り込まれる
console.log(input.name); // '田中太郎'

// --- パターン2: is(真偽値を返す) ---
if (is(input, User)) {
  console.log(input.email); // 型安全にアクセス可能
}

// --- パターン3: validate(エラー情報を取得) ---
const [error, value] = validate(input, User);
if (error) {
  console.error(error.message);
} else {
  console.log(value); // { name: '田中太郎', age: 30, email: 'tanaka@example.com' }
}

よく使うAPI

1. プリミティブ型スキーマ

import { string, number, boolean, date, any, unknown as unknownStruct } from 'superstruct';

const Name = string();    // string
const Age = number();     // number
const Active = boolean(); // boolean
const CreatedAt = date(); // Date オブジェクト

2. object — オブジェクトスキーマの定義

import { object, string, number, optional } from 'superstruct';

const Profile = object({
  username: string(),
  bio: optional(string()),  // undefined を許容
  followers: number(),
});

// TypeScriptの型を抽出
import type { Infer } from 'superstruct';
type ProfileType = Infer<typeof Profile>;
// => { username: string; bio?: string; followers: number }

3. array / tuple — 配列・タプル

import { array, string, number, tuple } from 'superstruct';

// string の配列
const Tags = array(string());
// => string[]

// 固定長タプル
const Coordinate = tuple([number(), number()]);
// => [number, number]

assert(['TypeScript', 'Node.js'], Tags); // OK
assert([35.6762, 139.6503], Coordinate); // OK

4. union / literal / enums — ユニオン型・リテラル型・列挙型

import { union, literal, enums, string, number } from 'superstruct';

// ユニオン型
const StringOrNumber = union([string(), number()]);

// リテラル型
const Direction = union([
  literal('north'),
  literal('south'),
  literal('east'),
  literal('west'),
]);

// 列挙型(文字列の集合)
const Role = enums(['admin', 'editor', 'viewer']);

assert('admin', Role);   // OK
assert('guest', Role);   // StructError がスローされる

5. coerce / defaulted — 値の変換とデフォルト値

import { coerce, string, number, defaulted, object, create, date } from 'superstruct';

// 文字列を Date に変換するスキーマ
const MyDate = coerce(date(), string(), (value) => new Date(value));

const result = create('2024-01-15', MyDate);
console.log(result); // Date オブジェクト: 2024-01-15T00:00:00.000Z

// デフォルト値付きスキーマ
const Config = object({
  host: defaulted(string(), 'localhost'),
  port: defaulted(number(), 3000),
});

const config = create({}, Config);
console.log(config); // { host: 'localhost', port: 3000 }

注意: createassert と異なり、coercedefaulted による変換を適用した値を返します。バリデーションだけでなく変換も行いたい場合は create を使ってください。

6. refine — カスタムバリデーション

import { refine, string, number, assert } from 'superstruct';

// メールアドレスの簡易バリデーション
const Email = refine(string(), 'Email', (value) => {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
});

// 正の整数
const PositiveInt = refine(number(), 'PositiveInt', (value) => {
  return Number.isInteger(value) && value > 0;
});

assert('user@example.com', Email); // OK
assert('invalid-email', Email);    // StructError

assert(42, PositiveInt);  // OK
assert(-1, PositiveInt);  // StructError

類似パッケージとの比較

特徴superstructzodyupjoi
バンドルサイズ非常に小さい(~4KB gzip)中程度(~13KB gzip)中程度(~15KB gzip)大きい(~30KB+)
TypeScript推論Infer で自動推論◎ ネイティブ対応△ 部分的× 別途型定義が必要
API設計思想関数合成ベースメソッドチェーンメソッドチェーンメソッドチェーン
coerce(型変換)
エラーメッセージのカスタマイズ△ シンプル◎ 柔軟◎ 柔軟◎ 柔軟
ブラウザ/Node.js両対応両対応両対応主にNode.js
学習コスト低い低い中程度中程度

選定の目安:

  • バンドルサイズを最小限にしたい → superstruct
  • エコシステムの充実度・エラーメッセージの柔軟性を重視 → zod
  • React Hook Form との統合が主目的 → zod または yup
  • サーバーサイドのみで使う → joi も選択肢に入る

注意点・Tips

1. assert vs create の使い分け

assert はバリデーションのみを行い、値を返しません(void)。coercedefaulted を使ったスキーマでは create を使わないと変換が適用されません

// ❌ assert では coerce が適用されない
assert('2024-01-15', MyDate); // バリデーションは通るが Date に変換されない

// ✅ create なら変換後の値が返る
const d = create('2024-01-15', MyDate); // Date オブジェクトが返る

2. Infer 型を活用する

スキーマから型を二重定義しないようにしましょう。Infer を使えばスキーマ定義が Single Source of Truth になります。

import type { Infer } from 'superstruct';

const Article = object({
  title: string(),
  body: string(),
  publishedAt: optional(date()),
});

// 手動で型を書かない
type Article = Infer<typeof Article>;

3. StructError のハンドリング

assert がスローする StructError には、どのフィールドがどう不正だったかの詳細情報が含まれます。

import { assert, object, string, number, StructError } from 'superstruct';

const User = object({ name: string(), age: number() });

try {
  assert({ name: 123, age: 'not a number' }, User);
} catch (e) {
  if (e instanceof StructError) {
    console.log(e.key);      // 'name'(最初に失敗したキー)
    console.log(e.type);     // 'string'
    console.log(e.path);     // ['name']
    console.log(e.value);    // 123

    // すべてのエラーを取得
    for (const failure of e.failures()) {
      console.log(failure.key, failure.message);
    }
  }
}

4. ネストしたオブジェクトの再利用

スキーマは通常のJavaScript変数なので、自由に合成できます。

const Address = object({
  street: string(),
  city: string(),
  zip: string(),
});

const Company = object({
  name: string(),
  address: Address, // 再利用
});

const Employee = object({
  name: string(),
  company: Company, // さらにネスト
});

5. partial でオプショナルに変換

既存のスキーマの全フィールドをオプショナルにしたい場合(PATCH APIなど)に便利です。

import { partial, object, string, number, create } from 'superstruct';

const User = object({
  name: string(),
  age: number(),
});

const PartialUser = partial(User);
// => { name?: string; age?: number }

const patched = create({ name: '更新太郎' }, PartialUser); // age がなくてもOK

まとめ

superstruct は、関数合成ベースのシンプルなAPIと極めて小さなバンドルサイズが魅力のバリデーションライブラリです。Infer による型推論でスキーマと型定義を一元管理でき、TypeScriptプロジェクトとの相性が非常に優れています。バンドルサイズに敏感なフロントエンドプロジェクトや、シンプルで見通しの良いバリデーションを求めるケースで特に力を発揮するでしょう。