MAGAZINE

MAGAZINE

2018.04.04

ブログ

axiosでリクエスト・レスポンスのキーをsnake_case・camelCaseに変換する

CamelCase/snake_case変換

近年、SPA (Single Page Application) 方式でWebアプリを開発することが増えています。
SPAだとajaxリクエストを多用することになるし、リクエストやレスポンスもネストが深い構造になりがちです。

するとサーバーサイドの言語によっては鬱陶しいのがCamelCase/snake_case問題。

フロントのJavascriptはCamelCaseなのに、サーバーサイドがPHPやRubyなどsnake_case派の言語を使用していると、
どこかでリクエスト/レスポンスのキーをCamelCase/snake_case変換する必要があります。

たとえば、以下のような会社情報を返すAPIがあるとき、

{
  "id": 1,
  "name": "株式会社イメジン",
  "users": [{
    "id": 1,
    "first_name: "Shu",
    "last_name": "Yoshioka"
  }]
}

JSではsnake_caseのまま使いたくないので

{
  id: 1,
  name: 株式会社イメジン,
  users: [{
    id: 1,
    firstName: 'Shu',
    lastName: 'Yoshioka'
  }]
}

という形に変換して扱いたいですよね。

今回はフロントエンドでajaxライブラリーに axios を使ってCamelCase/snake_case変換を実装しました。
サーバーサイドの言語・フレームワークに依存せず使いまわせるので、重宝するはず。

ネストされたオブジェクトのキーを変換する関数

まずは、オブジェクトのキー変換をする関数を、lodashのmapKeyを使って作ります。

上記のようなネスト構造のオブジェクトに対応するために再帰を使って書いてみました。
axiosではリクエスト・レスポンスのデータを変換する共通処理を行う関数を設定することができます。また、自前のAPIサーバーと外部APIを使う場合など、異なる共通処理を行いたいときには、インスタンスを分けて共通処理をそれぞれ設定できます。

import _ from 'lodash'

// 再帰関数でネストされたオブジェクトのキーを変換する
// obj: キーを変換したいオブジェクト
// cb: キーを変換するコールバック
function deepMapKeys (obj, cb) {
  // base case (再帰ストップ)
  if (!_.isObject(obj) || val instanceof File) {
    return obj
  }

  if (_.isArray(obj)) {
    // 配列ならキーの変換はなし
    return _.map(obj, val => {
      // 再帰
      return deepMapKeys(val, cb)
    })
  }

  // オブジェクトならキーを変換
  return _.chain(obj)
    .mapKeys(cb)
    .mapValues((val, key) => {
      // 再帰
      return deepMapKeys(val, cb)
    })
    .value()
}

axiosに共通処理を差し込む

axiosではリクエスト・レスポンスのデータを変換する共通処理を設定することができます。
また、自前のAPIサーバーへのリクエストと外部APIへのリクエストで別の処理をはさみたいときには、インスタンスを分けてそれぞれに異なる処理を設定できます。

インスタンスを作るときに、transformRequest / transformResponseに関数の配列を渡せばOK。
さきほど作ったdeepMapKeys関数にリクエスト・レスポンスのデータとコールバック (_.snakeCase または _.camelCase)を渡します。

import axios from 'axios'

// axiosインスタンスの生成
const apiServer = axios.create({
  // レスポンスをcamelCaseに変換。axiosのデフォルト処理に追加するために、concatで配列の最後に追加する。(※ pushするとすべてのインスタンスの共通処理が変わってしまう)
  transformResponse: _.conca(axios.defaults.transformResponse, function (data, headers) {
    if (!headers['content-type'] || headers['content-type'].indexOf('application/json') === -1) {
      return data
    }
    return deepMapKeys(data, (val, key) => {
      return _.camelCase(key)
    })
  }),

  // リクエストをsnake_caseに変換。こちらはaxiosのデフォルト処理がJSONをstringifyする前に実行したいので、配列の最初に追加する。
  transformRequest: _.conca(
    function (data, headers) {
      if (data instanceof FormData) {
        return data
      }
      return deepMapKeys(data, (val, key) => {
        return _.snakeCase(key)
      })
    }, axios.defaults.transformRequest),

  // GETリクエストのパラメーターもsnake_caseに変換。
  paramsSerializer (params) {
    return qs.stringify(deepMapKeys(params, (val, key) => {
      return _.snakeCase(key)
    }))
  }
})

まとめ

Vue.jsを使い始めてaxiosデビューしましたが、インスタンスを分けて共通処理を書けるのは便利です。インスタンスごとにヘッダーを設定したりエラー時の処理を共通化したりもできます。

リクエストデータの中にファイルが含まれるときの対応も共通化したので、また次回紹介します。

この記事を書いた人
吉岡 周

JS好きなCTOです。

@shujs1
CONTACT US

関連記事

RELATED ARTICLE