Comparing version 6.0.0 to 7.0.0
@@ -25,22 +25,3 @@ /** | ||
// First, prevent against endless circular recursion: | ||
// (this should never throw, but if it does, it needs to be handled | ||
// by the caller of `rebuildSanitized`) | ||
val = JSON.parse(stringifySafe(val)); | ||
// So doing that parse/stringify thing will remove keys that have undefined values on its own. | ||
// BUT, we still have to worry about removing array items which are undefined. | ||
// And the above operation actually converts these undefined items into `null` items. | ||
// But since we aren't really OK with `null` items either, we can just go ahead and strip | ||
// them out. So we do that in `_recursivelyRebuildAndSanitize`. | ||
// Then build a deep copy | ||
// (in the process, remove keys with undefined values from nested dictionaries recursively) | ||
return _recursivelyRebuildAndSanitize(val, allowNull); | ||
// TODO: | ||
// could consolidate this cloning + stripping undefined keys + prevention against | ||
// ∞-recursion into a single tree traversal, which would triple the efficiency, | ||
// because then instead of doing "stringify", then "parse", then "rebuild", it could | ||
// all be accomplished in just one iteration instead of three. | ||
return _rebuild(val, allowNull); | ||
}; | ||
@@ -76,92 +57,156 @@ | ||
function _recursivelyRebuildAndSanitize (val, allowNull) { | ||
if (_.isArray(val)) { | ||
return _.reduce(val,function (memo, item, i) { | ||
if (!_.isUndefined(item) && (allowNull || !_.isNull(item))) { | ||
memo.push(_recursivelyRebuildAndSanitize(item, allowNull)); | ||
} | ||
return memo; | ||
}, []); | ||
} | ||
else if (_.isObject(val)) { | ||
return _.reduce(val,function (memo, subVal, key) { | ||
if (!_.isUndefined(subVal) && (allowNull || !_.isNull(subVal))) { | ||
memo[key] = _recursivelyRebuildAndSanitize(subVal, allowNull); | ||
} | ||
return memo; | ||
}, {}); | ||
} | ||
else { | ||
return val; | ||
} | ||
} | ||
/** | ||
* This was modified by @mikermcneil from @isaacs' json-stringify-safe | ||
* (see https://github.com/isaacs/json-stringify-safe/commit/02cfafd45f06d076ac4bf0dd28be6738a07a72f9#diff-c3fcfbed30e93682746088e2ce1a4a24) | ||
* @param {===} val [description] | ||
* @return {String} [description] | ||
*/ | ||
function stringifySafe(val) { | ||
return JSON.stringify(val, serializer()); | ||
} | ||
function serializer(replacer, cycleReplacer) { | ||
function _rebuild(val, allowNull) { | ||
var stack = []; | ||
var keys = []; | ||
if (!cycleReplacer) { | ||
cycleReplacer = function(key, value) { | ||
// if (!cycleReplacer) { | ||
var cycleReplacer = function(unused, value) { | ||
if (stack[0] === value) return '[Circular ~]'; | ||
return '[Circular ~.' + keys.slice(0, stack.indexOf(value)).join('.') + ']'; | ||
}; | ||
} | ||
// } | ||
return function(key, value) { | ||
function _recursivelyRebuildAndSanitize (val, key) { | ||
// Handle circle jerks | ||
if (stack.length > 0) { | ||
var thisPos = stack.indexOf(this); | ||
~thisPos ? stack.splice(thisPos + 1) : stack.push(this); | ||
var self = this; | ||
var thisPos = stack.indexOf(self); | ||
~thisPos ? stack.splice(thisPos + 1) : stack.push(self); | ||
~thisPos ? keys.splice(thisPos, Infinity, key) : keys.push(key); | ||
if (~stack.indexOf(value)) value = cycleReplacer.call(this, key, value); | ||
if (~stack.indexOf(val)) { | ||
val = cycleReplacer.call(self, key, val); | ||
} | ||
} | ||
else stack.push(value); | ||
else stack.push(val); | ||
// Rebuild and strip undefineds/nulls | ||
if (_.isArray(val)) { | ||
return _.reduce(val,function (memo, item, i) { | ||
if (!_.isUndefined(item) && (allowNull || !_.isNull(item))) { | ||
memo.push(_recursivelyRebuildAndSanitize.call(val, item, i)); | ||
} | ||
return memo; | ||
}, []); | ||
} | ||
// Serialize errors, regexps, dates, and functions to strings: | ||
if (_.isError(value)){ | ||
value = value.stack; | ||
else if (_.isError(val)){ | ||
val = val.stack; | ||
} | ||
else if (_.isRegExp(value)){ | ||
value = value.toString(); | ||
else if (_.isRegExp(val)){ | ||
val = val.toString(); | ||
} | ||
else if (_.isFunction(value)){ | ||
value = value.toString(); | ||
else if (_.isDate(val)){ | ||
val = val.toJSON(); | ||
} | ||
else if (_.isObject(value)){ | ||
if (value instanceof Readable) { | ||
else if (_.isFunction(val)){ | ||
val = val.toString(); | ||
} | ||
else if (!_.isObject(val)) { | ||
// Coerce NaN, Infinity, and -Infinity to 0: | ||
if (_.isNaN(val)) { | ||
val = 0; | ||
} | ||
else if (val === Infinity) { | ||
val = 0; | ||
} | ||
else if (val === -Infinity) { | ||
val = 0; | ||
} | ||
} | ||
else if (_.isObject(val)) { | ||
if (val instanceof Readable) { | ||
return null; | ||
} | ||
if (value instanceof Buffer) { | ||
if (val instanceof Buffer) { | ||
return null; | ||
} | ||
return _.reduce(val,function (memo, subVal, key) { | ||
if (!_.isUndefined(subVal) && (allowNull || !_.isNull(subVal))) { | ||
memo[key] = _recursivelyRebuildAndSanitize.call(val, subVal, key); | ||
} | ||
return memo; | ||
}, {}); | ||
} | ||
// Coerce NaN, Infinity, and -Infinity to 0: | ||
if (_.isNaN(value)) { | ||
value = 0; | ||
} | ||
else if (value === Infinity) { | ||
value = 0; | ||
} | ||
else if (value === -Infinity) { | ||
value = 0; | ||
} | ||
if (!replacer) { | ||
return value; | ||
} | ||
return replacer.call(this, key, value); | ||
}; | ||
return val; | ||
} | ||
// Pass in the empty string to satisfy Mr. isaac's replacer | ||
return _recursivelyRebuildAndSanitize(val, ''); | ||
} | ||
// /** | ||
// * This was modified by @mikermcneil from @isaacs' json-stringify-safe | ||
// * (see https://github.com/isaacs/json-stringify-safe/commit/02cfafd45f06d076ac4bf0dd28be6738a07a72f9#diff-c3fcfbed30e93682746088e2ce1a4a24) | ||
// * @param {===} val [description] | ||
// * @return {String} [description] | ||
// */ | ||
// function stringifySafe(val) { | ||
// return JSON.stringify(val, serializer()); | ||
// } | ||
// function serializer(replacer, cycleReplacer) { | ||
// var stack = []; | ||
// var keys = []; | ||
// if (!cycleReplacer) { | ||
// cycleReplacer = function(key, value) { | ||
// if (stack[0] === value) return '[Circular ~]'; | ||
// return '[Circular ~.' + keys.slice(0, stack.indexOf(value)).join('.') + ']'; | ||
// }; | ||
// } | ||
// return function(key, value) { | ||
// if (stack.length > 0) { | ||
// var thisPos = stack.indexOf(this); | ||
// ~thisPos ? stack.splice(thisPos + 1) : stack.push(this); | ||
// ~thisPos ? keys.splice(thisPos, Infinity, key) : keys.push(key); | ||
// if (~stack.indexOf(value)) value = cycleReplacer.call(this, key, value); | ||
// } | ||
// else stack.push(value); | ||
// // Serialize errors, regexps, dates, and functions to strings: | ||
// if (_.isError(value)){ | ||
// value = value.stack; | ||
// } | ||
// else if (_.isRegExp(value)){ | ||
// value = value.toString(); | ||
// } | ||
// else if (_.isFunction(value)){ | ||
// value = value.toString(); | ||
// } | ||
// else if (_.isObject(value)){ | ||
// if (value instanceof Readable) { | ||
// return null; | ||
// } | ||
// if (value instanceof Buffer) { | ||
// return null; | ||
// } | ||
// } | ||
// // Coerce NaN, Infinity, and -Infinity to 0: | ||
// if (_.isNaN(value)) { | ||
// value = 0; | ||
// } | ||
// else if (value === Infinity) { | ||
// value = 0; | ||
// } | ||
// else if (value === -Infinity) { | ||
// value = 0; | ||
// } | ||
// if (!replacer) { | ||
// return value; | ||
// } | ||
// return replacer.call(this, key, value); | ||
// }; | ||
// } |
@@ -185,3 +185,3 @@ /** | ||
// ...expecting ANY json-compatible value (`"%json"`) | ||
// ...expecting ANY json-compatible value (`"*"`) | ||
if (allowAnyJSONCompatible) { | ||
@@ -196,3 +196,3 @@ // (run rebuildSanitized with `allowNull` enabled) | ||
if (allowAnyArray) { | ||
return rebuildSanitized(coercedValue); | ||
return rebuildSanitized(coercedValue, true); | ||
} | ||
@@ -203,2 +203,7 @@ | ||
return _.reduce(coercedValue, function (memo, coercedVal){ | ||
// Never consider `undefined` a real array item. Because things cannot be and also not be. | ||
if (_.isUndefined(coercedVal)) { | ||
return memo; | ||
} | ||
memo.push(_validateRecursive(arrayItemTpl, coercedVal, errors, ensureSerializable, undefined, strict)); | ||
@@ -213,3 +218,3 @@ return memo; | ||
if (allowAnyDictionary){ | ||
return rebuildSanitized(coercedValue); | ||
return rebuildSanitized(coercedValue, true); | ||
} | ||
@@ -216,0 +221,0 @@ // ...expecting a specific dictionary example |
{ | ||
"name": "rttc", | ||
"version": "6.0.0", | ||
"version": "7.0.0", | ||
"description": "Runtime type-checking for JavaScript.", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -225,2 +225,4 @@ # rttc | ||
| undefined | `undefined` | `null` | | ||
| [undefined] | `[undefined]` | [] | | ||
| {foo: undefined} | `{foo: undefined}` | {} | | ||
| Infinity | `Infinity` | `0` | | ||
@@ -262,8 +264,10 @@ | -Infinity | `-Infinity` | `0` | | ||
+ are guaranteed to be JSON-serializable, with a few additional affordances: | ||
+ normally, stringified JSON may contain `null` values. Instead, rttc removes `null` items from arrays and removes keys with `null` values from objects. | ||
+ normally, `Error` instances get stringified into empty objects. Instead, rttc turns them into human-readable strings by reducing them to their `.stack` property (this includes the error message and the stack trace w/ line numbers) | ||
+ normally, `RegExp` instances get stringified into empty objects. Instead, rttc turns them into human-readable strings like `'/some regexp/gi'` | ||
+ normally, `function()` instances get stringified into empty objects. Instead, rttc turns them into human-readable strings like `'function doStuff (a,b) { console.log(\'wow I can actually read this!\'); }'` | ||
+ keys with undefined values at any level will be stripped out | ||
+ undefined items in nested arrays will be stripped out | ||
+ keys with null values may be present | ||
+ null items in nested arrays may be present | ||
#### Faceted dictionaries | ||
@@ -302,6 +306,9 @@ | ||
+ are guaranteed to be JSON-serializable, with a few additional affordances: | ||
+ normally, stringified JSON may contain `null` values. Instead, rttc removes `null` items from arrays and removes keys with `null` values from objects. | ||
+ normally, `Error` instances get stringified into empty objects. Instead, rttc turns them into human-readable strings by reducing them to their `.stack` property (this includes the error message and the stack trace w/ line numbers) | ||
+ normally, `RegExp` instances get stringified into empty objects. Instead, rttc turns them into human-readable strings like `'/some regexp/gi'` | ||
+ normally, `function()` instances get stringified into empty objects. Instead, rttc turns them into human-readable strings like `'function doStuff (a,b) { console.log(\'wow I can actually read this!\'); }'` | ||
+ keys of nested dictionaries with undefined values will be stripped out | ||
+ undefined array items at any level will be stripped out | ||
+ keys of nested dictionaries with null values may be present | ||
+ null items in arrays at any level may be present | ||
@@ -323,2 +330,4 @@ | ||
Undefined items will always be stripped out of arrays. | ||
> Also note that, because of this, when providing a type schema or type-inference-able example for an array, you only need to provide one item in the array, e.g.: | ||
@@ -349,3 +358,3 @@ | ||
This works pretty much like the generic array or generic dictionary type, with two major differences: (1) the top-level value can be a string, boolean, number, dictionary, array, or null value. (2) `null` is permitted, both as a top-level value and recursively in nested arrays and dictionaries (and as you might expect, `null` values are NOT stripped from nested arrays and dictionaries when performing type coercion) | ||
This works pretty much like the generic array or generic dictionary type, with one major difference: the top-level value can be a string, boolean, number, dictionary, array, or null value. | ||
@@ -360,3 +369,3 @@ Other than the aforementioned exception for `null`, the generic JSON type follows the JSON-serializability rules from generic arrays and generic dictionaries. | ||
This special type allows anything except `undefined`. It also _does not rebuild objects_, which means it maintains the original reference (i.e. is `===`). It does not guarantee JSON-serializability. | ||
This special type allows anything except `undefined` at the top level (undefined is permitted at any other level). It also _does not rebuild objects_, which means it maintains the original reference (i.e. is `===`). It does not guarantee JSON-serializability. | ||
@@ -390,3 +399,3 @@ | ||
+ `undefined` IS, however, allowed as an item in a nested array or value in a nested dictionary, but only against the mutable reference type (`===`) | ||
+ `null` is only valid against the JSON (`*`) and mutable reference (`===`) types. | ||
+ `null` is only valid at the top level against the JSON (`*`) and mutable reference (`===`) types. | ||
@@ -393,0 +402,0 @@ ##### Weird psuedo-numeric values |
@@ -251,3 +251,3 @@ // Export the array of tests below. | ||
{ example: {}, actual: { x: -Infinity }, result: { x: 0 } }, | ||
{ example: {}, actual: { x: null }, result: {} }, | ||
{ example: {}, actual: { x: null }, result: { x:null } }, | ||
{ example: {}, actual: { x: function foo(a,b){return a+' '+b;} }, result: { x: 'function foo(a,b){return a+\' \'+b;}' } }, | ||
@@ -269,3 +269,3 @@ // { example: {}, actual: { x: undefined, null, NaN, -Infinity, Infinity, function(){} }, result: [] }, | ||
{ example: [], actual: [undefined], result: [] }, | ||
{ example: [], actual: [null], result: [] }, | ||
{ example: [], actual: [null], result: [null] }, | ||
{ example: [], actual: [NaN], result: [0] }, | ||
@@ -272,0 +272,0 @@ { example: [], actual: [Infinity], result: [0] }, |
@@ -75,8 +75,10 @@ /** | ||
// test one level of additional array nesting | ||
recursiveTests.push({ | ||
example: [ customCloneDeep(test.example) ], | ||
actual: [ customCloneDeep(test.actual) ], | ||
result: [ customCloneDeep(test.result) ], | ||
_meta: '+1 array depth' | ||
}); | ||
if (!_.isUndefined(test.actual)) { | ||
recursiveTests.push({ | ||
example: [ customCloneDeep(test.example) ], | ||
actual: [ customCloneDeep(test.actual) ], | ||
result: [ customCloneDeep(test.result) ], | ||
_meta: '+1 array depth' | ||
}); | ||
} | ||
@@ -107,9 +109,11 @@ // test one level of additional dictionary nesting | ||
// test two levels of additional array nesting | ||
recursiveTests.push({ | ||
example: [ [ customCloneDeep(test.example) ] ], | ||
actual: [ [ customCloneDeep(test.actual) ] ], | ||
result: [ [ customCloneDeep(test.result) ] ], | ||
_meta: '+2 array depth' | ||
}); | ||
if (!_.isUndefined(test.actual)) { | ||
// test two levels of additional array nesting | ||
recursiveTests.push({ | ||
example: [ [ customCloneDeep(test.example) ] ], | ||
actual: [ [ customCloneDeep(test.actual) ] ], | ||
result: [ [ customCloneDeep(test.result) ] ], | ||
_meta: '+2 array depth' | ||
}); | ||
} | ||
@@ -125,16 +129,20 @@ // test two levels of additional dictionary nesting AND 1 level of array nesting | ||
// test two levels of additional dictionary nesting and one level of array nesting, then WITHIN that, 1 level of array nesting | ||
recursiveTests.push({ | ||
example: [ { xtra: { xtra2: [customCloneDeep(test.example)] } } ], | ||
actual: [ { xtra: { xtra2: [customCloneDeep(test.actual)] } } ], | ||
result: [ { xtra:{ xtra2: [customCloneDeep(test.result)] } } ], | ||
_meta: '+1 array depth, +2 dictionary depth, +1 nested array depth' | ||
}); | ||
if (!_.isUndefined(test.actual)) { | ||
recursiveTests.push({ | ||
example: [ { xtra: { xtra2: [customCloneDeep(test.example)] } } ], | ||
actual: [ { xtra: { xtra2: [customCloneDeep(test.actual)] } } ], | ||
result: [ { xtra:{ xtra2: [customCloneDeep(test.result)] } } ], | ||
_meta: '+1 array depth, +2 dictionary depth, +1 nested array depth' | ||
}); | ||
} | ||
// test two levels of additional dictionary nesting and one level of array nesting, then WITHIN that, 2 levels of array nesting | ||
recursiveTests.push({ | ||
example: [ { xtra: { xtra2: [[customCloneDeep(test.example)]] } } ], | ||
actual: [ { xtra: { xtra2: [[customCloneDeep(test.actual)]] } } ], | ||
result: [ { xtra:{ xtra2: [[customCloneDeep(test.result)]] } } ], | ||
_meta: '+1 array depth, +2 dictionary depth, +2 nested array depth' | ||
}); | ||
if (!_.isUndefined(test.actual)) { | ||
// test two levels of additional dictionary nesting and one level of array nesting, then WITHIN that, 2 levels of array nesting | ||
recursiveTests.push({ | ||
example: [ { xtra: { xtra2: [[customCloneDeep(test.example)]] } } ], | ||
actual: [ { xtra: { xtra2: [[customCloneDeep(test.actual)]] } } ], | ||
result: [ { xtra:{ xtra2: [[customCloneDeep(test.result)]] } } ], | ||
_meta: '+1 array depth, +2 dictionary depth, +2 nested array depth' | ||
}); | ||
} | ||
} | ||
@@ -141,0 +149,0 @@ |
@@ -249,3 +249,3 @@ // Export the array of tests below | ||
{ example: {}, actual: { x: -Infinity }, result: {x:0} }, | ||
{ example: {}, actual: { x: null }, result: {} }, | ||
{ example: {}, actual: { x: null }, result: {x: null} }, | ||
{ example: {}, actual: { x: function foo(a,b){return a+' '+b;} }, result: { x: 'function foo(a,b){return a+\' \'+b;}' } }, | ||
@@ -267,3 +267,3 @@ // { example: {}, actual: { x: undefined, null, NaN, -Infinity, Infinity, function(){} }, result: [] }, | ||
{ example: [], actual: [undefined], result: [] }, | ||
{ example: [], actual: [null], result: [] }, | ||
{ example: [], actual: [null], result: [null] }, | ||
{ example: [], actual: [NaN], result: [0] }, | ||
@@ -572,3 +572,3 @@ { example: [], actual: [Infinity], result: [0] }, | ||
actual: [{a:3}, undefined, {a: 5}, undefined, {a: 7}, {a:9, b: [undefined, 9,2,4,undefined,8]}], | ||
error: true | ||
result: [{a:3}, {a: 5}, {a: 7}, {a:9, b: [undefined, 9,2,4,undefined,8]}] | ||
}, | ||
@@ -575,0 +575,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
166687
3413
598