Skip to content

Commit

Permalink
deepMapWithKeys
Browse files Browse the repository at this point in the history
  • Loading branch information
kla committed Oct 20, 2024
1 parent 3778fe8 commit 8ecdab3
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 2 deletions.
27 changes: 26 additions & 1 deletion src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,6 @@ export function deepMap(
): Record<string, any> {
for (const [key, value] of Object.entries(obj)) {
const currentPath = path ? `${path}.${key}` : key

const newValue = callback(currentPath, value)

if (typeof newValue === 'object' && newValue !== null && !Array.isArray(newValue)) {
Expand All @@ -392,6 +391,32 @@ export function deepMap(
return obj
}

/**
* Recursively iterates over each property in an object and runs a callback function.
* The key and value of each property can be modified based on the callback's return value.
* @param obj - The object to iterate over.
* @param callback - The callback function to run for each property.
* @param path - The current path in dot notation (used for recursion).
* @returns The modified object.
*/
export function deepMapWithKeys(
obj: Record<string, any>,
callback: (path: string, key: string, value: any) => [string, any],
path: string = ''
): Record<string, any> {
return Object.entries(obj).reduce((acc, [key, value]) => {
const currentPath = path ? `${path}.${key}` : key
const [newKey, newValue] = callback(currentPath, key, value)

if (typeof newValue === 'object' && newValue !== null && !Array.isArray(newValue))
acc[newKey] = deepMapWithKeys(newValue, callback, currentPath)
else
acc[newKey] = newValue

return acc
}, {})
}

/**
* Resolves a file path, expanding the tilde character if present,
* and then applies path.resolve with any additional path segments.
Expand Down
56 changes: 55 additions & 1 deletion tests/unit/utils.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { beforeEach, describe, expect, it, mock } from 'bun:test'
import { csvKeyValuePairs, dasherize, deepRemoveKeys, dig, directoryExists, flattenObject, isFile, timeAgo, truthy, verifyDirectory, presence, deepMap, resolve } from '~/utils'
import { csvKeyValuePairs, dasherize, deepRemoveKeys, dig, directoryExists, flattenObject, isFile, timeAgo, truthy, verifyDirectory, presence, deepMap, resolve, deepMapWithKeys } from '~/utils'
import * as os from 'os'
import * as path from 'path'

Expand Down Expand Up @@ -482,3 +482,57 @@ describe('resolve', () => {
it('resolves mixed absolute and relative paths', () => expect(resolve('/root', 'user', '../documents')).toBe(path.resolve('/root', 'user', '../documents')))
it('handles tilde expansion only for the first argument', () => expect(resolve('~/documents', '~/projects')).toBe(path.resolve(os.homedir(), 'documents', '~/projects')))
})

describe('deepMapWithKeys', () => {
it('transforms keys and values in a simple object', () => {
const input = { a: 1, b: 2 }
const result = deepMapWithKeys(input, (path, key, value) => [key.toUpperCase(), value * 2])
expect(result).toEqual({ A: 2, B: 4 })
})

it('handles nested objects', () => {
const input = { a: { b: 1, c: { d: 2 } } }
const result = deepMapWithKeys(input, (path, key, value) => [
`${key}_modified`,
typeof value === 'number' ? value + 1 : value
])
expect(result).toEqual({
a_modified: {
b_modified: 2,
c_modified: {
d_modified: 3
}
}
})
})

it('preserves arrays', () => {
const input = { a: [1, 2, 3], b: { c: [4, 5] } }
const result = deepMapWithKeys(input, (path, key, value) => [key, value])
expect(result).toEqual(input)
})

it('uses the correct path for nested properties', () => {
const input = { a: { b: { c: 1 } } }
const paths = []
deepMapWithKeys(input, (path, key, value) => {
paths.push(path)
return [key, value]
})
expect(paths).toEqual(['a', 'a.b', 'a.b.c'])
})

it('allows removal of properties by returning undefined', () => {
const input = { a: 1, b: 2, c: 3 }
const result = deepMapWithKeys(input, (path, key, value) =>
key === 'b' ? [undefined, undefined] : [key, value]
)
expect(result).toEqual({ a: 1, c: 3 })
})

it('handles empty objects', () => {
const input = {}
const result = deepMapWithKeys(input, (path, key, value) => [key, value])
expect(result).toEqual({})
})
})

0 comments on commit 8ecdab3

Please sign in to comment.