index.js
  1  var defaultIsMergeableObject = require('is-mergeable-object')
  2  
  3  function emptyTarget(val) {
  4  	return Array.isArray(val) ? [] : {}
  5  }
  6  
  7  function cloneUnlessOtherwiseSpecified(value, options) {
  8  	return (options.clone !== false && options.isMergeableObject(value))
  9  		? deepmerge(emptyTarget(value), value, options)
 10  		: value
 11  }
 12  
 13  function defaultArrayMerge(target, source, options) {
 14  	return target.concat(source).map(function(element) {
 15  		return cloneUnlessOtherwiseSpecified(element, options)
 16  	})
 17  }
 18  
 19  function getMergeFunction(key, options) {
 20  	if (!options.customMerge) {
 21  		return deepmerge
 22  	}
 23  	var customMerge = options.customMerge(key)
 24  	return typeof customMerge === 'function' ? customMerge : deepmerge
 25  }
 26  
 27  function getEnumerableOwnPropertySymbols(target) {
 28  	return Object.getOwnPropertySymbols
 29  		? Object.getOwnPropertySymbols(target).filter(function(symbol) {
 30  			return target.propertyIsEnumerable(symbol)
 31  		})
 32  		: []
 33  }
 34  
 35  function getKeys(target) {
 36  	return Object.keys(target).concat(getEnumerableOwnPropertySymbols(target))
 37  }
 38  
 39  function propertyIsOnObject(object, property) {
 40  	try {
 41  		return property in object
 42  	} catch(_) {
 43  		return false
 44  	}
 45  }
 46  
 47  // Protects from prototype poisoning and unexpected merging up the prototype chain.
 48  function propertyIsUnsafe(target, key) {
 49  	return propertyIsOnObject(target, key) // Properties are safe to merge if they don't exist in the target yet,
 50  		&& !(Object.hasOwnProperty.call(target, key) // unsafe if they exist up the prototype chain,
 51  			&& Object.propertyIsEnumerable.call(target, key)) // and also unsafe if they're nonenumerable.
 52  }
 53  
 54  function mergeObject(target, source, options) {
 55  	var destination = {}
 56  	if (options.isMergeableObject(target)) {
 57  		getKeys(target).forEach(function(key) {
 58  			destination[key] = cloneUnlessOtherwiseSpecified(target[key], options)
 59  		})
 60  	}
 61  	getKeys(source).forEach(function(key) {
 62  		if (propertyIsUnsafe(target, key)) {
 63  			return
 64  		}
 65  
 66  		if (propertyIsOnObject(target, key) && options.isMergeableObject(source[key])) {
 67  			destination[key] = getMergeFunction(key, options)(target[key], source[key], options)
 68  		} else {
 69  			destination[key] = cloneUnlessOtherwiseSpecified(source[key], options)
 70  		}
 71  	})
 72  	return destination
 73  }
 74  
 75  function deepmerge(target, source, options) {
 76  	options = options || {}
 77  	options.arrayMerge = options.arrayMerge || defaultArrayMerge
 78  	options.isMergeableObject = options.isMergeableObject || defaultIsMergeableObject
 79  	// cloneUnlessOtherwiseSpecified is added to `options` so that custom arrayMerge()
 80  	// implementations can use it. The caller may not replace it.
 81  	options.cloneUnlessOtherwiseSpecified = cloneUnlessOtherwiseSpecified
 82  
 83  	var sourceIsArray = Array.isArray(source)
 84  	var targetIsArray = Array.isArray(target)
 85  	var sourceAndTargetTypesMatch = sourceIsArray === targetIsArray
 86  
 87  	if (!sourceAndTargetTypesMatch) {
 88  		return cloneUnlessOtherwiseSpecified(source, options)
 89  	} else if (sourceIsArray) {
 90  		return options.arrayMerge(target, source, options)
 91  	} else {
 92  		return mergeObject(target, source, options)
 93  	}
 94  }
 95  
 96  deepmerge.all = function deepmergeAll(array, options) {
 97  	if (!Array.isArray(array)) {
 98  		throw new Error('first argument should be an array')
 99  	}
100  
101  	return array.reduce(function(prev, next) {
102  		return deepmerge(prev, next, options)
103  	}, {})
104  }
105  
106  module.exports = deepmerge