Skip to content

Commit

Permalink
mergeKeysDeep and mergeKeysDeepButNotCyclic rewirte. Can now handle n…
Browse files Browse the repository at this point in the history
…on objects
  • Loading branch information
maximilianMairinger committed Jan 28, 2024
1 parent d8fb42f commit 4cc8ee1
Show file tree
Hide file tree
Showing 2 changed files with 157 additions and 30 deletions.
53 changes: 24 additions & 29 deletions app/src/circClone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import sani, { OBJECT, matches, OR } from "sanitize-against"
export {polyfill} from "sanitize-against"


// maybe use https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator/filter over iterare
// maybe use https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator/filter over iterare, polyfill much bigger than iterare though...


export const cloneKeysButKeepSym = (() => {
Expand All @@ -29,46 +29,41 @@ export const cloneKeysButKeepSym = (() => {



// todo: change from and into
export function mergeKeysDeepButNotCyclic<Into extends object, From extends object>(into: Into, from: From): Into & From {
for (const key of Object.keys(from)) {
const intoVal = into[key]
const fromVal = from[key]
if (intoVal !== undefined && !Object.hasOwn(into, key)) continue // prototype poisoning protection
export function mergeKeysDeepButNotCyclic<Into extends unknown, From extends unknown>(into: Into, from: From): Into & From {
if (isPlainObjectOrArray(from) && isPlainObjectOrArray(into)) {
for (const key of Object.keys(from)) {
const intoVal = into[key] as any
const fromVal = from[key] as any
if (intoVal !== undefined && !Object.hasOwn(into as any, key)) continue // prototype poisoning protection


if (isPlainObjectOrArray(fromVal)) {
if (isPlainObjectOrArray(intoVal)) mergeKeysDeepButNotCyclic(intoVal, fromVal)
else into[key] = cloneKeys(from[key])
(into as any)[key] = mergeKeysDeepButNotCyclic(intoVal, fromVal)
}
else into[key] = fromVal
return into as any
}
return into as any
else return from as any
}


export const mergeKeysDeep = (() => {
let known: WeakMap<any, any>
return function mergeKeysDeep<Into extends object, From extends object>(into: Into, from: From): Into & From {
return function mergeKeysDeep<Into extends unknown, From extends unknown>(into: Into, from: From): Into & From {
known = new WeakMap()
mergeKeysDeepRec(into, from)
return into as any
return mergeKeysDeepRec(into, from)
}
function mergeKeysDeepRec(into: object, from: object) {
known.set(from, into)
for (const key of Object.keys(from)) {
const intoVal = into[key]
const fromVal = from[key]
if (intoVal !== undefined && !Object.hasOwn(into, key)) continue // prototype poisoning protection


if (isPlainObjectOrArray(fromVal)) {
if (known.has(from[key])) into[key] = known.get(from[key])
else if (isPlainObjectOrArray(intoVal)) mergeKeysDeepRec(intoVal, fromVal)
else into[key] = cloneKeys(from[key])
function mergeKeysDeepRec(into: unknown, from: unknown) {
if (isPlainObjectOrArray(from) && isPlainObjectOrArray(into)) {
if (known.has(from)) return known.get(from)
known.set(from, into)
for (const key of Object.keys(from)) {
const intoVal = into[key]
const fromVal = from[key]
if (intoVal !== undefined && !Object.hasOwn(into as any, key)) continue // prototype poisoning protection

(into as any)[key] = mergeKeysDeepRec(intoVal, fromVal)
}
else into[key] = fromVal
return into
}
else return from
}
})()

Expand Down
134 changes: 133 additions & 1 deletion test/src/test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,137 @@
import "./extend"
import { iterateOverObject, findShortestPathToPrimitive, flatten, cloneKeys, uniqueMatch } from "../../app/src/circClone"
import { iterateOverObject, findShortestPathToPrimitive, flatten, cloneKeys, uniqueMatch, mergeKeysDeep, mergeKeysDeepButNotCyclic } from "../../app/src/circClone"


describe("mergeKeysDeep", () => {
test("multi ref: primitive set", () => {
const bob = {
lel: 2
} as any

const ob = {
a: bob,
b: bob,
c: bob
}

expect(mergeKeysDeep(ob, {a: undefined})).eq({a: undefined, b: bob, c: bob})

expect(mergeKeysDeep(ob, {a: undefined}).b).toBe(bob)
expect(mergeKeysDeep(ob, {a: undefined}).c).toBe(bob)
})

test("multi ref: ref set", () => {
const bob = {
lel: 2
} as any

const ob = {
a: bob,
b: bob,
c: bob
}

expect(mergeKeysDeep(ob, {a: {lel: 3}})).eq({a: {lel: 3}, b: {lel: 3}, c: {lel: 3}})

expect(mergeKeysDeep(ob, {a: {lel: 4}}).b).toBe(bob)
expect(bob.lel).toBe(4)
expect(mergeKeysDeep(ob, {a: {lel: 5}}).c).toBe(bob)
expect(bob.lel).toBe(5)
})

test("init primitive to", () => {
expect(mergeKeysDeep(undefined, {a: 2, b: 3})).eq({a: 2, b: 3})
expect(mergeKeysDeep(null, {a: 2, b: 3})).eq({a: 2, b: 3})
expect(mergeKeysDeep("qwe", {a: 2, b: 3})).eq({a: 2, b: 3})
expect(mergeKeysDeep(2, {a: 2, b: 3})).eq({a: 2, b: 3})


const ob = {a: 2, b: 3}
expect(mergeKeysDeep(undefined, ob)).toBe(ob)
})

test("init primitive from", () => {
expect(mergeKeysDeep({a: 2, b: 3}, undefined)).eq(undefined)
expect(mergeKeysDeep({a: 2, b: 3}, null)).eq(null)
expect(mergeKeysDeep({a: 2, b: 3}, "qwe")).eq("qwe")
expect(mergeKeysDeep({a: 2, b: 3}, 2)).eq(2)
})

test("prototype poisoning", () => {
const obj = Object.create(null)
obj.__proto__ = {a: 2}

expect(mergeKeysDeep({}, obj).a).not.toBe(2)
})
})

describe("mergeKeysButNotDeep", () => {
test("multi ref: primitive set", () => {
const bob = {
lel: 2
} as any

const ob = {
a: bob,
b: bob,
c: bob
}

expect(mergeKeysDeepButNotCyclic(ob, {a: undefined})).eq({a: undefined, b: bob, c: bob})

expect(mergeKeysDeepButNotCyclic(ob, {a: undefined}).b).toBe(bob)
expect(mergeKeysDeepButNotCyclic(ob, {a: undefined}).c).toBe(bob)
const o = mergeKeysDeepButNotCyclic(ob, {a: undefined})
expect(o.b).toBe(o.c)
})

test("multi ref: ref set", () => {
const bob = {
lel: 2
} as any

const ob = {
a: bob,
b: bob,
c: bob
}

expect(mergeKeysDeepButNotCyclic(ob, {a: {lel: 3}})).eq({a: {lel: 3}, b: {lel: 3}, c: {lel: 3}})

expect(mergeKeysDeepButNotCyclic(ob, {a: {lel: 3}}).a).toBe(bob)

expect(mergeKeysDeepButNotCyclic(ob, {a: {lel: 4}}).b).toBe(bob)
expect(bob.lel).toBe(4)
expect(mergeKeysDeepButNotCyclic(ob, {a: {lel: 5}}).c).toBe(bob)
expect(bob.lel).toBe(5)
})

test("init primitive to", () => {
expect(mergeKeysDeepButNotCyclic(undefined, {a: 2, b: 3})).eq({a: 2, b: 3})
expect(mergeKeysDeepButNotCyclic(null, {a: 2, b: 3})).eq({a: 2, b: 3})
expect(mergeKeysDeepButNotCyclic("qwe", {a: 2, b: 3})).eq({a: 2, b: 3})
expect(mergeKeysDeepButNotCyclic(2, {a: 2, b: 3})).eq({a: 2, b: 3})


const ob = {a: 2, b: 3}
expect(mergeKeysDeepButNotCyclic(undefined, ob)).toBe(ob)
})

test("init primitive from", () => {
expect(mergeKeysDeepButNotCyclic({a: 2, b: 3}, undefined)).eq(undefined)
expect(mergeKeysDeepButNotCyclic({a: 2, b: 3}, null)).eq(null)
expect(mergeKeysDeepButNotCyclic({a: 2, b: 3}, "qwe")).eq("qwe")
expect(mergeKeysDeepButNotCyclic({a: 2, b: 3}, 2)).eq(2)
})

test("prototype poisoning", () => {
const obj = Object.create(null)
obj.__proto__ = {a: 2}

expect(mergeKeysDeepButNotCyclic({}, obj).a).not.toBe(2)
})
})



describe("iterateOverObject", () => {
Expand Down

0 comments on commit 4cc8ee1

Please sign in to comment.