applyToDefaults.js 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. 'use strict';
  2. const Assert = require('./assert');
  3. const Clone = require('./clone');
  4. const Merge = require('./merge');
  5. const Reach = require('./reach');
  6. const internals = {};
  7. module.exports = function (defaults, source, options = {}) {
  8. Assert(defaults && typeof defaults === 'object', 'Invalid defaults value: must be an object');
  9. Assert(!source || source === true || typeof source === 'object', 'Invalid source value: must be true, falsy or an object');
  10. Assert(typeof options === 'object', 'Invalid options: must be an object');
  11. if (!source) { // If no source, return null
  12. return null;
  13. }
  14. if (options.shallow) {
  15. return internals.applyToDefaultsWithShallow(defaults, source, options);
  16. }
  17. const copy = Clone(defaults);
  18. if (source === true) { // If source is set to true, use defaults
  19. return copy;
  20. }
  21. const nullOverride = options.nullOverride !== undefined ? options.nullOverride : false;
  22. return Merge(copy, source, { nullOverride, mergeArrays: false });
  23. };
  24. internals.applyToDefaultsWithShallow = function (defaults, source, options) {
  25. const keys = options.shallow;
  26. Assert(Array.isArray(keys), 'Invalid keys');
  27. const seen = new Map();
  28. const merge = source === true ? null : new Set();
  29. for (let key of keys) {
  30. key = Array.isArray(key) ? key : key.split('.'); // Pre-split optimization
  31. const ref = Reach(defaults, key);
  32. if (ref &&
  33. typeof ref === 'object') {
  34. seen.set(ref, merge && Reach(source, key) || ref);
  35. }
  36. else if (merge) {
  37. merge.add(key);
  38. }
  39. }
  40. const copy = Clone(defaults, {}, seen);
  41. if (!merge) {
  42. return copy;
  43. }
  44. for (const key of merge) {
  45. internals.reachCopy(copy, source, key);
  46. }
  47. const nullOverride = options.nullOverride !== undefined ? options.nullOverride : false;
  48. return Merge(copy, source, { nullOverride, mergeArrays: false });
  49. };
  50. internals.reachCopy = function (dst, src, path) {
  51. for (const segment of path) {
  52. if (!(segment in src)) {
  53. return;
  54. }
  55. const val = src[segment];
  56. if (typeof val !== 'object' || val === null) {
  57. return;
  58. }
  59. src = val;
  60. }
  61. const value = src;
  62. let ref = dst;
  63. for (let i = 0; i < path.length - 1; ++i) {
  64. const segment = path[i];
  65. if (typeof ref[segment] !== 'object') {
  66. ref[segment] = {};
  67. }
  68. ref = ref[segment];
  69. }
  70. ref[path[path.length - 1]] = value;
  71. };