ajaxリクエストでFileをアップロードするとき
axiosでリクエスト・レスポンスのキーをsnake_case・camelCaseに変換するの続編です。
ajaxでファイルアップロードするのって結構だるいですよね。通常はjsonでデータをやりとりすることが多いですが、Fileオブジェクトはjsonで送れないので、FormDataオブジェクトを作って、Fileオブジェクトをappendして・・・と。
アプリケーションレベルで考えなくていいように、Fileを含むリクエストデータをFormDataに変換する共通処理に差し込んでしまいましょう。
方針
- axiosのtransformRequestにリクエストデータを変換する関数を追加する
- 変換する条件は「データにFileオブジェクトが含まれるとき」
- ネスト構造のデータにも対応する
データにFileオブジェクトが含まれるかチェックする関数
再帰を使って実装しました。
function containFile (val) { if (val instanceof File) { // Fileがみつかればtrue return true } // オブジェクト・配列は再帰で子どもたちもチェック if (_.isObject(val) || _.isArray(val)) { return _.some(val, containFile) } // Fileじゃなければfalse return false }
オブジェクトをFormDataに変換する関数
関数の中でsetDataという再帰関数を作って、その中で引数のオブジェクトをなめて外側に定義したFormDataに値を突っ込んでいきます。
好きです、再帰。
function toFormData (original) { const data = new FormData() // これに値を突っ込んでいく // 再帰関数 function setData (path, obj) { _.each(obj, (val, key) => { let nextPath = path.length > 0 ? path + '[' + key + ']' : key if (_.isArray(obj)) { nextPath = path + '[]' } if (val instanceof File) { data.append(nextPath, val) // Fileは突っ込む return } if (_.isObject(val)) { // File以外のオブジェクト(配列含む)だったら再帰 setData(nextPath, val) return } data.append(nextPath, val) // 突っ込む }) } // 再帰関数を起動して、値を突っ込まれたFormDataを返す setData('', original) return data }
transformRequestに追加
あとはtransformRequestに追加する関数を書いてできあがりです。
const apiServer = axios.create({ transformRequest: _.conca( // リクエストをFormDataに変換。 function (data, header) { if (data instanceof FormData) { return data } if (containFile(data)) { return toFormData(data) } return data }, axios.defaults.transformRequest), } })
まとめ
この辺りはライブラリがデフォルトで対応してくれても問題ないと思うんですがどうなんでしょう?不都合が出てきたら更新します。