Skip to content

Commit

Permalink
feat: Add web and programmatic interfaces to JSON Schema validation
Browse files Browse the repository at this point in the history
* Introduce a new module "jsonlint/lib/validator".
* Add schema support on the web page with the on-line validator.
  • Loading branch information
prantlf committed May 19, 2019
1 parent b8b041b commit d45b243
Show file tree
Hide file tree
Showing 11 changed files with 220 additions and 76 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Expand Up @@ -2,3 +2,4 @@ jsonlint*.js
test/fails
test/passes
test/recursive
web/*.min.*
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -2,3 +2,4 @@
coverage
node_modules
jsonlint*.js
web/*.min.*
26 changes: 14 additions & 12 deletions README.md
Expand Up @@ -11,8 +11,8 @@ A JSON parser and validator with a command-line client. A [pure JavaScript versi

This is a fork of the original package with the following extensions:

* Handles multiple files on the command line (Greg Inman).
* Walks directories recursively (Paul Vollmer).
* Handles multiple files on the command line (by Greg Inman).
* Walks directories recursively (by Paul Vollmer).
* Supports JSON schema drafts 04, 06 and 07.
* Can parse and skip JavaScript-style comments.
* Depends on up-to-date npm modules with no installation warnings.
Expand Down Expand Up @@ -43,7 +43,7 @@ or process all `.json` files in a directory:

Usage: jsonlint [options] [<file or directory> ...]

JSON validator - checks syntax of JSON files.
JSON parser and validator - checks syntax and semantics of JSON data.

Options:
-s, --sort-keys sort object keys
Expand Down Expand Up @@ -75,14 +75,21 @@ Install `jsonlint` with `npm` locally to be able to use the module programmatica
You might prefer methods this module to the built-in `JSON.parse` method because of a better error reporting or support for JavaScript-like comments:

```js
var jsonlint = require('jsonlint')
const { parser } = require('jsonlint')
// Fails at the position of the character "?".
jsonlint.parse('{"creative?": false}') // fails
parser.parse('{"creative?": false}') // fails
// Succeeds returning the parsed JSON object.
jsonlint.parseWithComments('{"creative": false /* for creativity */}')
parser.parseWithComments('{"creative": false /* for creativity */}')
```

Parsing methods return the parsed object or throw an `Error`.
Parsing methods return the parsed object or throw an `Error`. If the data cam be parsed, you will be able to validate them against a JSON schema:

```js
const { parser } = require('jsonlint')
const validator = require('jsonlint/lib/validator')
const validate = validator.compile('string with JSON schema')
validate(parser.parse('string with JSON data'))
```

### Performance

Expand All @@ -94,11 +101,6 @@ These are the results of parsing a 2.2 KB formatted string (package.json) with N

The custom pure-JavaScript parser is a lot slower than the built-in one. However, it is more important to have a clear error reporting than the highest speed in scenarios like parsing configuration files.

## Vim Plugins

* [Syntastic](http://www.vim.org/scripts/script.php?script_id=2736)
* [sourcebeautify](http://www.vim.org/scripts/script.php?script_id=4079)

## License

Copyright (C) 2012-2019 Zachary Carter, Ferdinand Prantl
Expand Down
43 changes: 17 additions & 26 deletions lib/cli.js
Expand Up @@ -3,8 +3,8 @@
var fs = require('fs')
var path = require('path')
var parser = require('./jsonlint').parser
var formatter = require('./formatter.js').formatter
var Ajv = require('ajv')
var formatter = require('./formatter')
var validator = require('./validator')
var pkg = require('../package')

function collectExtensions (extension) {
Expand Down Expand Up @@ -55,35 +55,26 @@ function parse (source, file) {
parsed = sortObject(parsed)
}
if (options.validate) {
var ajv
if (options.environment === 'json-schema-draft-07') {
ajv = new Ajv()
} else if (options.environment === 'json-schema-draft-06') {
ajv = new Ajv()
ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-06.json'))
} else if (options.environment === 'json-schema-draft-04') {
ajv = new Ajv({ schemaId: 'auto' })
ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-04.json'))
} else {
throw new Error('Unsupported environment for the JSON schema validation: "' +
options.environment + '".')
}
var validate
try {
var schema = parser.parse(fs.readFileSync(path.normalize(options.validate), 'utf8'))
validate = ajv.compile(schema)
var schema = fs.readFileSync(path.normalize(options.validate), 'utf8')
validate = validator.compile(schema, options.environment)
} catch (error) {
throw new Error('Loading the JSON schema failed: "' +
options.validate + '".\n' + error.message)
}
var result = validate(parsed)
if (!result) {
var message = ajv.errorsText(validate.errors)
var message = 'Loading the JSON schema failed: "' +
options.validate + '".\n' + error.message
if (options.compact) {
console.error(currentFileName + ':', message)
console.error(currentFileName + ':', error.message)
}
throw new Error(message)
}
try {
validate(parsed)
} catch (error) {
if (options.compact) {
console.error(currentFileName + ':', error.message)
}
throw error
}
}
return JSON.stringify(parsed, null, options.indent)
} catch (e) {
Expand All @@ -94,7 +85,7 @@ function parse (source, file) {
* manual formatter because the automatic one is faster and probably more reliable.
*/
try {
formatted = formatter.formatJson(source, options.indent)
formatted = formatter.format(source, options.indent)
// Re-parse so exception output gets better line numbers
parsed = parser.parse(formatted)
} catch (e) {
Expand All @@ -119,7 +110,7 @@ function processFile (file) {
currentFileName = path.normalize(file)
var source = parse(fs.readFileSync(currentFileName, 'utf8'), currentFileName)
if (options.inPlace) {
fs.writeSync(fs.openSync(currentFileName, 'w+'), source, 0, 'utf8')
fs.writeFileSync(currentFileName, source)
} else {
if (!options.quiet) { console.log(source) };
}
Expand Down
56 changes: 56 additions & 0 deletions lib/validator.js
@@ -0,0 +1,56 @@
var validator = (function () {
var jsonlint, Ajv, requireDraft
if (typeof window !== 'undefined') {
jsonlint = window.jsonlint
Ajv = window.Ajv
requireDraft = function (environment) {
return window.drafts[environment]
}
} else {
jsonlint = require('./jsonlint')
Ajv = require('ajv')
requireDraft = function (environment) {
return require('ajv/lib/refs/' + environment + '.json')
}
}

function compile (schema, environment) {
var ajv
if (!environment) {
ajv = new Ajv({ schemaId: 'auto' })
ajv.addMetaSchema(requireDraft('json-schema-draft-04'))
ajv.addMetaSchema(requireDraft('json-schema-draft-06'))
} else if (environment === 'json-schema-draft-07') {
ajv = new Ajv()
} else if (environment === 'json-schema-draft-06') {
ajv = new Ajv()
ajv.addMetaSchema(requireDraft('json-schema-draft-06'))
} else if (environment === 'json-schema-draft-04') {
ajv = new Ajv({ schemaId: 'id' })
ajv.addMetaSchema(requireDraft('json-schema-draft-04'))
} else {
throw new Error('Unsupported environment for the JSON schema validation: "' +
environment + '".')
}
var validate
try {
schema = jsonlint.parse(schema)
validate = ajv.compile(schema)
} catch (error) {
throw new Error('Compiling the JSON schema failed.\n' + error.message)
}
return function (data) {
var result = validate(data)
if (!result) {
var message = ajv.errorsText(validate.errors)
throw new Error(message)
}
}
}

return { compile: compile }
}())

if (typeof require !== 'undefined' && typeof exports !== 'undefined') {
exports.compile = validator.compile
}
8 changes: 5 additions & 3 deletions package.json
@@ -1,7 +1,7 @@
{
"name": "@prantlf/jsonlint",
"version": "0.0.0-development",
"description": "JSON validator - checks syntax of JSON files.",
"description": "JSON parser and validator - checks syntax and semantics of JSON data.",
"author": "Ferdinand Prantl <prantlf@gmail.com> (http://prantl.tk)",
"contributors": [
"Ferdinand Prantl <prantlf@gmail.com> (http://prantl.tk)",
Expand Down Expand Up @@ -35,13 +35,15 @@
"scripts": {
"prepare": "npm run lint && npm run build",
"lint": "standard --fix -v",
"build": "jison -o lib/jsonlint.js src/jsonlint.y src/jsonlint.l && node scripts/bundle.js | uglifyjs > web/jsonlint.js",
"build": "jison -o lib/jsonlint.js src/jsonlint.y src/jsonlint.l && node scripts/bundle-script lib/jsonlint.js jsonlint | uglifyjs -o web/jsonlint.min.js --source-map \"filename='jsonlint.js',url='jsonlint.min.js.map'\" && node scripts/bundle-script lib/validator.js validator | uglifyjs -o web/validator.min.js --source-map \"filename='validator.js',url='validator.min.js.map'\" && node scripts/bundle-drafts | uglifyjs -o web/drafts.min.js --source-map \"filename='drafts.js',url='drafts.min.js.map'\" && cp node_modules/ajv/dist/ajv.min.* web/",
"benchmarks": "jison -o benchmarks/jsonlint-pure.js benchmarks/jsonlint-pure.y benchmarks/jsonlint-pure.l && node benchmarks/parse.js",
"test": "npm run test:module && npm run test:cli",
"test:module": "node test/all-tests",
"test:cli": "nyc --silent node lib/cli package.json test/recursive && nyc --silent --no-clean node lib/cli -s -e json-schema-draft-04 -V test/passes/3.schema.json test/passes/3.json && nyc --silent --no-clean node lib/cli -C test/passes/comments.txt && nyc --silent --no-clean node lib/cli -v && nyc --silent --no-clean node lib/cli -h && nyc --silent --no-clean node lib/cli -pc test/fails/10.json || nyc report",
"coverage": "cat coverage/lcov.info | npx coveralls",
"site": "cp web/jsonlint.js ../jsonlint-pages/ && cp web/jsonlint.html ../jsonlint-pages/index.html",
"start": "python -m SimpleHTTPServer",
"web": "npm run site && npm run deploy",
"site": "cp web/*.min.* ../jsonlint-pages/ && cp web/jsonlint.html ../jsonlint-pages/index.html",
"deploy": "cd ../jsonlint-pages && git commit -a -m 'Deploy site updates' && git push origin gh-pages"
},
"standard": {
Expand Down
18 changes: 18 additions & 0 deletions scripts/bundle-drafts.js
@@ -0,0 +1,18 @@
var fs = require('fs')
var path = require('path')

var environments = [
'json-schema-draft-04',
'json-schema-draft-06',
'json-schema-draft-07'
]
var drafts = environments.map(function (environment) {
var draftFile = path.join(__dirname, '../node_modules/ajv/lib/refs/' + environment + '.json')
var draftSource = fs.readFileSync(draftFile)
return 'exports["' + environment + '"]=' + draftSource
})
var source = 'var drafts=(function(){var require=true,module=false,exports={};\n' +
drafts.join('\n') +
'\nreturn exports})()'

console.log(source)
11 changes: 11 additions & 0 deletions scripts/bundle-script.js
@@ -0,0 +1,11 @@
var fs = require('fs')

var scriptFile = process.argv[2]
var exportName = process.argv[3]

var source = 'var ' + exportName +
'=(function(){var require=true,module=false,exports={};\n' +
fs.readFileSync(scriptFile, 'utf8') +
'\nreturn exports})()'

console.log(source)
8 changes: 0 additions & 8 deletions scripts/bundle.js

This file was deleted.

15 changes: 15 additions & 0 deletions test/all-tests.js
Expand Up @@ -6,6 +6,7 @@ var path = require('path')
var assert = require('assert')

var parser = require('../lib/jsonlint').parser
var validator = require('../lib/validator')

exports['test object'] = function () {
var json = '{"foo": "bar"}'
Expand Down Expand Up @@ -233,4 +234,18 @@ exports['test pass-3'] = function () {
assert.doesNotThrow(function () { parser.parse(json) }, 'should pass')
}

exports['test schema validation success'] = function () {
var data = fs.readFileSync(path.join(__dirname, '/passes/3.json')).toString()
var schema = fs.readFileSync(path.join(__dirname, '/passes/3.schema.json')).toString()
var validate = validator.compile(schema)
assert.doesNotThrow(function () { validate(parser.parse(data)) }, 'should pass')
}

exports['test schema validation failure'] = function () {
var data = fs.readFileSync(path.join(__dirname, '/passes/3.schema.json')).toString()
var schema = fs.readFileSync(path.join(__dirname, '/passes/3.schema.json')).toString()
var validate = validator.compile(schema)
assert['throws'](function () { validate(parser.parse(data)) }, 'should throw error')
}

if (require.main === module) { require('test').run(exports) }

0 comments on commit d45b243

Please sign in to comment.