APIからのレスポンスを動的にチェックする方法

動的にレスポンスの構造をチェックしたい

TypeScriptによって静的な型チェックを行うことはできるが、APIからのレスポンスなどの動的なチェックを行いたい。

レスポンスのバリデーション用の構造とTypeScriptの型を一元管理したい。

io-tsを使用する

io-tsというライブラリーで可能。

https://github.com/gcanti/io-ts

手順

io-tsでAPIからのレスポンスの構造を定義する

const HogeResValidator = t.type({
  id: t.number,
  name: t.string,
  age: t.number,
  description: t.union([t.string, t.null]),
})

上記の例では idはnumber, nameはstring, ageはnumber, descriptionはstring | null となっている。

optionalなプロパティーを定義する場合は、t.partialを利用する。

https://github.com/gcanti/io-ts#mixing-required-and-optional-props

TypeScriptの型を生成する

上記の構造からTypeScript用の型を生成する。

type HogeResType = t.TypeOf<typeof HogeResValidator>

APIコールにレスポンスの型とバリデーション処理を追加する

今回はaxiosを使った例。

axios.get<HogeResType>('/user')
  .then(res => {
    const v = HogeResValidator.decode(res.data)
    if(isLeft(v)) throw new Error('failed to validate response')
    
    // バリデーション成功時の処理
  })
  .catch(err => {
  // エラー処理
  })

PathReporter.report を使用することで、バリデーションエラー時にどこがNGだったかを取得することができるので便利。

https://github.com/gcanti/io-ts#error-reporters

全体のコード

const HogeResValidator = t.type({
  id: t.number,
  name: t.string,
  age: t.number,
  description: t.union([t.string, t.null]),
})

type HogeResType = t.TypeOf<typeof HogeResValidator>

axios.get<HogeResType>('/user')
  .then(res => {
    const v = HogeResValidator.decode(res.data)
    if(isLeft(v)) throw new Error('failed to validate response')
    
    // バリデーション成功時の処理
  })
  .catch(err => {
  // エラー処理
  })

レスポンスの構造とそれから作成された型(HogeResValidatorとHogeResType)は別ファイルで管理すると良い。

感想

バリデーション用のデータ構造からTypeScriptの型を生成できるので、一元管理できるので便利。

もし、データ構造が変わった場合は、HogeResValidator(バリデーション用の構造)を変更することでHogeResType(TypeScriptの型)も自動で変更される。

API側で変更が入ってレスポンスの構造が変わった時に、レスポンス取得時のバリデーションで検知できるので、バグを見つけやすくなる。