From e3a8000d04e6e9276a8b489ed7b23f94066ece05 Mon Sep 17 00:00:00 2001 From: "Matt R. Wilson" Date: Sat, 12 Feb 2022 11:10:32 -0700 Subject: [PATCH] Make use of new safe-stable-stringify 2.x features (#134) This commit originally included an upgrade of the lib itself from 1.x to 2.x. After a Dependabot PR was merged in to do the upgrade, this commit was rebased to only include the changes that take advantage of the new features. https://github.com/BridgeAR/safe-stable-stringify/releases ---- - Allow `opts` to configure the lib. - Update types for new opts. - Add tests to fill in coverage for json.js. NB. The `Buffer.toString('base64')` conversion was removed. Buffer implements `toJSON` so this block of code was not being executed anyway, as the replacer is called with the result of `toJSON`. Base64 was never being returned. https://nodejs.org/api/buffer.html#buftojson --- index.d.ts | 36 +++++++++++++++++++++++++++++++ json.js | 11 +++++----- test/json.test.js | 55 ++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 96 insertions(+), 6 deletions(-) diff --git a/index.d.ts b/index.d.ts index e37a6e8..02ae253 100644 --- a/index.d.ts +++ b/index.d.ts @@ -83,6 +83,42 @@ export interface JsonOptions { * The number of white space used to format the json. */ space?: number; + + // The following options come from safe-stable-stringify + // https://github.com/BridgeAR/safe-stable-stringify/blob/main/index.d.ts + + /** + * If `true`, bigint values are converted to a number. Otherwise, they are ignored. + * This option is ignored by default as Logform stringifies BigInt in the default replacer. + * @default true + */ + bigint?: boolean, + /** + * Defines the value for circular references. + * Set to `undefined`, circular properties are not serialized (array entries are replaced with null). + * Set to `Error`, to throw on circular references. + * @default "[Circular]" + */ + circularValue?: string | null | TypeErrorConstructor | ErrorConstructor, + /** + * If `true`, guarantee a deterministic key order instead of relying on the insertion order. + * @default true + */ + deterministic?: boolean, + /** + * Maximum number of entries to serialize per object (at least one). + * The serialized output contains information about how many entries have not been serialized. + * Ignored properties are counted as well (e.g., properties with symbol values). + * Using the array replacer overrules this option. + * @default Infinity + */ + maximumBreadth?: number, + /** + * Maximum number of object nesting levels (at least 1) that will be serialized. + * Objects at the maximum level are serialized as `"[Object]"` and arrays as `"[Array]"`. + * @default Infinity + */ + maximumDepth?: number, } export interface LabelOptions { diff --git a/json.js b/json.js index 6af9803..fb91c97 100644 --- a/json.js +++ b/json.js @@ -2,16 +2,16 @@ const format = require('./format'); const { MESSAGE } = require('triple-beam'); -const jsonStringify = require('safe-stable-stringify'); +const stringify = require('safe-stable-stringify'); /* * function replacer (key, value) * Handles proper stringification of Buffer and bigint output. */ function replacer(key, value) { - if (value instanceof Buffer) - return value.toString('base64'); - // eslint-disable-next-line valid-typeof + // safe-stable-stringify does support BigInt, however, it doesn't wrap the value in quotes. + // Leading to a loss in fidelity if the resulting string is parsed. + // It would also be a breaking change for logform. if (typeof value === 'bigint') return value.toString(); return value; @@ -23,7 +23,8 @@ function replacer(key, value) { * object into pure JSON. This was previously exposed as { json: true } * to transports in `winston < 3.0.0`. */ -module.exports = format((info, opts = {}) => { +module.exports = format((info, opts) => { + const jsonStringify = stringify.configure(opts); info[MESSAGE] = jsonStringify(info, opts.replacer || replacer, opts.space); return info; }); diff --git a/test/json.test.js b/test/json.test.js index 89fb394..3aa3cf6 100644 --- a/test/json.test.js +++ b/test/json.test.js @@ -53,7 +53,6 @@ describe('json', () => { } )); - it('json() can handle circular JSON objects', (done) => { // Define an info with a circular reference. const circular = { level: 'info', message: 'has a circular ref ok!', filtered: true }; @@ -74,5 +73,59 @@ describe('json', () => { stream.write(fmt.transform(circular, fmt.options)); }); + // https://nodejs.org/api/buffer.html#buftojson + it('json() can handle Buffers', assumeFormatted( + json(), + { level: 'info', message: 'has a buffer!', buf: Buffer.from('foo') }, + (info) => { + assume(info.level).is.a('string'); + assume(info.level).equals('info'); + + const parsed = JSON.parse(info[MESSAGE]); + assume(parsed).deep.equal({ + level: 'info', + message: 'has a buffer!', + buf: { + type: 'Buffer', + data: [102, 111, 111] + } + }); + } + )); + + it('json() can handle BigInt', assumeFormatted( + json(), + // the ESLint env target we're on doesn't understand BigInt + // eslint-disable-next-line + { level: 'info', message: 'has a BigInt!', howBig: BigInt(9007199254740991) }, + (info) => { + assume(info.level).is.a('string'); + assume(info.level).equals('info'); + + const parsed = JSON.parse(info[MESSAGE]); + assume(parsed).deep.equal({ + level: 'info', + message: 'has a BigInt!', + howBig: '9007199254740991' + }); + } + )); + + it('json() can pass safe-stable-stringify options', assumeFormatted( + json({ maximumDepth: 1 }), + { level: 'info', message: 'has a BigInt!', nested: { foo: 'bar' }}, + (info) => { + assume(info.level).is.a('string'); + assume(info.level).equals('info'); + + const parsed = JSON.parse(info[MESSAGE]); + assume(parsed).deep.equal({ + level: 'info', + message: 'has a BigInt!', + nested: '[Object]' + }); + } + )); + it('exposes the Format prototype', assumeHasPrototype(json)); });