json5-utils
Advanced tools
Comparing version 0.2.1 to 0.3.0
104
lib/parse.js
@@ -72,4 +72,24 @@ /* | ||
, position = 0 | ||
, opt_nullproto = options.duplicate_keys !== 'throw' && options.duplicate_keys !== 'ignore' | ||
var valueStart = function() {} | ||
var valueEnd = function(v) {return v} | ||
if (options._structure) { | ||
;(function() { | ||
var indent | ||
valueStart = function() { | ||
} | ||
valueEnd = function(v) { | ||
if (v === undefined) return undefined | ||
return { | ||
indent: indent, | ||
value: v, | ||
} | ||
} | ||
})() | ||
} | ||
function fail(msg) { | ||
@@ -117,9 +137,12 @@ var column = position - linestart | ||
} else if (chr === '"' || (chr === '\'' && !legacy)) { | ||
return parseString(chr) | ||
valueStart() | ||
return valueEnd(parseString(chr)) | ||
} else if (chr === '{') { | ||
return parseObject() | ||
valueStart() | ||
return valueEnd(parseObject()) | ||
} else if (chr === '[') { | ||
return parseArray() | ||
valueStart() | ||
return valueEnd(parseArray()) | ||
@@ -132,15 +155,19 @@ } else if (chr === '-' | ||
) { | ||
return parseNumber(is_key) | ||
valueStart() | ||
return valueEnd(parseNumber(is_key)) | ||
} else if (chr === 'n' && !is_key) { | ||
valueStart() | ||
parseKeyword('null') | ||
return null | ||
return valueEnd(null) | ||
} else if (chr === 't' && !is_key) { | ||
valueStart() | ||
parseKeyword('true') | ||
return true | ||
return valueEnd(true) | ||
} else if (chr === 'f' && !is_key) { | ||
valueStart() | ||
parseKeyword('false') | ||
return false | ||
return valueEnd(false) | ||
@@ -157,2 +184,3 @@ } else if (chr === '/' | ||
// unicode char or a unicode sequence | ||
valueStart() | ||
var rollback = position - 1 | ||
@@ -163,5 +191,5 @@ var result = parseIdentifier() | ||
position = rollback | ||
return | ||
return valueEnd(undefined) | ||
} else { | ||
return result | ||
return valueEnd(result) | ||
} | ||
@@ -217,3 +245,3 @@ | ||
function parseObject() { | ||
var result = opt_nullproto ? Object.create(null) : {} | ||
var result = options.null_prototype ? Object.create(null) : {} | ||
@@ -248,8 +276,14 @@ while (position < length) { | ||
} else { | ||
Object.defineProperty(result, item1, { | ||
value: item2, | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
}) | ||
if (typeof(options.reviver) === 'function') { | ||
item2 = options.reviver.call(null, item1, item2) | ||
} | ||
if (item2 !== undefined) { | ||
Object.defineProperty(result, item1, { | ||
value: item2, | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
}) | ||
} | ||
} | ||
@@ -291,3 +325,11 @@ | ||
if (item !== undefined) { | ||
result.push(item) | ||
if (typeof(options.reviver) === 'function') { | ||
item = options.reviver.call(null, String(result.length), item) | ||
} | ||
if (item === undefined) { | ||
result.length++ | ||
item = true // hack for check below, not included into result | ||
} else { | ||
result.push(item) | ||
} | ||
} | ||
@@ -499,2 +541,5 @@ | ||
if (result2 === undefined && position >= length) { | ||
if (typeof(options.reviver) === 'function') { | ||
result = options.reviver.call(null, '', result) | ||
} | ||
return result | ||
@@ -510,3 +555,20 @@ } else { | ||
/* | ||
* parse(text, options) | ||
* or | ||
* parse(text, reviver) | ||
* | ||
* where: | ||
* text - string | ||
* options - object | ||
* reviver - function | ||
*/ | ||
module.exports.parse = function parseJSON(input, options) { | ||
// support legacy functions | ||
if (typeof(options) === 'function') { | ||
options = { | ||
reviver: options | ||
} | ||
} | ||
if (input === undefined) { | ||
@@ -524,4 +586,10 @@ // parse(stringify(x)) should be equal x | ||
if (options.duplicate_keys === 'throw' || options.duplicate_keys === 'ignore') { | ||
if (options.null_prototype == null) { | ||
options.null_prototype = true | ||
} | ||
} | ||
return parse(input, options) | ||
} | ||
@@ -40,3 +40,3 @@ /* | ||
function _stringify(object, options, indentLvl, currentKey) { | ||
function _stringify(object, options, recursiveLvl, currentKey) { | ||
var opt_json = options.mode === 'json' | ||
@@ -64,3 +64,3 @@ /* | ||
var result = '' | ||
var count = indentLvl + (add || 0) | ||
var count = recursiveLvl + (add || 0) | ||
for (var i=0; i<count; i++) result += options.indent | ||
@@ -184,3 +184,3 @@ return result + str + (add ? '\n' : '') | ||
for (var i=0; i<object.length; i++) { | ||
var t = _stringify(object[i], options, indentLvl+1, String(i)) | ||
var t = _stringify(object[i], options, recursiveLvl+1, String(i)) | ||
if (t === undefined) t = 'null' | ||
@@ -193,11 +193,18 @@ len += t.length + 2 | ||
braces = '{}' | ||
for (var key in object) { | ||
if (!hasOwnProperty.call(object, key)) continue | ||
var t = _stringify(object[key], options, indentLvl+1, key) | ||
if (t !== undefined) { | ||
t = _stringify_key(key) + ':' + (options.indent ? ' ' : '') + t + ',' | ||
len += t.length + 1 | ||
result.push(t) | ||
var fn = function(key) { | ||
if (hasOwnProperty.call(object, key)) { | ||
var t = _stringify(object[key], options, recursiveLvl+1, key) | ||
if (t !== undefined) { | ||
t = _stringify_key(key) + ':' + (options.indent ? ' ' : '') + t + ',' | ||
len += t.length + 1 | ||
result.push(t) | ||
} | ||
} | ||
} | ||
if (Array.isArray(options.replacer)) { | ||
for (var i=0; i<options.replacer.length; i++) fn(options.replacer[i]) | ||
} else { | ||
for (var key in object) fn(key) | ||
} | ||
} | ||
@@ -208,3 +215,4 @@ | ||
// anything in the middle depends on indentation level | ||
if (options.indent && (len > 68 - indentLvl * 4 || len > 48) ) { | ||
len -= 2 | ||
if (options.indent && (len > options._splitMax - recursiveLvl * options.indent.length || len > options._splitMin) ) { | ||
// remove trailing comma in multiline if asked to | ||
@@ -234,2 +242,6 @@ if (options.no_trailing_comma && result.length) { | ||
function _stringify_nonobject(object) { | ||
if (typeof(options.replacer) === 'function') { | ||
object = options.replacer.call(null, currentKey, object) | ||
} | ||
switch(typeof(object)) { | ||
@@ -272,7 +284,8 @@ case 'string': | ||
if (typeof(object.toJSON5) === 'function' && options.mode !== 'json') { | ||
object = object.toJSON5(currentKey) | ||
var t | ||
if (typeof(t = object.toJSON5) === 'function' && options.mode !== 'json') { | ||
object = t.call(object, currentKey) | ||
} else if (typeof(object.toJSON) === 'function') { | ||
object = object.toJSON(currentKey) | ||
} else if (typeof(t = object.toJSON) === 'function') { | ||
object = t.call(object, currentKey) | ||
} | ||
@@ -292,2 +305,7 @@ | ||
} else { | ||
if (typeof(options.replacer) === 'function') { | ||
object = options.replacer.call(null, currentKey, object) | ||
if (typeof(object) !== 'object') return _stringify_nonobject(object) | ||
} | ||
return _stringify_object(object) | ||
@@ -300,4 +318,26 @@ } | ||
module.exports.stringify = function stringifyJSON(object, options) { | ||
if (options == null) options = {} | ||
/* | ||
* stringify(value, options) | ||
* or | ||
* stringify(value, replacer, space) | ||
* | ||
* where: | ||
* value - anything | ||
* options - object | ||
* replacer - function or array | ||
* space - boolean or number or string | ||
*/ | ||
module.exports.stringify = function stringifyJSON(object, options, _space) { | ||
// support legacy syntax | ||
if (typeof(options) === 'function' || Array.isArray(options)) { | ||
options = { | ||
replacer: options | ||
} | ||
} else if (typeof(options) === 'object') { | ||
// nothing to do | ||
} else { | ||
options = {} | ||
} | ||
if (_space != null) options.indent = _space | ||
if (options.indent == null) options.indent = '\t' | ||
@@ -319,6 +359,16 @@ if (options.quote == null) options.quote = "'" | ||
// why would anyone use such objects? | ||
if (typeof(options.indent) === 'object') { | ||
if (options.indent.constructor === Number | ||
|| options.indent.constructor === Boolean | ||
|| options.indent.constructor === String) | ||
options.indent = options.indent.valueOf() | ||
} | ||
// gap is capped at 10 characters | ||
if (typeof(options.indent) === 'number') { | ||
if (options.indent >= 0 && options.indent < 11) { | ||
options.indent = Array(~~options.indent+1).join(' ') | ||
if (options.indent >= 0) { | ||
options.indent = Array(Math.min(~~options.indent, 10) + 1).join(' ') | ||
} else { | ||
options.indent = false | ||
} | ||
@@ -329,4 +379,7 @@ } else if (typeof(options.indent) === 'string') { | ||
if (options._splitMin == null) options._splitMin = 50 | ||
if (options._splitMax == null) options._splitMax = 70 | ||
return _stringify(object, options, 0, '') | ||
} | ||
@@ -1,1 +0,1 @@ | ||
{"name":"json5-utils","version":"0.2.1","description":"JSON and JSON5 parsers done right","author":{"name":"Alex Kocharin","email":"alex@kocharin.ru"},"repository":{"type":"git","url":"git://github.com/rlidwka/json5-utils"},"bugs":{"url":"https://github.com/rlidwka/json5-utils/issues"},"main":"parse.js","scripts":{"test":"node test/test.js"},"keywords":["json","json5","parser"],"license":"WTFPL"} | ||
{"name":"json5-utils","version":"0.3.0","description":"JSON/JSON5 parser and serializer","author":{"name":"Alex Kocharin","email":"alex@kocharin.ru"},"repository":{"type":"git","url":"git://github.com/rlidwka/json5-utils"},"bugs":{"url":"https://github.com/rlidwka/json5-utils/issues"},"main":"parse.js","scripts":{"test":"node test/test.js"},"keywords":["json","json5","parser"],"license":"WTFPL"} |
187
README.md
@@ -0,2 +1,154 @@ | ||
JSON5-utils - JSON/JSON5 parser and serializer for JavaScript. | ||
## Installation | ||
``` | ||
npm install json5-utils | ||
``` | ||
## Usage | ||
```javascript | ||
var JSON5 = require('json5-utils') | ||
``` | ||
### JSON5.parse() function | ||
```javascript | ||
/* | ||
* Main syntax: | ||
* | ||
* `text` - text to parse, type: String | ||
* `options` - parser options, type: Object | ||
*/ | ||
JSON5.parse(text[, options]) | ||
// compatibility syntax | ||
JSON5.parse(text[, reviver]) | ||
``` | ||
Options: | ||
- duplicate\_keys - what to do with duplicate keys (String, default="throw") | ||
- "ignore" - ignore duplicate keys, including inherited ones | ||
- "throw" - throw SyntaxError in case of duplicate keys, including inherited ones | ||
- "replace" - replace duplicate keys, this is the default JSON.parse behaviour, unsafe | ||
```javascript | ||
// 'ignore' will cause duplicated keys to be ignored: | ||
parse('{q: 1, q: 2}', {duplicate_keys: 'ignore'}) == {q: 1} | ||
parse('{hasOwnProperty: 1}', {duplicate_keys: 'ignore'}) == {} | ||
parse('{hasOwnProperty: 1, x: 2}', {duplicate_keys: 'ignore'}).hasOwnProperty('x') == true | ||
// 'throw' will cause SyntaxError in these cases: | ||
parse('{q: 1, q: 2}', {duplicate_keys: 'throw'}) == SyntaxError | ||
parse('{hasOwnProperty: 1}', {duplicate_keys: 'throw'}) == SyntaxError | ||
// 'replace' will replace duplicated keys with new ones: | ||
parse('{q: 1, q: 2}', {duplicate_keys: 'throw'}) == {q: 2} | ||
parse('{hasOwnProperty: 1}', {duplicate_keys: 'throw'}) == {hasOwnProperty: 1} | ||
parse('{hasOwnProperty: 1, x: 2}', {duplicate_keys: 'ignore'}).hasOwnProperty('x') == TypeError | ||
``` | ||
- null\_prototype - create object as Object.create(null) instead of '{}' (Boolean) | ||
if `duplicate_keys != 'replace'`, default is **false** | ||
if `duplicate_keys == 'replace'`, default is **true** | ||
It is usually unsafe and not recommended to change this option to false in the last case. | ||
- reviver - reviver function - Function | ||
This function should follow JSON specification | ||
### JSON5.stringify() function | ||
```javascript | ||
/* | ||
* Main syntax: | ||
* | ||
* `value` - value to serialize, type: * | ||
* `options` - serializer options, type: Object | ||
*/ | ||
JSON5.stringify(value[, options]) | ||
// compatibility syntax | ||
JSON5.stringify(value[, replacer [, indent]) | ||
``` | ||
Options: | ||
- ascii - output ascii only (Boolean, default=false) | ||
If this option is enabled, output will not have any characters except of 0x20-0x7f. | ||
- indent - indentation (String, Number or Boolean, default='\t') | ||
This option follows JSON specification. | ||
- quote - enquoting char (String, "'" or '"', default="'") | ||
- quote\_keys - whether keys quoting in objects is required or not (String, default=false) | ||
If you want `{"q": 1}` instead of `{q: 1}`, set it to true. | ||
- replacer - replacer function or array (Function or Array) | ||
This option follows JSON specification. | ||
- no\_trailing\_comma = don't output trailing comma (Boolean, default=false) | ||
If this option is set, arrays like this `[1,2,3,]` will never be generated. Otherwise they may be generated for pretty printing. | ||
- mode - operation mode, set it to 'json' if you want correct json in the output (String) | ||
Currently it's either 'json' or something else. If it is 'json', following options are implied: | ||
- options.quote = '"' | ||
- options.no\_trailing\_comma = true | ||
- options.quote\_keys = true | ||
- '\x' literals are not used | ||
## Advantages over existing JSON libraries | ||
In a few cases it makes sense to use this module instead of built-in JSON methods. | ||
Parser: | ||
- better error reporting with source code and line numbers | ||
In case of syntax error, JSON.parse does not return any good information to the user. This module does: | ||
``` | ||
$ node -e 'require("json5-utils").parse("[1,1,1,1,invalid]")' | ||
SyntaxError: Unexpected token 'i' at 0:9 | ||
[1,1,1,1,invalid] | ||
^ | ||
``` | ||
This module is about 5 times slower, so if user experience matters to you more than performance, use this module. If you're working with a lot of machine-generated data, use JSON.parse instead. | ||
Stringifier: | ||
- util.inspect-like pretty printing | ||
This module behaves more smart when dealing with object and arrays, and does not always print newlines in them: | ||
``` | ||
$ node -e 'console.log(require("./").stringify([[,,,],,,[,,,,]], {mode:"json"}))' | ||
[ | ||
[null, null, null], | ||
null, | ||
null, | ||
[null, null, null, null] | ||
] | ||
``` | ||
JSON.stringify will split this into 15 lines, and it's hard to read. | ||
Yet again, this feature comes with a performance hit, so if user experience matters to you more than performance, use this module. If your JSON will be consumed by machines, use JSON.stringify instead. | ||
As a rule of thumb, if you use "space" argument to indent your JSON, you'd better use this module instead. | ||
## JSON5 syntax | ||
I support slighly modified version of JSON5, see https://groups.google.com/forum/#!topic/json5/3DjClVYI6Wg | ||
I started from ES5 specification and added a set of additional restrictions on top of ES5 spec. So I'd expect my implementation to be much closer to javascript. It's no longer an extension of json, but a reduction of ecmascript, which was my original intent. | ||
This JSON5 version is a subset of ES5 language, specification is here: | ||
@@ -20,2 +172,3 @@ | ||
If you're unsure whether a behaviour of this library is a bug or not, you can run this test: | ||
@@ -35,32 +188,7 @@ | ||
## Options | ||
parse(string, options): | ||
## Weirdness of JSON5 | ||
stringify(object, options): | ||
- ascii - output ascii only (bool, default=false) | ||
- indent - indentation (string or number, default='\t') | ||
- quote - enquoting char (string, "'" or '"', default="'") | ||
These are the parts that I don't particulary like, but see no good way to fix: | ||
## Modes of operation | ||
TODO: not yet working | ||
- simple: | ||
no custom datatypes | ||
no .toJSON/.toJSON5 | ||
for serializing simple json-compatible objects only (i.e. user-generated data) | ||
- full | ||
custom datatypes | ||
.toJSON/.toJSON5 present (prototype only) | ||
for representing arbitrary data structures as close as possible | ||
- json: | ||
no custom datatypes | ||
.toJSON present | ||
json compatible | ||
## Weirdness of JSON5 | ||
- no elisions, `[,,,] -> [null,null,null]` | ||
@@ -70,5 +198,9 @@ - `[Object], [Circular]` aren't parsed | ||
- unicode property names are way to hard to implement | ||
- Date and other custom objects | ||
- incompatible with YAML (at least comments) | ||
## Unicode support | ||
This version fully support unicode. But if you're writing JSON5 implementation and don't want to support it, you should follow these guidelines: | ||
```javascript | ||
@@ -88,1 +220,2 @@ // 27 whitespace characters (unicode defines 26 characters, and ES5 spec also adds \uFEFF as a whitespace) | ||
@@ -70,1 +70,4 @@ | ||
assert.equal(expected, stringify(array, {indent: false})) | ||
assert.strictEqual(stringify([1,2,3], function(){}), undefined) | ||
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
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
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
77038
18
1279
218