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