Created
August 30, 2025 09:28
-
-
Save rymizuki/8ae555138f3be37f0d83c9e25b4b6244 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { isArray, isObject, isString } from '@/type-guards'; | |
| import type { CamelizeRecursive } from '@/type-utils/camelize'; | |
| import { camelize } from '../string/camelize'; | |
| /** | |
| * 指定したオブジェクトのプロパティを再起的にcamelizeする | |
| * | |
| * @example | |
| * ``` | |
| * expect(camelizeRecursive({ prop_a: 1, prop_b: 2 })) | |
| * .toStrictEquals({ propA: 1, propB: 2 }) | |
| * ``` | |
| */ | |
| export function camelizeRecursive<T = unknown>(input: T): CamelizeRecursive<T> { | |
| if (input === null || input === undefined) | |
| return input as CamelizeRecursive<T>; | |
| if (!isObject(input)) { | |
| return input as CamelizeRecursive<T>; | |
| } | |
| if (isArray(input)) { | |
| return input.map((item) => | |
| isObject(item) ? camelizeRecursive(item) : item, | |
| ) as CamelizeRecursive<T>; | |
| } | |
| const result = {} as Record<string, unknown>; | |
| const keys = Object.keys(input) as Array<keyof T>; | |
| for (const prop of keys) { | |
| const value = input[prop]; | |
| const key = isString(prop) ? camelize(prop) : prop; | |
| result[key as string] = camelizeRecursive(value); | |
| } | |
| return result as CamelizeRecursive<T>; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import type { Camelize } from '@/type-utils/camelize'; | |
| /** | |
| * 与えられた文字列をcamelCaseに変換する | |
| * | |
| * @example | |
| * ``` | |
| * expect(camelize('some_value')).toBe('someValue') | |
| * ``` | |
| * | |
| */ | |
| export function camelize<T extends string>(input: T): Camelize<T> { | |
| let value = input as string; | |
| if (value.startsWith('_')) return value as Camelize<T>; | |
| if (/[A-Z]/.test(value)) { | |
| value = value | |
| .replace(/^([A-Z]+)/, (_, x: string) => x.toLowerCase()) | |
| .replaceAll(/([A-Z]+)/g, (_, x: string) => `_${x.toLowerCase()}`); | |
| } | |
| return value | |
| .toLowerCase() | |
| .replaceAll(/[.-\s_]+(\w|$)/g, (_, x: string) => | |
| x.toUpperCase(), | |
| ) as Camelize<T>; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { describe, expect, test } from 'vitest'; | |
| import { camelizeRecursive } from './camelize-recursive'; | |
| describe('utils / object / camelizeRecursive', () => { | |
| test.each([ | |
| [{ a: { a_b: { ab_c: null } } }, { a: { aB: { abC: null } } }], | |
| [{ a: { a_b: [{ ab_c: null }] } }, { a: { aB: [{ abC: null }] } }], | |
| [null, null], | |
| [undefined, undefined], | |
| [1000, 1000], | |
| ['snake_case', 'snake_case'], | |
| ['camelCase', 'camelCase'], | |
| ])('camelizeRecursive(%j) => %j', (input, expected) => { | |
| expect(camelizeRecursive(input)).toStrictEqual(expected); | |
| }); | |
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { describe, expect, test } from 'vitest'; | |
| import { camelize } from './camelize'; | |
| describe('utils / string / camelize', () => { | |
| test.each([ | |
| ['example', 'example'], | |
| ['snake_case', 'snakeCase'], | |
| ['PascalCase', 'pascalCase'], | |
| ['kebabu-case', 'kebabuCase'], | |
| ['space between', 'spaceBetween'], | |
| ['UPPER', 'upper'], | |
| ['UPPER_CASE', 'upperCase'], | |
| ['_ignored_value', '_ignored_value'], | |
| ])('camelize(%s) => %s', (input, expected) => { | |
| expect(camelize(input)).toStrictEqual(expected); | |
| }); | |
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { describe, expect, test } from 'vitest'; | |
| import { decamelizeRecursive } from './decamelize-recursive'; | |
| describe('decamelizeRecursive', () => { | |
| describe('separator is default', () => { | |
| test.each([ | |
| [{ a: { aB: { abC: null } } }, { a: { a_b: { ab_c: null } } }], | |
| [{ a: { aB: [{ abC: null }] } }, { a: { a_b: [{ ab_c: null }] } }], | |
| [null, null], | |
| [undefined, undefined], | |
| [1000, 1000], | |
| ['snake_case', 'snake_case'], | |
| ['camelCase', 'camelCase'], | |
| ])('decamelizeRecursive(%j) => %j', (input, expected) => { | |
| expect(decamelizeRecursive(input)).toStrictEqual(expected); | |
| }); | |
| }); | |
| describe("separator is '-'", () => { | |
| test.each([ | |
| [{ a: { aB: { abC: null } } }, { a: { 'a-b': { 'ab-c': null } } }], | |
| [{ a: { aB: [{ abC: null }] } }, { a: { 'a-b': [{ 'ab-c': null }] } }], | |
| [null, null], | |
| [undefined, undefined], | |
| [1000, 1000], | |
| ['snake_case', 'snake_case'], | |
| ['camelCase', 'camelCase'], | |
| ])('decamelizeRecursive(%j) => %j', (input, expected) => { | |
| expect(decamelizeRecursive(input, '-')).toStrictEqual(expected); | |
| }); | |
| }); | |
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { isArray, isObject, isString } from '@/type-guards'; | |
| import type { DecamelizeRecursive } from '@/type-utils/decamelize'; | |
| import { decamelize } from '../string/decamelize'; | |
| /** | |
| * 指定したオブジェクトのプロパティを再起的にdecamelizeする | |
| * | |
| * @example | |
| * ``` | |
| * expect(decamelizeRecursive({ propA: 1, propB: 2 })) | |
| * .toStrictEquals({ prop_a: 1, prop_b: 2 }) | |
| * ``` | |
| */ | |
| export function decamelizeRecursive<T = unknown, S extends string = '_'>( | |
| input: T, | |
| separator: S = '_' as S, | |
| ): DecamelizeRecursive<T, S> { | |
| if (input === null || input === undefined) | |
| return input as DecamelizeRecursive<T, S>; | |
| if (!isObject(input)) return input as DecamelizeRecursive<T, S>; | |
| if (isArray(input)) { | |
| return input.map((item) => | |
| isObject(item) ? decamelizeRecursive(item, separator) : item, | |
| ) as DecamelizeRecursive<T, S>; | |
| } | |
| const result = {} as Record<string, unknown>; | |
| const keys = Object.keys(input) as Array<keyof T>; | |
| for (const prop of keys) { | |
| const value = input[prop]; | |
| const key = isString(prop) ? decamelize(prop, separator) : prop; | |
| result[key as string] = decamelizeRecursive(value, separator); | |
| } | |
| return result as DecamelizeRecursive<T, S>; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { describe, expect, test } from 'vitest'; | |
| import { decamelize } from './decamelize'; | |
| describe('utils / string / decamelize', () => { | |
| describe('separator is default', () => { | |
| test.each([ | |
| ['example', 'example'], | |
| ['snake_case', 'snake_case'], | |
| ['camelCase', 'camel_case'], | |
| ['PascalCase', 'pascal_case'], | |
| ['kebabu-case', 'kebabu_case'], | |
| ['space between', 'space_between'], | |
| ['UPPER', 'upper'], | |
| ['UPPER_CASE', 'upper_case'], | |
| ['_ignored_value', '_ignored_value'], | |
| ])('decamelize(%s) => %s', (input, expected) => { | |
| expect(decamelize(input)).toStrictEqual(expected); | |
| }); | |
| }); | |
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import type { Decamelize } from '@/type-utils/decamelize'; | |
| import { camelize } from './camelize'; | |
| /** | |
| * 与えられた文字列をcamelCaseを分解し、任意の文字列(初期値は`_`)で結合する。 | |
| * | |
| * @example | |
| * ``` | |
| * expect(decamelize('someValue')).toBe('some_value') | |
| * ``` | |
| */ | |
| export function decamelize<T extends string, S extends string = '_'>( | |
| input: T, | |
| separator: S = '_' as S, | |
| ): Decamelize<T, S> { | |
| let value = input as string; | |
| if (value.startsWith('_')) return value as Decamelize<T, S>; | |
| value = camelize(input); | |
| return value.replaceAll( | |
| /[A-Z]/g, | |
| (x) => `${separator}${x.toLowerCase()}`, | |
| ) as Decamelize<T, S>; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment