Skip to content

Commit

Permalink
feat: new object function to merge changes from objects of same type (#…
Browse files Browse the repository at this point in the history
…269)

* feat: new object function to merge changes from objects of same type

* test: adding more usecases tests and fit function

it pass all tests wow 🚀

* feat: separating tests and improve code

* chore: lint fix

* chore: remove extra if statement

* fix: brute check on values instead of hasOwnProperty

add more tests

* chore: use diff values on tests
  • Loading branch information
gabrielforster authored May 21, 2024
1 parent 8b9a12c commit b4b22c3
Show file tree
Hide file tree
Showing 3 changed files with 307 additions and 3 deletions.
244 changes: 243 additions & 1 deletion __tests__/object/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isObject, objToStr, isObjEqual, hasOwnProperty, compareJsonDiff } from '../../src/object'
import { isObject, objToStr, isObjEqual, hasOwnProperty, compareJsonDiff, mergeObjectChanges } from '../../src/object'

const objectA = {
name: 'adapcon',
Expand Down Expand Up @@ -124,3 +124,245 @@ describe('isObjEqual', () => {
expect(isObjEqual(null, null)).toEqual(true)
})
})

describe('Merge Objects Changes tests', () => {
describe('Merge Objects Changes tests without custom params', () => {
it('should merge two objects', () => {
const obj1 = { a: 1, b: 2 }
const obj2 = { a: 1, b: 4 }
const result = mergeObjectChanges(obj1, obj2)
expect(result).toStrictEqual({ a: 1, b: 4 })
})

it('should merge two objects with nested objects', () => {
const obj1 = { a: 1, b: { c: 2, d: 3 } }
const obj2 = { a: 1, b: { c: 4, d: 3 } }
const result = mergeObjectChanges(obj1, obj2)
expect(result).toStrictEqual({ a: 1, b: { c: 4, d: 3 } })
})

it('should merge two objects with nested objects on old and undefined on new', <T>() => {
const obj1 = { a: 1, b: { c: 2, d: 3 } } as T
const obj2 = { a: 1, b: undefined } as T
const result = mergeObjectChanges(obj1, obj2)
expect(result).toStrictEqual({ a: 1 })
})

it('should merge two objects with nested objects and arrays', () => {
const obj1 = { a: 1, b: { c: 2, d: [1, 2] } }
const obj2 = { a: 1, b: { c: 4, d: [1, 3] } }
const result = mergeObjectChanges(obj1, obj2)
expect(result).toStrictEqual({ a: 1, b: { c: 4, d: [1, 3] } })
})

it('should merge two objects with undefined values with not present on new object without options param', () => {
const obj1 = { a: 1, b: 2 }
const obj2 = { a: 1 }
const result = mergeObjectChanges(obj1, obj2)
expect(result).toStrictEqual({ a: 1 })
})
})

describe('Merge Objects Changes tests without custom params', () => {
it('should merge two objects with same type but diff keys and same values on keys that are in both', <T>() => {
const obj1 = { a: 1, b: 2 } as T
const obj2 = { a: 1, c: 3 } as T
const result = mergeObjectChanges(obj1, obj2, {
useOldKeysIfNotPresentInNew: true
})
expect(result).toStrictEqual({ a: 1, b: 2, c: 3 })
})

it('should merge two objects with same type but diff keys and diff values on keys that are in both', <T>() => {
const obj1 = { a: 1, b: 2 } as T
const obj2 = { a: 2, c: 3 } as T
const result = mergeObjectChanges(obj1, obj2, {
useOldKeysIfNotPresentInNew: true
})
expect(result).toStrictEqual({ a: 2, b: 2, c: 3 })
})
})

describe('Merge Objects Changes tests with useOldKeysIfNotPresentInNew custom params', () => {
it('should merge two objects with new values if key is present on newObj and not on oldObj with useOldKeysIfNotPresentInNew param as true', () => {
const obj1 = { a: 1, b: 2 }
const obj2 = { a: 2 }
const result = mergeObjectChanges(obj1, obj2, {
useOldKeysIfNotPresentInNew: true
})
expect(result).toStrictEqual({ a: 2, b: 2 })
})

it('should merge two objects with new values if key is present on newObj and not on oldObj - useOldKeysIfNotPresentInNew set as false', () => {
const obj1 = { a: 1 }
const obj2 = { a: 2, b: 2 }
const result = mergeObjectChanges(obj1, obj2, {
useOldKeysIfNotPresentInNew: false
})
expect(result).toStrictEqual({ a: 2, b: 2 })
})

it('should merge two objects with undefined values with not present on new object - useOldKeysIfNotPresentInNew param not set', () => {
const obj1 = { a: 1, b: 2 }
const obj2 = { a: 2 }
const result = mergeObjectChanges(obj1, obj2)
expect(result).toStrictEqual({ a: 2 })
})

it('should merge two objects with nested objects on old and undefined on new', <T>() => {
const obj1 = { a: 1, b: { c: 2, d: 3 } } as T
const obj2 = { a: 1, b: undefined } as T
const result = mergeObjectChanges(obj1, obj2, {
useOldKeysIfNotPresentInNew: true
})
expect(result).toStrictEqual({ a: 1, b: { c: 2, d: 3 } })
})
})

describe('Merge Objects Changes tests with addNewKeys param', () => {
it('should add new values with addNewKeys as true', () => {
const obj1 = { a: 1 }
const obj2 = { a: 1, b: 2 }
const result = mergeObjectChanges(obj1, obj2, {
addNewKeys: true
})
expect(result).toStrictEqual({ a: 1, b: 2 })
})

it('should not merge new values with addNewKeys params as false', () => {
const obj1 = { a: 1 }
const obj2 = { a: 1, b: 2 }
const result = mergeObjectChanges(obj1, obj2, {
addNewKeys: false
})
expect(result).toStrictEqual({ a: 1 })
})

it('should add new values when addNewKeys is not set (default value is true)', () => {
const obj1 = { a: 1 }
const obj2 = { a: 1, b: 2 }
const result = mergeObjectChanges(obj1, obj2)
expect(result).toStrictEqual({ a: 1, b: 2 })
})

it('should merge two objects with nested objects on old and undefined on new', <T>() => {
const obj1 = { a: 1, b: undefined } as T
const obj2 = { a: 1, b: { c: 2, d: 3 } } as T
const result = mergeObjectChanges(obj1, obj2, {
addNewKeys: true
})
expect(result).toStrictEqual({ a: 1, b: { c: 2, d: 3 } })
})

it('should merge two objects with nested objects on old and undefined on new', <T>() => {
const obj1 = { a: 1, b: undefined } as T
const obj2 = { a: 1, b: { c: 2, d: 3 } } as T
const result = mergeObjectChanges(obj1, obj2, {
addNewKeys: false
})
expect(result).toStrictEqual({ a: 1 })
})
})

describe('Merge Objects Changes tests with both useOldKeysIfNotPresentInNew and addNewKeys params', () => {
it('should merge two objects with new values if key is present on newObj and not on oldObj with useOldKeysIfNotPresentInNew param as true and addNewKeys as true', () => {
const obj1 = { a: 1, b: 2 }
const obj2 = { a: 2 }
const result = mergeObjectChanges(obj1, obj2, {
useOldKeysIfNotPresentInNew: true,
addNewKeys: true
})
expect(result).toStrictEqual({ a: 2, b: 2 })
})
})

it('should merge two objects with new values if key is present on newObj and not on oldObj with useOldKeysIfNotPresentInNew param as true and addNewKeys as false', () => {
const obj1 = { a: 1, b: 2 }
const obj2 = { a: 2 }
const result = mergeObjectChanges(obj1, obj2, {
useOldKeysIfNotPresentInNew: true,
addNewKeys: false
})
expect(result).toStrictEqual({ a: 2, b: 2 })
})

it('should merge two objects with new values if key is present on newObj and not on oldObj with useOldKeysIfNotPresentInNew param as false and addNewKeys as true', () => {
const obj1 = { a: 'um' }
const obj2 = { a: 'dois', b: 2 }
const result = mergeObjectChanges(obj1, obj2, {
useOldKeysIfNotPresentInNew: false,
addNewKeys: true
})
expect(result).toStrictEqual({ a: 'dois', b: 2 })
})

it('should merge two objects with new values if key is present on newObj and not on oldObj with useOldKeysIfNotPresentInNew param as false and addNewKeys as false', () => {
const obj1 = { a: 1 }
const obj2 = { a: 1, b: 2 }
const result = mergeObjectChanges(obj1, obj2, {
useOldKeysIfNotPresentInNew: false,
addNewKeys: false
})
expect(result).toStrictEqual({ a: 1 })
})

it('should merge objects with keys present singly in new and old but not in both with useOldKeysIfNotPresentInNew and addNewKeys params ', <T>() => {
const obj1 = { a: 1, b: 2 } as T
const obj2 = { a: 1, c: 2 } as T
const result = mergeObjectChanges(obj1, obj2, {
useOldKeysIfNotPresentInNew: true,
addNewKeys: true
})
expect(result).toStrictEqual({ a: 1, b: 2, c: 2 })
})

it('should merge two objects with nested objects on old and undefined on new', <T>() => {
const obj1 = { a: 1, b: { c: 2, d: 3 } } as T
const obj2 = { a: 1, b: undefined, e: 4 } as T
const result = mergeObjectChanges(obj1, obj2, {
useOldKeysIfNotPresentInNew: true,
addNewKeys: true
})
expect(result).toStrictEqual({ a: 1, b: { c: 2, d: 3 }, e: 4 })
})

it('should merge two objects with nested objects on old and undefined on new', <T>() => {
const obj1 = { a: 1, b: { c: 2, d: 3 } } as T
const obj2 = { a: 1, b: undefined } as T
const result = mergeObjectChanges(obj1, obj2, {
useOldKeysIfNotPresentInNew: true,
addNewKeys: false
})
expect(result).toStrictEqual({ a: 1, b: { c: 2, d: 3 } })
})

it('should merge two objects with nested objects on old and undefined on new', <T>() => {
const obj1 = { a: 1, b: { c: 2, d: 3 } } as T
const obj2 = { a: 1, b: undefined, e: 5 } as T
const result = mergeObjectChanges(obj1, obj2, {
useOldKeysIfNotPresentInNew: false,
addNewKeys: true
})
expect(result).toStrictEqual({ a: 1, e: 5 })
})

it('should merge two objects with nested objects on old and undefined on new', <T>() => {
const obj1 = { a: 1, b: { c: 2, d: 3 } } as T
const obj2 = { a: 1, b: undefined, e: 4 } as T
const result = mergeObjectChanges(obj1, obj2, {
useOldKeysIfNotPresentInNew: false,
addNewKeys: false
})
expect(result).toStrictEqual({ a: 1 })
})

it('should merge two objects with nested objects on old and undefined on new', <T>() => {
const obj1 = { a: 1, b: { c: 2, d: 3 } } as T
const obj2 = { a: 1, b: undefined, e: 4 } as T
const result = mergeObjectChanges(obj1, obj2, {
useOldKeysIfNotPresentInNew: false,
addNewKeys: true
})
expect(result).toStrictEqual({ a: 1, e: 4 })
})
})
62 changes: 62 additions & 0 deletions src/object/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,65 @@ export const compareJsonDiff = ({ baseObject, compareObject }: { baseObject: obj

return { diff, removed }
}

type MergeObjectChangesOptions = {
useOldKeysIfNotPresentInNew?: boolean
addNewKeys?: boolean
}
export function mergeObjectChanges<T> (
oldObj: T,
newObj: T,
options: MergeObjectChangesOptions = {
useOldKeysIfNotPresentInNew: false,
addNewKeys: true
}
): T {
options.useOldKeysIfNotPresentInNew = options.useOldKeysIfNotPresentInNew ?? false
options.addNewKeys = options.addNewKeys ?? true

const result = {} as T

function checkKeys (obj: T) {
for (const key in obj) {
if (Array.isArray(oldObj[key]) && Array.isArray(newObj[key])) {
result[key] = newObj[key]
} else if (typeof oldObj[key] === 'object' && typeof newObj[key] === 'object') {
result[key] = mergeObjectChanges(oldObj[key], newObj[key])
continue
}

if (
// has key with value on old but not on new
(newObj[key] === undefined || newObj[key] === null) &&
(oldObj[key] !== undefined && oldObj[key] !== null) &&
options.useOldKeysIfNotPresentInNew
) {
result[key] = oldObj[key]
continue
} else if (
// has key with value on new but not on old
(newObj[key] !== undefined && newObj[key] !== null) &&
(oldObj[key] === undefined || oldObj[key] === null) &&
options.addNewKeys
) {
result[key] = newObj[key]
continue
} else if (
// has key with value on both
(newObj[key] !== undefined && newObj[key] !== null) &&
(oldObj[key] !== undefined && oldObj[key] !== null)
) {
if (oldObj[key] !== newObj[key]) {
result[key] = newObj[key]
} else {
result[key] = oldObj[key]
}
}
}
}

checkKeys(oldObj)
checkKeys(newObj)

return result
}
4 changes: 2 additions & 2 deletions src/string/encrypt.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import crypto from 'crypto'
import { InternalError } from '..'

export function encryptPassword(passwordToEncrypt: string, encryptionKey: string): string {
export function encryptPassword (passwordToEncrypt: string, encryptionKey: string): string {
const salt = crypto.randomBytes(16).toString('hex')
const key = crypto.scryptSync(encryptionKey, salt, 24)
const iv = Buffer.alloc(16, 0)
Expand All @@ -11,7 +11,7 @@ export function encryptPassword(passwordToEncrypt: string, encryptionKey: string
return `${salt}:${encryptedPassword}`
}

export function decryptPassword(storedPassword: string, encryptionKey: string): string {
export function decryptPassword (storedPassword: string, encryptionKey: string): string {
try {
const [salt, encryptedPassword] = storedPassword.split(':')
const key = crypto.scryptSync(encryptionKey, salt, 24)
Expand Down

0 comments on commit b4b22c3

Please sign in to comment.