Created
May 16, 2023 17:41
-
-
Save drecdroid/0e8649baf06ffe422ed8d6ce24f8e608 to your computer and use it in GitHub Desktop.
This file contains 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
export const DELETE = Symbol('DELETE'); | |
/** | |
* Traverse an object and apply a transformation function to each value. | |
* The transformation function is called with the value, the key and the key path. | |
* | |
* The transformation function can return a new value or the same value. | |
* If the transformation function returns a new value, the value in the object is replaced with the new value. | |
* | |
* The transformation function can return an object. | |
* If the transformation function returns an object, the object is traversed as well. | |
* | |
* Warning: The transformation could loop infinitely if it always returns an object. | |
* | |
* @param obj { object } - the object to traverse | |
* @param transform { (value: unknown, key: string, keyPath: string) => unknown } - a function that transforms the value, the key and the key path | |
*/ | |
export async function traverse(obj, transform) { | |
/** | |
* @type { object[] } | |
*/ | |
const objectStack = [obj]; | |
/** | |
* @type { string[] } | |
*/ | |
const keysStack = [] | |
while (objectStack.length > 0) { | |
const obj = objectStack.pop(); | |
if (obj) { | |
for (const key in obj) { | |
keysStack.push(key); | |
const keyPath = keysStack.join('.'); | |
const value = obj[key]; | |
const transformed = transform(value, key, keyPath); | |
if (transformed === DELETE) { | |
delete obj[key]; | |
keysStack.pop(); | |
continue; | |
} | |
if (transformed !== value) { | |
obj[key] = transformed; | |
} | |
if (typeof transformed === 'object' && transformed !== null) { | |
objectStack.push(transformed); | |
} else { | |
keysStack.pop(); | |
} | |
} | |
} | |
} | |
} | |
/** | |
* Traverse an object and apply a transformation function to each value. | |
* The transformation function is called with the value, the key and the key path. | |
* | |
* The transformation function can return a new value or the same value. | |
* If the transformation function returns a new value, the value in the object is replaced with the new value. | |
* | |
* The transformation function can return an object. | |
* If the transformation function returns an object, the object is traversed as well. | |
* | |
* The transformation function can return a promise. | |
* If the transformation function returns a promise, the promise is awaited and the resolved value is used. | |
* | |
* Warning: The transformation could loop infinitely if it always returns an object. | |
* | |
* @param obj { object } - the object to traverse | |
* @param transform { (value: unknown, key: string, keyPath: string) => unknown | Promise<unknown> } - a function that transforms the value, the key and the key path | |
* @returns | |
**/ | |
export async function traverseAsync(obj, transform) { | |
/** | |
* @type { object[] } | |
*/ | |
const objectStack = [obj]; | |
/** | |
* @type { string[] } | |
*/ | |
const keysStack = [] | |
while (objectStack.length > 0) { | |
const obj = objectStack.pop(); | |
if (obj) { | |
for (const key in obj) { | |
keysStack.push(key); | |
const keyPath = keysStack.join('.'); | |
const value = obj[key]; | |
const awaitedValue = await value; | |
const transformed = await transform(awaitedValue, key, keyPath); | |
if (transformed === DELETE) { | |
delete obj[key]; | |
keysStack.pop(); | |
continue; | |
} | |
if (value !== transformed) { | |
obj[key] = transformed; | |
} | |
if (typeof transformed === 'object' && transformed !== null) { | |
objectStack.push(transformed); | |
} else { | |
keysStack.pop(); | |
} | |
} | |
} | |
} | |
} | |
const obj = { a: 1, b: { c: [{ a: 2 }, { d: 20 }] } } | |
traverse(obj, (value, key, keyPath) => { | |
if (keyPath === 'a') { | |
return DELETE; | |
} | |
return value | |
}) | |
obj | |
const asyncObj = { a: 1, b: { c: Promise.resolve([{ a: 2 }]) } } | |
traverseAsync(asyncObj, (value, key, keyPath) => { | |
if (keyPath === 'a') { | |
return DELETE; | |
} | |
return value | |
}).then(() => { | |
asyncObj | |
}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment