diff --git a/README.md b/README.md index aae2b13..f04430f 100644 --- a/README.md +++ b/README.md @@ -292,7 +292,7 @@ jsesc(['a', 'b', 'c'], { #### `json` -The `json` option takes a boolean value (`true` or `false`), and defaults to `false` (disabled). When enabled, the output is valid JSON. [Hexadecimal character escape sequences](https://mathiasbynens.be/notes/javascript-escapes#hexadecimal) and [the `\v` or `\0` escape sequences](https://mathiasbynens.be/notes/javascript-escapes#single) are not used. Setting `json: true` implies `quotes: 'double', wrap: true, es6: false`, although these values can still be overridden if needed — but in such cases, the output won’t be valid JSON anymore. +The `json` option takes a boolean value (`true` or `false`), and defaults to `false` (disabled). When enabled, the output is valid JSON. [Hexadecimal character escape sequences](https://mathiasbynens.be/notes/javascript-escapes#hexadecimal) and [the `\v` or `\0` escape sequences](https://mathiasbynens.be/notes/javascript-escapes#single) are not used. Setting `json: true` implies `quotes: 'double', wrap: true, es6: false`, although these values can still be overridden if needed — but in such cases, the output won’t be valid JSON anymore. In general, the `json` flag attempts to mimick the coercion behavior of `JSON.stringify` - `Date` objects will be formatted as strings, `Set` objects will become empty objects `{}`, and `undefined` values in an array will become `null`. ```js jsesc('foo\x00bar\xFF\uFFFDbaz', { @@ -321,6 +321,9 @@ jsesc([ undefined, -Infinity ], { 'json': true }); // → '[null,null]' + +jsesc({ '!date': new Date(), '!set': new Set([1, 2])) }); +// → {"!date":"2020-04-03T15:34:00.000Z","!set":{}} ``` **Note:** Using this option on objects or arrays that contain non-string values relies on `JSON.stringify()`. For legacy environments like IE ≤ 7, use [a `JSON` polyfill](http://bestiejs.github.io/json3/). diff --git a/jsesc.js b/jsesc.js index 7b8b923..866104f 100644 --- a/jsesc.js +++ b/jsesc.js @@ -35,6 +35,9 @@ const isObject = (value) => { // This is a very simple check, but it’s good enough for what we need. return toString.call(value) == '[object Object]'; }; +const isDate = (value) => { + return toString.call(value) == '[object Date]'; +}; const isString = (value) => { return typeof value == 'string' || toString.call(value) == '[object String]'; @@ -134,7 +137,7 @@ const jsesc = (argument, options) => { } if (!isString(argument)) { - if (isMap(argument)) { + if (!json && isMap(argument)) { if (argument.size == 0) { return 'new Map()'; } @@ -144,7 +147,7 @@ const jsesc = (argument, options) => { } return 'new Map(' + jsesc(Array.from(argument), options) + ')'; } - if (isSet(argument)) { + if (!json && isSet(argument)) { if (argument.size == 0) { return 'new Set()'; } @@ -205,6 +208,9 @@ const jsesc = (argument, options) => { if (useOctNumbers) { return '0o' + argument.toString(8); } + } else if (isDate(argument)) { + // Date objects have a toJSON method, which implies json: false here. + return 'new Date(' + jsesc(argument.valueOf(), options) + ')'; } else if (!isObject(argument)) { if (json) { // For some values (e.g. `undefined`, `function` objects), diff --git a/src/jsesc.js b/src/jsesc.js index d3eaff1..47fb7e4 100644 --- a/src/jsesc.js +++ b/src/jsesc.js @@ -35,6 +35,9 @@ const isObject = (value) => { // This is a very simple check, but it’s good enough for what we need. return toString.call(value) == '[object Object]'; }; +const isDate = (value) => { + return toString.call(value) == '[object Date]'; +}; const isString = (value) => { return typeof value == 'string' || toString.call(value) == '[object String]'; @@ -134,7 +137,7 @@ const jsesc = (argument, options) => { } if (!isString(argument)) { - if (isMap(argument)) { + if (!json && isMap(argument)) { if (argument.size == 0) { return 'new Map()'; } @@ -144,7 +147,7 @@ const jsesc = (argument, options) => { } return 'new Map(' + jsesc(Array.from(argument), options) + ')'; } - if (isSet(argument)) { + if (!json && isSet(argument)) { if (argument.size == 0) { return 'new Set()'; } @@ -205,6 +208,9 @@ const jsesc = (argument, options) => { if (useOctNumbers) { return '0o' + argument.toString(8); } + } else if (isDate(argument)) { + // Date objects have a toJSON method, which implies json: false here. + return 'new Date(' + jsesc(argument.valueOf(), options) + ')'; } else if (!isObject(argument)) { if (json) { // For some values (e.g. `undefined`, `function` objects), diff --git a/tests/tests.js b/tests/tests.js index 403b9c0..3541915 100644 --- a/tests/tests.js +++ b/tests/tests.js @@ -342,6 +342,18 @@ describe('common usage', function() { 'new Map([\n\t[\'a\', 1],\n\t[\'b\', new Map([\n\t\t[\'x\', 2],\n\t\t[\'y\', 3]\n\t])]\n])', 'Stringifying a Map with `compact: false`' ); + assert.equal( + jsesc( + new Map([ + ['a', 1] + ]), + { + 'json': true + } + ), + '{}', + 'Stringifying a Map with `json: true`' + ); assert.equal( jsesc( new Set([]) @@ -377,6 +389,19 @@ describe('common usage', function() { 'new Set([\n\t[\n\t\t\'a\'\n\t],\n\t\'b\',\n\t{}\n])', 'Stringifying a Set with `compact: false`' ); + assert.equal( + jsesc( + new Set([ + ['a'], + 'b' + ]), + { + 'json': true + } + ), + '{}', + 'Stringifying a Set with `json: true`' + ); // Buffer assert.equal( jsesc( @@ -395,6 +420,31 @@ describe('common usage', function() { 'Buffer.from([\n\t19,\n\t55,\n\t66\n])', 'Stringifying a Buffer with `compact: false`' ); + // Date + assert.equal( + jsesc( + new Date('2020-04-03T15:34:00Z') + ), + 'new Date(1585928040000)' + ); + assert.equal( + jsesc( + new Date('2020-04-03T15:34:00Z'), + { + 'numbers': 'hexadecimal' + } + ), + 'new Date(0x17140AD6E40)' + ); + assert.equal( + jsesc( + new Date('2020-04-03T15:34:00Z'), + { + 'json': true + } + ), + '"2020-04-03T15:34:00.000Z"' + ); // JSON assert.equal( jsesc('foo\x00bar\xFF\uFFFDbaz', {