snake_case api 응답 데이터를 camelCase로 변경하기

pds·2023년 3월 13일
0

TIL

목록 보기
34/60
post-thumbnail

문제상황

미니 프로젝트를 하는데 백엔드 서버 api의 응답 데이터 포맷이 snake_case로 되어있었다.

하지만 나는 이미 타입스크립트로 camelCase를 사용하여 타입을 정의하고 테스트를 위한 msw의 handler api들도 이미 camelCase로 사용하고 있었고 camelCase를 더 좋아한다.

이걸 하나하나 snake_case로 변경하고 싶지 않았기 때문에 일괄적으로 변환하여 처리하고 싶었다.


생각한 계획

(1) snake_case object에서 keycamelCase로 변환해주는 함수를 만든다.

(2) axios response interceptor에 적용해 api응답 데이터를 변환한다.

(3) 이렇게 사용하면 기존 코드를 건드리지 않아도 되고 test api도 변경하지 않아도 된다.


lodash를 사용해 변환 함수 만들기

snake_case 키를 가진 객체를 camelCase로 변환해야 되는데

직접 해도 되지만 좀 더 편하게 변환하고 싶었고 많은 시간을 들이고 싶지 않았다.

Lodash?

Lodash는 자바스크립트 유틸리티 라이브러리로, 배열, 숫자, 객체, 문자열 등의 데이터를 다루는데 유용한 다양한 함수들을 제공합니다. Lodash의 함수들은 자바스크립트에서 제공하는 기본적인 함수와 비슷한 기능을 제공하지만, 더욱 간결하고 성능이 더 뛰어나며, 깊이 있는 객체의 다루기가 쉬운 등의 장점이 있습니다. 또한, IE 11을 포함한 대부분의 모던 브라우저와 Node.js에서 사용할 수 있습니다.

자바스크립트 객체를 다루기 쉽게 해주는 라이브러리인데 들어만 보았지 사용해보는 것은 처음이었다.

이번에는 일부 함수들만 사용하게 되겠지만 나중에 유용한 함수들이 무엇이 있는지 알아보고 더 사용해봐야겠다.

설치하기

yarn add lodash

사용하기

import _ from 'lodash';

_.??? 으로 사용한다.

보통 본인은 자바스크립트에서 함수를 호출할 때 사용하지 않는 파라미터를 _로 선언해 사용하는데 lodash가 있다면 주의해야 할 것 같다.

내가 쓸 함수

isArray: 배열 여부를 판단하는 함수

isObject: 객체 여부를 판단하는 함수

mapValues: 객체의 값을 변형하는 함수

mapKeys: 객체의 키를 변형하는 함수

const obj = { a: 1, b: 2, c: 3 };
const newObj = _.mapKeys(obj, (value, key) => key.toUpperCase());
console.log(newObj); // { A: 1, B: 2, C: 3 }

camelCase: 문자열을 카멜케이스로 변경해주는 함수


함수 구현하기

import _ from 'lodash';

interface ObjectLiteral {
  [key: string]: any;
}

function snakeToCamel(obj: ObjectLiteral): ObjectLiteral {
  if (_.isArray(obj)) {
    return obj.map((v) => snakeToCamel(v));
  }
  if (_.isObject(obj)) {
    return _.mapValues(
      _.mapKeys(obj, (v, k) => _.camelCase(k)),
      (v) => snakeToCamel(v),
    );
  }
  return obj;
}

당연하지만 재귀구조로 변환하게 만들어야 한다.

응답 객체 데이터의 깊이가 1일것이라는 보장은 없다.

배열이라면 항목마다 변환할 수 있게 재귀를 돌고

오브젝트라면 키를 변경하고 재귀를 돌며

값이라면 값을 그대로 리턴한다.

이렇게 해서 value는 유지시키고 객체의 key만 camelCase로 변경한다.


테스트

간단하게 nested 구조나 배열이 포함되어있거나 키 말고 값이 snake_case인데 camelCase로 변경되지 않는지 정도만 테스트했다.

import snakeToCamel from '@/utils/snakeToCamel';

describe('snakeToCamel', () => {
  test('snake_case 객체는 camelCase 객체로 변환되어야 한다.', () => {
    const snakeCaseData = {
      first_name: 'John',
      last_name: 'Doe',
      age: 28,
    };
    const expected = {
      firstName: 'John',
      lastName: 'Doe',
      age: 28,
    };
    expect(snakeToCamel(snakeCaseData)).toEqual(expected);
  });

  test('nested snake_case 객체는 object의 key가 camelCase 객체로 모두 변환되어야 한다.', () => {
    const snakeCaseData = {
      user_info: {
        first_name: 'John',
        last_name: 'Doe',
        age: 28,
      },
      hello_world: {
        hell: 'hello_world',
      },
    };
    const expected = {
      userInfo: {
        firstName: 'John',
        lastName: 'Doe',
        age: 28,
      },
      helloWorld: {
        hell: 'hello_world',
      },
    };
    expect(snakeToCamel(snakeCaseData)).toEqual(expected);
  });

  test('배열이 포함된 nested snake_case 객체는 object의 key가 camelCase 객체로 모두 변환되어야 한다.', () => {
    const snakeCaseData = {
      data: [
        {
          user_info: {
            user_name: 'user_name',
            skill_list: ['sk_1', 'sk_2', 'sK3'],
          },
        },
        {
          user_info: {
            user_name: 'user_name_2',
            skill_list: ['sk_1', 'sk_2', 'sK3'],
            hell_world: [{ hell_world: 1234 }, { abc_hello: '1234' }],
          },
        },
      ],
    };
    const expected = {
      data: [
        {
          userInfo: {
            userName: 'user_name',
            skillList: ['sk_1', 'sk_2', 'sK3'],
          },
        },
        {
          userInfo: {
            userName: 'user_name_2',
            skillList: ['sk_1', 'sk_2', 'sK3'],
            hellWorld: [{ hellWorld: 1234 }, { abcHello: '1234' }],
          },
        },
      ],
    };
    expect(snakeToCamel(snakeCaseData)).toEqual(expected);
  });
});

Axios Response Interceptor에 적용하기

클라이언트 사이드에서 사용되는 모든 api를 axios로 호출하는 형태라 응답 인터셉터에 적용해서 쉽게 변환해서 사용할 수 있다.

function getObject(response: AxiosResponse) {
  const { data } = response;
  return data !== null && typeof data === 'object' ? snakeToCamel(response.data) : {};
}

instance.interceptors.response.use(
  (response) => getObject(response),
  (error) => genErrorResponse(error),
);

하던거하기

이제 원래 하던대로 테스트 api를 사용하여 개발하면 되고 실제 백엔드 api를 연결해서 사용할 때도 똑같이 camelCase를 사용할 수 있게 되었다.

재귀를 돌리기 때문에 응답 데이터 케이스가 엄청 많을 경우 변환 자체가 성능에 영향을 주는지는 추후 테스트해보아야 할 것 같다.

이렇게 기존의 코드에는 변경없이 쉽게 포맷을 변환해서 사용할 수 있게 되었다!

profile
강해지고 싶은 주니어 프론트엔드 개발자

0개의 댓글