feat(core-extensions): Add Object.mapKeys
This commit is contained in:
@@ -29,6 +29,14 @@
|
|||||||
"author": "T. R. Bernstein <ljspkgs01-project@tabshift.dev>",
|
"author": "T. R. Bernstein <ljspkgs01-project@tabshift.dev>",
|
||||||
"license": "EUPL-1.2",
|
"license": "EUPL-1.2",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
"./object/map-keys.f": {
|
||||||
|
"import": "./dist/object/map-keys.f.js",
|
||||||
|
"types": "./dist/object/map-keys.f.d.ts"
|
||||||
|
},
|
||||||
|
"./object/map-keys": {
|
||||||
|
"import": "./dist/object/map-keys.js",
|
||||||
|
"types": "./dist/object/map-keys.d.ts"
|
||||||
|
},
|
||||||
"./object/map-values.f": {
|
"./object/map-values.f": {
|
||||||
"import": "./dist/object/map-values.f.js",
|
"import": "./dist/object/map-values.f.js",
|
||||||
"types": "./dist/object/map-values.f.d.ts"
|
"types": "./dist/object/map-values.f.d.ts"
|
||||||
@@ -38,4 +46,4 @@
|
|||||||
"types": "./dist/object/map-values.d.ts"
|
"types": "./dist/object/map-values.d.ts"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export type MappedKeys<T, R extends string> =
|
||||||
|
T extends Array<infer U>
|
||||||
|
? Array<MappedKeys<U, R>>
|
||||||
|
: T extends object
|
||||||
|
? {
|
||||||
|
[K in keyof T as R]: MappedKeys<T[K], R>
|
||||||
|
}
|
||||||
|
: T
|
||||||
32
packages/core-extensions/src/object/map-keys.f.ts
Normal file
32
packages/core-extensions/src/object/map-keys.f.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import type { MappedKeys } from '@/internal/mapped-keys.type.js'
|
||||||
|
|
||||||
|
function isRecord(item: any): item is Record<string, any> {
|
||||||
|
const isNotNull = item != null
|
||||||
|
const isObject = typeof item === 'object'
|
||||||
|
const isNotAnArray = !Array.isArray(item)
|
||||||
|
return isNotNull && isNotAnArray && isObject
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapKeysInArrayItems<T extends any[], R extends string>(arr: T, transform: (key: string) => R) {
|
||||||
|
const result = arr.map((item) => (isRecord(item) ? mapKeys(item, transform) : item))
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mapKeys<T extends Record<string, any>, R extends string>(
|
||||||
|
obj: T,
|
||||||
|
transform: (key: string) => R
|
||||||
|
): MappedKeys<T, R> {
|
||||||
|
const result = <MappedKeys<T, R>>{}
|
||||||
|
for (let key of Object.keys(obj)) {
|
||||||
|
let newKey = transform(key)
|
||||||
|
const value = obj[key]
|
||||||
|
const newValue = isRecord(value)
|
||||||
|
? mapKeys(value, transform)
|
||||||
|
: Array.isArray(value)
|
||||||
|
? mapKeysInArrayItems(value, transform)
|
||||||
|
: value
|
||||||
|
result[newKey] = newValue
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
55
packages/core-extensions/src/object/map-keys.test.ts
Normal file
55
packages/core-extensions/src/object/map-keys.test.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { describe, it, test, expect } from 'vitest'
|
||||||
|
import { mapKeys } from './map-keys.f.js'
|
||||||
|
|
||||||
|
describe('mapKeys', () => {
|
||||||
|
it('does nothing for empty object', async () => {
|
||||||
|
const obj = <Record<string, any>>{}
|
||||||
|
const result = mapKeys(obj, (key) => `${key}_`)
|
||||||
|
|
||||||
|
expect(result).toEqual(obj)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('maps first level keys', async () => {
|
||||||
|
const obj = {
|
||||||
|
a: 1,
|
||||||
|
b: 2
|
||||||
|
}
|
||||||
|
const result = mapKeys(obj, (key) => `${key}_`)
|
||||||
|
|
||||||
|
expect(result).toEqual({ a_: 1, b_: 2 })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('maps second level keys', async () => {
|
||||||
|
const obj = {
|
||||||
|
a: { b: 1 }
|
||||||
|
}
|
||||||
|
const result = mapKeys(obj, (key) => `${key}_`)
|
||||||
|
|
||||||
|
expect(result).toEqual({ a_: { b_: 1 } })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('maps second level keys in arrays', async () => {
|
||||||
|
const obj = {
|
||||||
|
a: [{ b: 1 }]
|
||||||
|
}
|
||||||
|
const result = mapKeys(obj, (key) => `${key}_`)
|
||||||
|
|
||||||
|
expect(result).toEqual({ a_: [{ b_: 1 }] })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('maps keys with unusual values', async () => {
|
||||||
|
const func = () => 1
|
||||||
|
const obj = {
|
||||||
|
a: func,
|
||||||
|
b: undefined,
|
||||||
|
c: null
|
||||||
|
}
|
||||||
|
const result = mapKeys(obj, (key) => `${key}_`)
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
a_: func,
|
||||||
|
b_: undefined,
|
||||||
|
c_: null
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
21
packages/core-extensions/src/object/map-keys.ts
Normal file
21
packages/core-extensions/src/object/map-keys.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import type { MappedKeys } from '@/internal/mapped-keys.type.js'
|
||||||
|
import { mapKeys } from './map-keys.f.js'
|
||||||
|
|
||||||
|
const objectMapKeys = function <T extends object, R extends string>(
|
||||||
|
this: T,
|
||||||
|
callback: (key: string) => R
|
||||||
|
): MappedKeys<T, R> {
|
||||||
|
return mapKeys(this, callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Object {
|
||||||
|
mapKeys: typeof objectMapKeys
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.defineProperty(Object.prototype, 'mapKeys', {
|
||||||
|
value: objectMapKeys
|
||||||
|
})
|
||||||
|
|
||||||
|
export {}
|
||||||
Reference in New Issue
Block a user