index.js 3.2 KB

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