APIのレスポンスを変換する

5 min read読了の目安(約4900字

はじめに

ワークフローを実現するApache Airflowを使用しており、使用している技術スタックなどを隠蔽するためにAirflowのAPIを薄くラップして独自のマイクロサービスを実装する必要がありました。実装している他のマイクロサービスはNode.jsで実装しており、主に返却するレスポンスのキーはキャメルケースでAPIのレスポンスを実装されています。AirflowのAPIのレスポンスとしてはスネークケースで返却されます。そのためlodashを用いてAPIのレスポンスをスネークケースからキャメルケースに変換してみたので、紹介しようと思います。

環境

  • Node.js: v12.18.3
  • lodash: 4.17.21
  • Airflow: 2.0.1

Airflow API

今回は例として、AirflowのDAGを取得する/dagsを例に実装したいと思います。

{
  "dags": [
    {
      "dag_id": "string",
      "root_dag_id": "string",
      "is_paused": true,
      "is_subdag": true,
      "fileloc": "string",
      "file_token": "string",
      "owners": [
        "string"
      ],
      "description": "string",
      "schedule_interval": {
        "__type": "string",
        "days": 0,
        "seconds": 0,
        "microseconds": 0
      },
      "tags": [
        {
          "name": "string"
        }
      ]
    }
  ],
  "total_entries": 0
}

期待するAPIの形としては不要な項目を削除して、スネークケースの部分をキャメルケースに変換していきます。また、dag_idworkflowIdなどに変換しています。

{
  [
    {
      "jobId": "string",
      "rootJobId": "string",
      "isPaused": true,
      "isSubdag": true,
      "fileloc": "string",
      "fileToken": "string",
      "owners": [
        "string"
      ],
      "description": "string",
      "scheduleInterval": {
        "__type": "string",
        "days": 0,
        "seconds": 0,
        "microseconds": 0
      },
      "tags": [
        {
          "name": "string"
        }
      ]
    }
  ]
}

lodashを用いて変換するコード

utilities.js
'use strict';
cosnt _ = require('lodash');

module.exports = {
    isPrimitive: val => _.includes(['string', 'number', 'boolean'], typeof val),

    changeKeyFormat: (object, toStyle = _.snakeCase) => {
        function _localize(obj) {
            if (module.exports.isPrimitive(obj) || obj === null) return obj;
            if (Array.isArray(obj)) return obj.map(item => _localize(item));
            if (typeof obj === 'object') {
                const convertedObj = {};
                Object.keys(obj).forEach(key => {
                    if (module.exports.isPrimitivve(obj[key]) || obj[key] === null) {
                        if (key === '__type') return (convertedObj[key] = obj[key]);
                        else return (convertedObj[toStyle(key)] = obj[key]);
                    }

                    if (typeof obj[key] === 'object')
                        return (convertedObj[toStyle(key)] = _localize(obj[key]));
                });

                return convertedObj;
            }
        }

        return _localize(object);
    },

    replaceKeys: object => 
        _.mapKeys(object, (value, key) => {
            if (key === 'dag_id') return 'workflowId';
            else if (key === 'dag_run_id') return 'jobId';
            else return key;
        }),

    formatResponse: object => {
        function createChildren(listObject) {
            return listObject.map(eachItem => module.exports.replaceKeys(eachItem));
        }
        let formatedObj;
        if ('dags' in object) formatedObj = createChildren(object['dags']);
        else if ('dag_runs' in object) formatedObj = createChildren(object['dag_runs']);
        else if ('task_instances' in object) formatedObj = createChildren(object['task_instances']);
        else formatedObj = module.exports.replaceKeys(object);
        
        return module.exports.changeKeyFormat(formatedObj, _.camelCase);
    },

    formatRequest: object => {
        const replaced = module.exports.replaceKeys(object);

        return module.exports.changeKeyFormat(replaced);
    }
}
  • isPrimitive: keyに対応する値が、"string", "number", "boolean"であるかを判定する関数
  • changeKeyFormat: オブジェクトのキー名を変換するための関数で、値が配列なのか、オブジェクトなのか等で処理を分けています。
    • "string", "number", "boolean"の場合: そのまま値を返却する
    • 配列の場合: 各要素ごとに、内部関数の_localizeを呼び出す
    • オブジェクトの場合: キーを変換して別のオブジェクトに格納し、返却する
  • replaceKeys: AirflowのAPIに存在するdag_id, dag_run_idworkflowId, jobIdに変換するための関数
  • formatResponse: AirflowのAPIレスポンスをキャメルケースに変換するための関数
  • formatRequest: Airflowの値を更新するために、リクエストをスネークケースに変換するための関数

以上のコードをutilitis.jsとして作成しておき、マイクロサービスの実装している部分で、使用します。

レスポンスの変換

sampleService.js
const utils = require('somewhere/utilities);

const convertedResponse = utils.formatResponse(data);

リクエストの変換

マイクロサービスから、Airflowのdag_runを更新して、有効・無効化する、ジョブ実行周期の変更などを実行したかったので、リクエストの形式をこちらはキャメルケースからスネークケースに変換します。

sample2Service.js
cosnt utils = require('somewhere/utilities');

const convertedRequest = utils.formatRequest(data);

おわりに

lodashを用いて、Javascriptのオブジェクトのキーの変換を実装できました。
今回使用しているのはlodashの実装されているメソッドの中で、ごくわずかなので便利なものがあれば、また実装して紹介しようと思います。

もし、コードの実装が綺麗でない部分であったり、もう少しいいやり方や違う方法があれば、ご指摘ください。

参考リンク