improve handling of non-object values in clever merge
improve caching
This commit is contained in:
parent
b067bad545
commit
6107ab8cca
|
@ -25,9 +25,14 @@ const DYNAMIC_INFO = Symbol("cleverMerge dynamic info");
|
|||
* {a: 2}
|
||||
* @param {T} first first object
|
||||
* @param {O} second second object
|
||||
* @returns {T & O} merged object of first and second object
|
||||
* @returns {T & O | T | O} merged object of first and second object
|
||||
*/
|
||||
const cachedCleverMerge = (first, second) => {
|
||||
if (second === undefined) return first;
|
||||
if (first === undefined) return second;
|
||||
if (typeof second !== "object" || second === null) return second;
|
||||
if (typeof first !== "object" || first === null) return first;
|
||||
|
||||
let innerCache = mergeCache.get(first);
|
||||
if (innerCache === undefined) {
|
||||
innerCache = new WeakMap();
|
||||
|
@ -35,7 +40,7 @@ const cachedCleverMerge = (first, second) => {
|
|||
}
|
||||
const prevMerge = innerCache.get(second);
|
||||
if (prevMerge !== undefined) return prevMerge;
|
||||
const newMerge = cleverMerge(first, second, true);
|
||||
const newMerge = _cleverMerge(first, second, true);
|
||||
innerCache.set(second, newMerge);
|
||||
return newMerge;
|
||||
};
|
||||
|
@ -238,15 +243,31 @@ const getValueType = value => {
|
|||
|
||||
/**
|
||||
* Merges two objects. Objects are deeply clever merged.
|
||||
* Arrays might reference the old value with "..."
|
||||
* Arrays might reference the old value with "...".
|
||||
* Non-object values take preference over object values.
|
||||
* @template T
|
||||
* @template O
|
||||
* @param {T} first first object
|
||||
* @param {O} second second object
|
||||
* @returns {T & O | T | O} merged object of first and second object
|
||||
*/
|
||||
const cleverMerge = (first, second) => {
|
||||
if (second === undefined) return first;
|
||||
if (first === undefined) return second;
|
||||
if (typeof second !== "object" || second === null) return second;
|
||||
if (typeof first !== "object" || first === null) return first;
|
||||
|
||||
return _cleverMerge(first, second, false);
|
||||
};
|
||||
|
||||
/**
|
||||
* Merges two objects. Objects are deeply clever merged.
|
||||
* @param {object} first first object
|
||||
* @param {object} second second object
|
||||
* @param {boolean} internalCaching should parsing of objects and nested merges be cached
|
||||
* @returns {object} merged object of first and second object
|
||||
*/
|
||||
const cleverMerge = (first, second, internalCaching = false) => {
|
||||
if (second === undefined) return first;
|
||||
if (first === undefined) return second;
|
||||
const _cleverMerge = (first, second, internalCaching = false) => {
|
||||
const firstObject = internalCaching
|
||||
? cachedParseObject(first)
|
||||
: parseObject(first);
|
||||
|
@ -259,15 +280,14 @@ const cleverMerge = (first, second, internalCaching = false) => {
|
|||
if (fnInfo) {
|
||||
second = internalCaching
|
||||
? cachedCleverMerge(fnInfo[1], second)
|
||||
: cleverMerge(fnInfo[1], second, false);
|
||||
: cleverMerge(fnInfo[1], second);
|
||||
fn = fnInfo[0];
|
||||
}
|
||||
const newFn = (...args) => {
|
||||
const fnResult = fn(...args);
|
||||
if (typeof fnResult !== "object" || fnResult === null) return fnResult;
|
||||
return internalCaching
|
||||
? cachedCleverMerge(fnResult, second)
|
||||
: cleverMerge(fnResult, second, false);
|
||||
: cleverMerge(fnResult, second);
|
||||
};
|
||||
newFn[DYNAMIC_INFO] = [fn, second];
|
||||
return serializeObject(firstObject.static, { byProperty, fn: newFn });
|
||||
|
@ -505,29 +525,33 @@ const removeOperations = obj => {
|
|||
};
|
||||
|
||||
/**
|
||||
* @param {object} obj the object
|
||||
* @param {string} byProperty the by description
|
||||
* @template T
|
||||
* @param {T} obj the object
|
||||
* @param {keyof T} byProperty the by description
|
||||
* @param {...any} values values
|
||||
* @returns {object} object with merged byProperty
|
||||
* @returns {T} object with merged byProperty
|
||||
*/
|
||||
const resolveByProperty = (obj, byProperty, ...values) => {
|
||||
if (!(byProperty in obj)) {
|
||||
if (typeof obj !== "object" || obj === null || !(byProperty in obj)) {
|
||||
return obj;
|
||||
}
|
||||
const { [byProperty]: byValue, ...remaining } = obj;
|
||||
const { [byProperty]: _byValue, ..._remaining } = obj;
|
||||
const remaining = /** @type {T} */ (_remaining);
|
||||
const byValue = /** @type {Record<string, T> | function(...any[]): T} */ (
|
||||
/** @type {unknown} */ (_byValue)
|
||||
);
|
||||
if (typeof byValue === "object") {
|
||||
const key = values[0];
|
||||
if (key in byValue) {
|
||||
return cleverMerge(remaining, byValue[key]);
|
||||
return cachedCleverMerge(remaining, byValue[key]);
|
||||
} else if ("default" in byValue) {
|
||||
return cleverMerge(remaining, byValue.default);
|
||||
return cachedCleverMerge(remaining, byValue.default);
|
||||
} else {
|
||||
return remaining;
|
||||
return /** @type {T} */ (remaining);
|
||||
}
|
||||
} else if (typeof byValue === "function") {
|
||||
const result = byValue.apply(null, values);
|
||||
if (typeof result !== "object" || result === null) return result;
|
||||
return cleverMerge(
|
||||
return cachedCleverMerge(
|
||||
remaining,
|
||||
resolveByProperty(result, byProperty, ...values)
|
||||
);
|
||||
|
|
|
@ -4,7 +4,8 @@ const {
|
|||
cleverMerge,
|
||||
DELETE,
|
||||
removeOperations,
|
||||
resolveByProperty
|
||||
resolveByProperty,
|
||||
cachedCleverMerge
|
||||
} = require("../lib/util/cleverMerge");
|
||||
|
||||
describe("cleverMerge", () => {
|
||||
|
@ -648,14 +649,26 @@ describe("cleverMerge", () => {
|
|||
byArguments: () => false
|
||||
},
|
||||
false
|
||||
]
|
||||
],
|
||||
nonObject1: [1, 2, 2],
|
||||
nonObject2: [1, { a: 1 }, 1],
|
||||
nonObject3: [{ a: 1 }, 1, 1],
|
||||
nonObject4: [{ a: 1 }, undefined, { a: 1 }],
|
||||
nonObject5: [undefined, { a: 1 }, { a: 1 }]
|
||||
};
|
||||
for (const key of Object.keys(cases)) {
|
||||
const testCase = cases[key];
|
||||
it(`should merge ${key} correctly`, () => {
|
||||
let merged = cleverMerge(testCase[0], testCase[1]);
|
||||
let merged1 = cachedCleverMerge(testCase[0], testCase[1]);
|
||||
let merged2 = cachedCleverMerge(testCase[0], testCase[1]);
|
||||
expect(merged2).toBe(merged1);
|
||||
merged = resolveByProperty(merged, "byArguments", 1, 2, 3, 4, 5);
|
||||
merged1 = resolveByProperty(merged1, "byArguments", 1, 2, 3, 4, 5);
|
||||
merged2 = resolveByProperty(merged2, "byArguments", 1, 2, 3, 4, 5);
|
||||
expect(merged).toEqual(testCase[2]);
|
||||
expect(merged1).toEqual(testCase[2]);
|
||||
expect(merged2).toEqual(testCase[2]);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue