696 lines
80 KiB
JavaScript
696 lines
80 KiB
JavaScript
|
//////////////////////////////////////////////////////////////////////////
|
||
|
// //
|
||
|
// This is a generated file. You can view the original //
|
||
|
// source in your browser if your browser supports source maps. //
|
||
|
// //
|
||
|
// If you are using Chrome, open the Developer Tools and click the gear //
|
||
|
// icon in its lower right corner. In the General Settings panel, turn //
|
||
|
// on 'Enable source maps'. //
|
||
|
// //
|
||
|
// If you are using Firefox 23, go to `about:config` and set the //
|
||
|
// `devtools.debugger.source-maps-enabled` preference to true. //
|
||
|
// (The preference should be on by default in Firefox 24; versions //
|
||
|
// older than 23 do not support source maps.) //
|
||
|
// //
|
||
|
//////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
|
||
|
(function () {
|
||
|
|
||
|
/* Imports */
|
||
|
var Meteor = Package.meteor.Meteor;
|
||
|
var JSON = Package.json.JSON;
|
||
|
var _ = Package.underscore._;
|
||
|
var Base64 = Package.base64.Base64;
|
||
|
|
||
|
/* Package-scope variables */
|
||
|
var EJSON, EJSONTest;
|
||
|
|
||
|
(function () {
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
// //
|
||
|
// packages/ejson/ejson.js //
|
||
|
// //
|
||
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
/** // 1
|
||
|
* @namespace // 2
|
||
|
* @summary Namespace for EJSON functions // 3
|
||
|
*/ // 4
|
||
|
EJSON = {}; // 5
|
||
|
EJSONTest = {}; // 6
|
||
|
// 7
|
||
|
// 8
|
||
|
// 9
|
||
|
// Custom type interface definition // 10
|
||
|
/** // 11
|
||
|
* @class CustomType // 12
|
||
|
* @instanceName customType // 13
|
||
|
* @memberOf EJSON // 14
|
||
|
* @summary The interface that a class must satisfy to be able to become an // 15
|
||
|
* EJSON custom type via EJSON.addType. // 16
|
||
|
*/ // 17
|
||
|
// 18
|
||
|
/** // 19
|
||
|
* @function typeName // 20
|
||
|
* @memberOf EJSON.CustomType // 21
|
||
|
* @summary Return the tag used to identify this type. This must match the tag used to register this type with [`EJSON.addType`](#ejson_add_type).
|
||
|
* @locus Anywhere // 23
|
||
|
* @instance // 24
|
||
|
*/ // 25
|
||
|
// 26
|
||
|
/** // 27
|
||
|
* @function toJSONValue // 28
|
||
|
* @memberOf EJSON.CustomType // 29
|
||
|
* @summary Serialize this instance into a JSON-compatible value. // 30
|
||
|
* @locus Anywhere // 31
|
||
|
* @instance // 32
|
||
|
*/ // 33
|
||
|
// 34
|
||
|
/** // 35
|
||
|
* @function clone // 36
|
||
|
* @memberOf EJSON.CustomType // 37
|
||
|
* @summary Return a value `r` such that `this.equals(r)` is true, and modifications to `r` do not affect `this` and vice versa.
|
||
|
* @locus Anywhere // 39
|
||
|
* @instance // 40
|
||
|
*/ // 41
|
||
|
// 42
|
||
|
/** // 43
|
||
|
* @function equals // 44
|
||
|
* @memberOf EJSON.CustomType // 45
|
||
|
* @summary Return `true` if `other` has a value equal to `this`; `false` otherwise. // 46
|
||
|
* @locus Anywhere // 47
|
||
|
* @param {Object} other Another object to compare this to. // 48
|
||
|
* @instance // 49
|
||
|
*/ // 50
|
||
|
// 51
|
||
|
// 52
|
||
|
var customTypes = {}; // 53
|
||
|
// Add a custom type, using a method of your choice to get to and // 54
|
||
|
// from a basic JSON-able representation. The factory argument // 55
|
||
|
// is a function of JSON-able --> your object // 56
|
||
|
// The type you add must have: // 57
|
||
|
// - A toJSONValue() method, so that Meteor can serialize it // 58
|
||
|
// - a typeName() method, to show how to look it up in our type table. // 59
|
||
|
// It is okay if these methods are monkey-patched on. // 60
|
||
|
// EJSON.clone will use toJSONValue and the given factory to produce // 61
|
||
|
// a clone, but you may specify a method clone() that will be // 62
|
||
|
// used instead. // 63
|
||
|
// Similarly, EJSON.equals will use toJSONValue to make comparisons, // 64
|
||
|
// but you may provide a method equals() instead. // 65
|
||
|
/** // 66
|
||
|
* @summary Add a custom datatype to EJSON. // 67
|
||
|
* @locus Anywhere // 68
|
||
|
* @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.
|
||
|
*/ // 71
|
||
|
EJSON.addType = function (name, factory) { // 72
|
||
|
if (_.has(customTypes, name)) // 73
|
||
|
throw new Error("Type " + name + " already present"); // 74
|
||
|
customTypes[name] = factory; // 75
|
||
|
}; // 76
|
||
|
// 77
|
||
|
var isInfOrNan = function (obj) { // 78
|
||
|
return _.isNaN(obj) || obj === Infinity || obj === -Infinity; // 79
|
||
|
}; // 80
|
||
|
// 81
|
||
|
var builtinConverters = [ // 82
|
||
|
{ // Date // 83
|
||
|
matchJSONValue: function (obj) { // 84
|
||
|
return _.has(obj, '$date') && _.size(obj) === 1; // 85
|
||
|
}, // 86
|
||
|
matchObject: function (obj) { // 87
|
||
|
return obj instanceof Date; // 88
|
||
|
}, // 89
|
||
|
toJSONValue: function (obj) { // 90
|
||
|
return {$date: obj.getTime()}; // 91
|
||
|
}, // 92
|
||
|
fromJSONValue: function (obj) { // 93
|
||
|
return new Date(obj.$date); // 94
|
||
|
} // 95
|
||
|
}, // 96
|
||
|
{ // NaN, Inf, -Inf. (These are the only objects with typeof !== 'object' // 97
|
||
|
// which we match.) // 98
|
||
|
matchJSONValue: function (obj) { // 99
|
||
|
return _.has(obj, '$InfNaN') && _.size(obj) === 1; // 100
|
||
|
}, // 101
|
||
|
matchObject: isInfOrNan, // 102
|
||
|
toJSONValue: function (obj) { // 103
|
||
|
var sign; // 104
|
||
|
if (_.isNaN(obj)) // 105
|
||
|
sign = 0; // 106
|
||
|
else if (obj === Infinity) // 107
|
||
|
sign = 1; // 108
|
||
|
else // 109
|
||
|
sign = -1; // 110
|
||
|
return {$InfNaN: sign}; // 111
|
||
|
}, // 112
|
||
|
fromJSONValue: function (obj) { // 113
|
||
|
return obj.$InfNaN/0; // 114
|
||
|
} // 115
|
||
|
}, // 116
|
||
|
{ // Binary // 117
|
||
|
matchJSONValue: function (obj) { // 118
|
||
|
return _.has(obj, '$binary') && _.size(obj) === 1; // 119
|
||
|
}, // 120
|
||
|
matchObject: function (obj) { // 121
|
||
|
return typeof Uint8Array !== 'undefined' && obj instanceof Uint8Array // 122
|
||
|
|| (obj && _.has(obj, '$Uint8ArrayPolyfill')); // 123
|
||
|
}, // 124
|
||
|
toJSONValue: function (obj) { // 125
|
||
|
return {$binary: Base64.encode(obj)}; // 126
|
||
|
}, // 127
|
||
|
fromJSONValue: function (obj) { // 128
|
||
|
return Base64.decode(obj.$binary); // 129
|
||
|
} // 130
|
||
|
}, // 131
|
||
|
{ // Escaping one level // 132
|
||
|
matchJSONValue: function (obj) { // 133
|
||
|
return _.has(obj, '$escape') && _.size(obj) === 1; // 134
|
||
|
}, // 135
|
||
|
matchObject: function (obj) { // 136
|
||
|
if (_.isEmpty(obj) || _.size(obj) > 2) { // 137
|
||
|
return false; // 138
|
||
|
} // 139
|
||
|
return _.any(builtinConverters, function (converter) { // 140
|
||
|
return converter.matchJSONValue(obj); // 141
|
||
|
}); // 142
|
||
|
}, // 143
|
||
|
toJSONValue: function (obj) { // 144
|
||
|
var newObj = {}; // 145
|
||
|
_.each(obj, function (value, key) { // 146
|
||
|
newObj[key] = EJSON.toJSONValue(value); // 147
|
||
|
}); // 148
|
||
|
return {$escape: newObj}; // 149
|
||
|
}, // 150
|
||
|
fromJSONValue: function (obj) { // 151
|
||
|
var newObj = {}; // 152
|
||
|
_.each(obj.$escape, function (value, key) { // 153
|
||
|
newObj[key] = EJSON.fromJSONValue(value); // 154
|
||
|
}); // 155
|
||
|
return newObj; // 156
|
||
|
} // 157
|
||
|
}, // 158
|
||
|
{ // Custom // 159
|
||
|
matchJSONValue: function (obj) { // 160
|
||
|
return _.has(obj, '$type') && _.has(obj, '$value') && _.size(obj) === 2; // 161
|
||
|
}, // 162
|
||
|
matchObject: function (obj) { // 163
|
||
|
return EJSON._isCustomType(obj); // 164
|
||
|
}, // 165
|
||
|
toJSONValue: function (obj) { // 166
|
||
|
var jsonValue = Meteor._noYieldsAllowed(function () { // 167
|
||
|
return obj.toJSONValue(); // 168
|
||
|
}); // 169
|
||
|
return {$type: obj.typeName(), $value: jsonValue}; // 170
|
||
|
}, // 171
|
||
|
fromJSONValue: function (obj) { // 172
|
||
|
var typeName = obj.$type; // 173
|
||
|
if (!_.has(customTypes, typeName)) // 174
|
||
|
throw new Error("Custom EJSON type " + typeName + " is not defined"); // 175
|
||
|
var converter = customTypes[typeName]; // 176
|
||
|
return Meteor._noYieldsAllowed(function () { // 177
|
||
|
return converter(obj.$value); // 178
|
||
|
}); // 179
|
||
|
} // 180
|
||
|
} // 181
|
||
|
]; // 182
|
||
|
// 183
|
||
|
EJSON._isCustomType = function (obj) { // 184
|
||
|
return obj && // 185
|
||
|
typeof obj.toJSONValue === 'function' && // 186
|
||
|
typeof obj.typeName === 'function' && // 187
|
||
|
_.has(customTypes, obj.typeName()); // 188
|
||
|
}; // 189
|
||
|
// 190
|
||
|
// 191
|
||
|
// for both arrays and objects, in-place modification. // 192
|
||
|
var adjustTypesToJSONValue = // 193
|
||
|
EJSON._adjustTypesToJSONValue = function (obj) { // 194
|
||
|
// Is it an atom that we need to adjust? // 195
|
||
|
if (obj === null) // 196
|
||
|
return null; // 197
|
||
|
var maybeChanged = toJSONValueHelper(obj); // 198
|
||
|
if (maybeChanged !== undefined) // 199
|
||
|
return maybeChanged; // 200
|
||
|
// 201
|
||
|
// Other atoms are unchanged. // 202
|
||
|
if (typeof obj !== 'object') // 203
|
||
|
return obj; // 204
|
||
|
// 205
|
||
|
// Iterate over array or object structure. // 206
|
||
|
_.each(obj, function (value, key) { // 207
|
||
|
if (typeof value !== 'object' && value !== undefined && // 208
|
||
|
!isInfOrNan(value)) // 209
|
||
|
return; // continue // 210
|
||
|
// 211
|
||
|
var changed = toJSONValueHelper(value); // 212
|
||
|
if (changed) { // 213
|
||
|
obj[key] = changed; // 214
|
||
|
return; // on to the next key // 215
|
||
|
} // 216
|
||
|
// if we get here, value is an object but not adjustable // 217
|
||
|
// at this level. recurse. // 218
|
||
|
adjustTypesToJSONValue(value); // 219
|
||
|
}); // 220
|
||
|
return obj; // 221
|
||
|
}; // 222
|
||
|
// 223
|
||
|
// Either return the JSON-compatible version of the argument, or undefined (if // 224
|
||
|
// the item isn't itself replaceable, but maybe some fields in it are) // 225
|
||
|
var toJSONValueHelper = function (item) { // 226
|
||
|
for (var i = 0; i < builtinConverters.length; i++) { // 227
|
||
|
var converter = builtinConverters[i]; // 228
|
||
|
if (converter.matchObject(item)) { // 229
|
||
|
return converter.toJSONValue(item); // 230
|
||
|
} // 231
|
||
|
} // 232
|
||
|
return undefined; // 233
|
||
|
}; // 234
|
||
|
// 235
|
||
|
/** // 236
|
||
|
* @summary Serialize an EJSON-compatible value into its plain JSON representation. // 237
|
||
|
* @locus Anywhere // 238
|
||
|
* @param {EJSON} val A value to serialize to plain JSON. // 239
|
||
|
*/ // 240
|
||
|
EJSON.toJSONValue = function (item) { // 241
|
||
|
var changed = toJSONValueHelper(item); // 242
|
||
|
if (changed !== undefined) // 243
|
||
|
return changed; // 244
|
||
|
if (typeof item === 'object') { // 245
|
||
|
item = EJSON.clone(item); // 246
|
||
|
adjustTypesToJSONValue(item); // 247
|
||
|
} // 248
|
||
|
return item; // 249
|
||
|
}; // 250
|
||
|
// 251
|
||
|
// for both arrays and objects. Tries its best to just // 252
|
||
|
// use the object you hand it, but may return something // 253
|
||
|
// different if the object you hand it itself needs changing. // 254
|
||
|
// // 255
|
||
|
var adjustTypesFromJSONValue = // 256
|
||
|
EJSON._adjustTypesFromJSONValue = function (obj) { // 257
|
||
|
if (obj === null) // 258
|
||
|
return null; // 259
|
||
|
var maybeChanged = fromJSONValueHelper(obj); // 260
|
||
|
if (maybeChanged !== obj) // 261
|
||
|
return maybeChanged; // 262
|
||
|
// 263
|
||
|
// Other atoms are unchanged. // 264
|
||
|
if (typeof obj !== 'object') // 265
|
||
|
return obj; // 266
|
||
|
// 267
|
||
|
_.each(obj, function (value, key) { // 268
|
||
|
if (typeof value === 'object') { // 269
|
||
|
var changed = fromJSONValueHelper(value); // 270
|
||
|
if (value !== changed) { // 271
|
||
|
obj[key] = changed; // 272
|
||
|
return; // 273
|
||
|
} // 274
|
||
|
// if we get here, value is an object but not adjustable // 275
|
||
|
// at this level. recurse. // 276
|
||
|
adjustTypesFromJSONValue(value); // 277
|
||
|
} // 278
|
||
|
}); // 279
|
||
|
return obj; // 280
|
||
|
}; // 281
|
||
|
// 282
|
||
|
// Either return the argument changed to have the non-json // 283
|
||
|
// rep of itself (the Object version) or the argument itself. // 284
|
||
|
// 285
|
||
|
// DOES NOT RECURSE. For actually getting the fully-changed value, use // 286
|
||
|
// EJSON.fromJSONValue // 287
|
||
|
var fromJSONValueHelper = function (value) { // 288
|
||
|
if (typeof value === 'object' && value !== null) { // 289
|
||
|
if (_.size(value) <= 2 // 290
|
||
|
&& _.all(value, function (v, k) { // 291
|
||
|
return typeof k === 'string' && k.substr(0, 1) === '$'; // 292
|
||
|
})) { // 293
|
||
|
for (var i = 0; i < builtinConverters.length; i++) { // 294
|
||
|
var converter = builtinConverters[i]; // 295
|
||
|
if (converter.matchJSONValue(value)) { // 296
|
||
|
return converter.fromJSONValue(value); // 297
|
||
|
} // 298
|
||
|
} // 299
|
||
|
} // 300
|
||
|
} // 301
|
||
|
return value; // 302
|
||
|
}; // 303
|
||
|
// 304
|
||
|
/** // 305
|
||
|
* @summary Deserialize an EJSON value from its plain JSON representation. // 306
|
||
|
* @locus Anywhere // 307
|
||
|
* @param {JSONCompatible} val A value to deserialize into EJSON. // 308
|
||
|
*/ // 309
|
||
|
EJSON.fromJSONValue = function (item) { // 310
|
||
|
var changed = fromJSONValueHelper(item); // 311
|
||
|
if (changed === item && typeof item === 'object') { // 312
|
||
|
item = EJSON.clone(item); // 313
|
||
|
adjustTypesFromJSONValue(item); // 314
|
||
|
return item; // 315
|
||
|
} else { // 316
|
||
|
return changed; // 317
|
||
|
} // 318
|
||
|
}; // 319
|
||
|
// 320
|
||
|
/** // 321
|
||
|
* @summary Serialize a value to a string. // 322
|
||
|
// 323
|
||
|
For EJSON values, the serialization fully represents the value. For non-EJSON values, serializes the same way as `JSON.stringify`.
|
||
|
* @locus Anywhere // 325
|
||
|
* @param {EJSON} val A value to stringify. // 326
|
||
|
* @param {Object} [options] // 327
|
||
|
* @param {Boolean | Integer | String} options.indent Indents objects and arrays for easy readability. When `true`, indents by 2 spaces; when an integer, indents by that number of spaces; and when a string, uses the string as the indentation pattern.
|
||
|
* @param {Boolean} options.canonical When `true`, stringifies keys in an object in sorted order. // 329
|
||
|
*/ // 330
|
||
|
EJSON.stringify = function (item, options) { // 331
|
||
|
var json = EJSON.toJSONValue(item); // 332
|
||
|
if (options && (options.canonical || options.indent)) { // 333
|
||
|
return EJSON._canonicalStringify(json, options); // 334
|
||
|
} else { // 335
|
||
|
return JSON.stringify(json); // 336
|
||
|
} // 337
|
||
|
}; // 338
|
||
|
// 339
|
||
|
/** // 340
|
||
|
* @summary Parse a string into an EJSON value. Throws an error if the string is not valid EJSON. // 341
|
||
|
* @locus Anywhere // 342
|
||
|
* @param {String} str A string to parse into an EJSON value. // 343
|
||
|
*/ // 344
|
||
|
EJSON.parse = function (item) { // 345
|
||
|
if (typeof item !== 'string') // 346
|
||
|
throw new Error("EJSON.parse argument should be a string"); // 347
|
||
|
return EJSON.fromJSONValue(JSON.parse(item)); // 348
|
||
|
}; // 349
|
||
|
// 350
|
||
|
/** // 351
|
||
|
* @summary Returns true if `x` is a buffer of binary data, as returned from [`EJSON.newBinary`](#ejson_new_binary). // 352
|
||
|
* @param {Object} x The variable to check. // 353
|
||
|
* @locus Anywhere // 354
|
||
|
*/ // 355
|
||
|
EJSON.isBinary = function (obj) { // 356
|
||
|
return !!((typeof Uint8Array !== 'undefined' && obj instanceof Uint8Array) || // 357
|
||
|
(obj && obj.$Uint8ArrayPolyfill)); // 358
|
||
|
}; // 359
|
||
|
// 360
|
||
|
/** // 361
|
||
|
* @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 // 363
|
||
|
* @param {EJSON} a // 364
|
||
|
* @param {EJSON} b // 365
|
||
|
* @param {Object} [options] // 366
|
||
|
* @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`.
|
||
|
*/ // 368
|
||
|
EJSON.equals = function (a, b, options) { // 369
|
||
|
var i; // 370
|
||
|
var keyOrderSensitive = !!(options && options.keyOrderSensitive); // 371
|
||
|
if (a === b) // 372
|
||
|
return true; // 373
|
||
|
if (_.isNaN(a) && _.isNaN(b)) // 374
|
||
|
return true; // This differs from the IEEE spec for NaN equality, b/c we don't want // 375
|
||
|
// anything ever with a NaN to be poisoned from becoming equal to anything. // 376
|
||
|
if (!a || !b) // if either one is falsy, they'd have to be === to be equal // 377
|
||
|
return false; // 378
|
||
|
if (!(typeof a === 'object' && typeof b === 'object')) // 379
|
||
|
return false; // 380
|
||
|
if (a instanceof Date && b instanceof Date) // 381
|
||
|
return a.valueOf() === b.valueOf(); // 382
|
||
|
if (EJSON.isBinary(a) && EJSON.isBinary(b)) { // 383
|
||
|
if (a.length !== b.length) // 384
|
||
|
return false; // 385
|
||
|
for (i = 0; i < a.length; i++) { // 386
|
||
|
if (a[i] !== b[i]) // 387
|
||
|
return false; // 388
|
||
|
} // 389
|
||
|
return true; // 390
|
||
|
} // 391
|
||
|
if (typeof (a.equals) === 'function') // 392
|
||
|
return a.equals(b, options); // 393
|
||
|
if (typeof (b.equals) === 'function') // 394
|
||
|
return b.equals(a, options); // 395
|
||
|
if (a instanceof Array) { // 396
|
||
|
if (!(b instanceof Array)) // 397
|
||
|
return false; // 398
|
||
|
if (a.length !== b.length) // 399
|
||
|
return false; // 400
|
||
|
for (i = 0; i < a.length; i++) { // 401
|
||
|
if (!EJSON.equals(a[i], b[i], options)) // 402
|
||
|
return false; // 403
|
||
|
} // 404
|
||
|
return true; // 405
|
||
|
} // 406
|
||
|
// fallback for custom types that don't implement their own equals // 407
|
||
|
switch (EJSON._isCustomType(a) + EJSON._isCustomType(b)) { // 408
|
||
|
case 1: return false; // 409
|
||
|
case 2: return EJSON.equals(EJSON.toJSONValue(a), EJSON.toJSONValue(b)); // 410
|
||
|
} // 411
|
||
|
// fall back to structural equality of objects // 412
|
||
|
var ret; // 413
|
||
|
if (keyOrderSensitive) { // 414
|
||
|
var bKeys = []; // 415
|
||
|
_.each(b, function (val, x) { // 416
|
||
|
bKeys.push(x); // 417
|
||
|
}); // 418
|
||
|
i = 0; // 419
|
||
|
ret = _.all(a, function (val, x) { // 420
|
||
|
if (i >= bKeys.length) { // 421
|
||
|
return false; // 422
|
||
|
} // 423
|
||
|
if (x !== bKeys[i]) { // 424
|
||
|
return false; // 425
|
||
|
} // 426
|
||
|
if (!EJSON.equals(val, b[bKeys[i]], options)) { // 427
|
||
|
return false; // 428
|
||
|
} // 429
|
||
|
i++; // 430
|
||
|
return true; // 431
|
||
|
}); // 432
|
||
|
return ret && i === bKeys.length; // 433
|
||
|
} else { // 434
|
||
|
i = 0; // 435
|
||
|
ret = _.all(a, function (val, key) { // 436
|
||
|
if (!_.has(b, key)) { // 437
|
||
|
return false; // 438
|
||
|
} // 439
|
||
|
if (!EJSON.equals(val, b[key], options)) { // 440
|
||
|
return false; // 441
|
||
|
} // 442
|
||
|
i++; // 443
|
||
|
return true; // 444
|
||
|
}); // 445
|
||
|
return ret && _.size(b) === i; // 446
|
||
|
} // 447
|
||
|
}; // 448
|
||
|
// 449
|
||
|
/** // 450
|
||
|
* @summary Return a deep copy of `val`. // 451
|
||
|
* @locus Anywhere // 452
|
||
|
* @param {EJSON} val A value to copy. // 453
|
||
|
*/ // 454
|
||
|
EJSON.clone = function (v) { // 455
|
||
|
var ret; // 456
|
||
|
if (typeof v !== "object") // 457
|
||
|
return v; // 458
|
||
|
if (v === null) // 459
|
||
|
return null; // null has typeof "object" // 460
|
||
|
if (v instanceof Date) // 461
|
||
|
return new Date(v.getTime()); // 462
|
||
|
// RegExps are not really EJSON elements (eg we don't define a serialization // 463
|
||
|
// for them), but they're immutable anyway, so we can support them in clone. // 464
|
||
|
if (v instanceof RegExp) // 465
|
||
|
return v; // 466
|
||
|
if (EJSON.isBinary(v)) { // 467
|
||
|
ret = EJSON.newBinary(v.length); // 468
|
||
|
for (var i = 0; i < v.length; i++) { // 469
|
||
|
ret[i] = v[i]; // 470
|
||
|
} // 471
|
||
|
return ret; // 472
|
||
|
} // 473
|
||
|
// XXX: Use something better than underscore's isArray // 474
|
||
|
if (_.isArray(v) || _.isArguments(v)) { // 475
|
||
|
// For some reason, _.map doesn't work in this context on Opera (weird test // 476
|
||
|
// failures). // 477
|
||
|
ret = []; // 478
|
||
|
for (i = 0; i < v.length; i++) // 479
|
||
|
ret[i] = EJSON.clone(v[i]); // 480
|
||
|
return ret; // 481
|
||
|
} // 482
|
||
|
// handle general user-defined typed Objects if they have a clone method // 483
|
||
|
if (typeof v.clone === 'function') { // 484
|
||
|
return v.clone(); // 485
|
||
|
} // 486
|
||
|
// handle other custom types // 487
|
||
|
if (EJSON._isCustomType(v)) { // 488
|
||
|
return EJSON.fromJSONValue(EJSON.clone(EJSON.toJSONValue(v)), true); // 489
|
||
|
} // 490
|
||
|
// handle other objects // 491
|
||
|
ret = {}; // 492
|
||
|
_.each(v, function (value, key) { // 493
|
||
|
ret[key] = EJSON.clone(value); // 494
|
||
|
}); // 495
|
||
|
return ret; // 496
|
||
|
}; // 497
|
||
|
// 498
|
||
|
/** // 499
|
||
|
* @summary Allocate a new buffer of binary data that EJSON can serialize. // 500
|
||
|
* @locus Anywhere // 501
|
||
|
* @param {Number} size The number of bytes of binary data to allocate. // 502
|
||
|
*/ // 503
|
||
|
// EJSON.newBinary is the public documented API for this functionality, // 504
|
||
|
// but the implementation is in the 'base64' package to avoid // 505
|
||
|
// introducing a circular dependency. (If the implementation were here, // 506
|
||
|
// then 'base64' would have to use EJSON.newBinary, and 'ejson' would // 507
|
||
|
// also have to use 'base64'.) // 508
|
||
|
EJSON.newBinary = Base64.newBinary; // 509
|
||
|
// 510
|
||
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
}).call(this);
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
(function () {
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
// //
|
||
|
// packages/ejson/stringify.js //
|
||
|
// //
|
||
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Based on json2.js from https://github.com/douglascrockford/JSON-js // 1
|
||
|
// // 2
|
||
|
// json2.js // 3
|
||
|
// 2012-10-08 // 4
|
||
|
// // 5
|
||
|
// Public Domain. // 6
|
||
|
// // 7
|
||
|
// NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. // 8
|
||
|
// 9
|
||
|
function quote(string) { // 10
|
||
|
return JSON.stringify(string); // 11
|
||
|
} // 12
|
||
|
// 13
|
||
|
var str = function (key, holder, singleIndent, outerIndent, canonical) { // 14
|
||
|
// 15
|
||
|
// Produce a string from holder[key]. // 16
|
||
|
// 17
|
||
|
var i; // The loop counter. // 18
|
||
|
var k; // The member key. // 19
|
||
|
var v; // The member value. // 20
|
||
|
var length; // 21
|
||
|
var innerIndent = outerIndent; // 22
|
||
|
var partial; // 23
|
||
|
var value = holder[key]; // 24
|
||
|
// 25
|
||
|
// What happens next depends on the value's type. // 26
|
||
|
// 27
|
||
|
switch (typeof value) { // 28
|
||
|
case 'string': // 29
|
||
|
return quote(value); // 30
|
||
|
case 'number': // 31
|
||
|
// JSON numbers must be finite. Encode non-finite numbers as null. // 32
|
||
|
return isFinite(value) ? String(value) : 'null'; // 33
|
||
|
case 'boolean': // 34
|
||
|
return String(value); // 35
|
||
|
// If the type is 'object', we might be dealing with an object or an array or // 36
|
||
|
// null. // 37
|
||
|
case 'object': // 38
|
||
|
// Due to a specification blunder in ECMAScript, typeof null is 'object', // 39
|
||
|
// so watch out for that case. // 40
|
||
|
if (!value) { // 41
|
||
|
return 'null'; // 42
|
||
|
} // 43
|
||
|
// Make an array to hold the partial results of stringifying this object value. // 44
|
||
|
innerIndent = outerIndent + singleIndent; // 45
|
||
|
partial = []; // 46
|
||
|
// 47
|
||
|
// Is the value an array? // 48
|
||
|
if (_.isArray(value) || _.isArguments(value)) { // 49
|
||
|
// 50
|
||
|
// The value is an array. Stringify every element. Use null as a placeholder // 51
|
||
|
// for non-JSON values. // 52
|
||
|
// 53
|
||
|
length = value.length; // 54
|
||
|
for (i = 0; i < length; i += 1) { // 55
|
||
|
partial[i] = str(i, value, singleIndent, innerIndent, canonical) || 'null'; // 56
|
||
|
} // 57
|
||
|
// 58
|
||
|
// Join all of the elements together, separated with commas, and wrap them in // 59
|
||
|
// brackets. // 60
|
||
|
// 61
|
||
|
if (partial.length === 0) { // 62
|
||
|
v = '[]'; // 63
|
||
|
} else if (innerIndent) { // 64
|
||
|
v = '[\n' + innerIndent + partial.join(',\n' + innerIndent) + '\n' + outerIndent + ']'; // 65
|
||
|
} else { // 66
|
||
|
v = '[' + partial.join(',') + ']'; // 67
|
||
|
} // 68
|
||
|
return v; // 69
|
||
|
} // 70
|
||
|
// 71
|
||
|
// 72
|
||
|
// Iterate through all of the keys in the object. // 73
|
||
|
var keys = _.keys(value); // 74
|
||
|
if (canonical) // 75
|
||
|
keys = keys.sort(); // 76
|
||
|
_.each(keys, function (k) { // 77
|
||
|
v = str(k, value, singleIndent, innerIndent, canonical); // 78
|
||
|
if (v) { // 79
|
||
|
partial.push(quote(k) + (innerIndent ? ': ' : ':') + v); // 80
|
||
|
} // 81
|
||
|
}); // 82
|
||
|
// 83
|
||
|
// 84
|
||
|
// Join all of the member texts together, separated with commas, // 85
|
||
|
// and wrap them in braces. // 86
|
||
|
// 87
|
||
|
if (partial.length === 0) { // 88
|
||
|
v = '{}'; // 89
|
||
|
} else if (innerIndent) { // 90
|
||
|
v = '{\n' + innerIndent + partial.join(',\n' + innerIndent) + '\n' + outerIndent + '}'; // 91
|
||
|
} else { // 92
|
||
|
v = '{' + partial.join(',') + '}'; // 93
|
||
|
} // 94
|
||
|
return v; // 95
|
||
|
} // 96
|
||
|
} // 97
|
||
|
// 98
|
||
|
// If the JSON object does not yet have a stringify method, give it one. // 99
|
||
|
// 100
|
||
|
EJSON._canonicalStringify = function (value, options) { // 101
|
||
|
// Make a fake root object containing our value under the key of ''. // 102
|
||
|
// Return the result of stringifying the value. // 103
|
||
|
options = _.extend({ // 104
|
||
|
indent: "", // 105
|
||
|
canonical: false // 106
|
||
|
}, options); // 107
|
||
|
if (options.indent === true) { // 108
|
||
|
options.indent = " "; // 109
|
||
|
} else if (typeof options.indent === 'number') { // 110
|
||
|
var newIndent = ""; // 111
|
||
|
for (var i = 0; i < options.indent; i++) { // 112
|
||
|
newIndent += ' '; // 113
|
||
|
} // 114
|
||
|
options.indent = newIndent; // 115
|
||
|
} // 116
|
||
|
return str('', {'': value}, options.indent, "", options.canonical); // 117
|
||
|
}; // 118
|
||
|
// 119
|
||
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
}).call(this);
|
||
|
|
||
|
|
||
|
/* Exports */
|
||
|
if (typeof Package === 'undefined') Package = {};
|
||
|
Package.ejson = {
|
||
|
EJSON: EJSON,
|
||
|
EJSONTest: EJSONTest
|
||
|
};
|
||
|
|
||
|
})();
|