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

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

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: {}
  }
}

参考リンク

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