diff --git a/.eslintrc.js b/.eslintrc.js index 5879732..b634a0e 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,4 +1,8 @@ module.exports = { + parserOptions: { + // To support BigInt + ecmaVersion: 2020 + }, extends: require.resolve('@ostai/eslint-config'), rules: { 'no-underscore-dangle': 0, diff --git a/README.md b/README.md index ec44d84..84c53ad 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,7 @@ parse(text, reviver? = null, remove_comments? = false) - **text** `string` The string to parse as JSON. See the [JSON](http://json.org/) object for a description of JSON syntax. - **reviver?** `Function() | null` Default to `null`. It acts the same as the second parameter of [`JSON.parse`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse). If a function, prescribes how the value originally produced by parsing is transformed, before being returned. + - `comment-json` also passes the 3rd parameter `context` to the function `reviver`, as described in https://github.com/tc39/proposal-json-parse-with-source, which will be useful to parse a JSON string with `BigInt` values. - **remove_comments?** `boolean = false` If true, the comments won't be maintained, which is often used when we want to get a clean object. Returns `CommentJSONValue` (`object | string | number | boolean | null`) corresponding to the given JSON text. @@ -591,6 +592,12 @@ And it will print: } ``` +## Dealing with `BigInt`s + +> Advanced Section + +`comment-json` implements the TC39 proposal [proposal-json-parse-with-source](https://github.com/tc39/proposal-json-parse-with-source) + ## License [MIT](LICENSE) diff --git a/src/parse.js b/src/parse.js index 62aad48..015e3fa 100644 --- a/src/parse.js +++ b/src/parse.js @@ -83,9 +83,9 @@ const symbolFor = prefix => Symbol.for( : prefix ) -const transform = (k, v) => reviver - ? reviver(k, v) - : v +const transform = (k, {value, context = {}}) => reviver + ? reviver(k, value, context) + : value const unexpected = () => { const error = new SyntaxError(`Unexpected token '${current.value.slice(0, 1)}', "${current_code}" is not valid JSON`) @@ -370,12 +370,16 @@ function walk () { if (tt === CURLY_BRACKET_OPEN) { next() - return parse_object() + return { + value: parse_object() + } } if (tt === BRACKET_OPEN) { next() - return parse_array() + return { + value: parse_array() + } } let negative = EMPTY @@ -388,6 +392,7 @@ function walk () { } let v + let source switch (tt) { case 'String': @@ -396,8 +401,17 @@ function walk () { case 'Numeric': v = current.value next() - return JSON.parse(negative + v) + + source = negative + v + return { + value: JSON.parse(source), + context: { + source + } + } default: + // => unexpected token + return {} } } @@ -423,7 +437,7 @@ const parse = (code, rev, no_comments) => { parse_comments(PREFIX_BEFORE_ALL) - let result = walk() + const final = walk() parse_comments(PREFIX_AFTER_ALL) @@ -431,6 +445,11 @@ const parse = (code, rev, no_comments) => { unexpected() } + // reviver + let result = transform('', final) + + // We should run reviver before the checks below, + // otherwise the comment info will be lost if (!no_comments && result !== null) { if (!isObject(result)) { // 1 -> new Number(1) @@ -446,9 +465,6 @@ const parse = (code, rev, no_comments) => { restore_comments_host() - // reviver - result = transform('', result) - free() return result diff --git a/test/parse.test.js b/test/parse.test.js index da30a88..9bd4e00 100644 --- a/test/parse.test.js +++ b/test/parse.test.js @@ -312,6 +312,25 @@ g*/ //g2 t.is(l.value, 'l') t.is(l.inline, false) } + }, + { + d: 'reviver for BigInt', + s: `{ + // big int + "a":9007199254740999 +}`, + o: '{"a":9007199254740999}', + r: (k, v, {source}) => /^[0-9]+$/.test(source) ? BigInt(source) : v, + e (t, obj) { + // eslint-disable-next-line + t.is(obj.a, 9007199254740999n) + + const [comment] = obj[Symbol.for('before:a')] + + t.is(comment.value, ' big int') + t.is(comment.inline, false) + t.is(comment.type, 'LineComment') + } } ] @@ -320,12 +339,24 @@ cases.forEach(c => { ? test.only : test + // - d: description + // - r: reviver + // - s: source + // - o: equivalent JSON string of s with comments removed + // - e: the expect function to test the result tt(c.d, t => { - c.e(t, parser.parse(c.s)) + c.e( + t, + c.r + // reviver + ? parser.parse(c.s, c.r) + : parser.parse(c.s)) }) tt(`${c.d}, removes comments`, t => { - t.deepEqual(parser.parse(c.s, null, true), parser.parse(c.o)) + c.r + ? t.deepEqual(parser.parse(c.s, c.r, true), parser.parse(c.o, c.r)) + : t.deepEqual(parser.parse(c.s, null, true), parser.parse(c.o)) }) })