AngularでJsonをClassに変換する

# Json to Class (Angular 4 on TypeScript)
先日、ようやくWeb apiと通信できるようになったのだが返却値をクラスにシリアライズする処理をべた書きしていたのでここをなんとかしたい。

最近は[マイクロサービス](https://www.salesforce.com/jp/blog/2016/03/microservices.html)が流行っているし、Web apiでのやりとりは今後少なくないはず。なので、こういった単純かつ繰り返し使う部分は自動化しておきたい。

```typescript
/**
 * これを
 */
const charInfo = data.json()['character_list'][0]
const char = new Character(
  charInfo['id'],
  charInfo['name'],
  charInfo['name_ruby'],
  charInfo['family_name'],
  charInfo['first_name'],
  charInfo['family_name_ruby'],
  charInfo['first_name_ruby'],
  charInfo['is_foreigner_name'],
  charInfo['birth_month'],
  charInfo['birth_day'],
  charInfo['gender'],
  charInfo['is_idol'],
  charInfo['character_type'],
  charInfo['arrival_date'],
  charInfo['origin_media'],
  charInfo['cv'],
  charInfo['class_name'],
);

------------------------------------------------
/**
 * こうしたい
 */
const charInfo = data.json()['character_list'][0]
const char = new Character();
char.fillFromJSON(charInfo, true);
```

PHPのSymfonyフレームワークにserializerがあるように、javascript - typescript - angularのいずれかにもそういったヘルパーがあるはずだ。

## StackOverflow
実はしばらく前からこの機能を探していたがなかなか見つからず、ようやくピンとくる記事を見つけた。
[JSON to TypeScript class instance?](https://stackoverflow.com/questions/29758765/json-to-typescript-class-instance)

よさげなヘルパーはないらしく、どの回答でも自前で実装していた。今回もそうすることにする。なお、今回はAPIレスポンスがスネークケースで返却されることを考慮して[キャメルケースに変換するためのモジュール](https://github.com/sindresorhus/camelcase)を追加している。

### 1. 基底クラスを実装
```typescript
export class Serializable {

    /**
     * @see https://github.com/sindresorhus/camelcase
     */
    protected camelizer;

    constructor() {
        this.camelizer = require('camelcase');
    };

    /**
     * classにjsonデータを注入します
     *
     * @param string        json
     * @param boolean       isCamelizeProp
     * @param Array<string> ignoreProp
     */
    public fillFromJSON(jsonObj, isCamelizeProp?: boolean, ignoreProp: Array<string> = []) {
        for (let propName in jsonObj) {
            if (ignoreProp.length > 0 && ignoreProp.some(v => v === propName)) {
                // 指定された属性を無視
                continue;
            }

            /* tslint:disable */
            const rawPropName = propName;

            // キーをキャメルケースに変換
            if (isCamelizeProp === true) {
                propName = this.camelizer(propName);
            }

            // プロパティの存在確認
            if (this.hasOwnProperty(propName) === false) {
                continue;
            }

            this[propName] = jsonObj[rawPropName];
        }
    }

}
```

### 2. 基底クラスを継承する
```typescript
import { Serializable } from '../../base/serializable';

export class Character extends Serializable {

    constructor(
        public id?: string,
        public name?: string,
        public nameRuby?: string,
        public familyName?: string,
        public firstName?: string,
        public familyNameRuby?: string,
        public firstNameRuby?: string,
        public isForeignerName?: string,
        public birthMonth?: string,
        public birthDay?: string,
        public gender?: string,
        public isIdol?: string,
        public characterType?: string,
        public arrivalDate?: string,
        public originMedia?: string,
        public cv?: string,
        public className?: string
    ) {
        // 親コンストラクタの呼び出し
        super();
    }

}
```

Typescriptでは継承を行った場合、子クラスのコンストラクタで `super()` を呼ぶ必要がある。プロパティ名うしろの `?` はオプション引数という意味を示す。

### 3. 上記を使ってシリアライズ

```typescript
// 変換対象のJson
const json = ..;

// 値を詰めるオブジェクト
const char = new Character();

// 第三引数は無視する値
char.fillFromJSON(json, true, ['profile_list']);

// 完了。配列の値が詰められたオブジェクト
this.char = char;
```

## おわりに
こういった処理を自前で実装するとメンテナスが辛くなりそうな気配がするので、Anglarさんとかで最初からできるようになってくれると…ありがたい。

あるいは自分が知らないだけで既に定番のライブラリがあるのだろうか。