From 0062c0225598ccc4291386ba3be2ea044716bd5c Mon Sep 17 00:00:00 2001 From: Avior Date: Mon, 3 Jun 2024 17:30:02 +0200 Subject: [PATCH] feat(object-util): add an objectGet function Signed-off-by: Avior --- packages/object-util/__tests__/index.test.ts | 26 +++++- packages/object-util/src/ObjectUtil.ts | 89 ++++++++++++++++---- 2 files changed, 98 insertions(+), 17 deletions(-) diff --git a/packages/object-util/__tests__/index.test.ts b/packages/object-util/__tests__/index.test.ts index 8e8b98c..d9309d4 100644 --- a/packages/object-util/__tests__/index.test.ts +++ b/packages/object-util/__tests__/index.test.ts @@ -1,6 +1,6 @@ /// -import { isObject, objectClean, objectClone, objectEqual, objectFind, objectKeys, objectLoop, objectMap, objectOmit, objectRemap, objectSet, objectSize, objectSort, objectValues } from '../src/ObjectUtil' +import { isObject, objectClean, objectClone, objectEqual, objectFind, objectGet, objectKeys, objectLoop, objectMap, objectOmit, objectRemap, objectSet, objectSize, objectSort, objectValues } from '../src/ObjectUtil' describe('Throw if parameter is not an object', () => { it('should works', () => { @@ -486,3 +486,27 @@ describe('object find', () => { })).toEqual(undefined) }) }) + + +describe('object get', () => { + it('should deeply get an object value', () => { + expect(objectGet({a: { b: [{ c: 'pouet' }]}}, ['a', 'b', 0, 'c'])) + .toEqual('pouet') + }) + it('should deeply get an object value from a string', () => { + expect(objectGet({a: { b: [{ c: 'pouet' }]}}, 'a.b.0.c')) + .toEqual('pouet') + }) + it('return undefined if key is too profound', () => { + expect(objectGet({a: { b: [{ c: 'pouet' }]}}, 'a.b.0.c.d')) + .toEqual(undefined) + }) + it('return the object if key is too shallow', () => { + expect(objectGet({a: { b: [{ c: 'pouet' }]}}, 'a.b.0')) + .toEqual({ c: 'pouet' }) + }) + it('return undefined if key is invalid', () => { + expect(objectGet({a: { b: [{ c: 'pouet' }]}}, 'a.c.0')) + .toEqual(undefined) + }) +}) diff --git a/packages/object-util/src/ObjectUtil.ts b/packages/object-util/src/ObjectUtil.ts index 64ecb91..a172c95 100644 --- a/packages/object-util/src/ObjectUtil.ts +++ b/packages/object-util/src/ObjectUtil.ts @@ -40,15 +40,21 @@ export function objectMap( obj: BasicObject, fn: (value: T, key: K, index: number) => {key: keyof J, value: J[typeof key]}, - options?: {strict?: boolean} + options?: { strict?: boolean } ): J { mustBeObject(obj) + + // create a clone const clone: J = {} as any + + // loop through each keys objectLoop(obj, (item, oldKey, index) => { const { key, value } = fn(item, oldKey, index) if (options?.strict && key in clone) { throw new Error('objectRemap strict mode active, you can\'t remap the same key twice') } + + // set new key pair into the clone clone[key] = value }) return clone @@ -66,7 +72,11 @@ export function objectLoop fn: (value: T, key: K, index: number) => boolean | void ): boolean { mustBeObject(obj) + + // get the object keys const keys = objectKeys(obj) + + // loop trough each keys for (let index = 0; index < keys.length; index++) { const key = keys[index] const stop = fn(obj[key] as T, key as K, index) @@ -327,6 +337,49 @@ export function objectFind return res } +/** + * go through an object to get a specific value + * + * note: it will be slower than getting it directly (ex: `obj['pouet']`) + * + * @param obj the object to go through + * @param {Array | string} path the path to follow (if path is a string it will be splitted with `.` and ints will be parsed) + * + * @returns the value if found or undefined if it was not found + */ +export function objectGet(obj: object, path: Array | string): T | undefined { + mustBeObject(obj) + + // transform path into an Array + if (typeof path === 'string') { + path = path.split('.').map((it) => /^\d+$/g.test(it) ? Number.parseInt(it) : it) + } + + // the pointer + let pointer: object = obj + + // loop through each keys + for (let index = 0; index < path.length; index++) { + const key = path[index] + const nextIndex = index + 1; + + // handle key being undefined or pointer not having key and not the last key + if (typeof key === 'undefined' || !Object.prototype.hasOwnProperty.call(pointer, key) && nextIndex < path.length) { + return undefined + } + + // if last index + if (nextIndex === path.length) { + return (pointer as any)[key] as T + } + + // move pointer to new key + pointer = (pointer as any)[key] + } + + throw new Error(`it should never be here ! (${JSON.stringify(obj)}, ${path}, ${JSON.stringify(pointer)})`) +} + /** * return if an item is an object * @@ -346,28 +399,32 @@ export function isObject(item: any): item is BasicObject { * @returns {boolean} true is the item is an object, else throw an error */ export function mustBeObject(item: any): item is BasicObject { - if (isObject(item)) { - return true - } else { + if (!isObject(item)) { throw new Error('Input is not an object!') } + return true } export default { - objectMap, - objectRemap, - objectLoop, - objectToArray, + objectClean, + objectClone, + objectEqual, + objectFind, + objectGet, objectKeys, + objectLoop, + objectMap, + objectOmit, + objectRemap, + objectSet, objectSize, objectSort, - cloneObject, - objectClone, - objectSet, - objectEqual, - objectClean, - objectOmit, - objectFind, + + // helpers isObject, - mustBeObject + mustBeObject, + + // deprecated + objectToArray, + cloneObject, }