Cover image for TypeScriptで再帰的なデータ構造を型安心にする
typescript

TypeScriptで再帰的なデータ構造を型安心にする

April 25, 2019

4 min read

mitsuruogMitsuru Ogawa

TypeScript の小ネタ。

TypeScript で再帰的なデータ構造のデータに対して型を適用する方法です。 例えば、クローリングしてきた Web サイトの情報など。JSON の key があって、値がstring | number | boolean | Array | Objectになる可能性がある面倒なやつです。

普通に考えると、[key: string]の Index signature と Union Type を持つ型を使えばいいかと思うのですが、肝心の Union の型定義が再帰構造なので表現できません。

interface WebSiteData {
  // Unionの部分が再帰的なので表現できない!!
  [key: string]: string | number | boolean | Array<
    string | number | boolean | Array<
      string| number | boolean | Array<
        ...え、ちょっと待って
      >
    >
  >
}

まずこの問題を解決するには、JSON の値が取る型定義を最初にイメージします。名前をJSONValueTypeとして、後で定義する Array や Object はこの型構造を使うことにします。

JSONValueTypeは値がstring | number | boolean | Array | Objectになる可能性があるので、型定義はだいたい次のようなものになると考えます。

// Array<JSONValueType> | Object<JSONValueType>はあとで置き換えます。
type JSONValueType =
  | string
  | number
  | boolean
  | Array<JSONValueType>
  | Object<JSONValueType>;

次にArray<JSONValueType>の型定義をします。名前はJSONValueTypeArrayにしましょう。

定義するにはJSONValueType型の Array を継承した interface を作成します(ちょっとトリッキーです)。定義した後は、JSONValueTypeの型定義も置き換えます。

type JSONValueType =
  | string
  | number
  | boolean
  | JSONValueTypeArray
  | Object<JSONValueType>;

interface JSONValueTypeArray extends Array<JSONValueType> {}

次にObject<JSONValueType>の型定義をします。名前はJSONValueTypeObjectにしましょう。

こちらも定義するには[key: string]の Index signature を持ってJSONValueTypeの値を持つ interface を定義します。 定義した後は、JSONValueTypeの型定義も置き換えます。

type JSONValueType =
  | string
  | number
  | boolean
  | JSONValueTypeArray
  | JSONValueTypeObject;

...

interface JSONValueTypeObject {
  [key: string] : JSONValueType;
}

これで不定形の JSON データ構造をJSONValueTypeObjectを使って型安全に定義することができました。

const data: JSONValueTypeObject = {
  name: "mitsuruog",
  age: 20,
  alive: true,
  profile: [
    {
      profileName: "aaa",
      profileNumber: 1,
      profileBool: true,
      profileArray: [],
      profileObject: {},
    },
  ],
  address: {
    addressString: "aaa",
    addressNumber: 1,
    addressBool: true,
    addressArray: [],
    addressObject: {},
  },
};

参考リンク

もう少し簡単に定義できるようにしたかったみたいですが、実現されなかったみたいですね。。。