├── .gitignore ├── Makefile ├── README.md ├── bower.json ├── lib ├── cli.js ├── formatter.js └── jsonlint.js ├── package-lock.json ├── package.json ├── scripts └── bundle.js ├── src ├── jsonlint.l └── jsonlint.y ├── test ├── all-tests.js ├── fails │ ├── 10.json │ ├── 11.json │ ├── 12.json │ ├── 13.json │ ├── 14.json │ ├── 15.json │ ├── 16.json │ ├── 17.json │ ├── 19.json │ ├── 2.json │ ├── 20.json │ ├── 21.json │ ├── 22.json │ ├── 23.json │ ├── 24.json │ ├── 25.json │ ├── 26.json │ ├── 27.json │ ├── 28.json │ ├── 29.json │ ├── 3.json │ ├── 30.json │ ├── 31.json │ ├── 32.json │ ├── 33.json │ ├── 34.json │ ├── 4.json │ ├── 5.json │ ├── 6.json │ ├── 7.json │ ├── 8.json │ └── 9.json └── passes │ ├── 1.json │ ├── 2.json │ └── 3.json └── web ├── json2.js ├── jsonlint.html └── jsonlint.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | all: build test site 3 | 4 | build: 5 | ./node_modules/jison/lib/cli.js src/jsonlint.y src/jsonlint.l 6 | mv jsonlint.js lib/jsonlint.js 7 | node scripts/bundle.js | ./node_modules/uglify-js/bin/uglifyjs > web/jsonlint.js 8 | 9 | site: 10 | cp web/jsonlint.js ../jsonlint-pages/jsonlint.js 11 | 12 | deploy: site 13 | cd ../jsonlint-pages && git commit -a -m 'deploy site updates' && git push origin gh-pages 14 | 15 | test: lib/jsonlint.js test/all-tests.js 16 | node test/all-tests.js 17 | 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | JSON Lint 2 | ========= 3 | 4 | A pure [JavaScript version](http://zaach.github.com/jsonlint/) of the service provided at [jsonlint.com](http://jsonlint.com). 5 | 6 | ## Command line interface 7 | Install jsonlint with npm to use the command line interface: 8 | 9 | npm install jsonlint -g 10 | 11 | Validate a file like so: 12 | 13 | jsonlint myfile.json 14 | 15 | or pipe input into stdin: 16 | 17 | cat myfile.json | jsonlint 18 | 19 | jsonlint will either report a syntax error with details or pretty print the source if it is valid. 20 | 21 | ### Options 22 | 23 | $ jsonlint -h 24 | 25 | Usage: jsonlint [file] [options] 26 | 27 | file file to parse; otherwise uses stdin 28 | 29 | Options: 30 | -v, --version print version and exit 31 | -s, --sort-keys sort object keys 32 | -i, --in-place overwrite the file 33 | -t CHAR, --indent CHAR character(s) to use for indentation [ ] 34 | -c, --compact compact error display 35 | -V, --validate a JSON schema to use for validation 36 | -e, --environment which specification of JSON Schema the validation file uses [json-schema-draft-03] 37 | -q, --quiet do not print the parsed json to STDOUT [false] 38 | -p, --pretty-print force pretty printing even if invalid 39 | 40 | 41 | ## Module interface 42 | 43 | I'm not sure why you wouldn't use the built in `JSON.parse` but you can use jsonlint from a CommonJS module: 44 | 45 | var jsonlint = require("jsonlint"); 46 | 47 | jsonlint.parse('{"creative?": false}'); 48 | 49 | It returns the parsed object or throws an `Error`. 50 | 51 | ## Vim Plugins 52 | 53 | * [Syntastic](http://www.vim.org/scripts/script.php?script_id=2736) 54 | * [sourcebeautify](http://www.vim.org/scripts/script.php?script_id=4079) 55 | * [ALE](https://github.com/w0rp/ale) 56 | 57 | ## MIT License 58 | 59 | Copyright (C) 2012 Zachary Carter 60 | 61 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 62 | 63 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 64 | 65 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 66 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jsonlint", 3 | "version": "1.6.0", 4 | "main": "lib/jsonlint.js", 5 | "ignore": [ 6 | "**/.*", 7 | "node_modules", 8 | "scripts", 9 | "src", 10 | "test", 11 | "web", 12 | "package.json", 13 | "Makefile", 14 | "README.md" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /lib/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var fs = require("fs"); 4 | var path = require("path"); 5 | var parser = require("./jsonlint").parser; 6 | var JSV = require("JSV").JSV; 7 | var formatter = require("./formatter.js").formatter; 8 | 9 | var options = require("@gerhobbelt/nomnom") 10 | .script("jsonlint") 11 | .options({ 12 | file: { 13 | position: 0, 14 | help: "file to parse; otherwise uses stdin" 15 | }, 16 | version: { 17 | flag : true, 18 | string: '-v, --version', 19 | help: 'print version and exit', 20 | callback: function() { 21 | return require("../package").version; 22 | } 23 | }, 24 | sort : { 25 | flag : true, 26 | string: '-s, --sort-keys', 27 | help: 'sort object keys' 28 | }, 29 | inplace : { 30 | flag : true, 31 | string: '-i, --in-place', 32 | help: 'overwrite the file' 33 | }, 34 | indent : { 35 | string: '-t CHAR, --indent CHAR', 36 | "default": " ", 37 | help: 'character(s) to use for indentation' 38 | }, 39 | compact : { 40 | flag : true, 41 | string: '-c, --compact', 42 | help : 'compact error display' 43 | }, 44 | validate : { 45 | string: '-V, --validate', 46 | help : 'a JSON schema to use for validation' 47 | }, 48 | env : { 49 | string: '-e, --environment', 50 | "default": "json-schema-draft-03", 51 | help: 'which specification of JSON Schema the validation file uses' 52 | }, 53 | quiet: { 54 | flag: true, 55 | key: "value", 56 | string: '-q, --quiet', 57 | "default": false, 58 | help: 'do not print the parsed json to STDOUT' 59 | }, 60 | forcePrettyPrint: { 61 | flag: true, 62 | string: '-p, --pretty-print', 63 | help: 'force pretty printing even if invalid' 64 | } 65 | }).parse(); 66 | 67 | if (options.compact) { 68 | var fileName = options.file? options.file + ': ' : ''; 69 | parser.parseError = parser.lexer.parseError = function(str, hash) { 70 | console.error(fileName + 'line '+ hash.loc.first_line +', col '+ hash.loc.last_column +', found: \''+ hash.token +'\' - expected: '+ hash.expected.join(', ') +'.'); 71 | throw new Error(str); 72 | }; 73 | } 74 | 75 | function parse (source) { 76 | var parsed, 77 | formatted; 78 | 79 | try { 80 | parsed = options.sort ? 81 | sortObject(parser.parse(source)) : 82 | parser.parse(source); 83 | 84 | if (options.validate) { 85 | var env = JSV.createEnvironment(options.env); 86 | var schema = JSON.parse(fs.readFileSync(path.normalize(options.validate), "utf8")); 87 | var report = env.validate(parsed, schema); 88 | if (report.errors.length) { 89 | throw report.errors.reduce(schemaError, 'Validation Errors:'); 90 | } 91 | } 92 | 93 | return JSON.stringify(parsed, null, options.indent); 94 | } catch (e) { 95 | if (options.forcePrettyPrint) { 96 | /* From https://github.com/umbrae/jsonlintdotcom: 97 | * If we failed to validate, run our manual formatter and then re-validate so that we 98 | * can get a better line number. On a successful validate, we don't want to run our 99 | * manual formatter because the automatic one is faster and probably more reliable. 100 | */ 101 | 102 | try { 103 | formatted = formatter.formatJson(source, options.indent); 104 | // Re-parse so exception output gets better line numbers 105 | parsed = parser.parse(formatted); 106 | } catch (e) { 107 | if (! options.compact) { 108 | console.error(e); 109 | } 110 | // force the pretty print before exiting 111 | console.log(formatted); 112 | } 113 | } else { 114 | if (! options.compact) { 115 | console.error(e); 116 | } 117 | } 118 | process.exit(1); 119 | } 120 | } 121 | 122 | function schemaError (str, err) { 123 | return str + 124 | "\n\n"+err.message + 125 | "\nuri: " + err.uri + 126 | "\nschemaUri: " + err.schemaUri + 127 | "\nattribute: " + err.attribute + 128 | "\ndetails: " + JSON.stringify(err.details); 129 | } 130 | 131 | function main (args) { 132 | var source = ''; 133 | if (options.file) { 134 | var json = path.normalize(options.file); 135 | source = parse(fs.readFileSync(json, "utf8")); 136 | if (options.inplace) { 137 | fs.writeSync(fs.openSync(json,'w+'), source, 0, "utf8"); 138 | } else { 139 | if (! options.quiet) { console.log(source)}; 140 | } 141 | } else { 142 | var stdin = process.openStdin(); 143 | stdin.setEncoding('utf8'); 144 | 145 | stdin.on('data', function (chunk) { 146 | source += chunk.toString('utf8'); 147 | }); 148 | stdin.on('end', function () { 149 | if (! options.quiet) {console.log(parse(source))}; 150 | }); 151 | } 152 | } 153 | 154 | // from http://stackoverflow.com/questions/1359761/sorting-a-json-object-in-javascript 155 | function sortObject(o) { 156 | if (Array.isArray(o)) { 157 | return o.map(sortObject); 158 | } else if (Object.prototype.toString.call(o) !== '[object Object]') { 159 | return o; 160 | } 161 | 162 | var sorted = {}, 163 | key, a = []; 164 | 165 | for (key in o) { 166 | if (o.hasOwnProperty(key)) { 167 | a.push(key); 168 | } 169 | } 170 | 171 | a.sort(); 172 | 173 | for (key = 0; key < a.length; key++) { 174 | sorted[a[key]] = sortObject(o[a[key]]); 175 | } 176 | return sorted; 177 | } 178 | 179 | main(process.argv.slice(1)); 180 | -------------------------------------------------------------------------------- /lib/formatter.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Manual formatter taken straight from https://github.com/umbrae/jsonlintdotcom 5 | **/ 6 | 7 | /*jslint white: true, devel: true, onevar: true, browser: true, undef: true, nomen: true, regexp: true, plusplus: false, bitwise: true, newcap: true, maxerr: 50, indent: 4 */ 8 | 9 | /** 10 | * jsl.format - Provide json reformatting in a character-by-character approach, so that even invalid JSON may be reformatted (to the best of its ability). 11 | * 12 | **/ 13 | var formatter = (function () { 14 | 15 | function repeat(s, count) { 16 | return new Array(count + 1).join(s); 17 | } 18 | 19 | function formatJson(json, indentChars) { 20 | var i = 0, 21 | il = 0, 22 | tab = (typeof indentChars !== "undefined") ? indentChars : " ", 23 | newJson = "", 24 | indentLevel = 0, 25 | inString = false, 26 | currentChar = null; 27 | 28 | for (i = 0, il = json.length; i < il; i += 1) { 29 | currentChar = json.charAt(i); 30 | 31 | switch (currentChar) { 32 | case '{': 33 | case '[': 34 | if (!inString) { 35 | newJson += currentChar + "\n" + repeat(tab, indentLevel + 1); 36 | indentLevel += 1; 37 | } else { 38 | newJson += currentChar; 39 | } 40 | break; 41 | case '}': 42 | case ']': 43 | if (!inString) { 44 | indentLevel -= 1; 45 | newJson += "\n" + repeat(tab, indentLevel) + currentChar; 46 | } else { 47 | newJson += currentChar; 48 | } 49 | break; 50 | case ',': 51 | if (!inString) { 52 | newJson += ",\n" + repeat(tab, indentLevel); 53 | } else { 54 | newJson += currentChar; 55 | } 56 | break; 57 | case ':': 58 | if (!inString) { 59 | newJson += ": "; 60 | } else { 61 | newJson += currentChar; 62 | } 63 | break; 64 | case ' ': 65 | case "\n": 66 | case "\t": 67 | if (inString) { 68 | newJson += currentChar; 69 | } 70 | break; 71 | case '"': 72 | if (i > 0 && json.charAt(i - 1) !== '\\') { 73 | inString = !inString; 74 | } 75 | newJson += currentChar; 76 | break; 77 | default: 78 | newJson += currentChar; 79 | break; 80 | } 81 | } 82 | 83 | return newJson; 84 | } 85 | 86 | return { "formatJson": formatJson }; 87 | 88 | }()); 89 | 90 | if (typeof require !== 'undefined' && typeof exports !== 'undefined') { 91 | exports.formatter = formatter; 92 | } -------------------------------------------------------------------------------- /lib/jsonlint.js: -------------------------------------------------------------------------------- 1 | /* Jison generated parser */ 2 | var jsonlint = (function(){ 3 | var parser = {trace: function trace() { }, 4 | yy: {}, 5 | symbols_: {"error":2,"JSONString":3,"STRING":4,"JSONNumber":5,"NUMBER":6,"JSONNullLiteral":7,"NULL":8,"JSONBooleanLiteral":9,"TRUE":10,"FALSE":11,"JSONText":12,"JSONValue":13,"EOF":14,"JSONObject":15,"JSONArray":16,"{":17,"}":18,"JSONMemberList":19,"JSONMember":20,":":21,",":22,"[":23,"]":24,"JSONElementList":25,"$accept":0,"$end":1}, 6 | terminals_: {2:"error",4:"STRING",6:"NUMBER",8:"NULL",10:"TRUE",11:"FALSE",14:"EOF",17:"{",18:"}",21:":",22:",",23:"[",24:"]"}, 7 | productions_: [0,[3,1],[5,1],[7,1],[9,1],[9,1],[12,2],[13,1],[13,1],[13,1],[13,1],[13,1],[13,1],[15,2],[15,3],[20,3],[19,1],[19,3],[16,2],[16,3],[25,1],[25,3]], 8 | performAction: function anonymous(yytext,yyleng,yylineno,yy,yystate,$$,_$) { 9 | 10 | var $0 = $$.length - 1; 11 | switch (yystate) { 12 | case 1: // replace escaped characters with actual character 13 | this.$ = yytext.replace(/\\(\\|")/g, "$"+"1") 14 | .replace(/\\n/g,'\n') 15 | .replace(/\\r/g,'\r') 16 | .replace(/\\t/g,'\t') 17 | .replace(/\\v/g,'\v') 18 | .replace(/\\f/g,'\f') 19 | .replace(/\\b/g,'\b'); 20 | 21 | break; 22 | case 2:this.$ = Number(yytext); 23 | break; 24 | case 3:this.$ = null; 25 | break; 26 | case 4:this.$ = true; 27 | break; 28 | case 5:this.$ = false; 29 | break; 30 | case 6:return this.$ = $$[$0-1]; 31 | break; 32 | case 13:this.$ = {}; 33 | break; 34 | case 14:this.$ = $$[$0-1]; 35 | break; 36 | case 15:this.$ = [$$[$0-2], $$[$0]]; 37 | break; 38 | case 16:this.$ = {}; this.$[$$[$0][0]] = $$[$0][1]; 39 | break; 40 | case 17:this.$ = $$[$0-2]; $$[$0-2][$$[$0][0]] = $$[$0][1]; 41 | break; 42 | case 18:this.$ = []; 43 | break; 44 | case 19:this.$ = $$[$0-1]; 45 | break; 46 | case 20:this.$ = [$$[$0]]; 47 | break; 48 | case 21:this.$ = $$[$0-2]; $$[$0-2].push($$[$0]); 49 | break; 50 | } 51 | }, 52 | table: [{3:5,4:[1,12],5:6,6:[1,13],7:3,8:[1,9],9:4,10:[1,10],11:[1,11],12:1,13:2,15:7,16:8,17:[1,14],23:[1,15]},{1:[3]},{14:[1,16]},{14:[2,7],18:[2,7],22:[2,7],24:[2,7]},{14:[2,8],18:[2,8],22:[2,8],24:[2,8]},{14:[2,9],18:[2,9],22:[2,9],24:[2,9]},{14:[2,10],18:[2,10],22:[2,10],24:[2,10]},{14:[2,11],18:[2,11],22:[2,11],24:[2,11]},{14:[2,12],18:[2,12],22:[2,12],24:[2,12]},{14:[2,3],18:[2,3],22:[2,3],24:[2,3]},{14:[2,4],18:[2,4],22:[2,4],24:[2,4]},{14:[2,5],18:[2,5],22:[2,5],24:[2,5]},{14:[2,1],18:[2,1],21:[2,1],22:[2,1],24:[2,1]},{14:[2,2],18:[2,2],22:[2,2],24:[2,2]},{3:20,4:[1,12],18:[1,17],19:18,20:19},{3:5,4:[1,12],5:6,6:[1,13],7:3,8:[1,9],9:4,10:[1,10],11:[1,11],13:23,15:7,16:8,17:[1,14],23:[1,15],24:[1,21],25:22},{1:[2,6]},{14:[2,13],18:[2,13],22:[2,13],24:[2,13]},{18:[1,24],22:[1,25]},{18:[2,16],22:[2,16]},{21:[1,26]},{14:[2,18],18:[2,18],22:[2,18],24:[2,18]},{22:[1,28],24:[1,27]},{22:[2,20],24:[2,20]},{14:[2,14],18:[2,14],22:[2,14],24:[2,14]},{3:20,4:[1,12],20:29},{3:5,4:[1,12],5:6,6:[1,13],7:3,8:[1,9],9:4,10:[1,10],11:[1,11],13:30,15:7,16:8,17:[1,14],23:[1,15]},{14:[2,19],18:[2,19],22:[2,19],24:[2,19]},{3:5,4:[1,12],5:6,6:[1,13],7:3,8:[1,9],9:4,10:[1,10],11:[1,11],13:31,15:7,16:8,17:[1,14],23:[1,15]},{18:[2,17],22:[2,17]},{18:[2,15],22:[2,15]},{22:[2,21],24:[2,21]}], 53 | defaultActions: {16:[2,6]}, 54 | parseError: function parseError(str, hash) { 55 | throw new Error(str); 56 | }, 57 | parse: function parse(input) { 58 | var self = this, 59 | stack = [0], 60 | vstack = [null], // semantic value stack 61 | lstack = [], // location stack 62 | table = this.table, 63 | yytext = '', 64 | yylineno = 0, 65 | yyleng = 0, 66 | recovering = 0, 67 | TERROR = 2, 68 | EOF = 1; 69 | 70 | //this.reductionCount = this.shiftCount = 0; 71 | 72 | this.lexer.setInput(input); 73 | this.lexer.yy = this.yy; 74 | this.yy.lexer = this.lexer; 75 | if (typeof this.lexer.yylloc == 'undefined') 76 | this.lexer.yylloc = {}; 77 | var yyloc = this.lexer.yylloc; 78 | lstack.push(yyloc); 79 | 80 | if (typeof this.yy.parseError === 'function') 81 | this.parseError = this.yy.parseError; 82 | 83 | function popStack (n) { 84 | stack.length = stack.length - 2*n; 85 | vstack.length = vstack.length - n; 86 | lstack.length = lstack.length - n; 87 | } 88 | 89 | function lex() { 90 | var token; 91 | token = self.lexer.lex() || 1; // $end = 1 92 | // if token isn't its numeric value, convert 93 | if (typeof token !== 'number') { 94 | token = self.symbols_[token] || token; 95 | } 96 | return token; 97 | } 98 | 99 | var symbol, preErrorSymbol, state, action, a, r, yyval={},p,len,newState, expected; 100 | while (true) { 101 | // retreive state number from top of stack 102 | state = stack[stack.length-1]; 103 | 104 | // use default actions if available 105 | if (this.defaultActions[state]) { 106 | action = this.defaultActions[state]; 107 | } else { 108 | if (symbol == null) 109 | symbol = lex(); 110 | // read action for current state and first input 111 | action = table[state] && table[state][symbol]; 112 | } 113 | 114 | // handle parse error 115 | _handle_error: 116 | if (typeof action === 'undefined' || !action.length || !action[0]) { 117 | 118 | if (!recovering) { 119 | // Report error 120 | expected = []; 121 | for (p in table[state]) if (this.terminals_[p] && p > 2) { 122 | expected.push("'"+this.terminals_[p]+"'"); 123 | } 124 | var errStr = ''; 125 | if (this.lexer.showPosition) { 126 | errStr = 'Parse error on line '+(yylineno+1)+":\n"+this.lexer.showPosition()+"\nExpecting "+expected.join(', ') + ", got '" + this.terminals_[symbol]+ "'"; 127 | } else { 128 | errStr = 'Parse error on line '+(yylineno+1)+": Unexpected " + 129 | (symbol == 1 /*EOF*/ ? "end of input" : 130 | ("'"+(this.terminals_[symbol] || symbol)+"'")); 131 | } 132 | this.parseError(errStr, 133 | {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected}); 134 | } 135 | 136 | // just recovered from another error 137 | if (recovering == 3) { 138 | if (symbol == EOF) { 139 | throw new Error(errStr || 'Parsing halted.'); 140 | } 141 | 142 | // discard current lookahead and grab another 143 | yyleng = this.lexer.yyleng; 144 | yytext = this.lexer.yytext; 145 | yylineno = this.lexer.yylineno; 146 | yyloc = this.lexer.yylloc; 147 | symbol = lex(); 148 | } 149 | 150 | // try to recover from error 151 | while (1) { 152 | // check for error recovery rule in this state 153 | if ((TERROR.toString()) in table[state]) { 154 | break; 155 | } 156 | if (state == 0) { 157 | throw new Error(errStr || 'Parsing halted.'); 158 | } 159 | popStack(1); 160 | state = stack[stack.length-1]; 161 | } 162 | 163 | preErrorSymbol = symbol; // save the lookahead token 164 | symbol = TERROR; // insert generic error symbol as new lookahead 165 | state = stack[stack.length-1]; 166 | action = table[state] && table[state][TERROR]; 167 | recovering = 3; // allow 3 real symbols to be shifted before reporting a new error 168 | } 169 | 170 | // this shouldn't happen, unless resolve defaults are off 171 | if (action[0] instanceof Array && action.length > 1) { 172 | throw new Error('Parse Error: multiple actions possible at state: '+state+', token: '+symbol); 173 | } 174 | 175 | switch (action[0]) { 176 | 177 | case 1: // shift 178 | //this.shiftCount++; 179 | 180 | stack.push(symbol); 181 | vstack.push(this.lexer.yytext); 182 | lstack.push(this.lexer.yylloc); 183 | stack.push(action[1]); // push state 184 | symbol = null; 185 | if (!preErrorSymbol) { // normal execution/no error 186 | yyleng = this.lexer.yyleng; 187 | yytext = this.lexer.yytext; 188 | yylineno = this.lexer.yylineno; 189 | yyloc = this.lexer.yylloc; 190 | if (recovering > 0) 191 | recovering--; 192 | } else { // error just occurred, resume old lookahead f/ before error 193 | symbol = preErrorSymbol; 194 | preErrorSymbol = null; 195 | } 196 | break; 197 | 198 | case 2: // reduce 199 | //this.reductionCount++; 200 | 201 | len = this.productions_[action[1]][1]; 202 | 203 | // perform semantic action 204 | yyval.$ = vstack[vstack.length-len]; // default to $$ = $1 205 | // default location, uses first token for firsts, last for lasts 206 | yyval._$ = { 207 | first_line: lstack[lstack.length-(len||1)].first_line, 208 | last_line: lstack[lstack.length-1].last_line, 209 | first_column: lstack[lstack.length-(len||1)].first_column, 210 | last_column: lstack[lstack.length-1].last_column 211 | }; 212 | r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack); 213 | 214 | if (typeof r !== 'undefined') { 215 | return r; 216 | } 217 | 218 | // pop off stack 219 | if (len) { 220 | stack = stack.slice(0,-1*len*2); 221 | vstack = vstack.slice(0, -1*len); 222 | lstack = lstack.slice(0, -1*len); 223 | } 224 | 225 | stack.push(this.productions_[action[1]][0]); // push nonterminal (reduce) 226 | vstack.push(yyval.$); 227 | lstack.push(yyval._$); 228 | // goto new state = table[STATE][NONTERMINAL] 229 | newState = table[stack[stack.length-2]][stack[stack.length-1]]; 230 | stack.push(newState); 231 | break; 232 | 233 | case 3: // accept 234 | return true; 235 | } 236 | 237 | } 238 | 239 | return true; 240 | }}; 241 | /* Jison generated lexer */ 242 | var lexer = (function(){ 243 | var lexer = ({EOF:1, 244 | parseError:function parseError(str, hash) { 245 | if (this.yy.parseError) { 246 | this.yy.parseError(str, hash); 247 | } else { 248 | throw new Error(str); 249 | } 250 | }, 251 | setInput:function (input) { 252 | this._input = input; 253 | this._more = this._less = this.done = false; 254 | this.yylineno = this.yyleng = 0; 255 | this.yytext = this.matched = this.match = ''; 256 | this.conditionStack = ['INITIAL']; 257 | this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0}; 258 | return this; 259 | }, 260 | input:function () { 261 | var ch = this._input[0]; 262 | this.yytext+=ch; 263 | this.yyleng++; 264 | this.match+=ch; 265 | this.matched+=ch; 266 | var lines = ch.match(/\n/); 267 | if (lines) this.yylineno++; 268 | this._input = this._input.slice(1); 269 | return ch; 270 | }, 271 | unput:function (ch) { 272 | this._input = ch + this._input; 273 | return this; 274 | }, 275 | more:function () { 276 | this._more = true; 277 | return this; 278 | }, 279 | less:function (n) { 280 | this._input = this.match.slice(n) + this._input; 281 | }, 282 | pastInput:function () { 283 | var past = this.matched.substr(0, this.matched.length - this.match.length); 284 | return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, ""); 285 | }, 286 | upcomingInput:function () { 287 | var next = this.match; 288 | if (next.length < 20) { 289 | next += this._input.substr(0, 20-next.length); 290 | } 291 | return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, ""); 292 | }, 293 | showPosition:function () { 294 | var pre = this.pastInput(); 295 | var c = new Array(pre.length + 1).join("-"); 296 | return pre + this.upcomingInput() + "\n" + c+"^"; 297 | }, 298 | next:function () { 299 | if (this.done) { 300 | return this.EOF; 301 | } 302 | if (!this._input) this.done = true; 303 | 304 | var token, 305 | match, 306 | tempMatch, 307 | index, 308 | col, 309 | lines; 310 | if (!this._more) { 311 | this.yytext = ''; 312 | this.match = ''; 313 | } 314 | var rules = this._currentRules(); 315 | for (var i=0;i < rules.length; i++) { 316 | tempMatch = this._input.match(this.rules[rules[i]]); 317 | if (tempMatch && (!match || tempMatch[0].length > match[0].length)) { 318 | match = tempMatch; 319 | index = i; 320 | if (!this.options.flex) break; 321 | } 322 | } 323 | if (match) { 324 | lines = match[0].match(/\n.*/g); 325 | if (lines) this.yylineno += lines.length; 326 | this.yylloc = {first_line: this.yylloc.last_line, 327 | last_line: this.yylineno+1, 328 | first_column: this.yylloc.last_column, 329 | last_column: lines ? lines[lines.length-1].length-1 : this.yylloc.last_column + match[0].length} 330 | this.yytext += match[0]; 331 | this.match += match[0]; 332 | this.yyleng = this.yytext.length; 333 | this._more = false; 334 | this._input = this._input.slice(match[0].length); 335 | this.matched += match[0]; 336 | token = this.performAction.call(this, this.yy, this, rules[index],this.conditionStack[this.conditionStack.length-1]); 337 | if (this.done && this._input) this.done = false; 338 | if (token) return token; 339 | else return; 340 | } 341 | if (this._input === "") { 342 | return this.EOF; 343 | } else { 344 | this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(), 345 | {text: "", token: null, line: this.yylineno}); 346 | } 347 | }, 348 | lex:function lex() { 349 | var r = this.next(); 350 | if (typeof r !== 'undefined') { 351 | return r; 352 | } else { 353 | return this.lex(); 354 | } 355 | }, 356 | begin:function begin(condition) { 357 | this.conditionStack.push(condition); 358 | }, 359 | popState:function popState() { 360 | return this.conditionStack.pop(); 361 | }, 362 | _currentRules:function _currentRules() { 363 | return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules; 364 | }, 365 | topState:function () { 366 | return this.conditionStack[this.conditionStack.length-2]; 367 | }, 368 | pushState:function begin(condition) { 369 | this.begin(condition); 370 | }}); 371 | lexer.options = {}; 372 | lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) { 373 | 374 | var YYSTATE=YY_START 375 | switch($avoiding_name_collisions) { 376 | case 0:/* skip whitespace */ 377 | break; 378 | case 1:return 6 379 | break; 380 | case 2:yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2); return 4 381 | break; 382 | case 3:return 17 383 | break; 384 | case 4:return 18 385 | break; 386 | case 5:return 23 387 | break; 388 | case 6:return 24 389 | break; 390 | case 7:return 22 391 | break; 392 | case 8:return 21 393 | break; 394 | case 9:return 10 395 | break; 396 | case 10:return 11 397 | break; 398 | case 11:return 8 399 | break; 400 | case 12:return 14 401 | break; 402 | case 13:return 'INVALID' 403 | break; 404 | } 405 | }; 406 | lexer.rules = [/^(?:\s+)/,/^(?:(-?([0-9]|[1-9][0-9]+))(\.[0-9]+)?([eE][-+]?[0-9]+)?\b)/,/^(?:"(?:\\[\\"bfnrt/]|\\u[a-fA-F0-9]{4}|[^\\\0-\x09\x0a-\x1f"])*")/,/^(?:\{)/,/^(?:\})/,/^(?:\[)/,/^(?:\])/,/^(?:,)/,/^(?::)/,/^(?:true\b)/,/^(?:false\b)/,/^(?:null\b)/,/^(?:$)/,/^(?:.)/]; 407 | lexer.conditions = {"INITIAL":{"rules":[0,1,2,3,4,5,6,7,8,9,10,11,12,13],"inclusive":true}}; 408 | 409 | 410 | ; 411 | return lexer;})() 412 | parser.lexer = lexer; 413 | return parser; 414 | })(); 415 | if (typeof require !== 'undefined' && typeof exports !== 'undefined') { 416 | exports.parser = jsonlint; 417 | exports.parse = function () { return jsonlint.parse.apply(jsonlint, arguments); } 418 | exports.main = function commonjsMain(args) { 419 | if (!args[1]) 420 | throw new Error('Usage: '+args[0]+' FILE'); 421 | if (typeof process !== 'undefined') { 422 | var source = require('fs').readFileSync(require('path').join(process.cwd(), args[1]), "utf8"); 423 | } else { 424 | var cwd = require("file").path(require("file").cwd()); 425 | var source = cwd.join(args[1]).read({charset: "utf-8"}); 426 | } 427 | return exports.parser.parse(source); 428 | } 429 | if (typeof module !== 'undefined' && require.main === module) { 430 | exports.main(typeof process !== 'undefined' ? process.argv.slice(1) : require("system").args); 431 | } 432 | } -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jsonlint", 3 | "version": "1.6.3", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "JSONSelect": { 8 | "version": "0.4.0", 9 | "resolved": "https://registry.npmjs.org/JSONSelect/-/JSONSelect-0.4.0.tgz", 10 | "integrity": "sha1-oI7cxn6z/L6Z7WMIVTRKDPKCu40=", 11 | "dev": true 12 | }, 13 | "JSV": { 14 | "version": "4.0.2", 15 | "resolved": "https://registry.npmjs.org/JSV/-/JSV-4.0.2.tgz", 16 | "integrity": "sha1-0Hf2glVx+CEy+d/67Vh7QCn+/1c=" 17 | }, 18 | "amdefine": { 19 | "version": "1.0.1", 20 | "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", 21 | "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", 22 | "dev": true, 23 | "optional": true 24 | }, 25 | "ansi-font": { 26 | "version": "0.0.2", 27 | "resolved": "https://registry.npmjs.org/ansi-font/-/ansi-font-0.0.2.tgz", 28 | "integrity": "sha1-iQMBvVhBRi/TnAt3Ca/R9SUUMzE=", 29 | "dev": true 30 | }, 31 | "ansi-styles": { 32 | "version": "1.0.0", 33 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.0.0.tgz", 34 | "integrity": "sha1-yxAt8cVvUSPquLZ817mAJ6AnkXg=" 35 | }, 36 | "chalk": { 37 | "version": "0.4.0", 38 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.4.0.tgz", 39 | "integrity": "sha1-UZmj3c0MHv4jvAjBsCewYXbgxk8=", 40 | "requires": { 41 | "ansi-styles": "1.0.0", 42 | "has-color": "0.1.7", 43 | "strip-ansi": "0.1.1" 44 | } 45 | }, 46 | "cjson": { 47 | "version": "0.3.0", 48 | "resolved": "https://registry.npmjs.org/cjson/-/cjson-0.3.0.tgz", 49 | "integrity": "sha1-5kObkHA9MS/24iJAl76pLOPQKhQ=", 50 | "dev": true, 51 | "requires": { 52 | "jsonlint": "1.6.0" 53 | } 54 | }, 55 | "colors": { 56 | "version": "0.5.1", 57 | "resolved": "https://registry.npmjs.org/colors/-/colors-0.5.1.tgz", 58 | "integrity": "sha1-fQAj6usVTo7p/Oddy5I9DtFmd3Q=", 59 | "dev": true 60 | }, 61 | "commander": { 62 | "version": "2.14.1", 63 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.14.1.tgz", 64 | "integrity": "sha512-+YR16o3rK53SmWHU3rEM3tPAh2rwb1yPcQX5irVn7mb0gXbwuCCrnkbV5+PBfETdfg1vui07nM6PCG1zndcjQw==", 65 | "dev": true 66 | }, 67 | "ebnf-parser": { 68 | "version": "0.1.10", 69 | "resolved": "https://registry.npmjs.org/ebnf-parser/-/ebnf-parser-0.1.10.tgz", 70 | "integrity": "sha1-zR9rpHfFY4xAyX7ZtXLbW6tdgzE=", 71 | "dev": true 72 | }, 73 | "escodegen": { 74 | "version": "1.3.3", 75 | "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.3.3.tgz", 76 | "integrity": "sha1-8CQBb1qI4Eb9EgBQVek5gC5sXyM=", 77 | "dev": true, 78 | "requires": { 79 | "esprima": "1.1.1", 80 | "estraverse": "1.5.1", 81 | "esutils": "1.0.0", 82 | "source-map": "0.1.43" 83 | } 84 | }, 85 | "esprima": { 86 | "version": "1.1.1", 87 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.1.1.tgz", 88 | "integrity": "sha1-W28VR/TRAuZw4UDFCb5ncdautUk=", 89 | "dev": true 90 | }, 91 | "estraverse": { 92 | "version": "1.5.1", 93 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.5.1.tgz", 94 | "integrity": "sha1-hno+jlip+EYYr7bC3bzZFrfLr3E=", 95 | "dev": true 96 | }, 97 | "esutils": { 98 | "version": "1.0.0", 99 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-1.0.0.tgz", 100 | "integrity": "sha1-gVHTWOIMisx/t0XnRywAJf5JZXA=", 101 | "dev": true 102 | }, 103 | "has-color": { 104 | "version": "0.1.7", 105 | "resolved": "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz", 106 | "integrity": "sha1-ZxRKUmDDT8PMpnfQQdr1L+e3iy8=" 107 | }, 108 | "jison": { 109 | "version": "0.4.18", 110 | "resolved": "https://registry.npmjs.org/jison/-/jison-0.4.18.tgz", 111 | "integrity": "sha512-FKkCiJvozgC7VTHhMJ00a0/IApSxhlGsFIshLW6trWJ8ONX2TQJBBz6DlcO1Gffy4w9LT+uL+PA+CVnUSJMF7w==", 112 | "dev": true, 113 | "requires": { 114 | "JSONSelect": "0.4.0", 115 | "cjson": "0.3.0", 116 | "ebnf-parser": "0.1.10", 117 | "escodegen": "1.3.3", 118 | "esprima": "1.1.1", 119 | "jison-lex": "0.3.4", 120 | "lex-parser": "0.1.4", 121 | "nomnom": "1.5.2" 122 | }, 123 | "dependencies": { 124 | "nomnom": { 125 | "version": "1.5.2", 126 | "resolved": "https://registry.npmjs.org/nomnom/-/nomnom-1.5.2.tgz", 127 | "integrity": "sha1-9DRUSKhTz71cDSYyDyR3qwUm/i8=", 128 | "dev": true, 129 | "requires": { 130 | "colors": "0.5.1", 131 | "underscore": "1.1.7" 132 | } 133 | }, 134 | "underscore": { 135 | "version": "1.1.7", 136 | "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.1.7.tgz", 137 | "integrity": "sha1-QLq4S60Z0jAJbo1u9ii/8FXYPbA=", 138 | "dev": true 139 | } 140 | } 141 | }, 142 | "jison-lex": { 143 | "version": "0.3.4", 144 | "resolved": "https://registry.npmjs.org/jison-lex/-/jison-lex-0.3.4.tgz", 145 | "integrity": "sha1-gcoo2E+ESZ36jFlNzePYo/Jux6U=", 146 | "dev": true, 147 | "requires": { 148 | "lex-parser": "0.1.4", 149 | "nomnom": "1.5.2" 150 | }, 151 | "dependencies": { 152 | "nomnom": { 153 | "version": "1.5.2", 154 | "resolved": "https://registry.npmjs.org/nomnom/-/nomnom-1.5.2.tgz", 155 | "integrity": "sha1-9DRUSKhTz71cDSYyDyR3qwUm/i8=", 156 | "dev": true, 157 | "requires": { 158 | "colors": "0.5.1", 159 | "underscore": "1.1.7" 160 | } 161 | }, 162 | "underscore": { 163 | "version": "1.1.7", 164 | "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.1.7.tgz", 165 | "integrity": "sha1-QLq4S60Z0jAJbo1u9ii/8FXYPbA=", 166 | "dev": true 167 | } 168 | } 169 | }, 170 | "jsonlint": { 171 | "version": "1.6.0", 172 | "resolved": "https://registry.npmjs.org/jsonlint/-/jsonlint-1.6.0.tgz", 173 | "integrity": "sha1-iKpGvCiaesk7tGyuLVihh6m7SUo=", 174 | "dev": true, 175 | "requires": { 176 | "JSV": "4.0.2", 177 | "nomnom": "1.8.1" 178 | } 179 | }, 180 | "lex-parser": { 181 | "version": "0.1.4", 182 | "resolved": "https://registry.npmjs.org/lex-parser/-/lex-parser-0.1.4.tgz", 183 | "integrity": "sha1-ZMTwJfF/1Tv7RXY/rrFvAVp0dVA=", 184 | "dev": true 185 | }, 186 | "nomnom": { 187 | "version": "1.8.1", 188 | "resolved": "https://registry.npmjs.org/nomnom/-/nomnom-1.8.1.tgz", 189 | "integrity": "sha1-IVH3Ikcrp55Qp2/BJbuMjy5Nwqc=", 190 | "requires": { 191 | "chalk": "0.4.0", 192 | "underscore": "1.6.0" 193 | } 194 | }, 195 | "source-map": { 196 | "version": "0.1.43", 197 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", 198 | "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", 199 | "dev": true, 200 | "optional": true, 201 | "requires": { 202 | "amdefine": "1.0.1" 203 | } 204 | }, 205 | "strip-ansi": { 206 | "version": "0.1.1", 207 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.1.1.tgz", 208 | "integrity": "sha1-OeipjQRNFQZgq+SmgIrPcLt7yZE=" 209 | }, 210 | "test": { 211 | "version": "0.6.0", 212 | "resolved": "https://registry.npmjs.org/test/-/test-0.6.0.tgz", 213 | "integrity": "sha1-WYasRF7Bd1QyJRLRBLoyyKY+k44=", 214 | "dev": true, 215 | "requires": { 216 | "ansi-font": "0.0.2" 217 | } 218 | }, 219 | "uglify-js": { 220 | "version": "3.3.11", 221 | "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.3.11.tgz", 222 | "integrity": "sha512-AKLsYcdV+sS5eAE4NtVXF6f2u/DCQynQm0jTGxF261+Vltu1dYNuHzjqDmk11gInj+H/zJIM2EAwXG3MzPb3VA==", 223 | "dev": true, 224 | "requires": { 225 | "commander": "2.14.1", 226 | "source-map": "0.6.1" 227 | }, 228 | "dependencies": { 229 | "source-map": { 230 | "version": "0.6.1", 231 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 232 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 233 | "dev": true 234 | } 235 | } 236 | }, 237 | "underscore": { 238 | "version": "1.6.0", 239 | "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", 240 | "integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=" 241 | } 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Zach Carter (http://zaa.ch)", 3 | "name": "jsonlint", 4 | "description": "Validate JSON", 5 | "keywords": [ 6 | "json", 7 | "validation", 8 | "lint", 9 | "jsonlint" 10 | ], 11 | "version": "1.6.3", 12 | "license": "MIT", 13 | "preferGlobal": true, 14 | "repository": { 15 | "type": "git", 16 | "url": "git://github.com/zaach/jsonlint.git" 17 | }, 18 | "bugs": { 19 | "url": "http://github.com/zaach/jsonlint/issues" 20 | }, 21 | "main": "lib/jsonlint.js", 22 | "bin": { 23 | "jsonlint": "lib/cli.js" 24 | }, 25 | "engines": { 26 | "node": ">= 0.6" 27 | }, 28 | "dependencies": { 29 | "@gerhobbelt/nomnom": "^1.8.4-27", 30 | "JSV": "^4.0.x" 31 | }, 32 | "devDependencies": { 33 | "test": "*", 34 | "jison": "*", 35 | "uglify-js": "*" 36 | }, 37 | "scripts": { 38 | "test": "node test/all-tests.js" 39 | }, 40 | "homepage": "http://zaach.github.com/jsonlint/", 41 | "optionalDependencies": {} 42 | } 43 | -------------------------------------------------------------------------------- /scripts/bundle.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | 3 | var source = "var jsonlint = (function(){var require=true,module=false;var exports={};" + 4 | fs.readFileSync(__dirname+'/../lib/jsonlint.js', 'utf8') + 5 | "return exports;})()"; 6 | 7 | console.log(source); 8 | 9 | -------------------------------------------------------------------------------- /src/jsonlint.l: -------------------------------------------------------------------------------- 1 | int "-"?([0-9]|[1-9][0-9]+) 2 | exp [eE][-+]?[0-9]+ 3 | frac "."[0-9]+ 4 | 5 | %% 6 | \s+ /* skip whitespace */ 7 | 8 | {int}{frac}?{exp}?\b return 'NUMBER' 9 | \"(?:'\\'[\\"bfnrt/]|'\\u'[a-fA-F0-9]{4}|[^\\\0-\x09\x0a-\x1f"])*\" yytext = yytext.substr(1,yyleng-2); return 'STRING' 10 | 11 | "{" return '{' 12 | "}" return '}' 13 | "[" return '[' 14 | "]" return ']' 15 | "," return ',' 16 | ":" return ':' 17 | "true" return 'TRUE' 18 | "false" return 'FALSE' 19 | "null" return 'NULL' 20 | <> return 'EOF' 21 | . return 'INVALID' 22 | 23 | %% 24 | 25 | -------------------------------------------------------------------------------- /src/jsonlint.y: -------------------------------------------------------------------------------- 1 | %start JSONText 2 | 3 | /* 4 | ECMA-262 5th Edition, 15.12.1 The JSON Grammar. 5 | */ 6 | 7 | 8 | %% 9 | 10 | JSONString 11 | : STRING 12 | { // replace escaped characters with actual character 13 | $$ = yytext.replace(/\\(\\|")/g, "$"+"1") 14 | .replace(/\\n/g,'\n') 15 | .replace(/\\r/g,'\r') 16 | .replace(/\\t/g,'\t') 17 | .replace(/\\v/g,'\v') 18 | .replace(/\\f/g,'\f') 19 | .replace(/\\b/g,'\b'); 20 | } 21 | ; 22 | 23 | JSONNumber 24 | : NUMBER 25 | {$$ = Number(yytext);} 26 | ; 27 | 28 | JSONNullLiteral 29 | : NULL 30 | {$$ = null;} 31 | ; 32 | 33 | JSONBooleanLiteral 34 | : TRUE 35 | {$$ = true;} 36 | | FALSE 37 | {$$ = false;} 38 | ; 39 | 40 | JSONText 41 | : JSONValue EOF 42 | {return $$ = $1;} 43 | ; 44 | 45 | JSONValue 46 | : JSONNullLiteral 47 | | JSONBooleanLiteral 48 | | JSONString 49 | | JSONNumber 50 | | JSONObject 51 | | JSONArray 52 | ; 53 | 54 | JSONObject 55 | : '{' '}' 56 | {{$$ = {};}} 57 | | '{' JSONMemberList '}' 58 | {$$ = $2;} 59 | ; 60 | 61 | JSONMember 62 | : JSONString ':' JSONValue 63 | {$$ = [$1, $3];} 64 | ; 65 | 66 | JSONMemberList 67 | : JSONMember 68 | {{$$ = {}; $$[$1[0]] = $1[1];}} 69 | | JSONMemberList ',' JSONMember 70 | {$$ = $1; $1[$3[0]] = $3[1];} 71 | ; 72 | 73 | JSONArray 74 | : '[' ']' 75 | {$$ = [];} 76 | | '[' JSONElementList ']' 77 | {$$ = $2;} 78 | ; 79 | 80 | JSONElementList 81 | : JSONValue 82 | {$$ = [$1];} 83 | | JSONElementList ',' JSONValue 84 | {$$ = $1; $1.push($3);} 85 | ; 86 | 87 | -------------------------------------------------------------------------------- /test/all-tests.js: -------------------------------------------------------------------------------- 1 | var fs = require("fs"), 2 | assert = require("assert"), 3 | parser = require("../lib/jsonlint").parser; 4 | 5 | exports["test object"] = function () { 6 | var json = '{"foo": "bar"}'; 7 | assert.deepEqual(parser.parse(json), {"foo": "bar"}); 8 | }; 9 | 10 | exports["test escaped backslash"] = function () { 11 | var json = '{"foo": "\\\\"}'; 12 | assert.deepEqual(parser.parse(json), {"foo": "\\"}); 13 | }; 14 | 15 | exports["test escaped chars"] = function () { 16 | var json = '{"foo": "\\\\\\\""}'; 17 | assert.deepEqual(parser.parse(json), {"foo": '\\\"'}); 18 | }; 19 | 20 | exports["test escaped \\n"] = function () { 21 | var json = '{"foo": "\\\\\\n"}'; 22 | assert.deepEqual(parser.parse(json), {"foo": '\\\n'}); 23 | }; 24 | 25 | exports["test string with escaped line break"] = function () { 26 | var json = '{"foo": "bar\\nbar"}'; 27 | assert.deepEqual(parser.parse(json), {"foo": "bar\nbar"}); 28 | assert.equal(JSON.stringify(parser.parse(json)).length, 18); 29 | }; 30 | 31 | exports["test string with line break"] = function () { 32 | var json = '{"foo": "bar\nbar"}'; 33 | assert["throws"](function () {parser.parse(json)}, "should throw error"); 34 | }; 35 | 36 | exports["test string literal"] = function () { 37 | var json = '"foo"'; 38 | assert.equal(parser.parse(json), "foo"); 39 | }; 40 | 41 | exports["test number literal"] = function () { 42 | var json = '1234'; 43 | assert.equal(parser.parse(json), 1234); 44 | }; 45 | 46 | exports["test null literal"] = function () { 47 | var json = '1234'; 48 | assert.equal(parser.parse(json), 1234); 49 | }; 50 | 51 | exports["test boolean literal"] = function () { 52 | var json = 'true'; 53 | assert.equal(parser.parse(json), true); 54 | }; 55 | 56 | exports["test unclosed array"] = function () { 57 | var json = fs.readFileSync(__dirname + "/fails/2.json").toString(); 58 | assert["throws"](function () {parser.parse(json)}, "should throw error"); 59 | }; 60 | 61 | exports["test unquotedkey keys must be quoted"] = function () { 62 | var json = fs.readFileSync(__dirname + "/fails/3.json").toString(); 63 | assert["throws"](function () {parser.parse(json)}, "should throw error"); 64 | }; 65 | 66 | exports["test extra comma"] = function () { 67 | var json = fs.readFileSync(__dirname + "/fails/4.json").toString(); 68 | assert["throws"](function () {parser.parse(json)}, "should throw error"); 69 | }; 70 | 71 | exports["test double extra comma"] = function () { 72 | var json = fs.readFileSync(__dirname + "/fails/5.json").toString(); 73 | assert["throws"](function () {parser.parse(json)}, "should throw error"); 74 | }; 75 | 76 | exports["test missing value"] = function () { 77 | var json = fs.readFileSync(__dirname + "/fails/6.json").toString(); 78 | assert["throws"](function () {parser.parse(json)}, "should throw error"); 79 | }; 80 | 81 | exports["test comma after the close"] = function () { 82 | var json = fs.readFileSync(__dirname + "/fails/7.json").toString(); 83 | assert["throws"](function () {parser.parse(json)}, "should throw error"); 84 | }; 85 | 86 | exports["test extra close"] = function () { 87 | var json = fs.readFileSync(__dirname + "/fails/8.json").toString(); 88 | assert["throws"](function () {parser.parse(json)}, "should throw error"); 89 | }; 90 | 91 | exports["test extra comma after value"] = function () { 92 | var json = fs.readFileSync(__dirname + "/fails/9.json").toString(); 93 | assert["throws"](function () {parser.parse(json)}, "should throw error"); 94 | }; 95 | 96 | exports["test extra value after close with misplaced quotes"] = function () { 97 | var json = fs.readFileSync(__dirname + "/fails/10.json").toString(); 98 | assert["throws"](function () {parser.parse(json)}, "should throw error"); 99 | }; 100 | 101 | exports["test illegal expression addition"] = function () { 102 | var json = fs.readFileSync(__dirname + "/fails/11.json").toString(); 103 | assert["throws"](function () {parser.parse(json)}, "should throw error"); 104 | }; 105 | 106 | exports["test illegal invocation of alert"] = function () { 107 | var json = fs.readFileSync(__dirname + "/fails/12.json").toString(); 108 | assert["throws"](function () {parser.parse(json)}, "should throw error"); 109 | }; 110 | 111 | exports["test numbers cannot have leading zeroes"] = function () { 112 | var json = fs.readFileSync(__dirname + "/fails/13.json").toString(); 113 | assert["throws"](function () {parser.parse(json)}, "should throw error"); 114 | }; 115 | 116 | exports["test numbers cannot be hex"] = function () { 117 | var json = fs.readFileSync(__dirname + "/fails/14.json").toString(); 118 | assert["throws"](function () {parser.parse(json)}, "should throw error"); 119 | }; 120 | 121 | exports["test illegal backslash escape \\0"] = function () { 122 | var json = fs.readFileSync(__dirname + "/fails/15.json").toString(); 123 | assert["throws"](function () {parser.parse(json)}, "should throw error"); 124 | }; 125 | 126 | exports["test unquoted text"] = function () { 127 | var json = fs.readFileSync(__dirname + "/fails/16.json").toString(); 128 | assert["throws"](function () {parser.parse(json)}, "should throw error"); 129 | }; 130 | 131 | exports["test illegal backslash escape \\x"] = function () { 132 | var json = fs.readFileSync(__dirname + "/fails/17.json").toString(); 133 | assert["throws"](function () {parser.parse(json)}, "should throw error"); 134 | }; 135 | 136 | exports["test missing colon"] = function () { 137 | var json = fs.readFileSync(__dirname + "/fails/19.json") 138 | assert["throws"](function () {parser.parse(json)}, "should throw error"); 139 | }; 140 | 141 | exports["test double colon"] = function () { 142 | var json = fs.readFileSync(__dirname + "/fails/20.json").toString(); 143 | assert["throws"](function () {parser.parse(json)}, "should throw error"); 144 | }; 145 | 146 | exports["test comma instead of colon"] = function () { 147 | var json = fs.readFileSync(__dirname + "/fails/21.json").toString(); 148 | assert["throws"](function () {parser.parse(json)}, "should throw error"); 149 | }; 150 | 151 | exports["test colon instead of comma"] = function () { 152 | var json = fs.readFileSync(__dirname + "/fails/22.json").toString(); 153 | assert["throws"](function () {parser.parse(json)}, "should throw error"); 154 | }; 155 | 156 | exports["test bad raw value"] = function () { 157 | var json = fs.readFileSync(__dirname + "/fails/23.json").toString(); 158 | assert["throws"](function () {parser.parse(json)}, "should throw error"); 159 | }; 160 | 161 | exports["test single quotes"] = function () { 162 | var json = fs.readFileSync(__dirname + "/fails/24.json").toString(); 163 | assert["throws"](function () {parser.parse(json)}, "should throw error"); 164 | }; 165 | 166 | exports["test tab character in string"] = function () { 167 | var json = fs.readFileSync(__dirname + "/fails/25.json").toString(); 168 | assert["throws"](function () {parser.parse(json)}, "should throw error"); 169 | }; 170 | 171 | exports["test tab character in string 2"] = function () { 172 | var json = fs.readFileSync(__dirname + "/fails/26.json").toString(); 173 | assert["throws"](function () {parser.parse(json)}, "should throw error"); 174 | }; 175 | 176 | exports["test line break in string"] = function () { 177 | var json = fs.readFileSync(__dirname + "/fails/27.json").toString(); 178 | assert["throws"](function () {parser.parse(json)}, "should throw error"); 179 | }; 180 | 181 | exports["test line break in string in array"] = function () { 182 | var json = fs.readFileSync(__dirname + "/fails/28.json").toString(); 183 | assert["throws"](function () {parser.parse(json)}, "should throw error"); 184 | }; 185 | 186 | exports["test 0e"] = function () { 187 | var json = fs.readFileSync(__dirname + "/fails/29.json").toString(); 188 | assert["throws"](function () {parser.parse(json)}, "should throw error"); 189 | }; 190 | 191 | exports["test 0e+"] = function () { 192 | var json = fs.readFileSync(__dirname + "/fails/30.json").toString(); 193 | assert["throws"](function () {parser.parse(json)}, "should throw error"); 194 | }; 195 | 196 | exports["test 0e+ 1"] = function () { 197 | var json = fs.readFileSync(__dirname + "/fails/31.json").toString(); 198 | assert["throws"](function () {parser.parse(json)}, "should throw error"); 199 | }; 200 | 201 | exports["test comma instead of closing brace"] = function () { 202 | var json = fs.readFileSync(__dirname + "/fails/32.json").toString(); 203 | assert["throws"](function () {parser.parse(json)}, "should throw error"); 204 | }; 205 | 206 | exports["test bracket mismatch"] = function () { 207 | var json = fs.readFileSync(__dirname + "/fails/33.json").toString(); 208 | assert["throws"](function () {parser.parse(json)}, "should throw error"); 209 | } 210 | 211 | exports["test extra brace"] = function () { 212 | var json = fs.readFileSync(__dirname + "/fails/34.json").toString(); 213 | assert["throws"](function () {parser.parse(json)}, "should throw error"); 214 | } 215 | 216 | exports["test pass-1"] = function () { 217 | var json = fs.readFileSync(__dirname + "/passes/1.json").toString(); 218 | assert.doesNotThrow(function () {parser.parse(json)}, "should pass"); 219 | } 220 | 221 | exports["test pass-2"] = function () { 222 | var json = fs.readFileSync(__dirname + "/passes/2.json").toString(); 223 | assert.doesNotThrow(function () {parser.parse(json)}, "should pass"); 224 | } 225 | 226 | exports["test pass-3"] = function () { 227 | var json = fs.readFileSync(__dirname + "/passes/3.json").toString(); 228 | assert.doesNotThrow(function () {parser.parse(json)}, "should pass"); 229 | } 230 | 231 | if (require.main === module) 232 | require("test").run(exports); 233 | -------------------------------------------------------------------------------- /test/fails/10.json: -------------------------------------------------------------------------------- 1 | {"Extra value after close": true} "misplaced quoted value" -------------------------------------------------------------------------------- /test/fails/11.json: -------------------------------------------------------------------------------- 1 | {"Illegal expression": 1 + 2} -------------------------------------------------------------------------------- /test/fails/12.json: -------------------------------------------------------------------------------- 1 | {"Illegal invocation": alert()} -------------------------------------------------------------------------------- /test/fails/13.json: -------------------------------------------------------------------------------- 1 | {"Numbers cannot have leading zeroes": 013} -------------------------------------------------------------------------------- /test/fails/14.json: -------------------------------------------------------------------------------- 1 | {"Numbers cannot be hex": 0x14} -------------------------------------------------------------------------------- /test/fails/15.json: -------------------------------------------------------------------------------- 1 | ["Illegal backslash escape: \x15"] -------------------------------------------------------------------------------- /test/fails/16.json: -------------------------------------------------------------------------------- 1 | [\naked] -------------------------------------------------------------------------------- /test/fails/17.json: -------------------------------------------------------------------------------- 1 | ["Illegal backslash escape: \017"] -------------------------------------------------------------------------------- /test/fails/19.json: -------------------------------------------------------------------------------- 1 | {"Missing colon" null} -------------------------------------------------------------------------------- /test/fails/2.json: -------------------------------------------------------------------------------- 1 | ["Unclosed array" -------------------------------------------------------------------------------- /test/fails/20.json: -------------------------------------------------------------------------------- 1 | {"Double colon":: null} -------------------------------------------------------------------------------- /test/fails/21.json: -------------------------------------------------------------------------------- 1 | {"Comma instead of colon", null} -------------------------------------------------------------------------------- /test/fails/22.json: -------------------------------------------------------------------------------- 1 | ["Colon instead of comma": false] -------------------------------------------------------------------------------- /test/fails/23.json: -------------------------------------------------------------------------------- 1 | ["Bad value", truth] -------------------------------------------------------------------------------- /test/fails/24.json: -------------------------------------------------------------------------------- 1 | ['single quote'] -------------------------------------------------------------------------------- /test/fails/25.json: -------------------------------------------------------------------------------- 1 | [" tab character in string "] -------------------------------------------------------------------------------- /test/fails/26.json: -------------------------------------------------------------------------------- 1 | ["tab\ character\ in\ string\ "] -------------------------------------------------------------------------------- /test/fails/27.json: -------------------------------------------------------------------------------- 1 | ["line 2 | break"] -------------------------------------------------------------------------------- /test/fails/28.json: -------------------------------------------------------------------------------- 1 | ["line\ 2 | break"] -------------------------------------------------------------------------------- /test/fails/29.json: -------------------------------------------------------------------------------- 1 | [0e] -------------------------------------------------------------------------------- /test/fails/3.json: -------------------------------------------------------------------------------- 1 | {unquoted_key: "keys must be quoted"} -------------------------------------------------------------------------------- /test/fails/30.json: -------------------------------------------------------------------------------- 1 | [0e+] -------------------------------------------------------------------------------- /test/fails/31.json: -------------------------------------------------------------------------------- 1 | [0e+-1] -------------------------------------------------------------------------------- /test/fails/32.json: -------------------------------------------------------------------------------- 1 | {"Comma instead if closing brace": true, -------------------------------------------------------------------------------- /test/fails/33.json: -------------------------------------------------------------------------------- 1 | ["mismatch"} -------------------------------------------------------------------------------- /test/fails/34.json: -------------------------------------------------------------------------------- 1 | {"extra brace": 1}} 2 | -------------------------------------------------------------------------------- /test/fails/4.json: -------------------------------------------------------------------------------- 1 | ["extra comma",] -------------------------------------------------------------------------------- /test/fails/5.json: -------------------------------------------------------------------------------- 1 | ["double extra comma",,] -------------------------------------------------------------------------------- /test/fails/6.json: -------------------------------------------------------------------------------- 1 | [ , "<-- missing value"] -------------------------------------------------------------------------------- /test/fails/7.json: -------------------------------------------------------------------------------- 1 | ["Comma after the close"], -------------------------------------------------------------------------------- /test/fails/8.json: -------------------------------------------------------------------------------- 1 | ["Extra close"]] -------------------------------------------------------------------------------- /test/fails/9.json: -------------------------------------------------------------------------------- 1 | {"Extra comma": true,} -------------------------------------------------------------------------------- /test/passes/1.json: -------------------------------------------------------------------------------- 1 | [ 2 | "JSON Test Pattern pass1", 3 | {"object with 1 member":["array with 1 element"]}, 4 | {}, 5 | [], 6 | -42, 7 | true, 8 | false, 9 | null, 10 | { 11 | "integer": 1234567890, 12 | "real": -9876.543210, 13 | "e": 0.123456789e-12, 14 | "E": 1.234567890E+34, 15 | "": 23456789012E66, 16 | "zero": 0, 17 | "one": 1, 18 | "space": " ", 19 | "quote": "\"", 20 | "backslash": "\\", 21 | "controls": "\b\f\n\r\t", 22 | "slash": "/ & \/", 23 | "alpha": "abcdefghijklmnopqrstuvwyz", 24 | "ALPHA": "ABCDEFGHIJKLMNOPQRSTUVWYZ", 25 | "digit": "0123456789", 26 | "0123456789": "digit", 27 | "special": "`1~!@#$%^&*()_+-={':[,]}|;.?", 28 | "hex": "\u0123\u4567\u89AB\uCDEF\uabcd\uef4A", 29 | "true": true, 30 | "false": false, 31 | "null": null, 32 | "array":[ ], 33 | "object":{ }, 34 | "address": "50 St. James Street", 35 | "url": "http://www.JSON.org/", 36 | "comment": "// /* */": " ", 38 | " s p a c e d " :[1,2 , 3 39 | 40 | , 41 | 42 | 4 , 5 , 6 ,7 ],"compact":[1,2,3,4,5,6,7], 43 | "jsontext": "{\"object with 1 member\":[\"array with 1 element\"]}", 44 | "quotes": "" \u0022 %22 0x22 034 "", 45 | "\/\\\"\uCAFE\uBABE\uAB98\uFCDE\ubcda\uef4A\b\f\n\r\t`1~!@#$%^&*()_+-=[]{}|;:',./<>?" 46 | : "A key can be any string" 47 | }, 48 | 0.5 ,98.6 49 | , 50 | 99.44 51 | , 52 | 53 | 1066, 54 | 1e1, 55 | 0.1e1, 56 | 1e-1, 57 | 1e00,2e+00,2e-00 58 | ,"rosebud"] -------------------------------------------------------------------------------- /test/passes/2.json: -------------------------------------------------------------------------------- 1 | [[[[[[[[[[[[[[[[[[["Not too deep"]]]]]]]]]]]]]]]]]]] -------------------------------------------------------------------------------- /test/passes/3.json: -------------------------------------------------------------------------------- 1 | { 2 | "JSON Test Pattern pass3": { 3 | "The outermost value": "must be an object or array.", 4 | "In this test": "It is an object." 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /web/json2.js: -------------------------------------------------------------------------------- 1 | 2 | /*jslint evil: true, strict: false */ 3 | 4 | /*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, 5 | call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, 6 | getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, 7 | lastIndex, length, parse, prototype, push, replace, slice, stringify, 8 | test, toJSON, toString, valueOf 9 | */ 10 | 11 | 12 | // Create a JSON object only if one does not already exist. We create the 13 | // methods in a closure to avoid creating global variables. 14 | 15 | if (!this.JSON) { 16 | this.JSON = {}; 17 | } 18 | 19 | (function () { 20 | 21 | function f(n) { 22 | // Format integers to have at least two digits. 23 | return n < 10 ? '0' + n : n; 24 | } 25 | 26 | if (typeof Date.prototype.toJSON !== 'function') { 27 | 28 | Date.prototype.toJSON = function (key) { 29 | 30 | return isFinite(this.valueOf()) ? 31 | this.getUTCFullYear() + '-' + 32 | f(this.getUTCMonth() + 1) + '-' + 33 | f(this.getUTCDate()) + 'T' + 34 | f(this.getUTCHours()) + ':' + 35 | f(this.getUTCMinutes()) + ':' + 36 | f(this.getUTCSeconds()) + 'Z' : null; 37 | }; 38 | 39 | String.prototype.toJSON = 40 | Number.prototype.toJSON = 41 | Boolean.prototype.toJSON = function (key) { 42 | return this.valueOf(); 43 | }; 44 | } 45 | 46 | var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 47 | escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 48 | gap, 49 | indent, 50 | meta = { // table of character substitutions 51 | '\b': '\\b', 52 | '\t': '\\t', 53 | '\n': '\\n', 54 | '\f': '\\f', 55 | '\r': '\\r', 56 | '"' : '\\"', 57 | '\\': '\\\\' 58 | }, 59 | rep; 60 | 61 | 62 | function quote(string) { 63 | 64 | // If the string contains no control characters, no quote characters, and no 65 | // backslash characters, then we can safely slap some quotes around it. 66 | // Otherwise we must also replace the offending characters with safe escape 67 | // sequences. 68 | 69 | escapable.lastIndex = 0; 70 | return escapable.test(string) ? 71 | '"' + string.replace(escapable, function (a) { 72 | var c = meta[a]; 73 | return typeof c === 'string' ? c : 74 | '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 75 | }) + '"' : 76 | '"' + string + '"'; 77 | } 78 | 79 | 80 | function str(key, holder) { 81 | 82 | // Produce a string from holder[key]. 83 | 84 | var i, // The loop counter. 85 | k, // The member key. 86 | v, // The member value. 87 | length, 88 | mind = gap, 89 | partial, 90 | value = holder[key]; 91 | 92 | // If the value has a toJSON method, call it to obtain a replacement value. 93 | 94 | if (value && typeof value === 'object' && 95 | typeof value.toJSON === 'function') { 96 | value = value.toJSON(key); 97 | } 98 | 99 | // If we were called with a replacer function, then call the replacer to 100 | // obtain a replacement value. 101 | 102 | if (typeof rep === 'function') { 103 | value = rep.call(holder, key, value); 104 | } 105 | 106 | // What happens next depends on the value's type. 107 | 108 | switch (typeof value) { 109 | case 'string': 110 | return quote(value); 111 | 112 | case 'number': 113 | 114 | // JSON numbers must be finite. Encode non-finite numbers as null. 115 | 116 | return isFinite(value) ? String(value) : 'null'; 117 | 118 | case 'boolean': 119 | case 'null': 120 | 121 | // If the value is a boolean or null, convert it to a string. Note: 122 | // typeof null does not produce 'null'. The case is included here in 123 | // the remote chance that this gets fixed someday. 124 | 125 | return String(value); 126 | 127 | // If the type is 'object', we might be dealing with an object or an array or 128 | // null. 129 | 130 | case 'object': 131 | 132 | // Due to a specification blunder in ECMAScript, typeof null is 'object', 133 | // so watch out for that case. 134 | 135 | if (!value) { 136 | return 'null'; 137 | } 138 | 139 | // Make an array to hold the partial results of stringifying this object value. 140 | 141 | gap += indent; 142 | partial = []; 143 | 144 | // Is the value an array? 145 | 146 | if (Object.prototype.toString.apply(value) === '[object Array]') { 147 | 148 | // The value is an array. Stringify every element. Use null as a placeholder 149 | // for non-JSON values. 150 | 151 | length = value.length; 152 | for (i = 0; i < length; i += 1) { 153 | partial[i] = str(i, value) || 'null'; 154 | } 155 | 156 | // Join all of the elements together, separated with commas, and wrap them in 157 | // brackets. 158 | 159 | v = partial.length === 0 ? '[]' : 160 | gap ? '[\n' + gap + 161 | partial.join(',\n' + gap) + '\n' + 162 | mind + ']' : 163 | '[' + partial.join(',') + ']'; 164 | gap = mind; 165 | return v; 166 | } 167 | 168 | // If the replacer is an array, use it to select the members to be stringified. 169 | 170 | if (rep && typeof rep === 'object') { 171 | length = rep.length; 172 | for (i = 0; i < length; i += 1) { 173 | k = rep[i]; 174 | if (typeof k === 'string') { 175 | v = str(k, value); 176 | if (v) { 177 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 178 | } 179 | } 180 | } 181 | } else { 182 | 183 | // Otherwise, iterate through all of the keys in the object. 184 | 185 | for (k in value) { 186 | if (Object.hasOwnProperty.call(value, k)) { 187 | v = str(k, value); 188 | if (v) { 189 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 190 | } 191 | } 192 | } 193 | } 194 | 195 | // Join all of the member texts together, separated with commas, 196 | // and wrap them in braces. 197 | 198 | v = partial.length === 0 ? '{}' : 199 | gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + 200 | mind + '}' : '{' + partial.join(',') + '}'; 201 | gap = mind; 202 | return v; 203 | } 204 | } 205 | 206 | // If the JSON object does not yet have a stringify method, give it one. 207 | 208 | if (typeof JSON.stringify !== 'function') { 209 | JSON.stringify = function (value, replacer, space) { 210 | 211 | // The stringify method takes a value and an optional replacer, and an optional 212 | // space parameter, and returns a JSON text. The replacer can be a function 213 | // that can replace values, or an array of strings that will select the keys. 214 | // A default replacer method can be provided. Use of the space parameter can 215 | // produce text that is more easily readable. 216 | 217 | var i; 218 | gap = ''; 219 | indent = ''; 220 | 221 | // If the space parameter is a number, make an indent string containing that 222 | // many spaces. 223 | 224 | if (typeof space === 'number') { 225 | for (i = 0; i < space; i += 1) { 226 | indent += ' '; 227 | } 228 | 229 | // If the space parameter is a string, it will be used as the indent string. 230 | 231 | } else if (typeof space === 'string') { 232 | indent = space; 233 | } 234 | 235 | // If there is a replacer, it must be a function or an array. 236 | // Otherwise, throw an error. 237 | 238 | rep = replacer; 239 | if (replacer && typeof replacer !== 'function' && 240 | (typeof replacer !== 'object' || 241 | typeof replacer.length !== 'number')) { 242 | throw new Error('JSON.stringify'); 243 | } 244 | 245 | // Make a fake root object containing our value under the key of ''. 246 | // Return the result of stringifying the value. 247 | 248 | return str('', {'': value}); 249 | }; 250 | } 251 | 252 | 253 | // If the JSON object does not yet have a parse method, give it one. 254 | 255 | if (typeof JSON.parse !== 'function') { 256 | JSON.parse = function (text, reviver) { 257 | 258 | // The parse method takes a text and an optional reviver function, and returns 259 | // a JavaScript value if the text is a valid JSON text. 260 | 261 | var j; 262 | 263 | function walk(holder, key) { 264 | 265 | // The walk method is used to recursively walk the resulting structure so 266 | // that modifications can be made. 267 | 268 | var k, v, value = holder[key]; 269 | if (value && typeof value === 'object') { 270 | for (k in value) { 271 | if (Object.hasOwnProperty.call(value, k)) { 272 | v = walk(value, k); 273 | if (v !== undefined) { 274 | value[k] = v; 275 | } else { 276 | delete value[k]; 277 | } 278 | } 279 | } 280 | } 281 | return reviver.call(holder, key, value); 282 | } 283 | 284 | 285 | // Parsing happens in four stages. In the first stage, we replace certain 286 | // Unicode characters with escape sequences. JavaScript handles many characters 287 | // incorrectly, either silently deleting them, or treating them as line endings. 288 | 289 | cx.lastIndex = 0; 290 | if (cx.test(text)) { 291 | text = text.replace(cx, function (a) { 292 | return '\\u' + 293 | ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 294 | }); 295 | } 296 | 297 | // In the second stage, we run the text against regular expressions that look 298 | // for non-JSON patterns. We are especially concerned with '()' and 'new' 299 | // because they can cause invocation, and '=' because it can cause mutation. 300 | // But just to be safe, we want to reject all unexpected forms. 301 | 302 | // We split the second stage into 4 regexp operations in order to work around 303 | // crippling inefficiencies in IE's and Safari's regexp engines. First we 304 | // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we 305 | // replace all simple value tokens with ']' characters. Third, we delete all 306 | // open brackets that follow a colon or comma or that begin the text. Finally, 307 | // we look to see that the remaining characters are only whitespace or ']' or 308 | // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. 309 | 310 | if (/^[\],:{}\s]*$/. 311 | test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@'). 312 | replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'). 313 | replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { 314 | 315 | // In the third stage we use the eval function to compile the text into a 316 | // JavaScript structure. The '{' operator is subject to a syntactic ambiguity 317 | // in JavaScript: it can begin a block or an object literal. We wrap the text 318 | // in parens to eliminate the ambiguity. 319 | 320 | j = eval('(' + text + ')'); 321 | 322 | // In the optional fourth stage, we recursively walk the new structure, passing 323 | // each name/value pair to a reviver function for possible transformation. 324 | 325 | return typeof reviver === 'function' ? 326 | walk({'': j}, '') : j; 327 | } 328 | 329 | // If the text is not JSON parseable, then a SyntaxError is thrown. 330 | 331 | throw new SyntaxError('JSON.parse'); 332 | }; 333 | } 334 | }()); 335 | -------------------------------------------------------------------------------- /web/jsonlint.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JSON Lint 6 | 7 | 8 | 27 | 44 | 45 | 46 |

JSON Lint

47 |

A pure JavaScript version of the service provided at jsonlint.com.

48 | 51 |

52 | 53 | 54 |

55 |

Results

56 |

57 |   

project on github

58 | 59 | 60 | -------------------------------------------------------------------------------- /web/jsonlint.js: -------------------------------------------------------------------------------- 1 | var jsonlint=function(){var a=!0,b=!1,c={},d=function(){var a={trace:function(){},yy:{},symbols_:{error:2,JSONString:3,STRING:4,JSONNumber:5,NUMBER:6,JSONNullLiteral:7,NULL:8,JSONBooleanLiteral:9,TRUE:10,FALSE:11,JSONText:12,JSONValue:13,EOF:14,JSONObject:15,JSONArray:16,"{":17,"}":18,JSONMemberList:19,JSONMember:20,":":21,",":22,"[":23,"]":24,JSONElementList:25,$accept:0,$end:1},terminals_:{2:"error",4:"STRING",6:"NUMBER",8:"NULL",10:"TRUE",11:"FALSE",14:"EOF",17:"{",18:"}",21:":",22:",",23:"[",24:"]"},productions_:[0,[3,1],[5,1],[7,1],[9,1],[9,1],[12,2],[13,1],[13,1],[13,1],[13,1],[13,1],[13,1],[15,2],[15,3],[20,3],[19,1],[19,3],[16,2],[16,3],[25,1],[25,3]],performAction:function(b,c,d,e,f,g,h){var i=g.length-1;switch(f){case 1:this.$=b.replace(/\\(\\|")/g,"$1").replace(/\\n/g,"\n").replace(/\\r/g,"\r").replace(/\\t/g," ").replace(/\\v/g," ").replace(/\\f/g,"\f").replace(/\\b/g,"\b");break;case 2:this.$=Number(b);break;case 3:this.$=null;break;case 4:this.$=!0;break;case 5:this.$=!1;break;case 6:return this.$=g[i-1];case 13:this.$={};break;case 14:this.$=g[i-1];break;case 15:this.$=[g[i-2],g[i]];break;case 16:this.$={},this.$[g[i][0]]=g[i][1];break;case 17:this.$=g[i-2],g[i-2][g[i][0]]=g[i][1];break;case 18:this.$=[];break;case 19:this.$=g[i-1];break;case 20:this.$=[g[i]];break;case 21:this.$=g[i-2],g[i-2].push(g[i])}},table:[{3:5,4:[1,12],5:6,6:[1,13],7:3,8:[1,9],9:4,10:[1,10],11:[1,11],12:1,13:2,15:7,16:8,17:[1,14],23:[1,15]},{1:[3]},{14:[1,16]},{14:[2,7],18:[2,7],22:[2,7],24:[2,7]},{14:[2,8],18:[2,8],22:[2,8],24:[2,8]},{14:[2,9],18:[2,9],22:[2,9],24:[2,9]},{14:[2,10],18:[2,10],22:[2,10],24:[2,10]},{14:[2,11],18:[2,11],22:[2,11],24:[2,11]},{14:[2,12],18:[2,12],22:[2,12],24:[2,12]},{14:[2,3],18:[2,3],22:[2,3],24:[2,3]},{14:[2,4],18:[2,4],22:[2,4],24:[2,4]},{14:[2,5],18:[2,5],22:[2,5],24:[2,5]},{14:[2,1],18:[2,1],21:[2,1],22:[2,1],24:[2,1]},{14:[2,2],18:[2,2],22:[2,2],24:[2,2]},{3:20,4:[1,12],18:[1,17],19:18,20:19},{3:5,4:[1,12],5:6,6:[1,13],7:3,8:[1,9],9:4,10:[1,10],11:[1,11],13:23,15:7,16:8,17:[1,14],23:[1,15],24:[1,21],25:22},{1:[2,6]},{14:[2,13],18:[2,13],22:[2,13],24:[2,13]},{18:[1,24],22:[1,25]},{18:[2,16],22:[2,16]},{21:[1,26]},{14:[2,18],18:[2,18],22:[2,18],24:[2,18]},{22:[1,28],24:[1,27]},{22:[2,20],24:[2,20]},{14:[2,14],18:[2,14],22:[2,14],24:[2,14]},{3:20,4:[1,12],20:29},{3:5,4:[1,12],5:6,6:[1,13],7:3,8:[1,9],9:4,10:[1,10],11:[1,11],13:30,15:7,16:8,17:[1,14],23:[1,15]},{14:[2,19],18:[2,19],22:[2,19],24:[2,19]},{3:5,4:[1,12],5:6,6:[1,13],7:3,8:[1,9],9:4,10:[1,10],11:[1,11],13:31,15:7,16:8,17:[1,14],23:[1,15]},{18:[2,17],22:[2,17]},{18:[2,15],22:[2,15]},{22:[2,21],24:[2,21]}],defaultActions:{16:[2,6]},parseError:function(b,c){throw new Error(b)},parse:function(b){function o(a){d.length=d.length-2*a,e.length=e.length-a,f.length=f.length-a}function p(){var a;return a=c.lexer.lex()||1,typeof a!="number"&&(a=c.symbols_[a]||a),a}var c=this,d=[0],e=[null],f=[],g=this.table,h="",i=0,j=0,k=0,l=2,m=1;this.lexer.setInput(b),this.lexer.yy=this.yy,this.yy.lexer=this.lexer,typeof this.lexer.yylloc=="undefined"&&(this.lexer.yylloc={});var n=this.lexer.yylloc;f.push(n),typeof this.yy.parseError=="function"&&(this.parseError=this.yy.parseError);var q,r,s,t,u,v,w={},x,y,z,A;for(;;){s=d[d.length-1],this.defaultActions[s]?t=this.defaultActions[s]:(q==null&&(q=p()),t=g[s]&&g[s][q]);if(typeof t=="undefined"||!t.length||!t[0]){if(!k){A=[];for(x in g[s])this.terminals_[x]&&x>2&&A.push("'"+this.terminals_[x]+"'");var B="";this.lexer.showPosition?B="Parse error on line "+(i+1)+":\n"+this.lexer.showPosition()+"\nExpecting "+A.join(", ")+", got '"+this.terminals_[q]+"'":B="Parse error on line "+(i+1)+": Unexpected "+(q==1?"end of input":"'"+(this.terminals_[q]||q)+"'"),this.parseError(B,{text:this.lexer.match,token:this.terminals_[q]||q,line:this.lexer.yylineno,loc:n,expected:A})}if(k==3){if(q==m)throw new Error(B||"Parsing halted.");j=this.lexer.yyleng,h=this.lexer.yytext,i=this.lexer.yylineno,n=this.lexer.yylloc,q=p()}for(;;){if(l.toString()in g[s])break;if(s==0)throw new Error(B||"Parsing halted.");o(1),s=d[d.length-1]}r=q,q=l,s=d[d.length-1],t=g[s]&&g[s][l],k=3}if(t[0]instanceof Array&&t.length>1)throw new Error("Parse Error: multiple actions possible at state: "+s+", token: "+q);switch(t[0]){case 1:d.push(q),e.push(this.lexer.yytext),f.push(this.lexer.yylloc),d.push(t[1]),q=null,r?(q=r,r=null):(j=this.lexer.yyleng,h=this.lexer.yytext,i=this.lexer.yylineno,n=this.lexer.yylloc,k>0&&k--);break;case 2:y=this.productions_[t[1]][1],w.$=e[e.length-y],w._$={first_line:f[f.length-(y||1)].first_line,last_line:f[f.length-1].last_line,first_column:f[f.length-(y||1)].first_column,last_column:f[f.length-1].last_column},v=this.performAction.call(w,h,j,i,this.yy,t[1],e,f);if(typeof v!="undefined")return v;y&&(d=d.slice(0,-1*y*2),e=e.slice(0,-1*y),f=f.slice(0,-1*y)),d.push(this.productions_[t[1]][0]),e.push(w.$),f.push(w._$),z=g[d[d.length-2]][d[d.length-1]],d.push(z);break;case 3:return!0}}return!0}},b=function(){var a={EOF:1,parseError:function(b,c){if(!this.yy.parseError)throw new Error(b);this.yy.parseError(b,c)},setInput:function(a){return this._input=a,this._more=this._less=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this},input:function(){var a=this._input[0];this.yytext+=a,this.yyleng++,this.match+=a,this.matched+=a;var b=a.match(/\n/);return b&&this.yylineno++,this._input=this._input.slice(1),a},unput:function(a){return this._input=a+this._input,this},more:function(){return this._more=!0,this},less:function(a){this._input=this.match.slice(a)+this._input},pastInput:function(){var a=this.matched.substr(0,this.matched.length-this.match.length);return(a.length>20?"...":"")+a.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var a=this.match;return a.length<20&&(a+=this._input.substr(0,20-a.length)),(a.substr(0,20)+(a.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var a=this.pastInput(),b=(new Array(a.length+1)).join("-");return a+this.upcomingInput()+"\n"+b+"^"},next:function(){if(this.done)return this.EOF;this._input||(this.done=!0);var a,b,c,d,e,f;this._more||(this.yytext="",this.match="");var g=this._currentRules();for(var h=0;hb[0].length)){b=c,d=h;if(!this.options.flex)break}}if(b){f=b[0].match(/\n.*/g),f&&(this.yylineno+=f.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:f?f[f.length-1].length-1:this.yylloc.last_column+b[0].length},this.yytext+=b[0],this.match+=b[0],this.yyleng=this.yytext.length,this._more=!1,this._input=this._input.slice(b[0].length),this.matched+=b[0],a=this.performAction.call(this,this.yy,this,g[d],this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1);if(a)return a;return}if(this._input==="")return this.EOF;this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var b=this.next();return typeof b!="undefined"?b:this.lex()},begin:function(b){this.conditionStack.push(b)},popState:function(){return this.conditionStack.pop()},_currentRules:function(){return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules},topState:function(){return this.conditionStack[this.conditionStack.length-2]},pushState:function(b){this.begin(b)}};return a.options={},a.performAction=function(b,c,d,e){var f=e;switch(d){case 0:break;case 1:return 6;case 2:return c.yytext=c.yytext.substr(1,c.yyleng-2),4;case 3:return 17;case 4:return 18;case 5:return 23;case 6:return 24;case 7:return 22;case 8:return 21;case 9:return 10;case 10:return 11;case 11:return 8;case 12:return 14;case 13:return"INVALID"}},a.rules=[/^(?:\s+)/,/^(?:(-?([0-9]|[1-9][0-9]+))(\.[0-9]+)?([eE][-+]?[0-9]+)?\b)/,/^(?:"(?:\\[\\"bfnrt/]|\\u[a-fA-F0-9]{4}|[^\\\0-\x09\x0a-\x1f"])*")/,/^(?:\{)/,/^(?:\})/,/^(?:\[)/,/^(?:\])/,/^(?:,)/,/^(?::)/,/^(?:true\b)/,/^(?:false\b)/,/^(?:null\b)/,/^(?:$)/,/^(?:.)/],a.conditions={INITIAL:{rules:[0,1,2,3,4,5,6,7,8,9,10,11,12,13],inclusive:!0}},a}();return a.lexer=b,a}();return typeof a!="undefined"&&typeof c!="undefined"&&(c.parser=d,c.parse=function(){return d.parse.apply(d,arguments)},c.main=function(d){if(!d[1])throw new Error("Usage: "+d[0]+" FILE");if(typeof process!="undefined")var e=a("fs").readFileSync(a("path").join(process.cwd(),d[1]),"utf8");else var f=a("file").path(a("file").cwd()),e=f.join(d[1]).read({charset:"utf-8"});return c.parser.parse(e)},typeof b!="undefined"&&a.main===b&&c.main(typeof process!="undefined"?process.argv.slice(1):a("system").args)),c}(); --------------------------------------------------------------------------------