json-parser
Advanced tools
Comparing version 2.1.2 to 3.0.0
{ | ||
"name": "json-parser", | ||
"version": "2.1.2", | ||
"version": "3.0.0", | ||
"description": "JSON parser to parse JSON object and MAINTAIN comments.", | ||
@@ -56,4 +56,4 @@ "main": "src/index.js", | ||
"dependencies": { | ||
"esprima": "^4.0.1" | ||
"comment-json": "^2.0.4" | ||
} | ||
} |
206
README.md
@@ -8,11 +8,4 @@ [![Build Status](https://travis-ci.org/kaelzhang/node-json-parser.svg?branch=master)](https://travis-ci.org/kaelzhang/node-json-parser) | ||
This is a very low level module. For most situations, recommend to use [`comment-json`](https://www.npmjs.org/package/comment-json) instead. | ||
Since `3.0.0`, `json-parser` depends on `comment-json`, and directly use the `parse` method of `comment-json` | ||
## What's new in `2.x` | ||
`json-parser@2.x` brings some breaking changes by completely refactoring with [`Symbol`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol)s | ||
- supports comments everywhere, yes, **EVERYWHERE** in a JSON file, eventually 😆 | ||
- fixes the known issue about comments inside arrays. | ||
## Install | ||
@@ -35,201 +28,6 @@ | ||
- **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. | ||
- **remove_comments?** `boolean = false` If true, the parsed JSON Object won't contain comments | ||
For details, see [https://github.com/kaelzhang/node-comment-json#parse](https://github.com/kaelzhang/node-comment-json#parse) | ||
Returns `object | string | number | boolean | null` corresponding to the given JSON text. | ||
content | ||
```js | ||
/** | ||
before-all | ||
*/ | ||
// before-all | ||
{ | ||
// before | ||
/* before */ | ||
"foo" /* after-prop:foo */ : // after-comma:foo | ||
1 // after-value:foo | ||
// after-value:foo | ||
, // after-comma:foo | ||
// after-comma: foo | ||
"bar": [ // before | ||
// before | ||
"baz" // after-value:0 | ||
// after-value:0 | ||
, // after-comma: 0 | ||
"quux" | ||
// after-value:1 | ||
] // after-value:bar | ||
// after-value:bar | ||
} | ||
// after-all | ||
``` | ||
```js | ||
console.log(parse(content)) | ||
``` | ||
And the result will be: | ||
```js | ||
{ | ||
// Comments before the JSON object | ||
[Symbol.for('before-all')]: [{ | ||
type: 'BlockComment', | ||
value: '\n before-all\n ', | ||
inline: false | ||
}, { | ||
type: 'LineComment', | ||
value: ' before-all', | ||
inline: false | ||
}], | ||
... | ||
[Symbol.for('after-prop:foo')]: [{ | ||
type: 'BlockComment', | ||
value: ' after-prop:foo ', | ||
inline: true | ||
}], | ||
// The real value | ||
foo: 1, | ||
bar: [ | ||
"baz", | ||
"quux, | ||
// The property of the array | ||
[Symbol.for('after-value:0')]: [{ | ||
type: 'LineComment', | ||
value: ' after-value:0', | ||
inline: true | ||
}, ...], | ||
... | ||
] | ||
} | ||
``` | ||
There are **SEVEN** kinds of symbol properties: | ||
```js | ||
// comment tokens before the JSON object | ||
Symbol.for('before-all') | ||
// comment tokens before any properties/items inside an object/array | ||
Symbol.for('before') | ||
// comment tokens after property key `prop` and before colon(`:`) | ||
Symbol.for(`after-prop:${prop}`) | ||
// comment tokens after the colon(`:`) of property `prop` and before property value | ||
Symbol.for(`after-colon:${prop}`) | ||
// comment tokens after the value of property `prop`/the item of index `prop` | ||
// and before the key-value/item delimiter(`,`) | ||
Symbol.for(`after-value:${prop}`) | ||
// comment tokens after the comma of `prop`-value pair | ||
// and before the next key-value pair/item | ||
Symbol.for(`after-comma:${prop}`) | ||
// comment tokens after the JSON object | ||
Symbol.for('after-all') | ||
``` | ||
And the value of each symbol property is an **array** of `CommentToken` | ||
```ts | ||
interface CommentToken { | ||
type: 'BlockComment' | 'LineComment' | ||
// The content of the comment, including whitespaces and line breaks | ||
value: string | ||
// If the start location is the same line as the previous token, | ||
// then `inline` is `true` | ||
inline: boolean | ||
} | ||
``` | ||
### Parse into an object without comments | ||
```js | ||
console.log(parse(content, null, true)) | ||
``` | ||
And the result will be: | ||
```js | ||
{ | ||
foo: 1, | ||
bar: [ | ||
"baz", | ||
"quux" | ||
] | ||
} | ||
``` | ||
### Special cases | ||
```js | ||
const parsed = parse(` | ||
// comment | ||
1 | ||
`) | ||
console.log(parsed === 1) | ||
// false | ||
``` | ||
If we parse a JSON of primative type with `remove_comments:false`, then the return value of `parse()` will be of object type. | ||
The value of `parsed` is equivalent to: | ||
```js | ||
const parsed = new Number(1) | ||
parsed[Symbol.for('before-all')] = [{ | ||
type: 'LineComment', | ||
value: ' comment', | ||
inline: false | ||
}] | ||
``` | ||
Which is similar for: | ||
- `Boolean` type | ||
- `String` type | ||
For example | ||
```js | ||
const parsed = parse(` | ||
"foo" /* comment */ | ||
`) | ||
``` | ||
Which is equivalent to | ||
```js | ||
const parsed = new String('foo') | ||
parsed[Symbol.for('after-all')] = [{ | ||
type: 'BlockComment', | ||
value: ' comment ', | ||
inline: true | ||
}] | ||
``` | ||
But there is one exception: | ||
```js | ||
const parsed = parse(` | ||
// comment | ||
null | ||
`) | ||
console.log(parsed === null) // true | ||
``` | ||
## License | ||
MIT |
327
src/index.js
@@ -1,328 +0,3 @@ | ||
// JSON formatting | ||
const {parse, tokenize} = require('comment-json') | ||
const esprima = require('esprima') | ||
const tokenize = code => esprima.tokenize(code, { | ||
comment: true, | ||
loc: true | ||
}) | ||
const UNDEFINED = undefined | ||
const previous_hosts = [] | ||
let comments_host = null | ||
const previous_props = [] | ||
let last_prop | ||
let remove_comments = false | ||
let inline = false | ||
let tokens = null | ||
let last = null | ||
let current = null | ||
let index | ||
let reviver = null | ||
const clean = () => { | ||
previous_props.length = | ||
previous_hosts.length = 0 | ||
last = null | ||
last_prop = UNDEFINED | ||
} | ||
const free = () => { | ||
clean() | ||
tokens.length = 0 | ||
comments_host = | ||
tokens = | ||
last = | ||
current = | ||
reviver = null | ||
} | ||
const PREFIX_BEFORE_ALL = 'before-all' | ||
const PREFIX_BEFORE = 'before' | ||
const PREFIX_AFTER_PROP = 'after-prop' | ||
const PREFIX_AFTER_COLON = 'after-colon' | ||
const PREFIX_AFTER_COMMA = 'after-comma' | ||
const PREFIX_AFTER_VALUE = 'after-value' | ||
const PREFIX_AFTER_ALL = 'after-all' | ||
const symbolFor = prefix => Symbol.for( | ||
last_prop !== UNDEFINED | ||
? `${prefix}:${last_prop}` | ||
: prefix | ||
) | ||
const transform = (k, v) => reviver | ||
? reviver(k, v) | ||
: v | ||
const unexpected = () => { | ||
const error = new SyntaxError(`Unexpected token ${current.value.slice(0, 1)}`) | ||
Object.assign(error, current.loc.start) | ||
throw error | ||
} | ||
const unexpected_end = () => { | ||
const error = new SyntaxError('Unexpected end of JSON input') | ||
Object.assign(error, last | ||
? last.loc.end | ||
// Empty string | ||
: { | ||
line: 1, | ||
column: 0 | ||
}) | ||
throw error | ||
} | ||
const next = () => { | ||
const new_token = tokens[++ index] | ||
inline = current | ||
&& new_token | ||
&& current.loc.end.line === new_token.loc.start.line | ||
|| false | ||
last = current | ||
current = new_token | ||
} | ||
const type = () => { | ||
if (!current) { | ||
unexpected_end() | ||
} | ||
return current.type === 'Punctuator' | ||
? current.value | ||
: current.type | ||
} | ||
const is = t => type() === t | ||
const expect = a => { | ||
if (!is(a)) { | ||
unexpected() | ||
} | ||
} | ||
const set_comments_host = new_host => { | ||
previous_hosts.push(comments_host) | ||
comments_host = new_host | ||
} | ||
const restore_comments_host = () => { | ||
comments_host = previous_hosts.pop() | ||
} | ||
const parse_comments = prefix => { | ||
const comments = [] | ||
while ( | ||
current | ||
&& ( | ||
is('LineComment') | ||
|| is('BlockComment') | ||
) | ||
) { | ||
const comment = { | ||
...current, | ||
inline | ||
} | ||
delete comment.loc | ||
comments.push(comment) | ||
next() | ||
} | ||
if (remove_comments) { | ||
return | ||
} | ||
if (comments.length) { | ||
comments_host[symbolFor(prefix)] = comments | ||
} | ||
} | ||
const set_prop = (prop, push) => { | ||
if (push) { | ||
previous_props.push(last_prop) | ||
} | ||
last_prop = prop | ||
} | ||
const restore_prop = () => { | ||
last_prop = previous_props.pop() | ||
} | ||
const parse_object = () => { | ||
const obj = {} | ||
set_comments_host(obj) | ||
set_prop(UNDEFINED, true) | ||
let started | ||
let name | ||
parse_comments(PREFIX_BEFORE) | ||
while (!is('}')) { | ||
if (started) { | ||
// key-value pair delimiter | ||
expect(',') | ||
next() | ||
parse_comments(PREFIX_AFTER_COMMA) | ||
} | ||
started = true | ||
expect('String') | ||
name = JSON.parse(current.value) | ||
set_prop(name) | ||
next() | ||
parse_comments(PREFIX_AFTER_PROP) | ||
expect(':') | ||
next() | ||
parse_comments(PREFIX_AFTER_COLON) | ||
obj[name] = transform(name, walk()) | ||
parse_comments(PREFIX_AFTER_VALUE) | ||
} | ||
// bypass } | ||
next() | ||
last_prop = undefined | ||
restore_comments_host() | ||
restore_prop() | ||
return obj | ||
} | ||
const parse_array = () => { | ||
const array = [] | ||
set_comments_host(array) | ||
set_prop(UNDEFINED, true) | ||
let started | ||
let i = 0 | ||
parse_comments(PREFIX_BEFORE) | ||
while (!is(']')) { | ||
if (started) { | ||
expect(',') | ||
next() | ||
parse_comments(PREFIX_AFTER_COMMA) | ||
} | ||
started = true | ||
set_prop(i) | ||
array[i] = transform(i, walk()) | ||
parse_comments(PREFIX_AFTER_VALUE) | ||
i ++ | ||
} | ||
next() | ||
last_prop = undefined | ||
restore_comments_host() | ||
restore_prop() | ||
return array | ||
} | ||
function walk () { | ||
let tt = type() | ||
if (tt === '{') { | ||
next() | ||
return parse_object() | ||
} | ||
if (tt === '[') { | ||
next() | ||
return parse_array() | ||
} | ||
let negative = '' | ||
// -1 | ||
if (tt === '-') { | ||
next() | ||
tt = type() | ||
negative = '-' | ||
} | ||
let v | ||
switch (tt) { | ||
case 'String': | ||
case 'Boolean': | ||
case 'Null': | ||
case 'Numeric': | ||
v = current.value | ||
next() | ||
return JSON.parse(negative + v) | ||
default: | ||
} | ||
} | ||
const isObject = subject => Object(subject) === subject | ||
const parse = (code, rev, no_comments) => { | ||
clean() | ||
tokens = tokenize(code) | ||
reviver = rev | ||
remove_comments = no_comments | ||
if (!tokens.length) { | ||
unexpected_end() | ||
} | ||
index = - 1 | ||
next() | ||
set_comments_host({}) | ||
parse_comments(PREFIX_BEFORE_ALL) | ||
let result = walk() | ||
parse_comments(PREFIX_AFTER_ALL) | ||
if (current) { | ||
unexpected() | ||
} | ||
if (!no_comments && result !== null) { | ||
if (!isObject(result)) { | ||
// 1 -> new Number(1) | ||
// true -> new Boolean(1) | ||
// "foo" -> new String("foo") | ||
// eslint-disable-next-line no-new-object | ||
result = new Object(result) | ||
} | ||
Object.assign(result, comments_host) | ||
} | ||
restore_comments_host() | ||
// reviver | ||
result = transform('', result) | ||
free() | ||
return result | ||
} | ||
module.exports = { | ||
@@ -329,0 +4,0 @@ parse, |
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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Trivial Package
Supply chain riskPackages less than 10 lines of code are easily copied into your own project and may not warrant the additional supply chain risk of an external dependency.
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
3390
5
5
32
2
+ Addedcomment-json@^2.0.4
+ Addedcomment-json@2.4.2(transitive)
+ Addedcore-util-is@1.0.3(transitive)
+ Addedhas-own-prop@2.0.0(transitive)
+ Addedrepeat-string@1.6.1(transitive)
- Removedesprima@^4.0.1