diff --git a/web/packages/@core/base/shared/src/utils/index.ts b/web/packages/@core/base/shared/src/utils/index.ts index c067c73..a5ca302 100644 --- a/web/packages/@core/base/shared/src/utils/index.ts +++ b/web/packages/@core/base/shared/src/utils/index.ts @@ -11,11 +11,8 @@ export * from './resources'; export * from './state-handler'; export * from './to'; export * from './tree'; +export * from './object'; export * from './unique'; export * from './update-css-variables'; export * from './util'; export * from './window'; -export { default as cloneDeep } from 'lodash.clonedeep'; -export { default as get } from 'lodash.get'; -export { default as isEqual } from 'lodash.isequal'; -export { default as set } from 'lodash.set'; diff --git a/web/packages/@core/base/shared/src/utils/object.ts b/web/packages/@core/base/shared/src/utils/object.ts new file mode 100644 index 0000000..0ced272 --- /dev/null +++ b/web/packages/@core/base/shared/src/utils/object.ts @@ -0,0 +1,121 @@ +type KeyPath = Array | number | string; + +const keyPathPattern = /[^.[\]]+|\[(\d+)\]/g; + +function toKeyPath(path: KeyPath): Array { + if (Array.isArray(path)) { + return path; + } + if (typeof path === 'number') { + return [path]; + } + const keys: Array = []; + path.replace(keyPathPattern, (match, index) => { + keys.push(index === undefined ? match : Number(index)); + return match; + }); + return keys; +} + +function cloneDeep(value: T): T { + if (value === null || typeof value !== 'object') { + return value; + } + if (value instanceof Date) { + return new Date(value) as T; + } + if (Array.isArray(value)) { + return value.map((item) => cloneDeep(item)) as T; + } + if (value instanceof Map) { + return new Map( + [...value.entries()].map(([key, item]) => [cloneDeep(key), cloneDeep(item)]), + ) as T; + } + if (value instanceof Set) { + return new Set([...value.values()].map((item) => cloneDeep(item))) as T; + } + + const result = {} as Record; + for (const [key, item] of Object.entries(value)) { + result[key] = cloneDeep(item); + } + return result as T; +} + +function get( + object: any, + path: KeyPath, + defaultValue?: T, +): T | undefined { + let result = object; + for (const key of toKeyPath(path)) { + if (result == null) { + return defaultValue; + } + result = result[key as keyof typeof result]; + } + return result === undefined ? defaultValue : result; +} + +function set>( + object: T, + path: KeyPath, + value: unknown, +): T { + const keys = toKeyPath(path); + let cursor: Record = object; + + keys.forEach((key, index) => { + if (index === keys.length - 1) { + cursor[key] = value; + return; + } + + const nextKey = keys[index + 1]; + cursor[key] ??= typeof nextKey === 'number' ? [] : {}; + cursor = cursor[key]; + }); + + return object; +} + +function isEqual(left: unknown, right: unknown): boolean { + if (Object.is(left, right)) { + return true; + } + if (left instanceof Date && right instanceof Date) { + return left.getTime() === right.getTime(); + } + if ( + left === null || + right === null || + typeof left !== 'object' || + typeof right !== 'object' + ) { + return false; + } + if (Array.isArray(left) || Array.isArray(right)) { + if (!Array.isArray(left) || !Array.isArray(right)) { + return false; + } + return ( + left.length === right.length && + left.every((item, index) => isEqual(item, right[index])) + ); + } + + const leftKeys = Object.keys(left); + const rightKeys = Object.keys(right); + return ( + leftKeys.length === rightKeys.length && + leftKeys.every((key) => + isEqual( + (left as Record)[key], + (right as Record)[key], + ), + ) + ); +} + +export { cloneDeep, get, isEqual, set };