Last active
December 17, 2021 17:33
-
-
Save brombal/c6c9ccdd9ed5a7bd1003ca791273da2f 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
/* | |
# mapObject | |
mapObject<TOut, TIn = any>( | |
object: TIn, | |
callback: (value: any, path: string[], root: any) => any, | |
options?: { | |
mutate?: boolean; | |
thisArg?: any; | |
omitEmpty?: boolean; | |
}, | |
): TOut; | |
Maps an object recursively by calling `callback` on each property and replacing the value with the result. | |
`callback` is not invoked for properties that contain objects or arrays, but instead `mapObject` is called | |
recursively on those values. | |
`callback` is passed the following parameters: | |
- `value` - The current value | |
- `path` - The path to the value as an array | |
- `object` - The original object being mapped | |
If the result of `callback` is undefined, the property will be absent from the resulting object. | |
`options` is an object with the following properties: | |
- `mutate` - If true, the original object will be mutated (default false) | |
- `omitEmpty` - If true, mapped objects and arrays that are empty will be omitted from the result (default false) | |
- `thisArg` - Sets the `this` value for `callback` | |
*/ | |
const isObject = (value: any) => value?.constructor.prototype === Object.prototype; | |
interface MapObjectFn { | |
<TIn>( | |
object: TIn, | |
callback: (value: any, path: string[], root: any) => any, | |
options?: MapObjectOptions, | |
): TIn; | |
<TOut, TIn = any>( | |
object: TIn, | |
callback: (value: any, path: string[], root: any) => any, | |
options?: MapObjectOptions, | |
): TOut; | |
} | |
interface MapObjectOptions { | |
mutate?: boolean; | |
thisArg?: any; | |
omitEmpty?: boolean; | |
} | |
export const mapObject: MapObjectFn = function mapObject<TOut, TIn>( | |
object: TIn, | |
callback: (value: any, path: string[], root: any) => any, | |
options?: MapObjectOptions, | |
): TOut { | |
return _mapObject(object, callback, options || {}, new WeakMap(), []); | |
}; | |
function _mapObject( | |
object: any, | |
callback: (value: any, path: string[], root: any) => any, | |
options: MapObjectOptions, | |
cache: any, | |
path: string[], | |
): any { | |
if (!Array.isArray(object) && !isObject(object)) return object; | |
if (cache.has(object)) return cache.get(object); | |
const result = options.mutate ? object : Array.isArray(object) ? [] : {}; | |
cache.set(object, result); | |
for (const k in object) { | |
if (!Object.prototype.hasOwnProperty.call(object, k)) continue; | |
const val = object[k]; | |
if (isObject(val) || Array.isArray(val)) { | |
const mapped = _mapObject(val, callback, options, cache, [...path, k]); | |
if ( | |
!options.omitEmpty || | |
(isObject(mapped) && Object.keys(mapped).length) || | |
(Array.isArray(mapped) && mapped.length) | |
) | |
result[k] = mapped; | |
else delete result[k]; | |
} else { | |
const mapped = callback.call(options.thisArg, val, [...path, k], object); | |
if (mapped !== undefined) result[k] = mapped; | |
} | |
} | |
return result; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment