Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use faster implementation for merging #186

Merged
merged 16 commits into from
Oct 5, 2022
3 changes: 2 additions & 1 deletion lib/Onyx.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import * as Logger from './Logger';
import cache from './OnyxCache';
import createDeferredTask from './createDeferredTask';
import mergeWithCustomized from './mergeWithCustomized';
import { merge } from './fastMerge';

// Keeps track of the last connectionID that was used so we can keep incrementing it
let lastConnectionID = 0;
Expand Down Expand Up @@ -761,7 +762,7 @@ function applyMerge(key, data) {
if (_.isObject(data) || _.every(mergeValues, _.isObject)) {
// Object values are merged one after the other
return _.reduce(mergeValues, (modifiedData, mergeValue) => {
const newData = mergeWithCustomized({}, modifiedData, mergeValue);
const newData = Object.assign({}, merge(modifiedData, mergeValue));

// We will also delete any object keys that are undefined or null.
// Deleting keys is not supported by AsyncStorage so we do it this way.
Expand Down
3 changes: 2 additions & 1 deletion lib/OnyxCache.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import _ from 'underscore';
import mergeWithCustomized from './mergeWithCustomized';
import { merge } from './fastMerge';
luacmartins marked this conversation as resolved.
Show resolved Hide resolved

const isDefined = _.negate(_.isUndefined);

Expand Down Expand Up @@ -110,7 +111,7 @@ class OnyxCache {
* @param {Record<string, *>} data - a map of (cache) key - values
*/
merge(data) {
this.storageMap = mergeWithCustomized({}, this.storageMap, data);
this.storageMap = Object.assign({}, merge(this.storageMap, data));
luacmartins marked this conversation as resolved.
Show resolved Hide resolved

const storageKeys = this.getAllKeys();
const mergedKeys = _.keys(data);
Expand Down
99 changes: 99 additions & 0 deletions lib/fastMerge.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// Mostly copied from https://medium.com/@lubaka.a/how-to-remove-lodash-performance-improvement-b306669ad0e1
mountiny marked this conversation as resolved.
Show resolved Hide resolved

/**
* Is the object Mergeable
*
* @param val
* @returns {*|boolean}
*/
mountiny marked this conversation as resolved.
Show resolved Hide resolved
function isMergeableObject(val) {
luacmartins marked this conversation as resolved.
Show resolved Hide resolved
var nonNullObject = val && typeof val === 'object';
return nonNullObject &&
Object.prototype.toString.call(val) !== '[object RegExp]' &&
Object.prototype.toString.call(val) !== '[object Date]';
}
/**
* Empty the Target
*
* @param val
* @returns {*}
*/
function emptyTarget(val) {
return Array.isArray(val) ? [] : {}
}
/**
* Clone if Necessary
*
* @param value
* @param optionsArgument
* @returns {*}
*/
function cloneIfNecessary(value, optionsArgument) {
var clone = optionsArgument && optionsArgument.clone === true;
luacmartins marked this conversation as resolved.
Show resolved Hide resolved
return (clone && isMergeableObject(value)) ? merge(emptyTarget(value), value, optionsArgument) : value;
}
/**
* Default Array Merge
*
* @param target
* @param source
* @param optionsArgument
* @returns {*}
*/
let valArr;
function defaultArrayMerge(target, source, optionsArgument) {
var destination = target.slice();
for (let i = 0; i < source.length; ++i) {
valArr = source[i];
if (i >= destination.length) {
destination.push(cloneIfNecessary(valArr, optionsArgument));
} else if (typeof destination[i] === 'undefined') {
destination[i] = cloneIfNecessary(valArr, optionsArgument);
} else if (isMergeableObject(valArr)) {
destination[i] = merge(target[i], valArr, optionsArgument);
}
}
return destination;
}
/**
* Merge Object
*
* @param target
* @param source
* @param optionsArgument
* @returns {{}}
*/
function mergeObject(target, source, optionsArgument) {
var destination = {};
if (isMergeableObject(target)) {
Object.keys(target).forEach(function (key) {
destination[key] = cloneIfNecessary(target[key], optionsArgument)
})
}
Object.keys(source).forEach(function (key) {
if (!isMergeableObject(source[key]) || !target[key]) {
destination[key] = cloneIfNecessary(source[key], optionsArgument)
} else {
destination[key] = merge(target[key], source[key], optionsArgument)
}
});
return destination
}
/**
* Merge Object and Arrays
*
* @param target
* @param source
* @param optionsArgument
* @returns {*}
*/
export function merge(target, source, optionsArgument) {
var array = Array.isArray(source);
var options = optionsArgument || { arrayMerge: defaultArrayMerge };
luacmartins marked this conversation as resolved.
Show resolved Hide resolved
var arrayMerge = options.arrayMerge || defaultArrayMerge;
if (array) { // We may not even need that as mergeWithCustomized suggests it
return Array.isArray(target) ? arrayMerge(target, source, optionsArgument) : cloneIfNecessary(source, optionsArgument);
} else {
return mergeObject(target, source, optionsArgument);
}
}