io-tsとは
Typescriptで型が合っているかどうかを検査したいときに使用する。
例えば、APIのリクエストパラメータにisActive
があり、isActive
にはtrue
またはfalse
が入っていることを期待しているとする。
しかし、実際には以下のような値が飛んでくる可能性がある。
-
"true"
→ 文字列なのでダメ -
1
→ 数値なのでダメ
これらのパターンを全てのリクエストパラメータに対して検査するのはとても大変である。
しかし、io-tsを使用すれば、こんな感じにスッキリ書くことができる。
import * as t from 'io-ts';
const ApiRequest = t.type({
isActive: t.boolean
});
const validation = ApiRequest.decode(req.body);
公式ドキュメントはこちら
サンプルコードも用意したので、よかったらご覧ください。
準備
$ yarn add io-ts fp-ts
fp-ts
はio-ts
と依存関係にあるので、一緒にインストールする。
これらの他に、一緒に入れると便利なものを紹介しておく。
-
io-ts-reporters
- decodeしたときに出力される文字列をいい感じに整形してくれる
-
io-ts-types
- NonEmptyStringなど、便利な型が定義されている(後ほど紹介)
基本的な流れ
まずは、io-tsをインポートする
import * as t from 'io-ts';
次に型を定義する
const Book = t.type({
id: t.number,
title: t.string,
author: t.string,
});
t.number
やt.string
以外にも、t.boolean
やt.null
などがある。
詳しくはこちらを参照。
最後に型が合っているかどうかチェックする
const data = {
id: 1,
title: 'タイトル',
author: '著者',
};
const validation = Book.decode(data); // 型をチェック
console.log(validation); // 結果を確認
基本的にはこれだけ。非常にシンプル。
応用編
t.string
などの基本的な型だけでなく、例えば、メールアドレスの形式が正しいかどうかなど、もっと厳密に値をチェックしたい場合もあると思う。
ここでは個人的に利用頻度が高いと思った書き方をいくつか紹介する。
OR
name: t.union([ t.string, t.null ])
この場合、name
はstring
でもnull
でもOKということになる。
特定の文字列のみ
const GenderTypes = {
male: 'male',
female: 'female',
other: 'other',
};
const User = t.type({
id: t.number,
name: t.string,
gender: t.keyof(GenderTypes),
});
この場合、gender
は、"male"
, "female"
, "other"
のいずれかの文字列のみOKということになる。
NonEmptyString
import { NonEmptyString } from 'io-ts-types';
const User = t.type({
id: t.number,
name: NonEmptyString,
});
io-ts-types
をインストールすると使える。
t.string
だと、スペースやタブだけの場合でも通ってしまうので、それらを弾くことができるNonEmptyStringは結構便利。
0以上の整数
interface IPositiveNumber {
readonly PositiveNumber: unique symbol
};
const PositiveNumber = t.brand(
t.number,
(n): n is t.Branded<number, IPositiveNumber> => 0 < n,
'PositiveNumber'
);
const User = t.type({
id: t.number,
name: t.string,
age: t.intersection([t.Int, PositiveNumber]),
});
t.number
だと、小数値やマイナス値でも通ってしまうが、この書き方ならage
は正の整数しか受け付けない。
メールアドレス形式
const REGEX_EMAIL = /^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/;
interface IEmail {
readonly Email: unique symbol;
};
const Email = t.brand(
t.string,
(input): input is t.Branded<string, IEmail> => REGEX_EMAIL.test(input),
'Email'
);
const User = t.type({
id: t.number,
name: t.string,
email: Email,
});
こちらは正規表現を利用した書き方。パスワードの形式チェックなんかにも使える。
注意すべきこと
例えば下のように、パラメータが不足している場合、チェックの段階でキチンと弾かれる。
const Book = t.type({
id: t.number,
title: t.string,
author: t.string,
});
// authorが不足している
const data = {
id: 1,
title: 'タイトル',
}
const validation = Book.decode(data); // NG
ところが、下のようにパラメータが余分な場合は、チェックが通ってしまうので要注意
const Book = t.type({
id: t.number,
title: t.string,
author: t.string,
});
// priceが余分
const data = {
id: 1,
title: 'タイトル',
author: '著者',
price: 500,
}
const validation = Book.decode(data); // OK