UNPKG

@blumintinc/flatten-anything

Version:

Flatten objects and replace nested props with 'prop.subprop'. A simple and small integration

181 lines (170 loc) 5.88 kB
'use strict'; /** Returns the object type of the given payload */ function getType(payload) { return Object.prototype.toString.call(payload).slice(8, -1); } /** Returns whether the payload is an array */ function isArray(payload) { return getType(payload) === 'Array'; } /** * Returns whether the payload is a plain JavaScript object (excluding special classes or objects * with other prototypes) */ function isPlainObject(payload) { if (getType(payload) !== 'Object') return false; const prototype = Object.getPrototypeOf(payload); return !!prototype && prototype.constructor === Object && prototype === Object.prototype; } /** Returns whether the payload is a an array with at least 1 item */ function isFullArray(payload) { return isArray(payload) && payload.length > 0; } /** * Returns whether the payload is a number (but not NaN) * * This will return `false` for `NaN`!! */ function isNumber(payload) { return getType(payload) === 'Number' && !isNaN(payload); } function pathsAreEqual(path, wildcardPath) { const wildcardPathPieces = wildcardPath.split('.'); const pathWithWildcards = path .split('.') .reduce((carry, piece, index) => { const add = wildcardPathPieces[index] === '*' ? '*' : piece; carry.push(add); return carry; }, []) .join('.'); return pathWithWildcards === wildcardPath; } function recursiveOmit(obj, omittedKeys, pathUntilNow = '') { if (!isPlainObject(obj)) { return obj; } return Object.entries(obj).reduce((carry, [key, value]) => { let path = pathUntilNow; if (path) path += '.'; path += key; if (omittedKeys.some((guardPath) => pathsAreEqual(path, guardPath))) { return carry; } // no further recursion needed if (!isPlainObject(value)) { carry[key] = value; return carry; } carry[key] = recursiveOmit(obj[key], omittedKeys, path); return carry; }, {}); } /** * omit returns a new object without the props you omit * * @export * @template T * @template K * @param {T} obj the target object to omit props from * @param {K[]} keys the prop names you want to omit * @returns {O.Omit<T, K>} a new object without the omitted props */ function omit(obj, keys) { if (!isFullArray(keys)) return obj; return recursiveOmit(obj, keys); } function retrievePaths(object, path, result, untilDepth) { if (!isPlainObject(object) || !Object.keys(object).length || `${object['methodName']}`?.includes('FieldValue')) { if (!path) return object; result[path] = object; return result; } if (isNumber(untilDepth)) untilDepth--; return Object.keys(object).reduce((carry, key) => { const pathUntilNow = path ? path + '.' : ''; const newPath = pathUntilNow + key; // last iteration or not const extra = untilDepth === -1 ? { [newPath]: object[key] } : retrievePaths(object[key], newPath, result, untilDepth); return Object.assign(carry, extra); }, {}); } /** * Flattens an object from `{a: {b: {c: 'd'}}}` to `{'a.b.c': 'd'}` * * @export * @param object the object to flatten * @param [number] how deep you want to flatten. 1 for flattening only the first nested prop, and keeping deeper objects as is. * @returns the flattened object */ function flattenObject(object, untilDepth) { const result = {}; return retrievePaths(object, null, result, untilDepth); } /** * Flattens an array from `[1, ['a', ['z']], 2]` to `[1, 'a', 'z', 2]` * * @export * @param array the array to flatten * @returns the flattened array */ function flattenArray(array) { return array.reduce((carry, item) => { return isArray(item) ? [...carry, ...flattenArray(item)] : [...carry, item]; }, []); } /** * Flattens certain props of an object. * * @export * @param object the object to flatten Eg. `{a: {subA: 1}, b: {subB: 1}}` * @param [props=[]] the prop names you want to flatten. Eg. `['a']` will return `{'a.subA': 1, b: {subB: 1}}` * @returns the flattened object */ function flattenObjectProps(object, props = []) { const flatObject = props.reduce((carry, propPath) => { const firstPropKey = propPath.split('.')[0] ?? ''; const target = { [`${firstPropKey}`]: object[firstPropKey] }; // calculate a certain depth to flatten or `null` to flatten everything const untilDepth = propPath.split('.').length - 1 || undefined; const flatPart = flattenObject(target, untilDepth); const flatPartFiltered = Object.entries(flatPart).reduce((carry, [key, value]) => { if (!key.startsWith(propPath)) return carry; carry[key] = value; return carry; }, {}); return { ...carry, ...flatPartFiltered }; }, {}); const omittedProps = props; const objectWithoutFlatProps = omit(object, omittedProps); return { ...objectWithoutFlatProps, ...flatObject }; } /** * Flattens an object or array. * Object example: `{a: {b: {c: 'd'}}}` to `{'a.b.c': 'd'}` * Array example: `[1, ['a', ['z']], 2]` to `[1, 'a', 'z', 2]` * * @export * @param objectOrArray the payload to flatten * @param [number] how deep you want to flatten. (currently only works with objects) 1 for flattening only the first nested prop, and keeping deeper objects as is. * @returns the flattened result */ function flatten(objectOrArray, untilDepth) { return isArray(objectOrArray) ? flattenArray(objectOrArray) : flattenObject(objectOrArray, untilDepth); } exports.flatten = flatten; exports.flattenArray = flattenArray; exports.flattenObject = flattenObject; exports.flattenObjectProps = flattenObjectProps;