modifyjs
Version:
Modify your objects with a mongo syntax.
148 lines (139 loc) • 5.41 kB
JavaScript
import { has, isNaN, size, isEmpty, any, each, all, isArguments, isArray } from 'underscore';
EJSON = {};
EJSONTest = {};
var customTypes = {};
// Add a custom type, using a method of your choice to get to and
// from a basic JSON-able representation. The factory argument
// is a function of JSON-able --> your object
// The type you add must have:
// - A toJSONValue() method, so that Meteor can serialize it
// - a typeName() method, to show how to look it up in our type table.
// It is okay if these methods are monkey-patched on.
// EJSON.clone will use toJSONValue and the given factory to produce
// a clone, but you may specify a method clone() that will be
// used instead.
// Similarly, EJSON.equals will use toJSONValue to make comparisons,
// but you may provide a method equals() instead.
/**
* @summary Add a custom datatype to EJSON.
* @locus Anywhere
* @param {String} name A tag for your custom type; must be unique among custom data types defined in your project, and must match the result of your type's `typeName` method.
* @param {Function} factory A function that deserializes a JSON-compatible value into an instance of your type. This should match the serialization performed by your type's `toJSONValue` method.
*/
var _ = { has: has, isNaN: isNaN, size: size, isEmpty: isEmpty, any: any, each: each, all: all, isArguments: isArguments, isArray: isArray };
EJSON.isBinary = function (obj) {
return !!(typeof Uint8Array !== 'undefined' && obj instanceof Uint8Array || obj && obj.$Uint8ArrayPolyfill);
};
/**
* @summary Return true if `a` and `b` are equal to each other. Return false otherwise. Uses the `equals` method on `a` if present, otherwise performs a deep comparison.
* @locus Anywhere
* @param {EJSON} a
* @param {EJSON} b
* @param {Object} [options]
* @param {Boolean} options.keyOrderSensitive Compare in key sensitive order, if supported by the JavaScript implementation. For example, `{a: 1, b: 2}` is equal to `{b: 2, a: 1}` only when `keyOrderSensitive` is `false`. The default is `false`.
*/
EJSON.equals = function (a, b, options) {
var i;
var keyOrderSensitive = !!(options && options.keyOrderSensitive);
if (a === b) return true;
if (_.isNaN(a) && _.isNaN(b)) return true; // This differs from the IEEE spec for NaN equality, b/c we don't want
// anything ever with a NaN to be poisoned from becoming equal to anything.
if (!a || !b) // if either one is falsy, they'd have to be === to be equal
return false;
if (!((typeof a === 'undefined' ? 'undefined' : babelHelpers.typeof(a)) === 'object' && (typeof b === 'undefined' ? 'undefined' : babelHelpers.typeof(b)) === 'object')) return false;
if (a instanceof Date && b instanceof Date) return a.valueOf() === b.valueOf();
if (EJSON.isBinary(a) && EJSON.isBinary(b)) {
if (a.length !== b.length) return false;
for (i = 0; i < a.length; i++) {
if (a[i] !== b[i]) return false;
}
return true;
}
if (typeof a.equals === 'function') return a.equals(b, options);
if (typeof b.equals === 'function') return b.equals(a, options);
if (a instanceof Array) {
if (!(b instanceof Array)) return false;
if (a.length !== b.length) return false;
for (i = 0; i < a.length; i++) {
if (!EJSON.equals(a[i], b[i], options)) return false;
}
return true;
}
// fall back to structural equality of objects
var ret;
if (keyOrderSensitive) {
var bKeys = [];
_.each(b, function (val, x) {
bKeys.push(x);
});
i = 0;
ret = _.all(a, function (val, x) {
if (i >= bKeys.length) {
return false;
}
if (x !== bKeys[i]) {
return false;
}
if (!EJSON.equals(val, b[bKeys[i]], options)) {
return false;
}
i++;
return true;
});
return ret && i === bKeys.length;
} else {
i = 0;
ret = _.all(a, function (val, key) {
if (!_.has(b, key)) {
return false;
}
if (!EJSON.equals(val, b[key], options)) {
return false;
}
i++;
return true;
});
return ret && _.size(b) === i;
}
};
/**
* @summary Return a deep copy of `val`.
* @locus Anywhere
* @param {EJSON} val A value to copy.
*/
EJSON.clone = function (v) {
var ret;
if ((typeof v === 'undefined' ? 'undefined' : babelHelpers.typeof(v)) !== "object") return v;
if (v === null) return null; // null has typeof "object"
if (v instanceof Date) return new Date(v.getTime());
// RegExps are not really EJSON elements (eg we don't define a serialization
// for them), but they're immutable anyway, so we can support them in clone.
if (v instanceof RegExp) return v;
if (EJSON.isBinary(v)) {
ret = EJSON.newBinary(v.length);
for (var i = 0; i < v.length; i++) {
ret[i] = v[i];
}
return ret;
}
// XXX: Use something better than underscore's isArray
if (_.isArray(v) || _.isArguments(v)) {
// For some reason, _.map doesn't work in this context on Opera (weird test
// failures).
ret = [];
for (i = 0; i < v.length; i++) {
ret[i] = EJSON.clone(v[i]);
}return ret;
}
// handle general user-defined typed Objects if they have a clone method
if (typeof v.clone === 'function') {
return v.clone();
}
// handle other objects
ret = {};
_.each(v, function (value, key) {
ret[key] = EJSON.clone(value);
});
return ret;
};
export { EJSON };