TypeScriptでJSONの型チェックをするための関数ジェネレーター ts-json-checker

TypeScriptでJSONの型チェックめんどくさい:angry:
そんなときはコレ:innocent::point_up:

# ts-json-checker

[GitHubリポジトリ](https://github.com/diontools/ts-json-checker)

## セールスポイント

* インターフェイス型などから型チェック関数を自動生成
* （チェッカーと言いつつ）指定した型を変換する機能あり
* ただの関数なので何が実行されるのか簡単に読める
* 実行時のライブラリ依存なし

「TypeScript JSON」でググると実験的機能なリフレクションで実現していたりして、いかにも重たそうで、もっとシンプルなコードにしたいと思っていました。
そこで**ts-json-checker**では、TypeScript Compiler APIを使用し型を解析して、型チェック関数を生成するようにしました。

## 使い方

npmからインストールして

```shell
npm install --save-dev ts-json-checker
```

設定ファイルを書いて

```typescript
// ts-json-config.ts
import { generate } from 'ts-json-checker'
const fileName = './generated.ts'

export interface Product {
    name: string
    price: number
    release: string
}

generate<Product>('parseProduct')
```

実行する

```shell
npx ts-json-checker
```

おわり！:ok_hand:
これで設定に書かれたファイル名('./generated.ts')に型チェック関数モジュールが出力されています。

```typescript
// generated.ts
export interface Product {
    name: string;
    price: number;
    release: string;
}

export function parseProduct(v: any): Product {
    if (v !== null && typeof v === "object")
        __check_1(v, "v");
    else
        throw new TypeError("v is not Object.");
    return <Product>v;
}

function __check_1(v: any, r: string) {
    if (typeof v.name === "string") { }
    else
        throw new TypeError(r + ".name is not String.");
    if (typeof v.price === "number") { }
    else
        throw new TypeError(r + ".price is not Number.");
    if (typeof v.release === "string") { }
    else
        throw new TypeError(r + ".release is not String.");
}
```

これはただの関数なのでインポートするだけで使えます。

```typescript
// test.ts
import { parseProduct } from './generated'

const productJson = `{
    "name": "json type check book",
    "price": "1980",
    "release": "2019-03-02"
}`

try {
    const product = parseProduct(JSON.parse(productJson))
    console.log(product)
} catch (err) {
    if (err instanceof TypeError) {
        console.error('invalid product type:', err.message)
    } else {
        throw err
    }
}
```

`npx ts-node test.ts`で実行すると`invalid product type: v.price is not Number.`と例外が発生します。:v:

## 特定のプロパティを変換

上記JSONの`price`は数値ですが`string`で表現されています。これを`number`に変換しましょう。

`convertProp`でプロパティの変換を指定します。

```typescript
// ts-json-config.ts
// convertPropをインポート
import { generate, convertProp } from 'ts-json-checker'

// Product型のpriceをstringからnumberに変換
convertProp<Product['price']>(v => {
    if (typeof v !== "string") throw new Error('price is not string.')
    const value = parseInt(v, 10)
    if (isNaN(value)) throw new Error('Unable to convert to number. value: ' + v)
    return value
})
```

```
npx ts-json-checker
npx ts-node test.ts
```

を実行すると

```javascript
{ name: 'json type check book',
  price: 1980,
  release: '2019-03-02' }
```

と表示され、`price`が`number`に変換されていることが確認できます。

## 型変換

また、型ごとにカスタム変換を行う機能があります。Product型の`release`を`string`から`Date`に変換しましょう。

`convert`関数で型変換を指定します。

```typescript
// ts-json-config.ts
// convertをインポート
import { generate, convert, convertProp } from 'ts-json-checker'
const fileName = './generated.ts'

export interface Product {
    name: string
    price: number
    release: Date // Date型に変更
}

// 文字列をDate型に変換
convert<Date>(v => {
    const dt = typeof v === "string" ? Date.parse(v) : NaN
    if (isNaN(dt)) throw new TypeError('Unable to convert to Date. value: ' + v)
    return new Date(dt)
})
```

もう一度

```
npx ts-json-checker
npx ts-node test.ts
```

を実行します。

```javascript
{ name: 'json type check book',
  price: 1980,
  release: 2019-03-02T00:00:00.000Z }
```

と表示され、`string`が`Date`型に変換されていることが分かります。:muscle:

## 設定ファイルの例
設定ファイルで記述できる型の例は[サンプル](https://github.com/diontools/ts-json-checker/blob/master/sample/ts-json-config.ts)を見てください。大体網羅できてると思います。

## おわりに
~~上記の`price`の`string`/`number`変換のような機能は欲しいと思いますので、今後実装していきたいです。
それと、設定ファイル内での型定義（インターフェイス宣言や型エイリアス）が無視される状態なので、これも使えるようにしたいです。~~対応しました。

他に実現したいシナリオなどコメントやイシューを頂ければ幸いです。