diff --git a/lib/util.js b/lib/util.js index 261ed79f..49d8f9b3 100644 --- a/lib/util.js +++ b/lib/util.js @@ -111,6 +111,40 @@ function safeJsonParse (obj) { } } +/* + * Stringifies the object passed in, converting Buffers into Strings for better + * display. This mimics JSON.stringify (see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify) + * except the `replacer` argument can only be a function. + * + * @param {object} obj - the object to be serialized + * @param {?function} replacer - function to transform the properties added to the + * serialized object + * @param {?number|string} space - used to insert white space into the output JSON + * string for readability purposes. Defaults to 2 + * returns {string} - the JSON object serialized as a string + */ +function jsonStringify (obj, replacer, space = 2) { + // if no replacer is passed, or it is not a function, just use a pass-through + if (!_.isFunction(replacer)) { + replacer = (k, v) => v; + } + + // Buffers cannot be serialized in a readable way + const bufferToJSON = Buffer.prototype.toJSON; + delete Buffer.prototype.toJSON; + try { + return JSON.stringify(obj, (key, value) => { + const updatedValue = Buffer.isBuffer(value) + ? value.toString('utf8') + : value; + return replacer(key, updatedValue); + }, space); + } finally { + // restore the function, so as to not break further serialization + Buffer.prototype.toJSON = bufferToJSON; + } +} + /* * Removes the wrapper from element, if it exists. * { ELEMENT: 4 } becomes 4 @@ -300,4 +334,5 @@ export { multiResolve, safeJsonParse, wrapElement, unwrapElement, filterObject, toReadableSizeString, isSubPath, W3C_WEB_ELEMENT_IDENTIFIER, isSameDestination, compareVersions, coerceVersion, quote, unleakString, + jsonStringify, }; diff --git a/test/util-specs.js b/test/util-specs.js index 7d40c482..ef466cd3 100644 --- a/test/util-specs.js +++ b/test/util-specs.js @@ -229,11 +229,77 @@ describe('util', function () { util.safeJsonParse(num).should.eql(num); }); it('should make a number from a string representation', function () { - let num = 42; + const num = 42; util.safeJsonParse(String(num)).should.eql(num); }); }); + describe('jsonStringify', function () { + it('should use JSON.stringify if no Buffer involved', function () { + const obj = { + k1: 'v1', + k2: 'v2', + k3: 'v3', + }; + const jsonString = JSON.stringify(obj, null, 2); + util.jsonStringify(obj).should.eql(jsonString); + }); + it('should serialize a Buffer', function () { + const obj = { + k1: 'v1', + k2: 'v2', + k3: Buffer.from('hi how are you today'), + }; + util.jsonStringify(obj).should.include('hi how are you today'); + }); + it('should use the replacer function on non-buffer values', function () { + const obj = { + k1: 'v1', + k2: 'v2', + k3: 'v3', + }; + function replacer (key, value) { + return _.isString(value) ? value.toUpperCase() : value; + } + const jsonString = util.jsonStringify(obj, replacer); + jsonString.should.include('V1'); + jsonString.should.include('V2'); + jsonString.should.include('V3'); + }); + it('should use the replacer function on buffers', function () { + const obj = { + k1: 'v1', + k2: 'v2', + k3: Buffer.from('hi how are you today'), + }; + function replacer (key, value) { + return _.isString(value) ? value.toUpperCase() : value; + } + const jsonString = util.jsonStringify(obj, replacer); + jsonString.should.include('V1'); + jsonString.should.include('V2'); + jsonString.should.include('HI HOW ARE YOU TODAY'); + }); + it('should use the replacer function recursively', function () { + const obj = { + k1: 'v1', + k2: 'v2', + k3: Buffer.from('hi how are you today'), + k4: { + k5: 'v5', + }, + }; + function replacer (key, value) { + return _.isString(value) ? value.toUpperCase() : value; + } + const jsonString = util.jsonStringify(obj, replacer); + jsonString.should.include('V1'); + jsonString.should.include('V2'); + jsonString.should.include('HI HOW ARE YOU TODAY'); + jsonString.should.include('V5'); + }); + }); + describe('unwrapElement', function () { it('should pass through an unwrapped element', function () { let el = 4;