NextJSでAPIのResponseをキャメルケースで扱いたい

## 背景
多くのAPIは、レスポンスのフィールド名やリクエストのボディをsnake_caseで扱います。しかし、JavaScriptではcamelCaseが一般的なスタイルです。そのため、APIから受け取るレスポンスや、APIに送信するリクエストボディをcamelCaseに変換する関数を作成して、コードの一貫性を保ちたいと思います。

### ASIS
```TS
const messageParams = {
  parent_message_id: 1,
  thread_id: 2,
  sender_id: 3,
  sender_type: "anonymous_user",
  text: "メッセージ本文",
}
```

### TOBE
```TS
const messageParams = {
  parentMessageId: 1,
  threadId: 2,
  senderId: 3,
  senderType: "anonymous_user",
  text: "メッセージ本文",
}
```


## やったこと
- APIにリクエストを投げる前に変換する関数
    ```TS
    // src/utils/conversion/toSnakeCase.ts
    
    // キャメルケース → スネークケース（文字列）
    const _toSnakeCaseStr = (str: string): string => {
      return str
        .split(/(?=[A-Z])/)
        .join('_')
        .toLowerCase()
    }
    
    // キャメルケース → スネークケース（オブジェクトの配列）
    const _toSnakeCaseArrayOfObjects = <T extends object>(array: T[]): T[] => {
      return array.map((obj) => toSnakeCase(obj))
    }
    
    // キャメルケース → スネークケース（オブジェクト）
    export const toSnakeCase = <T extends object>(obj: T): T => {
      const result = {} as T
      Object.entries(obj).forEach(([key, val]) => {
        const valType = toString.call(val).slice(8, -1).toLowerCase()
        if (valType === 'object') {
          val = toSnakeCase(val as Record<string, unknown>)
        } else if (valType === 'array') {
          val = _toSnakeCaseArrayOfObjects(val as Record<string, unknown>[])
        }
        ;(result as Record<string, unknown>)[_toSnakeCaseStr(key)] = val
      })
      return result
    }
    
    ```
    <br>

- APIからのレスポンスを変換する関数
    ```TS
    // src/utils/conversion/toCamelCase.ts
    
    // スネークケース → キャメルケース（文字列）
    const _toCamelCaseStr = (str: string): string => {
      return str
        .split('_')
        .map((word, index) => {
          if (index === 0) {
            return word.toLowerCase()
          }
          return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()
        })
        .join('')
    }
    
    // スネークケース → キャメルケース（オブジェクトの配列）
    const _toCamelCaseArrayOfObjects = <T extends object>(array: T[]): T[] => {
      const arrayType = toString.call(array).slice(8, -1).toLowerCase()
      if (arrayType !== 'array') {
        return []
      }
      return array.map((obj) => toCamelCase(obj))
    }
    
    // スネークケース → キャメルケース（オブジェクト）
    export const toCamelCase = <T extends object>(obj: T): T => {
      const result = {} as T
      Object.entries(obj).forEach(([key, val]) => {
        const valType = toString.call(val).slice(8, -1).toLowerCase()
        if (valType === 'object') {
          val = toCamelCase(val as Record<string, unknown>)
        } else if (valType === 'array') {
          val = _toCamelCaseArrayOfObjects(val as Record<string, unknown>[])
        }
        ;(result as Record<string, unknown>)[_toCamelCaseStr(key)] = val
      })
      return result
    }
    ```
    <br>

- 上記の関数を実際に呼び出してみる
    ```TS
    const messageParams = {
      parentMessageId: parentMessageId,
      threadId: thread,
      senderId: currentUser.id,
      senderType: currentUser.type,
      text: messageText,
    }


    const newUserMessage = await createUserMessage(messageParams)

    export const createUserMessage = async (
      message: UserMessageParams
    ): Promise<Message> => {
      try {
        const response = await fetch(`${API_V1}/messages`, {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
          },
          body: JSON.stringify(toSnakeCase(message)),
        })
    
        if (!response.ok) {
          throw new Error('Network response was not ok')
        }
    
        const data = await response.json()
        return toCamelCase(data)
      } catch (error) {
        console.error('Failed to create user message:', error)
        throw error
      }
    }
    ```

## まとめ
このように、APIとの通信においてキャメルケースとスネークケースの変換を自動化することで、コードの一貫性を保ちながら、開発をスムーズに進めることができます。これにより、フロントエンドとバックエンドのスタイルの違いによる混乱を減らし、メンテナンス性を向上させることができます。