MoneyForward仕訳帳エクスポートのAPI調査と実装
Chrome拡張のMF連携に仕訳帳エクスポートを追加した。REST APIでJSON→CSV変換する方式で着手し、途中でMF形式CSVエクスポートAPIを見つけ、最終的に両者を組み合わせるハイブリッド方式に着地した。方針が二転三転した一日の記録。
第1フェーズ: REST APIでJSON取得→CSV変換
最初に目をつけたのは /api/v1/journals エンドポイント。仕訳データがJSONで返ってくるので、クライアントサイドでMF形式のCSV列構成に組み替える方針で実装を始めた。
マスタAPIの構造調査
仕訳の勘定科目名や補助科目名を解決するために、マスタAPIを叩く必要がある。調査したエンドポイントは4つ。
categorized_items— 勘定科目sub_categories— 補助科目walletables— 口座departments— 部門
ここで最初につまずいた。マスタのアイテムが持つプロパティ名が name ではなく label だった。DevToolsのNetworkタブでレスポンスを確認するまで気づかず、科目名が全て undefined で出力されていた。
// NG: nameプロパティは存在しない
const name = item.name;
// OK: labelがMFのマスタAPIでの正しいプロパティ名
const name = item.label;
IDからマスタ名を引くルックアップMapを作り、仕訳JSONの各行を走査してCSV行に変換するところまで動いた。
壁: 作成者・最終更新者が取れない
REST APIのレスポンスをよく見ると、created_by や updated_by に相当するフィールドが存在しない。MFの仕訳帳CSV(管理画面からダウンロードできるもの)には「作成者」「最終更新者」列があるが、REST APIはこれを返さない。税額の計算ロジックもクライアント側で再現する必要があり、再実装コストが膨らみ始めた。
第2フェーズ: MF形式CSVエクスポートAPIの発見
REST APIに限界を感じてDevToolsのNetworkタブを眺めていたら、管理画面の「エクスポート」ボタンが叩いているAPIを捕捉した。
POST /exports/mf_journals_csv_file
→ 302 リダイレクト
→ GET /storage/files/{fid}
→ CSV本体がダウンロードされる
MFのサーバーサイドでCSVを生成するAPIが存在していた。これを使えば、作成者・最終更新者・税額など、REST APIでは取得できなかったフィールドが全て含まれる。列構成もMFの管理画面と完全に一致する。
REST API方式をいったん脇に置き、このCSVエクスポートAPIに乗り換えた。
エンコーディングの罠
CSVをダウンロードして中身を確認したら文字化けしていた。原因はエンコーディングの誤判定。MFのCSVはUTF-8で出力されるが、デコード処理がShift-JISを前提にしていた。
// NG: Shift-JISでデコードすると文字化け
const text = new TextDecoder('shift-jis').decode(buffer);
// OK: MFのCSVはUTF-8
const text = new TextDecoder('utf-8').decode(buffer);
もう一つの壁: 開始仕訳が含まれない
MF形式CSVを年度分ダウンロードして行数を確認したら、REST APIで取得した仕訳数より少ない。調べてみると、MFのサーバーサイドCSV生成は開始仕訳を除外する仕様だった。開始仕訳(期首の残高設定)は通常の仕訳と異なる扱いらしく、エクスポート対象から外される。
管理画面の仕訳帳で開始仕訳を表示させても、エクスポートボタンの対象にはならない。REST APIの /api/v1/journals では journal_type パラメータで開始仕訳を指定すれば取得できる。
第3フェーズ: ハイブリッド方式の採用
MF形式CSVだけでは開始仕訳が欠ける。REST APIだけでは作成者・税額が欠ける。どちらか一方では完全なデータが揃わない。そこで両者を組み合わせるハイブリッド方式を採用した。
方針:
- MF形式CSVをダウンロード(作成者・税額・本来の列構成を確保)
- REST APIから開始仕訳のみ取得してCSV行に変換
- 両者をマージして出力
開始仕訳は年度に数件しかないため、REST APIの呼び出し回数は最小限で済む。MF形式CSVが持つ情報量とREST APIの網羅性を両立できた。
年度別一括エクスポート対応
仕訳帳は年度単位で管理される。複数年度をまとめてエクスポートできるよう、仕訳帳専用の年度選択UIを追加した。
既存の明細取得で使っていた年度選択チップのコンポーネントを流用し、仕訳帳エクスポート用にインスタンスを分離した。選択された年度ごとにMF形式CSV取得→開始仕訳補完→マージの処理が走る。
純粋関数への分離とテスト
CSV列の組み立て、マスタIDの解決、開始仕訳のCSV行変換といったロジックを lib.js に切り出した。副作用(API呼び出し、DOM操作)はハンドラ層に残し、lib.js の関数は全て入力→出力が確定する純粋関数にした。
テストは39件追加。主なカバー範囲は以下の通り。
- マスタMapの構築(
categorized_items→ IDから科目名を引く) - 仕訳JSONからCSV行への変換(借方・貸方の展開)
- 開始仕訳のCSV行変換
- MF形式CSVと開始仕訳CSVのマージ
- エッジケース(補助科目なし、部門なし、税区分なし)
テストが手元で通る状態を維持しながらリファクタリングを進められたので、ハイブリッド方式への切り替え時も既存ロジックを壊さずに済んだ。
振り返り
方針が3回変わった。REST API→MF形式CSV→ハイブリッドと、調査が進むたびに「これだけでは足りない」が見つかった。最初からMFの内部APIを全て把握していれば一直線に進めたが、DevToolsでリクエストを一つずつ追いかけながら全体像を掴んでいく過程自体が、APIの仕様を理解する最短ルートだったとも思う。
ロジックを純粋関数に閉じ込めてテストを書いておいたことで、方針転換のたびにゼロから書き直す事態は避けられた。39件のテストが、リファクタリング中に何度も壊れかけた箇所を即座に教えてくれた。