Toggle navigation
MeasureThat.net
Create a benchmark
Tools
Feedback
FAQ
Register
Log In
Run results for:
fastMerge alternatives
Logic analysis over fastMerge() implementation versus other libraries
Go to the benchmark
Embed
Embed Benchmark Result
Run details:
User agent:
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36
Browser:
Chrome 140
Operating system:
Mac OS X 10.15.7
Device Platform:
Desktop
Date tested:
7 months ago
Test name
Executions per second
Onyx fastmerge
3186.5 Ops/sec
Optimized fastMerge
2252.8 Ops/sec
ImmerJs
4677.7 Ops/sec
Loadash-es
1264.0 Ops/sec
HTML Preparation code:
<!--your preparation HTML code goes here--> <script src='https://unpkg.com/fast-equals@5.2.2/dist/min/index.js'></script> <script src='https://unpkg.com/immer@6.0.3/dist/immer.umd.production.min.js'></script> <script src='https://unpkg.com/lodash-es@4.17.21/lodash.min.js'></script>
Script Preparation code:
function generateDeepObject(levels, breadth, currentLevel = 0) { if (currentLevel >= levels) { return { value: `leaf_${Math.random()}`, number: Math.random() * 1000, nullValue: Math.random() > 0.7 ? null : `value_${Math.random()}`, array: Array.from({length: 5}, (_, i) => i) }; } const obj = {}; for (let i = 0; i < breadth; i++) { obj[`key_${currentLevel}_${i}`] = generateDeepObject(levels, breadth, currentLevel + 1); // Add some null values if (Math.random() > 0.8) { obj[`null_key_${currentLevel}_${i}`] = null; } } return obj; }
Tests:
Onyx fastmerge
const ONYX_INTERNALS__REPLACE_OBJECT_MARK = 'ONYX_INTERNALS__REPLACE_OBJECT_MARK'; function fastMergeOriginal(target, source, options, metadata, basePath = []) { if (!metadata) { metadata = { replaceNullPatches: [], }; } if (Array.isArray(source) || source === null || source === undefined) { return {result: source, replaceNullPatches: metadata.replaceNullPatches}; } const optionsWithDefaults = { shouldRemoveNestedNulls: options?.shouldRemoveNestedNulls ?? false, objectRemovalMode: options?.objectRemovalMode ?? 'none', }; const mergedValue = mergeObjectOriginal(target, source, optionsWithDefaults, metadata, basePath); return {result: mergedValue, replaceNullPatches: metadata.replaceNullPatches}; } function mergeObjectOriginal(target, source, options, metadata, basePath) { const destination = {}; const targetObject = isMergeableObjectOriginal(target) ? target : undefined; if (targetObject) { Object.keys(targetObject).forEach((key) => { const targetProperty = targetObject?.[key]; const sourceProperty = source?.[key]; const shouldOmitNullishProperty = options.shouldRemoveNestedNulls && (targetProperty === null || sourceProperty === null); if (targetProperty === undefined || shouldOmitNullishProperty) { return; } destination[key] = targetProperty; }); } Object.keys(source).forEach((key) => { let targetProperty = targetObject?.[key]; const sourceProperty = source[key]; const shouldOmitNullishProperty = options.shouldRemoveNestedNulls && sourceProperty === null; if (sourceProperty === undefined || shouldOmitNullishProperty) { return; } if (!isMergeableObjectOriginal(sourceProperty)) { destination[key] = sourceProperty; return; } if (options.objectRemovalMode === 'mark' && targetProperty === null) { targetProperty = {[ONYX_INTERNALS__REPLACE_OBJECT_MARK]: true}; metadata.replaceNullPatches.push([[...basePath, key], {...sourceProperty}]); } if (options.objectRemovalMode === 'replace' && sourceProperty[ONYX_INTERNALS__REPLACE_OBJECT_MARK]) { const sourcePropertyWithoutMark = {...sourceProperty}; delete sourcePropertyWithoutMark.ONYX_INTERNALS__REPLACE_OBJECT_MARK; destination[key] = sourcePropertyWithoutMark; return; } destination[key] = fastMergeOriginal(targetProperty, sourceProperty, options, metadata, [...basePath, key]).result; }); return destination; } function isMergeableObjectOriginal(value) { const isNonNullObject = value != null ? typeof value === 'object' : false; return isNonNullObject && !(value instanceof RegExp) && !(value instanceof Date) && !Array.isArray(value); } const data1 = generateDeepObject(5, 3); const data2 = generateDeepObject(5, 3); // Test fastMergeOriginal(data1, data2, { shouldRemoveNestedNulls: true });
Optimized fastMerge
function fastMergeOptimized(target, source, options, metadata = { replaceNullPatches: [] }, basePath = []) { if (source === null || source === undefined || Array.isArray(source)) { return { result: source, replaceNullPatches: metadata.replaceNullPatches }; } const optionsWithDefaults = { shouldRemoveNestedNulls: options?.shouldRemoveNestedNulls ?? false, objectRemovalMode: options?.objectRemovalMode ?? 'none', }; const mergedValue = mergeObjectOptimized(target, source, optionsWithDefaults, metadata, basePath); return { result: mergedValue, replaceNullPatches: metadata.replaceNullPatches }; } function mergeObjectOptimized(target, source, options, metadata, basePath) { const destination = Object.create(null); const targetObject = isMergeableObjectOptimized(target) ? target : undefined; const { shouldRemoveNestedNulls, objectRemovalMode } = options; if (targetObject) { const targetKeys = Object.keys(targetObject); for (let i = 0; i < targetKeys.length; i++) { const key = targetKeys[i]; const targetProperty = targetObject[key]; const sourceProperty = source[key]; const shouldOmit = shouldRemoveNestedNulls && (targetProperty === null || sourceProperty === null); if (targetProperty !== undefined && !shouldOmit) { destination[key] = targetProperty; } } } const sourceKeys = Object.keys(source); for (let i = 0; i < sourceKeys.length; i++) { const key = sourceKeys[i]; let targetProperty = targetObject?.[key]; const sourceProperty = source[key]; if (sourceProperty === undefined) continue; const shouldOmitNullish = shouldRemoveNestedNulls && sourceProperty === null; if (shouldOmitNullish) continue; if (!isMergeableObjectOptimized(sourceProperty)) { destination[key] = sourceProperty; continue; } if (objectRemovalMode === 'mark' && targetProperty === null) { targetProperty = { [ONYX_INTERNALS__REPLACE_OBJECT_MARK]: true }; metadata.replaceNullPatches.push([basePath.concat(key), { ...sourceProperty }]); } if (objectRemovalMode === 'replace' && sourceProperty[ONYX_INTERNALS__REPLACE_OBJECT_MARK]) { const { [ONYX_INTERNALS__REPLACE_OBJECT_MARK]: _, ...cleanSource } = sourceProperty; destination[key] = cleanSource; continue; } const newPath = basePath.length === 0 ? [key] : basePath.concat(key); destination[key] = fastMergeOptimized(targetProperty, sourceProperty, options, metadata, newPath).result; } return destination; } const objectCtorString = Object.prototype.constructor.toString(); function isMergeableObjectOptimized(value) { if (value === null || typeof value !== 'object') return false; const proto = Object.getPrototypeOf(value); if (proto === null) return true; return proto.constructor && proto.constructor.toString() === objectCtorString; } const data1 = generateDeepObject(5, 3); const data2 = generateDeepObject(5, 3); // Test fastMergeOptimized(data1, data2, { shouldRemoveNestedNulls: true });
ImmerJs
const data1 = generateDeepObject(5, 3); const data2 = generateDeepObject(5, 3); // Test immer.produce(data1, draft => { Object.assign(draft, data2); });
Loadash-es
const data1 = generateDeepObject(5, 3); const data2 = generateDeepObject(5, 3); // Test _.merge(_.cloneDeep(data1), data2);