core-functions
Advanced tools
Comparing version 2.0.7 to 2.0.8
{ | ||
"name": "core-functions", | ||
"version": "2.0.7", | ||
"version": "2.0.8", | ||
"description": "Core functions, utilities and classes for working with Node/JavaScript primitives and built-in objects, including strings, booleans, Promises, base 64, Arrays, Objects, standard AppErrors, etc.", | ||
@@ -5,0 +5,0 @@ "author": "Byron du Preez", |
@@ -1,2 +0,2 @@ | ||
# core-functions v2.0.7 | ||
# core-functions v2.0.8 | ||
@@ -106,2 +106,10 @@ Core functions, utilities and classes for working with Node/JavaScript primitives and built-in objects, including | ||
### 2.0.8 | ||
- Changed `strings.js` module's `stringify` function: | ||
- To handle circular dependencies in objects and arrays | ||
- To recursively stringify objects & their properties itself, instead of relying on `JSON.stringify` | ||
- To add an optional `useToStringForErrors` argument to convert Errors using their `toString` methods (if true) or | ||
as objects (if false), but with their `message` and `name` (but not `stack`) properties visible in the result | ||
- To add an optional `quoteStrings` argument to return given string values surrounded with double-quotes (if true) | ||
### 2.0.7 | ||
@@ -108,0 +116,0 @@ - Change to `objects.js`: |
106
strings.js
'use strict'; | ||
const Numbers = require('./numbers'); | ||
/** | ||
@@ -80,16 +78,98 @@ * Module containing utilities for working with strings. | ||
/** | ||
* Returns the given value as a string with special case handling for string, String, undefined and special numbers | ||
* (i.e. Infinity, -Infinity and NaN), because string & String are already strings and {@linkcode JSON#stringify} | ||
* converts: Infinity, -Infinity and NaN to 'null'; an Error to '{}'; and undefined to undefined (not 'undefined'). | ||
* Returns the given value as a string with special case handling for undefined, null, strings, numbers, booleans, | ||
* Strings, Numbers, Booleans, Errors, Functions, Arrays, Objects and special numbers (i.e. Infinity, -Infinity and NaN) | ||
* and also handles circular dependencies. Similar to {@linkcode JSON#stringify}, but shows more about the given value | ||
* than JSON.stringify does, for example: | ||
* - JSON.stringify(undefined) returns undefined, but this returns 'undefined' | ||
* - JSON.stringify({a:undefined}) returns {}, but this returns '{"a":undefined}' | ||
* - JSON.stringify(new Error("Boom")) returns '{}', but this returns either '{"message":"Boom","name":"Error"}' (if | ||
* useToStringForErrors is false) or '[Error: Boom]' (if useToStringForErrors is true) | ||
* - JSON.stringify(func) with function func() {} returns undefined, but this returns '[Function: func]' | ||
* - JSON.stringify({fn: func}) with function func() {} returns '{}', but this returns '{"fn": [Function: func]}' | ||
* - JSON.stringify(NaN) returns 'null', but this returns 'NaN' | ||
* - JSON.stringify(Infinity) returns 'null', but this returns 'Infinity' | ||
* - JSON.stringify(-Infinity) returns 'null', but this returns '-Infinity' | ||
* - JSON.stringify applied to objects or arrays with circular dependencies throws an error (TypeError: Converting | ||
* circular structure to JSON), but this returns a string with circular dependencies replaced with [Circular: {name}], | ||
* where name refers to the original object that it references (using 'this' for the outermost object itself). | ||
* For example, given the following code: | ||
* const object = {a: 1, o: {b:2}}; | ||
* object.circular = object; | ||
* object.o.oAgain = object.o; | ||
* this function returns '{"a":1,"o":{"b":2,"oAgain":[Circular: this.o]},"circular":[Circular: this]}' | ||
* | ||
* @param {*} value the value to stringify | ||
* @param {boolean|undefined} [useToStringForErrors] - whether to stringify errors using toString or as normal objects (default) | ||
* @param {boolean|undefined} [quoteStrings] - whether to surround simple string values with double-quotes or not (default) | ||
* @returns {string} the value as a string | ||
*/ | ||
function stringify(value) { | ||
return value === undefined ? `${value}` : | ||
typeof value === 'string' ? value : | ||
value instanceof String ? value.valueOf() : | ||
Numbers.isSpecialNumber(value) || value instanceof Error ? `${value}` : | ||
typeof value === 'function' ? isNotBlank(value.name) ? `[Function: ${value.name}]` : '[Function: anonymous]' : | ||
Array.isArray(value) ? `[${value.map(stringify).join(", ")}]` : | ||
JSON.stringify(value); | ||
function stringify(value, useToStringForErrors, quoteStrings) { | ||
const history = new WeakMap(); | ||
function stringifyWithHistory(value, name, quoteStrings) { | ||
// Special cases for undefined and null | ||
if (value === undefined) return 'undefined'; | ||
if (value === null) return 'null'; | ||
const typeOfValue = typeof value; | ||
// Special cases for strings and Strings | ||
if (typeOfValue === 'string') return quoteStrings ? `"${value}"` : value; | ||
if (value instanceof String) return quoteStrings ? `"${value.valueOf()}"` : value.valueOf(); | ||
// Special cases for numbers and Numbers (and special numbers) | ||
//if (typeOfValue === 'number' || value instanceof Number || Numbers.isSpecialNumber(value)) return `${value}`; | ||
if (typeOfValue === 'number' || value instanceof Number) return `${value}`; | ||
// Special cases for booleans and Booleans | ||
if (typeOfValue === 'boolean' || value instanceof Boolean) return `${value}`; | ||
// Special case for Errors - use toString() if directed, since stringify on most errors, just returns "{}" | ||
const valueIsError = value instanceof Error; | ||
if (valueIsError && useToStringForErrors) return `${value}`; | ||
// Special case for Functions - show thm as [Function: {function name}] | ||
if (typeOfValue === 'function') return isNotBlank(value.name) ? `[Function: ${value.name}]` : '[Function: anonymous]'; | ||
if (typeOfValue === 'object') { | ||
if (history.has(value)) { | ||
// Special case for circular values - show thm as [Circular: {property name}] | ||
return `[Circular: ${history.get(value)}]`; | ||
} | ||
history.set(value, name); | ||
// Special case for Array objects, stringify each of its elements | ||
if (Array.isArray(value)) { | ||
return `[${value.map((e, i) => stringifyWithHistory(e, `${name}[${i}]`, quoteStrings)).join(", ")}]`; | ||
} | ||
// Stringify the object | ||
let names = Object.getOwnPropertyNames(value); | ||
if (valueIsError) { | ||
// Special case for Error objects - include message and name (if any), but exclude stack, which are all normally hidden with JSON.stringify | ||
names = names.filter(n => n !== 'stack'); | ||
if (value.name) { | ||
names.push('name'); | ||
} | ||
} | ||
let result = '{'; | ||
for (let i = 0; i < names.length; ++i) { | ||
const propertyName = names[i]; | ||
const propertyValue = value[propertyName]; | ||
if (i > 0) { | ||
result += ','; | ||
} | ||
result += `"${propertyName}":${stringifyWithHistory(propertyValue, `${name}.${propertyName}`, true)}` | ||
} | ||
result += '}'; | ||
return result; | ||
} | ||
// If anything else use JSON.stringify on it | ||
return JSON.stringify(value); | ||
} | ||
return stringifyWithHistory(value, 'this', quoteStrings); | ||
} | ||
@@ -96,0 +176,0 @@ |
{ | ||
"name": "core-functions-tests", | ||
"version": "2.0.7", | ||
"version": "2.0.8", | ||
"author": "Byron du Preez", | ||
@@ -5,0 +5,0 @@ "license": "Apache-2.0", |
@@ -245,2 +245,5 @@ 'use strict'; | ||
} | ||
function checkWithArgs(value, errorsAsObjects, quoteStrings, expected) { | ||
return checkEqual(t, Strings.stringify, [wrap(value, wrapInString), errorsAsObjects, quoteStrings], expected, toPrefix(value, wrapInString)); | ||
} | ||
@@ -255,3 +258,4 @@ // undefined | ||
check({}, wrapInString ? '[object Object]' : '{}'); | ||
check({a: 1, b: 2}, wrapInString ? '[object Object]' : `${JSON.stringify({a: 1, b: 2})}`); | ||
check({a: 1, b: 2}, wrapInString ? '[object Object]' : JSON.stringify({a: 1, b: 2})); | ||
check({a: 1, b: 2, o: {c: 'C'}}, wrapInString ? '[object Object]' : JSON.stringify({a: 1, b: 2, o: {c: 'C'}})); | ||
@@ -315,2 +319,80 @@ // booleans | ||
check('ABC', 'ABC'); | ||
checkWithArgs('ABC', false, true, '"ABC"'); | ||
// errors | ||
check(new Error('Planned error'), wrapInString ? 'Error: Planned error' : '{"message":"Planned error","name":"Error"}'); | ||
checkWithArgs(new Error('Planned error'), true, false, wrapInString ? 'Error: Planned error' : 'Error: Planned error'); | ||
// circular objects | ||
const circular0 = {a: 1, o: {b:2}}; | ||
circular0.circular = circular0; | ||
circular0.o.oAgain = circular0.o; | ||
check(circular0, wrapInString ? '[object Object]' : '{"a":1,"o":{"b":2,"oAgain":[Circular: this.o]},"circular":[Circular: this]}'); | ||
const circular1 = {a: 1, b: 2, o: {c: 'C', p: {d: 'D'}}}; | ||
circular1.thisAgain = circular1; | ||
circular1.o.thisAgain = circular1; | ||
circular1.o.p.thisAgain = circular1; | ||
circular1.oAgain = circular1.o; | ||
circular1.o.oAgain = circular1.o; | ||
circular1.o.p.oAgain = circular1.o; | ||
circular1.pAgain = circular1.o.p; | ||
circular1.o.pAgain = circular1.o.p; | ||
circular1.o.p.pAgain = circular1.o.p; | ||
check(circular1, wrapInString ? '[object Object]' : '{"a":1,"b":2,"o":{"c":"C","p":{"d":"D","thisAgain":[Circular: this],"oAgain":[Circular: this.o],"pAgain":[Circular: this.o.p]},"thisAgain":[Circular: this],"oAgain":[Circular: this.o],"pAgain":[Circular: this.o.p]},"thisAgain":[Circular: this],"oAgain":[Circular: this.o],"pAgain":[Circular: this.o.p]}'); | ||
// circular arrays with circular objects | ||
const array2 = ['a', {}, 123]; | ||
const circular2 = array2[1]; | ||
circular2.thisAgain = array2; | ||
circular2.this1Again = circular2; | ||
array2.push(array2); | ||
check(array2, wrapInString ? 'a,[object Object],123,' : '[a, {"thisAgain":[Circular: this],"this1Again":[Circular: this[1]]}, 123, [Circular: this]]'); | ||
const array3 = ['x', {y:'Y'}, 123]; | ||
const circular3 = array3[1]; | ||
circular3.thisAgain = circular3; | ||
circular3.arrayAgain = array3; | ||
array3.push(array3); | ||
check(circular3, wrapInString ? '[object Object]' : '{"y":"Y","thisAgain":[Circular: this],"arrayAgain":["x", [Circular: this], 123, [Circular: this.arrayAgain]]}'); | ||
// circular objects with circular arrays | ||
const array4 = ['b', {z: "Z"}, 456]; | ||
const circular4 = {a: 'A', array: array4}; | ||
circular4.thisAgain = circular4; | ||
circular4.arrayAgain = circular4.array; | ||
array4[1].thisAgain = circular4; | ||
array4[1].arrayAgain = circular4.array; | ||
array4.push(array4); | ||
check(circular4, wrapInString ? '[object Object]' : '{"a":"A","array":["b", {"z":"Z","thisAgain":[Circular: this],"arrayAgain":[Circular: this.array]}, 456, [Circular: this.array]],"thisAgain":[Circular: this],"arrayAgain":[Circular: this.array]}'); | ||
const array5 = ['c', {x: "X"}, 789]; | ||
const circular5 = {a: 'A', array: array5}; | ||
array5[1].thisAgain = array5; | ||
array5[1].this1Again = array5[1]; | ||
array5[1].circular5 = circular5; | ||
circular5.thisAgain = array5; | ||
circular5.this1Again = array5[1]; | ||
circular5.this1Circular5Again = circular5; | ||
circular5.this1Circular5ArrayAgain = circular5.array; | ||
array5.push(array5); | ||
check(array5, wrapInString ? 'c,[object Object],789,' : '[c, {"x":"X","thisAgain":[Circular: this],"this1Again":[Circular: this[1]],"circular5":{"a":"A","array":[Circular: this],"thisAgain":[Circular: this],"this1Again":[Circular: this[1]],"this1Circular5Again":[Circular: this[1].circular5],"this1Circular5ArrayAgain":[Circular: this]}}, 789, [Circular: this]]'); | ||
// Functions | ||
function func() {} | ||
check(func, wrapInString ? 'function func() {}' : '[Function: func]'); | ||
check({fn:func}, wrapInString ? '[object Object]' : '{"fn":[Function: func]}'); | ||
// undefined object properties | ||
check({a:undefined}, wrapInString ? '[object Object]' : '{"a":undefined}'); | ||
} | ||
@@ -317,0 +399,0 @@ |
@@ -68,5 +68,5 @@ 'use strict'; | ||
if (expected) { | ||
t.ok(actual, `${toPrefix(prefix)}${stringify(actual)} ${okSuffix}`); | ||
t.ok(actual, `${toPrefix(prefix)} ${okSuffix}`); | ||
} else { | ||
t.notOk(actual, `${toPrefix(prefix)}${stringify(actual)} ${notOkSuffix}`); | ||
t.notOk(actual, `${toPrefix(prefix)} ${notOkSuffix}`); | ||
} | ||
@@ -73,0 +73,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
188973
3677
171