is-equal.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. /**
  2. * Module dependencies
  3. */
  4. var _ = require('@sailshq/lodash');
  5. /**
  6. * A variation on the lodash equality check that uses the expected typeSchema
  7. * for additional context (it stringifies lamdas and compares them that way)
  8. *
  9. * If no typeSchema is provided, this is currently just the same thing as `_.isEqual`
  10. *
  11. * @param {===} firstValue
  12. * @param {===} secondValue
  13. * @param {*} typeSchema
  14. * @return {Boolean}
  15. */
  16. module.exports = function isEqual (firstValue, secondValue, typeSchema) {
  17. // If typeSchema is not provided, use `_.isEqual`
  18. if (_.isUndefined(typeSchema)) {
  19. return _.isEqual(firstValue, secondValue);
  20. }
  21. // Otherwise recursively crawl the type schema and ensure that `firstValue`
  22. // and `secondValue` are equivalent to one another in all the places.
  23. //
  24. // The following code returns the result via calling a self-calling
  25. // (but *named*) recursive function.
  26. /**
  27. * [_isEqualRecursive description]
  28. * @param {[type]} firstValue [description]
  29. * @param {[type]} secondValue [description]
  30. * @param {[type]} typeSchemaHere [description]
  31. * @param {[type]} keypathArray [description]
  32. * @param {[type]} keyOrIndex [description]
  33. * @return {Boolean} [description]
  34. */
  35. return (function _isEqualRecursive(firstValue, secondValue, typeSchemaHere, keypathArray, keyOrIndex){
  36. // If the key or index is a string and contains `.`, then fail with an error.
  37. if ((''+keyOrIndex).match(/\./)) {
  38. throw new Error('Keys containing dots (`.`) are not currently supported in values provided to `rttc.isEqual` when the 3rd argument is being used.');
  39. }
  40. // Clone (shallow) the keypath array so we can mutate it.
  41. if (_.isArray(keypathArray)){
  42. keypathArray = _.clone(keypathArray);
  43. }
  44. // If `keyOrIndex` is undefined, then this is the initial pass.
  45. // Otherwise it's a subsequent pass.
  46. if (!_.isUndefined(keyOrIndex)){
  47. // Keep track of indices/keys already traversed in order to dereference the appropriate part
  48. // of the type schema (`indexOrKey` will be undefined if this is the top-level)
  49. keypathArray.push(keyOrIndex);
  50. }
  51. // Now look up the two value segments we'll be comparing below.
  52. var firstValueSegment = keypathArray.length === 0 ? firstValue : _.get(firstValue, keypathArray.join('.'));
  53. var secondValueSegment = keypathArray.length === 0 ? secondValue : _.get(secondValue, keypathArray.join('.'));
  54. // console.log('(for key (`%s`), keypathArray:',keyOrIndex, keypathArray,'typeSchemaHere:', typeSchemaHere,')');
  55. // console.log('at `%s`, comparing:',keypathArray.join('.'), firstValueSegment,'vs',secondValueSegment);
  56. if (_.isArray(typeSchemaHere)) {
  57. // If this path expects a generic array (i.e. `['*']` or `[]` for short),
  58. // then just use lodash.
  59. if (_.isEqual(typeSchemaHere, [])){
  60. return _.isEqual(firstValueSegment, secondValueSegment);
  61. }
  62. // Otherwise, this is a pattern array so take the recursive step.
  63. // (but first check to make sure both things are actually arrays)
  64. if (!_.isArray(firstValueSegment) || !_.isArray(secondValueSegment)){
  65. return false;
  66. }
  67. return _.all(firstValueSegment, function checkEachItemIn1stArray(unused, i){
  68. return _isEqualRecursive(firstValue, secondValue, typeSchemaHere[0], keypathArray, i);
  69. }) &&
  70. _.all(secondValueSegment, function checkEachItemIn2ndArray(unused, i){
  71. return _isEqualRecursive(firstValue, secondValue, typeSchemaHere[0], keypathArray, i);
  72. });
  73. }
  74. else if (_.isObject(typeSchemaHere)) {
  75. // If this path expects a generic dictionary (i.e. `{}`), then just use lodash.
  76. if (_.isEqual(typeSchemaHere, {})){
  77. return _.isEqual(firstValueSegment, secondValueSegment);
  78. }
  79. // Otherwise, this is a facted dictionary so take the recursive step.
  80. // (but first check to make sure both things are actually dictionaries)
  81. if (_.isArray(firstValueSegment) || !_.isObject(firstValueSegment) || _.isArray(secondValueSegment) || !_.isObject(secondValueSegment)){
  82. return false;
  83. }
  84. return _.all(firstValueSegment, function checkEachItemIn1stDict(unused, key){
  85. return _isEqualRecursive(firstValue, secondValue, typeSchemaHere[key], keypathArray, key);
  86. }) &&
  87. _.all(secondValueSegment, function checkEachItemIn2ndDict(unused, key){
  88. return _isEqualRecursive(firstValue, secondValue, typeSchemaHere[key], keypathArray, key);
  89. });
  90. }
  91. // If this type is a lamda, `.toString()` the functions and compare
  92. // them that way.
  93. else if (typeSchemaHere === 'lamda') {
  94. // Look for any obvious mismatches and return false if they are encountered.
  95. if (!firstValueSegment || !secondValueSegment || !_.isFunction(firstValueSegment.toString) || !_.isFunction(secondValueSegment.toString)) {
  96. return false;
  97. }
  98. return (firstValueSegment.toString() === secondValueSegment.toString());
  99. }
  100. // Miscellaneous thing.
  101. else {
  102. return _.isEqual(firstValueSegment, secondValueSegment);
  103. }
  104. })(firstValue, secondValue, typeSchema, []);//</self-calling recursive fn>
  105. };