From 4efb33c0cb243ddf26fb144ea6c6d5c3714ea6ae Mon Sep 17 00:00:00 2001 From: Avior Date: Tue, 21 Sep 2021 21:39:16 +0200 Subject: [PATCH] Added objectClean and isObject functions Signed-off-by: Avior --- packages/object-util/__tests__/index.test.ts | 84 ++++++++++++++------ packages/object-util/src/ObjectUtil.ts | 60 ++++++++++++-- 2 files changed, 115 insertions(+), 29 deletions(-) diff --git a/packages/object-util/__tests__/index.test.ts b/packages/object-util/__tests__/index.test.ts index e2f3f5b..3a24165 100644 --- a/packages/object-util/__tests__/index.test.ts +++ b/packages/object-util/__tests__/index.test.ts @@ -1,6 +1,13 @@ /// -import { objectSize, objectMap, objectSort, objectEqual, objectKeys, objectSet, objectLoop, objectClone, objectValues } from '../src/ObjectUtil' +import { objectSize, objectMap, objectSort, objectEqual, objectKeys, objectSet, objectLoop, objectClone, objectValues, objectClean, isObject } from '../src/ObjectUtil' + +describe('Throw if parameter is not an object', () => { + it('should works', () => { + // @ts-ignore + expect(objectKeys).toThrow() + }) +}) describe('Object Map tests', () => { it('should works', () => { @@ -136,32 +143,20 @@ describe('Object Clone Tests', () => { expect(clone).not.toEqual(obj) }) - it('Should keep types', () => { - const obj = { - a: [], - b: '10', - c: 10, - d: {}, - e: [10], - f: {g: 10} - } + it('should clone an Array', () => { + const obj = ['one', 'two'] const clone = objectClone(obj) expect(clone).toEqual(obj) + clone[0] = 'three' + expect(clone).not.toEqual(obj) }) - it ('Should clone any types', () => { - const obj = { - a: [], - b: '10', - c: 10, - d: {}, - e: [10], - f: {g: 10} - } - objectLoop(obj, (subObj) => { - const clone = objectClone(subObj) - expect(clone).toEqual(subObj) - }) + it('should deeply clone an Array', () => { + const obj = ['one', 'two', ['three']] + const clone = objectClone(obj) + expect(clone).toEqual(obj) + ;(clone[2][0] as string) = 'zero' + expect(clone).not.toEqual(obj) }) }) @@ -236,3 +231,46 @@ describe('Object Equal Test', () => { })).toBe(true) }) }) + +describe('Object Clean Tests', () => { + it('should clean undefined by default', () => { + const obj = {a: '', b: null, c: undefined} + objectClean(obj) + expect(obj).toEqual({a: '', b: null}) + + const obj2 = {a: '', b: null, c: undefined} + objectClean(obj2, {cleanUndefined: false}) + expect(obj2).toEqual({a: '', b: null, c: undefined}) + }) + it('should clean null when set', () => { + const obj = {a: '', b: null, c: undefined} + objectClean(obj, {cleanNull: true}) + expect(obj).toEqual({a: ''}) + }) + it('should clean deep by default', () => { + const obj = {a: '', b: null, c: undefined, d: {da: '', db: null, dc: undefined}} + objectClean(obj) + expect(obj).toEqual({a: '', b: null, d: {da: '', db: null}}) + }) +}) + +describe('Is Object Tests', () => { + it('null is not an "object"', () => { + expect(isObject(null)).toBe(false) + }) + it('boolean is not an "object"', () => { + expect(isObject(true)).toBe(false) + }) + it('undefined is not an "object"', () => { + expect(isObject(undefined)).toBe(false) + }) + it('string is not an "object"', () => { + expect(isObject("null")).toBe(false) + }) + it('number is not an "object"', () => { + expect(isObject(0)).toBe(false) + }) + it('object is an "object"', () => { + expect(isObject({})).toBe(true) + }) +}) diff --git a/packages/object-util/src/ObjectUtil.ts b/packages/object-util/src/ObjectUtil.ts index cda57a3..3501161 100644 --- a/packages/object-util/src/ObjectUtil.ts +++ b/packages/object-util/src/ObjectUtil.ts @@ -9,6 +9,7 @@ export function objectMap( obj: Record, fn: (value: T, key: string, index: number) => J ): Array { + mustBeObject(obj) const list: Array = [] objectLoop(obj, (item, key, index) => { list.push(fn(item, key, index)) @@ -25,6 +26,7 @@ export function objectLoop( obj: Record, fn: (value: T, key: string, index: number) => boolean | void ): boolean { + mustBeObject(obj) const keys = objectKeys(obj) for (let index = 0; index < keys.length; index++) { const key = keys[index] @@ -41,6 +43,7 @@ export function objectLoop( * @param obj the object to transform */ export function objectValues(obj: Record): Array { + mustBeObject(obj) return Object.values(obj) } @@ -48,6 +51,7 @@ export function objectValues(obj: Record): Array { * @deprecated use `objectValues` */ export function objectToArray(obj: Record): Array { + mustBeObject(obj) return objectValues(obj) } @@ -56,6 +60,7 @@ export function objectToArray(obj: Record): Array { * @param obj the object */ export function objectKeys(obj: Record): Array { + mustBeObject(obj) return Object.keys(obj) } @@ -64,6 +69,7 @@ export function objectKeys(obj: Record): Array { * @param obj the object */ export function objectSize(obj: Record): number { + mustBeObject(obj) return objectKeys(obj).length } @@ -78,6 +84,7 @@ export function objectSort = Record>( obj: T, fn?: Array | ((a: keyof T, b: keyof T) => number) ): T { + mustBeObject(obj) const ordered: any = {} let sortedKeys: Array = [] if (Array.isArray(fn)) { @@ -95,6 +102,7 @@ export function objectSort = Record>( * @deprecated use `objectClone` */ export function cloneObject>(obj: T): T { + mustBeObject(obj) return objectClone(obj) } @@ -103,14 +111,15 @@ export function cloneObject>(obj: T): T { * @param obj the object to clone */ export function objectClone>(obj: T): T { - if (typeof obj !== 'object') { - const v = obj - return v - } + mustBeObject(obj) if (Array.isArray(obj)) { const arr: Array = [] for (const item of obj) { - arr.push(objectClone(item)) + if (isObject(item)) { + arr.push(objectClone(item)) + } else { + arr.push(item) + } } return arr as unknown as T } @@ -136,6 +145,7 @@ export function objectClone>(obj: T): T { * @param value the value */ export function objectSet(obj: Record, path: Array, value: any): void { + mustBeObject(obj) let pointer = obj for (let index = 0; index < path.length; index++) { const key = path[index] @@ -167,6 +177,8 @@ export function objectSet(obj: Record, path: Array * @param y the second object */ export function objectEqual(x: Record, y: Record): boolean { + mustBeObject(x) + mustBeObject(y) if (objectSize(x) !== objectSize(y)) { return false } @@ -186,6 +198,40 @@ export function objectEqual(x: Record, y: Record): boo return res } +/** + * deeply compare objects and return if they are equal or not + * @param x the first object + * @param y the second object + */ +export function objectClean(obj: Record, options?: {cleanUndefined?: boolean, cleanNull?: boolean, deep?: boolean}): void { + mustBeObject(obj) + objectLoop(obj, (item, key) => { + if ((typeof options?.cleanUndefined === 'undefined' || options?.cleanUndefined) && item === undefined) { + delete obj[key] + } + + if (options?.cleanNull && item === null) { + delete obj[key] + } + + if ((typeof options?.deep === 'undefined' || options?.deep) && isObject(item)) { + return objectClean(item, options) + } + }) +} + +export function isObject(item: any): item is Record { + return typeof item === 'object' && item !== null +} + +function mustBeObject(item: any): item is Record { + if (isObject(item)) { + return true + } else { + throw new Error("Input is not an object!") + } +} + export default { objectMap, objectLoop, @@ -196,5 +242,7 @@ export default { cloneObject, objectClone, objectSet, - objectEqual + objectEqual, + objectClean, + isObject }