relaxed-json
Advanced tools
Comparing version 0.1.1 to 0.2.0
@@ -7,14 +7,21 @@ "use strict"; | ||
grunt.initConfig({ | ||
pkg: grunt.file.readJSON("package.json"), | ||
jshint: { | ||
options: { | ||
jshintrc: ".jshintrc" | ||
jshintrc: ".jshintrc", | ||
}, | ||
gruntfile: { | ||
src: "Gruntfile.js" | ||
src: "Gruntfile.js", | ||
}, | ||
src: { | ||
src: "relaxed-json.js" | ||
src: "relaxed-json.js", | ||
options: { | ||
node: false, | ||
}, | ||
}, | ||
test: { | ||
src: "test/**/*.js", | ||
}, | ||
webcli: { | ||
src: "web/cli.js", | ||
src: "web.js", | ||
options: { | ||
@@ -26,2 +33,63 @@ browser: true, | ||
}, | ||
simplemocha: { | ||
options: { | ||
timeout: 3000, | ||
ui: "bdd", | ||
reporter: "spec" | ||
}, | ||
all: { src: "test/**/*.js" } | ||
}, | ||
uglify: { | ||
core: { | ||
src: "relaxed-json.js", | ||
dest: "relaxed-json.min.js", | ||
options: { | ||
sourceMap: "relaxed-json.min.js.map", | ||
report: "min", | ||
}, | ||
}, | ||
web: { | ||
src: [ | ||
"components/codemirror.js", | ||
"components/cm-mode-javascript.js", | ||
"components/jquery-2.0.3.js", | ||
"relaxed-json.js", | ||
"web.js", | ||
], | ||
dest: "web.min.js", | ||
options: { | ||
sourceMap: "web.min.js.map", | ||
report: "min", | ||
}, | ||
} | ||
}, | ||
less: { | ||
web: { | ||
src: [ | ||
"web.less", | ||
], | ||
dest: "web.min.css", | ||
options: { | ||
report: "min", | ||
compress: true, | ||
strictMath: true, | ||
strictImports: true, | ||
strictUnits: true, | ||
syncImport: true, | ||
sourceMap: true, | ||
sourceMapFilename: "web.min.css.map", | ||
} | ||
}, | ||
}, | ||
watch: { | ||
less: { | ||
files: "<%= less.web.src %>", | ||
tasks: ["less"], | ||
}, | ||
uglify: { | ||
files: "<%= uglify.web.src %>", | ||
tasks: ["uglify:web"], | ||
}, | ||
}, | ||
}); | ||
@@ -31,5 +99,9 @@ | ||
grunt.loadNpmTasks("grunt-contrib-jshint"); | ||
grunt.loadNpmTasks("grunt-contrib-uglify"); | ||
grunt.loadNpmTasks("grunt-contrib-less"); | ||
grunt.loadNpmTasks("grunt-contrib-watch"); | ||
grunt.loadNpmTasks("grunt-simple-mocha"); | ||
// Default task. | ||
grunt.registerTask("default", ["jshint"]); | ||
grunt.registerTask("default", ["jshint", "simplemocha"]); | ||
}; |
{ | ||
"name": "relaxed-json", | ||
"description": "relaxed JSON is strict superset JSON, relaxing strictness of JSON format", | ||
"version": "0.1.1", | ||
"description": "Relaxed JSON is strict superset JSON, relaxing strictness of valilla JSON", | ||
"version": "0.2.0", | ||
"homepage": "https://github.com/phadej/relaxed-json", | ||
@@ -33,3 +33,9 @@ "author": { | ||
"grunt-contrib-jshint": "~0.6.4", | ||
"grunt": "~0.4.1" | ||
"grunt": "~0.4.1", | ||
"grunt-contrib-watch": "~0.5.3", | ||
"grunt-contrib-uglify": "~0.2.5", | ||
"grunt-contrib-less": "~0.8.1", | ||
"grunt-simple-mocha": "~0.4.0", | ||
"jsverify": "~0.1.2", | ||
"underscore": "~1.5.2" | ||
}, | ||
@@ -36,0 +42,0 @@ "keywords": [ |
# Relaxed JSON | ||
Are you frustrated that you cannot add comments into your example JSON | ||
structure or easily strip them? Relaxed JSON is a simple solution. Small | ||
JavaScript library with only one exposed function `EJSON.transform /* string → string */`. | ||
[![Build Status](https://secure.travis-ci.org/phadej/jsverify.png?branch=master)](http://travis-ci.org/phadej/jsverify) | ||
[![NPM version](https://badge.fury.io/js/relaxed-json.png)](http://badge.fury.io/js/relaxed-json) | ||
[Relaxed JSON](http://oleg.fi/relaxed-json) (modified BSD license) is a strict superset of JSON. Valid JSON | ||
will not be changed by `RJSON.transform`. But in addition there are few | ||
extensions helping writing JSON by hand. | ||
Are you frustrated that you cannot add comments into your configuration JSON | ||
Relaxed JSON is a simple solution. | ||
Small JavaScript library with only one exposed function `RJSON.transform(text : string) : string` | ||
(and few convenient helpers). | ||
[Relaxed JSON](http://oleg.fi/relaxed-json) (modified BSD license) is a strict superset of JSON, | ||
relaxing strictness of valilla JSON. | ||
Valid, vanilla JSON will not be changed by `RJSON.transform`. But there are few additional | ||
features helping writing JSON by hand. | ||
* Comments are stripped : `// foo` and `/* bar */` → ` `. | ||
@@ -16,3 +21,3 @@ Comments are converted into whitespace, so your formatting is preserved. | ||
* Single quoted strings are allowed : `'say "Hello"'` → `"say \"Hello\""`. | ||
* More different characters is supported for identifiers: `foo-bar` → `"foo-bar"`. | ||
* More different characters is supported in identifiers: `foo-bar` → `"foo-bar"`. | ||
@@ -23,5 +28,8 @@ ## API | ||
Transforms Relaxed JSON text into JSON text. Doesn't verify (parse) the JSON, i.e result JSON might be invalid as well | ||
- `RJSON.parse(text : string, reviver : function) : obj`. | ||
- `RJSON.parse(text : string, reviver : function | opts : obj) : obj`. | ||
Parse the RJSON text, virtually `JSON.parse(JSON.transform(text), reviver)`. | ||
- `RJSON.parse2(text : string, reviver : function) : obj`. | ||
This is self-made parser function, which should act as `RJSON.parse`, but provides better error messages. | ||
You could pass a reviver function or an options object as the second argument. Supported options: | ||
- `reviver`: you could still pass a reviver | ||
- `relaxed`: use relaxed version of JSON (default: true) | ||
- `warnings`: use relaxed JSON own parser, supports better error messages (default: false). | ||
- `duplicate`: fail if there are duplicate keys in objects |
@@ -35,7 +35,3 @@ /* | ||
function some(array, f) { | ||
if (array.length === 0) { | ||
return false; | ||
} | ||
var acc; | ||
var acc = false; | ||
for (var i = 0; i < array.length; i++) { | ||
@@ -91,3 +87,3 @@ acc = f(array[i], i, array); | ||
var tokenSpecs = (function (){ | ||
function tokenSpecs(relaxed) { | ||
function f(type) { | ||
@@ -101,3 +97,3 @@ return function(m) { | ||
// String in single quotes | ||
var content = m[1].replace(/([^'\\]|\\['bnrt\\]|\\u[0-9a-fA-F]{4})/g, function (m) { | ||
var content = m[1].replace(/([^'\\]|\\['bnrtf\\]|\\u[0-9a-fA-F]{4})/g, function (m) { | ||
if (m === "\"") { | ||
@@ -152,3 +148,17 @@ return "\\\""; | ||
return [ | ||
function fKeyword(m) { | ||
var value; | ||
switch (m[1]) { | ||
case "null": value = null; break; | ||
case "true": value = true; break; | ||
case "false": value = false; break; | ||
} | ||
return { | ||
type: "atom", | ||
match: m[0], | ||
value: value, | ||
}; | ||
} | ||
var ret = [ | ||
{ re: /^\s+/, f: f(" ") }, | ||
@@ -161,15 +171,23 @@ { re: /^\{/, f: f("{") }, | ||
{ re: /^:/, f: f(":") }, | ||
{ re: /^(true|false|null)/, f: f("keyword") }, | ||
{ re: /^(true|false|null)/, f: fKeyword }, | ||
{ re: /^\-?\d+(\.\d+)?([eE][+-]?\d+)?/, f: fNumber }, | ||
{ re: /^"([^"\\]|\\["bnrt\\]|\\u[0-9a-fA-F]{4})*"/, f: fStringDouble }, | ||
// additional stuff | ||
{ re: /^'(([^'\\]|\\['bnrt\\]|\\u[0-9a-fA-F]{4})*)'/, f: fStringSingle }, | ||
{ re: /^\/\/.*?\n/, f: fComment }, | ||
{ re: /^\/\*[\s\S]*?\*\//, f: fComment }, | ||
{ re: /^[a-zA-Z0-9_\-+\.\*\?!\|&%\^\/#\\]+/, f: fIdentifier }, | ||
{ re: /^"([^"\\]|\\["bnrtf\\]|\\u[0-9a-fA-F]{4})*"/, f: fStringDouble }, | ||
]; | ||
}()); | ||
var lexer = makeLexer(tokenSpecs); | ||
// additional stuff | ||
if (relaxed) { | ||
ret = ret.concat([ | ||
{ re: /^'(([^'\\]|\\['bnrtf\\]|\\u[0-9a-fA-F]{4})*)'/, f: fStringSingle }, | ||
{ re: /^\/\/.*?\n/, f: fComment }, | ||
{ re: /^\/\*[\s\S]*?\*\//, f: fComment }, | ||
{ re: /^[a-zA-Z0-9_\-+\.\*\?!\|&%\^\/#\\]+/, f: fIdentifier }, | ||
]); | ||
} | ||
return ret; | ||
} | ||
var lexer = makeLexer(tokenSpecs(true)); | ||
var strictLexer = makeLexer(tokenSpecs(false)); | ||
function transformTokens(tokens) { | ||
@@ -216,21 +234,92 @@ return tokens.reduce(function (tokens, token) { | ||
function parse(text, reviver) { | ||
return JSON.parse(transform(text), reviver); | ||
} | ||
function popToken(tokens, state) { | ||
var token = tokens[state.pos]; | ||
state.pos += 1; | ||
function popToken(tokens) { | ||
var token = tokens.shift(); | ||
if (!token) { | ||
var err = new SyntaxError("Unexpected end-of-file"); | ||
throw err; | ||
var line = tokens.length !== 0 ? tokens[tokens.length - 1].line : 1; | ||
return { type: "eof", line: line }; | ||
} | ||
return token; | ||
} | ||
function parseObject(tokens, reviver) { | ||
var token = popToken(tokens); | ||
function strToken(token) { | ||
switch (token.type) { | ||
case "atom": | ||
case "string": | ||
case "number": | ||
return token.type + " " + token.match; | ||
case "eof": | ||
return "end-of-file"; | ||
default: | ||
return token.type; | ||
} | ||
} | ||
function skipColon(tokens, state) { | ||
var colon = popToken(tokens, state); | ||
if (colon.type !== ":") { | ||
var message = "Unexpected token: " + strToken(colon) + ", expected ':'"; | ||
if (state.tolerant) { | ||
state.warnings.push({ | ||
message: message, | ||
line: colon.line, | ||
}); | ||
state.pos -= 1; | ||
} else { | ||
var err = new SyntaxError(message); | ||
err.line = colon.line; | ||
throw err; | ||
} | ||
} | ||
} | ||
function parseObject(tokens, state) { | ||
var token = popToken(tokens, state); | ||
var obj = {}; | ||
var key, colon, value; | ||
var key, value; | ||
var err; | ||
var message; | ||
if (token.type !== "}" && token.type !== "string") { | ||
message = "Unexpected token: " + strToken(token) + ", expected '}' or string"; | ||
if (state.tolerant) { | ||
state.warnings.push({ | ||
message: message, | ||
line: token.line, | ||
}); | ||
if (token.type !== "eof" && token.type !== "number" && token.type !== "atom") { | ||
state.pos -= 1; | ||
} | ||
if (token.type === "number" || token.type === "atom") { | ||
token = { | ||
type: "string", | ||
value: ""+token.value, | ||
line: token.line, | ||
}; | ||
} else if (token.type === "eof") { | ||
token = { | ||
type: "}", | ||
line: token.line, | ||
}; | ||
} else { | ||
token = { | ||
type: "string", | ||
value: "null", | ||
line: token.line, | ||
}; | ||
} | ||
} else { | ||
err = new SyntaxError(message); | ||
err.line = token.line; | ||
throw err; | ||
} | ||
} | ||
switch (token.type) { | ||
@@ -242,11 +331,6 @@ case "}": | ||
key = token.value; | ||
colon = popToken(tokens); | ||
if (colon.type !== ":") { | ||
err = new SyntaxError("Unexpected token: " + colon.type + ", expected colon"); | ||
err.line = token.line; | ||
throw err; | ||
} | ||
value = parseAny(tokens, reviver); | ||
skipColon(tokens, state); | ||
value = parseAny(tokens, state); | ||
value = reviver ? reviver(key, value) : value; | ||
value = state.reviver ? state.reviver(key, value) : value; | ||
if (value !== undefined) { | ||
@@ -256,7 +340,2 @@ obj[key] = value; | ||
break; | ||
default: | ||
err = new SyntaxError("Unexpected token: " + token.type + ", expected string or }"); | ||
err.line = token.line; | ||
throw err; | ||
} | ||
@@ -266,4 +345,26 @@ | ||
while (true) { | ||
token = popToken(tokens); | ||
token = popToken(tokens, state); | ||
if (token.type !== "}" && token.type !== ",") { | ||
message = "Unexpected token: " + strToken(token) + ", expected ',' or ']'"; | ||
var newtype = token.type === "eof" ? "}" : ","; | ||
if (state.tolerant) { | ||
state.warnings.push({ | ||
message: message + "; assuming '" + newtype + "'", | ||
line: token.line, | ||
}); | ||
token = { | ||
type: newtype, | ||
line: token.line, | ||
}; | ||
state.pos -= 1; | ||
} else { | ||
err = new SyntaxError(message); | ||
err.line = token.line; | ||
throw err; | ||
} | ||
} | ||
switch (token.type) { | ||
@@ -274,18 +375,53 @@ case "}": | ||
case ",": | ||
token = popToken(tokens); | ||
token = popToken(tokens, state); | ||
if (token.type !== "string") { | ||
err = new SyntaxError("Unexpected token: " + token.type + ", expected string"); | ||
err.line = token.line; | ||
throw err; | ||
message = "Unexpected token: " + strToken(token) + ", expected string"; | ||
if (state.tolerant) { | ||
state.warnings.push({ | ||
message: message, | ||
line: token.line, | ||
}); | ||
if (token.type === "number" || token.type === "atom") { | ||
token = { | ||
type: "string", | ||
value: "" + token.value, | ||
line: token.line, | ||
}; | ||
} else { | ||
token = { | ||
type: "string", | ||
value: "null", | ||
line: token.line, | ||
}; | ||
state.pos -= 1; | ||
} | ||
} else { | ||
err = new SyntaxError(message); | ||
err.line = token.line; | ||
throw err; | ||
} | ||
} | ||
key = token.value; | ||
colon = popToken(tokens); | ||
if (colon.type !== ":") { | ||
err = new SyntaxError("Unexpected token: " + colon.type + ", expected colon"); | ||
err.line = token.line; | ||
throw err; | ||
if (state.duplicate && Object.prototype.hasOwnProperty.call(obj, key)) { | ||
message = "Duplicate key: " + key; | ||
if (state.tolerant) { | ||
state.warnings.push({ | ||
message: message, | ||
line: token.line, | ||
}); | ||
} else { | ||
err = new SyntaxError(message); | ||
err.line = token.line; | ||
throw err; | ||
} | ||
} | ||
value = parseAny(tokens, reviver); | ||
value = reviver ? reviver(key, value) : value; | ||
skipColon(tokens, state); | ||
value = parseAny(tokens, state); | ||
value = state.reviver ? state.reviver(key, value) : value; | ||
if (value !== undefined) { | ||
@@ -295,7 +431,2 @@ obj[key] = value; | ||
break; | ||
default: | ||
err = new SyntaxError("Unexpected token: " + token.type + ", expected , or }"); | ||
err.line = token.line; | ||
throw err; | ||
} | ||
@@ -305,8 +436,17 @@ } | ||
function parseArray(tokens, reviver) { | ||
var token = popToken(tokens); | ||
function parseArray(tokens, state) { | ||
var token = popToken(tokens, state); | ||
var arr = []; | ||
var key = 0, value; | ||
var err; | ||
var message; | ||
if (state.tolerant && token.type === "eof") { | ||
state.warnings.push({ | ||
message: "Unexpected token: " + strToken(token) + ", expected ']' or json object", | ||
line: token.line, | ||
}); | ||
token.type = "]"; | ||
} | ||
switch (token.type) { | ||
@@ -317,6 +457,6 @@ case "]": | ||
default: | ||
tokens.unshift(token); | ||
value = parseAny(tokens, reviver); | ||
state.pos -= 1; // push the token back | ||
value = parseAny(tokens, state); | ||
arr[key] = reviver ? reviver(key, value) : value; | ||
arr[key] = state.reviver ? state.reviver("" + key, value) : value; | ||
break; | ||
@@ -327,4 +467,26 @@ } | ||
while (true) { | ||
token = popToken(tokens); | ||
token = popToken(tokens, state); | ||
if (token.type !== "]" && token.type !== ",") { | ||
message = "Unexpected token: " + strToken(token) + ", expected ',' or ']'"; | ||
var newtype = token.type === "eof" ? "]" : ","; | ||
if (state.tolerant) { | ||
state.warnings.push({ | ||
message: message + "; assuming '" + newtype + "'", | ||
line: token.line, | ||
}); | ||
token = { | ||
type: newtype, | ||
line: token.line, | ||
}; | ||
state.pos -= 1; | ||
} else { | ||
err = new SyntaxError(message); | ||
err.line = token.line; | ||
throw err; | ||
} | ||
} | ||
switch (token.type) { | ||
@@ -336,10 +498,5 @@ case "]": | ||
key += 1; | ||
value = parseAny(tokens, reviver); | ||
arr[key] = reviver ? reviver(key, value) : value; | ||
value = parseAny(tokens, state); | ||
arr[key] = state.reviver ? state.reviver("" + key, value) : value; | ||
break; | ||
default: | ||
err = new SyntaxError("Unexpected token: " + token.type + ", expected , or }"); | ||
err.line = token.line; | ||
throw err; | ||
} | ||
@@ -349,36 +506,60 @@ } | ||
function parseAny(tokens, reviver, end) { | ||
var token = popToken(tokens); | ||
function parseAny(tokens, state, end) { | ||
var token = popToken(tokens, state); | ||
var ret; | ||
var err; | ||
var message; | ||
switch (token.type) { | ||
case "{": | ||
ret = parseObject(tokens, reviver); | ||
ret = reviver ? reviver("", ret) : ret; | ||
ret = parseObject(tokens, state); | ||
break; | ||
case "[": | ||
ret = parseArray(tokens, reviver); | ||
ret = reviver ? reviver("", ret) : ret; | ||
ret = parseArray(tokens, state); | ||
break; | ||
case "string": | ||
case "number": | ||
case "atom": | ||
ret = token.value; | ||
break; | ||
case "keyword": | ||
switch (token.match) { | ||
case "null": ret = null; break; | ||
case "true": ret = true; break; | ||
case "false": ret = false; break; | ||
default: | ||
message = "Unexpected token: " + strToken(token) + ", expected '[', '{', number, string or atom"; | ||
if (state.tolerant) { | ||
state.warnings.push({ | ||
message: message + "; assuming null", | ||
line: token.line, | ||
}); | ||
ret = null; | ||
} else { | ||
err = new SyntaxError(); | ||
err.line = token.line; | ||
throw err; | ||
} | ||
break; | ||
default: | ||
err = new SyntaxError("Unexpected token: " + token.type); | ||
err.line = token.line; | ||
throw err; | ||
} | ||
if (end && tokens.length !== 0) { | ||
err = new SyntaxError("Unexpected token: " + tokens[0].type + ", expected end-of-input"); | ||
err.line = tokens[0].line; | ||
if (end) { | ||
ret = state.reviver ? state.reviver("", ret) : ret; | ||
} | ||
if (end && state.pos < tokens.length) { | ||
message = "Unexpected token: " + strToken(tokens[state.pos]) + ", expected end-of-input"; | ||
if (state.tolerant) { | ||
state.warnings.push({ | ||
message: message, | ||
line: tokens[state.pos].line, | ||
}); | ||
} else { | ||
err = new SyntaxError(message); | ||
err.line = tokens[state.pos].line; | ||
throw err; | ||
} | ||
} | ||
// Throw error at the end | ||
if (end && state.tolerant && state.warnings.length !== 0) { | ||
message = state.warnings.length === 1 ? state.warnings[0].message : state.warnings.length + " parse warnings"; | ||
err = new SyntaxError(message); | ||
err.line = state.warnings[0].line; | ||
err.warnings = state.warnings; | ||
err.obj = ret; | ||
throw err; | ||
@@ -390,35 +571,54 @@ } | ||
function parse2(text, reviver) { | ||
// Tokenize contents | ||
var tokens = lexer(text); | ||
function parse(text, opts) { | ||
if (typeof opts === "function" || opts === undefined) { | ||
return JSON.parse(transform(text), opts); | ||
} else if (new Object(opts) !== opts) { | ||
throw new TypeError("opts/reviver should be undefined, a function or an object"); | ||
} | ||
// remove trailing commas | ||
tokens = transformTokens(tokens); | ||
opts.relaxed = opts.relaxed !== undefined ? opts.relaxed : true; | ||
opts.warnings = opts.warnings || opts.tolerant || false; | ||
opts.tolerant = opts.tolerant || false; | ||
opts.duplicate = opts.duplicate || false; | ||
tokens = tokens.filter(function (token) { | ||
return token.type !== " "; | ||
}); | ||
if (!opts.warnings && !opts.relaxed) { | ||
return JSON.parse(text, opts.reviver); | ||
} | ||
// concat stuff | ||
return parseAny(tokens, reviver, true); | ||
var tokens = opts.relaxed ? lexer(text) : strictLexer(text); | ||
if (opts.relaxed) { | ||
// Strip commas | ||
tokens = transformTokens(tokens); | ||
} | ||
if (opts.warnings) { | ||
// Strip whitespace | ||
tokens = tokens.filter(function (token) { | ||
return token.type !== " "; | ||
}); | ||
var state = { pos: 0, reviver: opts.reviver, tolerant: opts.tolerant, duplicate: opts.duplicate, warnings: [] }; | ||
return parseAny(tokens, state, true); | ||
} else { | ||
var newtext = tokens.reduce(function (str, token) { | ||
return str + token.match; | ||
}, ""); | ||
return JSON.parse(newtext, opts.reviver); | ||
} | ||
} | ||
// Export stuff | ||
var module = { | ||
var RJSON = { | ||
transform: transform, | ||
parse: parse, | ||
parse2: parse2, | ||
}; | ||
/* global window, exports */ | ||
/* global window, module */ | ||
if (typeof window !== "undefined") { | ||
window.RJSON = module; | ||
} else if (typeof exports !== "undefined") { | ||
for (var k in module) { | ||
// Check to make jshint happy | ||
if (Object.prototype.hasOwnProperty.call(module, k)) { | ||
exports[k] = module[k]; | ||
} | ||
} | ||
window.RJSON = RJSON; | ||
} else if (typeof module !== "undefined") { | ||
module.exports = RJSON; | ||
} | ||
}()); |
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
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
Unidentified License
License(Experimental) Something that seems like a license was found, but its contents could not be matched with a known license.
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
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
Unidentified License
License(Experimental) Something that seems like a license was found, but its contents could not be matched with a known license.
Found 1 instance in 1 package
2353584
27
14463
33
8