simple-squiggle

A restricted subset of Squiggle
Log | Files | Refs | README

object.js (9486B)


      1 import { isBigNumber } from './is.js';
      2 /**
      3  * Clone an object
      4  *
      5  *     clone(x)
      6  *
      7  * Can clone any primitive type, array, and object.
      8  * If x has a function clone, this function will be invoked to clone the object.
      9  *
     10  * @param {*} x
     11  * @return {*} clone
     12  */
     13 
     14 export function clone(x) {
     15   var type = typeof x; // immutable primitive types
     16 
     17   if (type === 'number' || type === 'string' || type === 'boolean' || x === null || x === undefined) {
     18     return x;
     19   } // use clone function of the object when available
     20 
     21 
     22   if (typeof x.clone === 'function') {
     23     return x.clone();
     24   } // array
     25 
     26 
     27   if (Array.isArray(x)) {
     28     return x.map(function (value) {
     29       return clone(value);
     30     });
     31   }
     32 
     33   if (x instanceof Date) return new Date(x.valueOf());
     34   if (isBigNumber(x)) return x; // bignumbers are immutable
     35 
     36   if (x instanceof RegExp) throw new TypeError('Cannot clone ' + x); // TODO: clone a RegExp
     37   // object
     38 
     39   return mapObject(x, clone);
     40 }
     41 /**
     42  * Apply map to all properties of an object
     43  * @param {Object} object
     44  * @param {function} callback
     45  * @return {Object} Returns a copy of the object with mapped properties
     46  */
     47 
     48 export function mapObject(object, callback) {
     49   var clone = {};
     50 
     51   for (var key in object) {
     52     if (hasOwnProperty(object, key)) {
     53       clone[key] = callback(object[key]);
     54     }
     55   }
     56 
     57   return clone;
     58 }
     59 /**
     60  * Extend object a with the properties of object b
     61  * @param {Object} a
     62  * @param {Object} b
     63  * @return {Object} a
     64  */
     65 
     66 export function extend(a, b) {
     67   for (var prop in b) {
     68     if (hasOwnProperty(b, prop)) {
     69       a[prop] = b[prop];
     70     }
     71   }
     72 
     73   return a;
     74 }
     75 /**
     76  * Deep extend an object a with the properties of object b
     77  * @param {Object} a
     78  * @param {Object} b
     79  * @returns {Object}
     80  */
     81 
     82 export function deepExtend(a, b) {
     83   // TODO: add support for Arrays to deepExtend
     84   if (Array.isArray(b)) {
     85     throw new TypeError('Arrays are not supported by deepExtend');
     86   }
     87 
     88   for (var prop in b) {
     89     // We check against prop not being in Object.prototype or Function.prototype
     90     // to prevent polluting for example Object.__proto__.
     91     if (hasOwnProperty(b, prop) && !(prop in Object.prototype) && !(prop in Function.prototype)) {
     92       if (b[prop] && b[prop].constructor === Object) {
     93         if (a[prop] === undefined) {
     94           a[prop] = {};
     95         }
     96 
     97         if (a[prop] && a[prop].constructor === Object) {
     98           deepExtend(a[prop], b[prop]);
     99         } else {
    100           a[prop] = b[prop];
    101         }
    102       } else if (Array.isArray(b[prop])) {
    103         throw new TypeError('Arrays are not supported by deepExtend');
    104       } else {
    105         a[prop] = b[prop];
    106       }
    107     }
    108   }
    109 
    110   return a;
    111 }
    112 /**
    113  * Deep test equality of all fields in two pairs of arrays or objects.
    114  * Compares values and functions strictly (ie. 2 is not the same as '2').
    115  * @param {Array | Object} a
    116  * @param {Array | Object} b
    117  * @returns {boolean}
    118  */
    119 
    120 export function deepStrictEqual(a, b) {
    121   var prop, i, len;
    122 
    123   if (Array.isArray(a)) {
    124     if (!Array.isArray(b)) {
    125       return false;
    126     }
    127 
    128     if (a.length !== b.length) {
    129       return false;
    130     }
    131 
    132     for (i = 0, len = a.length; i < len; i++) {
    133       if (!deepStrictEqual(a[i], b[i])) {
    134         return false;
    135       }
    136     }
    137 
    138     return true;
    139   } else if (typeof a === 'function') {
    140     return a === b;
    141   } else if (a instanceof Object) {
    142     if (Array.isArray(b) || !(b instanceof Object)) {
    143       return false;
    144     }
    145 
    146     for (prop in a) {
    147       // noinspection JSUnfilteredForInLoop
    148       if (!(prop in b) || !deepStrictEqual(a[prop], b[prop])) {
    149         return false;
    150       }
    151     }
    152 
    153     for (prop in b) {
    154       // noinspection JSUnfilteredForInLoop
    155       if (!(prop in a)) {
    156         return false;
    157       }
    158     }
    159 
    160     return true;
    161   } else {
    162     return a === b;
    163   }
    164 }
    165 /**
    166  * Recursively flatten a nested object.
    167  * @param {Object} nestedObject
    168  * @return {Object} Returns the flattened object
    169  */
    170 
    171 export function deepFlatten(nestedObject) {
    172   var flattenedObject = {};
    173 
    174   _deepFlatten(nestedObject, flattenedObject);
    175 
    176   return flattenedObject;
    177 } // helper function used by deepFlatten
    178 
    179 function _deepFlatten(nestedObject, flattenedObject) {
    180   for (var prop in nestedObject) {
    181     if (hasOwnProperty(nestedObject, prop)) {
    182       var value = nestedObject[prop];
    183 
    184       if (typeof value === 'object' && value !== null) {
    185         _deepFlatten(value, flattenedObject);
    186       } else {
    187         flattenedObject[prop] = value;
    188       }
    189     }
    190   }
    191 }
    192 /**
    193  * Test whether the current JavaScript engine supports Object.defineProperty
    194  * @returns {boolean} returns true if supported
    195  */
    196 
    197 
    198 export function canDefineProperty() {
    199   // test needed for broken IE8 implementation
    200   try {
    201     if (Object.defineProperty) {
    202       Object.defineProperty({}, 'x', {
    203         get: function get() {}
    204       });
    205       return true;
    206     }
    207   } catch (e) {}
    208 
    209   return false;
    210 }
    211 /**
    212  * Attach a lazy loading property to a constant.
    213  * The given function `fn` is called once when the property is first requested.
    214  *
    215  * @param {Object} object         Object where to add the property
    216  * @param {string} prop           Property name
    217  * @param {Function} valueResolver Function returning the property value. Called
    218  *                                without arguments.
    219  */
    220 
    221 export function lazy(object, prop, valueResolver) {
    222   var _uninitialized = true;
    223 
    224   var _value;
    225 
    226   Object.defineProperty(object, prop, {
    227     get: function get() {
    228       if (_uninitialized) {
    229         _value = valueResolver();
    230         _uninitialized = false;
    231       }
    232 
    233       return _value;
    234     },
    235     set: function set(value) {
    236       _value = value;
    237       _uninitialized = false;
    238     },
    239     configurable: true,
    240     enumerable: true
    241   });
    242 }
    243 /**
    244  * Traverse a path into an object.
    245  * When a namespace is missing, it will be created
    246  * @param {Object} object
    247  * @param {string | string[]} path   A dot separated string like 'name.space'
    248  * @return {Object} Returns the object at the end of the path
    249  */
    250 
    251 export function traverse(object, path) {
    252   if (path && typeof path === 'string') {
    253     return traverse(object, path.split('.'));
    254   }
    255 
    256   var obj = object;
    257 
    258   if (path) {
    259     for (var i = 0; i < path.length; i++) {
    260       var key = path[i];
    261 
    262       if (!(key in obj)) {
    263         obj[key] = {};
    264       }
    265 
    266       obj = obj[key];
    267     }
    268   }
    269 
    270   return obj;
    271 }
    272 /**
    273  * A safe hasOwnProperty
    274  * @param {Object} object
    275  * @param {string} property
    276  */
    277 
    278 export function hasOwnProperty(object, property) {
    279   return object && Object.hasOwnProperty.call(object, property);
    280 }
    281 /**
    282  * Test whether an object is a factory. a factory has fields:
    283  *
    284  * - factory: function (type: Object, config: Object, load: function, typed: function [, math: Object])   (required)
    285  * - name: string (optional)
    286  * - path: string    A dot separated path (optional)
    287  * - math: boolean   If true (false by default), the math namespace is passed
    288  *                   as fifth argument of the factory function
    289  *
    290  * @param {*} object
    291  * @returns {boolean}
    292  */
    293 
    294 export function isLegacyFactory(object) {
    295   return object && typeof object.factory === 'function';
    296 }
    297 /**
    298  * Get a nested property from an object
    299  * @param {Object} object
    300  * @param {string | string[]} path
    301  * @returns {Object}
    302  */
    303 
    304 export function get(object, path) {
    305   if (typeof path === 'string') {
    306     if (isPath(path)) {
    307       return get(object, path.split('.'));
    308     } else {
    309       return object[path];
    310     }
    311   }
    312 
    313   var child = object;
    314 
    315   for (var i = 0; i < path.length; i++) {
    316     var key = path[i];
    317     child = child ? child[key] : undefined;
    318   }
    319 
    320   return child;
    321 }
    322 /**
    323  * Set a nested property in an object
    324  * Mutates the object itself
    325  * If the path doesn't exist, it will be created
    326  * @param {Object} object
    327  * @param {string | string[]} path
    328  * @param {*} value
    329  * @returns {Object}
    330  */
    331 
    332 export function set(object, path, value) {
    333   if (typeof path === 'string') {
    334     if (isPath(path)) {
    335       return set(object, path.split('.'), value);
    336     } else {
    337       object[path] = value;
    338       return object;
    339     }
    340   }
    341 
    342   var child = object;
    343 
    344   for (var i = 0; i < path.length - 1; i++) {
    345     var key = path[i];
    346 
    347     if (child[key] === undefined) {
    348       child[key] = {};
    349     }
    350 
    351     child = child[key];
    352   }
    353 
    354   if (path.length > 0) {
    355     var lastKey = path[path.length - 1];
    356     child[lastKey] = value;
    357   }
    358 
    359   return object;
    360 }
    361 /**
    362  * Create an object composed of the picked object properties
    363  * @param {Object} object
    364  * @param {string[]} properties
    365  * @param {function} [transform] Optional value to transform a value when picking it
    366  * @return {Object}
    367  */
    368 
    369 export function pick(object, properties, transform) {
    370   var copy = {};
    371 
    372   for (var i = 0; i < properties.length; i++) {
    373     var key = properties[i];
    374     var value = get(object, key);
    375 
    376     if (value !== undefined) {
    377       set(copy, key, transform ? transform(value, key) : value);
    378     }
    379   }
    380 
    381   return copy;
    382 }
    383 /**
    384  * Shallow version of pick, creating an object composed of the picked object properties
    385  * but not for nested properties
    386  * @param {Object} object
    387  * @param {string[]} properties
    388  * @return {Object}
    389  */
    390 
    391 export function pickShallow(object, properties) {
    392   var copy = {};
    393 
    394   for (var i = 0; i < properties.length; i++) {
    395     var key = properties[i];
    396     var value = object[key];
    397 
    398     if (value !== undefined) {
    399       copy[key] = value;
    400     }
    401   }
    402 
    403   return copy;
    404 }
    405 export function values(object) {
    406   return Object.keys(object).map(key => object[key]);
    407 } // helper function to test whether a string contains a path like 'user.name'
    408 
    409 function isPath(str) {
    410   return str.indexOf('.') !== -1;
    411 }