Skip to content

Commit

Permalink
feat: Support JSON Schema drafts 2019-09 and 2020-12 and JSON Type De…
Browse files Browse the repository at this point in the history
…finition

Upgrade AJV to the latest version and retain the previous AJV@6
to be able to suport JSON Schema draft 04.

BREAKING CHANGE: The default environment recognises only JSON Schema drafts 06 and 07 automatically. Not 04 any more. The environment for JSON Schema drafts 04 has to be selected explicitly. Also, JSON Schema drafts 06 and 07 are handled by AJV@8 instead of AJV@6. It shouldn't make any difference, but the implementation is new and could perform a stricter validation.
  • Loading branch information
prantlf committed Mar 5, 2023
1 parent 45f481c commit 0b9130c
Show file tree
Hide file tree
Showing 21 changed files with 516 additions and 99 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,5 @@ benchmarks/nearley/*.js
coverage
node_modules
lib/jsonlint*.js
lib/schema-drafts.js
test/types.test.mjs
web/*.min.*
2 changes: 1 addition & 1 deletion .ncurc.yml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
reject:
- ajv
- ajv6
9 changes: 9 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@
"<node_internals>/**/*.js"
]
},
{
"name": "types",
"type": "node",
"request": "launch",
"program": "${workspaceRoot}/test/types.test.mjs",
"skipFiles": [
"<node_internals>/**/*.js"
]
},
{
"name": "fail",
"type": "node",
Expand Down
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ Usage: `jsonlint [options] [<file, directory, pattern> ...]`
-S, --single-quoted-strings support single quotes as string delimiters
-T, --trailing-commas ignore trailing commas in objects and arrays
-D, --no-duplicate-keys report duplicate object keys as an error
-V, --validate [file] JSON schema file to use for validation
-V, --validate [file] JSON Schema file to use for validation
-e, --environment [env] which specification of JSON Schema the
validation file uses
-x, --context [num] line count used as the diff context (default: 3)
Expand Down Expand Up @@ -164,8 +164,10 @@ A pattern to exclude from processing starts with "!".

Parsing mode can be "cjson" or "json5" to enable other flags automatically.
If no files or directories are specified, stdin will be parsed. Environments
for JSON schema validation are "json-schema-draft-04", "json-schema-draft-06"
or "json-schema-draft-07". If not specified, it will be auto-detected.
for JSON Schema validation are "draft-04", "draft-06", "draft-07",
"draft-2019-09" or "draft-2020-12". The environment may be prefixed
with "json-schema-". JSON Type Definition can be selected by "rfc8927",
"json-type-definition" or "jtd". If not specified, it will be "draft-07".

### Configuration

Expand Down Expand Up @@ -272,21 +274,19 @@ The `mode` parameter (string) sets parsing options to match a common format of i

### Schema Validation

You can validate the input against a JSON schema using the `lib/validator` module. The `validate` method accepts either an earlier parsed JSON data or a string with the JSON input:
You can validate the input against a JSON Schema using the `lib/validator` module. The `validate` method accepts either an earlier parsed JSON data or a string with the JSON input:

```js
const { compile } = require('@prantlf/jsonlint/lib/validator')
const validate = compile('string with JSON schema')
const validate = compile('string with JSON Schema')
// Throws an error in case of failure.
const parsed = validate('string with JSON data')
```

If a string is passed to the `validate` method, the same options as for parsing JSON data can be passed as the second parameter. Compiling JSON schema supports the same options as parsing JSON data too (except for `reviver`). They can be passed as the second (object) parameter. The optional second `environment` parameter can be passed either as a string or as an additional property in the options object too:
If a string is passed to the `validate` method, the same options as for parsing JSON data can be passed as the second parameter. Compiling JSON Schema supports the same options as parsing JSON data too (except for `reviver`). They can be passed as the second (object) parameter. The optional second `environment` parameter can be passed either as a string or as an additional property in the options object too:

```js
const validate = compile('string with JSON schema', {
environment: 'json-schema-draft-04'
})
const validate = compile('string with JSON Schema', { environment: 'draft-2020-12' })
```

### Pretty-Printing
Expand Down
10 changes: 6 additions & 4 deletions lib/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const commander = require('commander')
.option('-S, --single-quoted-strings', 'support single quotes as string delimiters')
.option('-T, --trailing-commas', 'ignore trailing commas in objects and arrays')
.option('-D, --no-duplicate-keys', 'report duplicate object keys as an error')
.option('-V, --validate [file]', 'JSON schema file to use for validation')
.option('-V, --validate [file]', 'JSON Schema file to use for validation')
.option('-e, --environment [env]', 'which specification of JSON Schema the validation file uses')
.option('-x, --context [num]', 'line count used as the diff context', 3)
.option('-l, --log-files', 'print only the parsed file names to stdout')
Expand All @@ -55,8 +55,10 @@ const commander = require('commander')
console.log()
console.log('Parsing mode can be "cjson" or "json5" to enable other flags automatically.')
console.log('If no files or directories are specified, stdin will be parsed. Environments')
console.log('for JSON schema validation are "json-schema-draft-04", "json-schema-draft-06"')
console.log('or "json-schema-draft-07". If not specified, it will be auto-detected.')
console.log('for JSON Schema validation are "draft-04", "draft-06", "draft-07",')
console.log('"draft-2019-09" or "draft-2020-12". The environment may be prefixed')
console.log('with "json-schema-". JSON Type Definition can be selected by "rfc8927",')
console.log('"json-type-definition" or "jtd". If not specified, it will be "draft-07".')
})
.parse(process.argv)

Expand Down Expand Up @@ -149,7 +151,7 @@ function processContents (source, file) {
parserOptions.environment = options.environment
validate = compile(schema, parserOptions)
} catch (error) {
const message = 'Loading the JSON schema failed: "' +
const message = 'Loading the JSON Schema failed: "' +
options.validate + '".\n' + error.message
throw new Error(message)
}
Expand Down
12 changes: 10 additions & 2 deletions lib/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,11 @@ declare module '@prantlf/jsonlint/lib/validator' {
/**
* Identifiers of supported JSON Schema drafts and JSON Type Definition.
*/
type Environment = 'json-schema-draft-04' | 'json-schema-draft-06' | 'json-schema-draft-07'
type Environment = 'json-schema-draft-04' | 'draft-04' |
'json-schema-draft-06' | 'draft-06' | 'json-schema-draft-07' | 'draft-07' |
'json-schema-draft-2019-09' | 'draft-2019-09' |
'json-schema-draft-2020-12' | 'draft-2020-12' |
'json-type-definition' | 'jtd' | 'rfc8927'

/**
* Options to customize a JSON Schema validator.
Expand Down Expand Up @@ -414,7 +418,11 @@ declare module '@prantlf/jsonlint/lib/validator' {
/**
* Choose the JSON Schema draft or JSON Type Definition.
*
* Available values: `'json-schema-draft-04' | 'json-schema-draft-06' | 'json-schema-draft-07'`
* Available values: `'json-schema-draft-04' | 'draft-04' |
* 'json-schema-draft-06' | 'draft-06' | 'json-schema-draft-07' | 'draft-07' |
* 'json-schema-draft-2019-09' | 'draft-2019-09' |
* 'json-schema-draft-2020-12' | 'draft-2020-12' |
* 'json-type-definition' | 'jtd' | 'rfc8927'`
*/
environment?: Environment
}
Expand Down
84 changes: 49 additions & 35 deletions lib/validator.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,40 @@
(function (global, factory) {
if (typeof exports === 'object' && typeof module !== 'undefined') {
const jsonlint = require('./jsonlint')
const Ajv = require('ajv')
// eslint-disable-next-line no-inner-declarations
function requireSchemaDraft (environment) {
return require('ajv/lib/refs/' + environment + '.json')
const ajv = {
AjvOld: 'ajv6',
Ajv07: 'ajv',
AjvJTD: 'ajv/dist/jtd',
Ajv2019: 'ajv/dist/2019',
Ajv2020: 'ajv/dist/2020',
Schema04: 'ajv6/lib/refs/json-schema-draft-04.json',
Schema06: 'ajv/dist/refs/json-schema-draft-06.json'
}
factory(exports, Ajv, jsonlint, requireSchemaDraft)
// eslint-disable-next-line no-undef
const requireAjv = name => {
const exported = require(ajv[name])
return !exported.$schema && exported.default || exported
}
factory(exports, jsonlint, requireAjv)
} else if (typeof define === 'function' && define.amd) {
// eslint-disable-next-line no-undef
define('jsonlint-validator', ['exports', 'ajv', 'jsonlint', 'jsonlint-schema-drafts'],
function (exports, jsonlint, Ajv, schemaDrafts) {
function requireSchemaDraft (environment) {
return schemaDrafts[environment]
define('jsonlint-validator', ['exports', 'jsonlint', 'ajv', 'ajv7'],
function (exports, jsonlint, ajv, ajv7) {
const requireAjv = name => {
if (name === 'AjvOld') return ajv
const exported = ajv7[name]
return !exported.$schema && exported.default || exported
}
factory(exports, Ajv, jsonlint, requireSchemaDraft)
factory(exports, jsonlint, requireAjv)
})
} else {
// eslint-disable-next-line no-undef
global = global || self
const requireSchemaDraft = function (environment) {
return global.jsonlintSchemaDrafts[environment]
const requireAjv = name => {
if (name === 'AjvOld') return global.Ajv
const exported = global.ajv7[name]
return !exported.$schema && exported.default || exported
}
factory(global.jsonlintValidator = {}, global.Ajv, global.jsonlint, requireSchemaDraft)
factory(global.jsonlintValidator = {}, global.jsonlint, requireAjv)
}
}(this, function (exports, Ajv, jsonlint, requireSchemaDraft) {
}(this, function (exports, jsonlint, requireAjv) {
'use strict'

function addErrorLocation (problem, input, tokens, dataPath) {
Expand Down Expand Up @@ -96,24 +105,29 @@
}

function createAjv (environment) {
const ajvOptions = { jsonPointers: true }
let ajv
if (!environment) {
ajvOptions.schemaId = 'auto'
ajv = new Ajv(ajvOptions)
ajv.addMetaSchema(requireSchemaDraft('json-schema-draft-04'))
ajv.addMetaSchema(requireSchemaDraft('json-schema-draft-06'))
} else if (environment === 'json-schema-draft-07') {
ajv = new Ajv(ajvOptions)
} else if (environment === 'json-schema-draft-06') {
ajv = new Ajv(ajvOptions)
ajv.addMetaSchema(requireSchemaDraft('json-schema-draft-06'))
} else if (environment === 'json-schema-draft-04') {
ajvOptions.schemaId = 'id'
ajv = new Ajv(ajvOptions)
ajv.addMetaSchema(requireSchemaDraft('json-schema-draft-04'))
if (!environment || environment === 'json-schema-draft-06' || environment === 'draft-06') {
const Ajv = requireAjv('Ajv07')
ajv = new Ajv()
ajv.addMetaSchema(requireAjv('Schema06'))
} else if (environment === 'json-schema-draft-07' || environment === 'draft-07') {
const Ajv = requireAjv('Ajv07')
ajv = new Ajv()
} else if (environment === 'json-schema-draft-04' || environment === 'draft-04') {
const Ajv = requireAjv('AjvOld')
ajv = new Ajv({ schemaId: 'id' })
ajv.addMetaSchema(requireAjv('Schema04'))
} else if (environment === 'json-schema-draft-2019-09' || environment === 'draft-2019-09') {
const Ajv = requireAjv('Ajv2019')
ajv = new Ajv()
} else if (environment === 'json-schema-draft-2020-12' || environment === 'draft-2020-12') {
const Ajv = requireAjv('Ajv2020')
ajv = new Ajv()
} else if (environment === 'json-type-definition' || environment === 'jtd' || environment === 'rfc8927') {
const Ajv = requireAjv('AjvJTD')
ajv = new Ajv()
} else {
throw new RangeError('Unsupported environment for the JSON schema validation: "' +
throw new RangeError('Unsupported environment for the JSON Schema validation: "' +
environment + '".')
}
return ajv
Expand All @@ -124,7 +138,7 @@
try {
parsed = jsonlint.parse(schema, parseOptions)
} catch (error) {
error.message = 'Parsing the JSON schema failed.\n' + error.message
error.message = 'Parsing the JSON Schema failed.\n' + error.message
throw error
}
try {
Expand All @@ -134,7 +148,7 @@
const betterError = errors
? createError(errors, parsed, schema, parseOptions)
: originalError
betterError.message = 'Compiling the JSON schema failed.\n' + betterError.message
betterError.message = 'Compiling the JSON Schema failed.\n' + betterError.message
throw betterError
}
}
Expand Down
16 changes: 10 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,11 @@
"node": ">= 14"
},
"scripts": {
"prepare": "npm run build",
"lint": "denolint",
"build": "npm run compile && npm run compile:tests",
"compile": "npm run compile:jsonlint && esbuild --minify --sourcemap --outfile=web/jsonlint.min.js lib/jsonlint.js && esbuild --minify --sourcemap --outfile=web/validator.min.js lib/validator.js && esbuild --minify --sourcemap --outfile=web/formatter.min.js lib/formatter.js && esbuild --minify --sourcemap --outfile=web/sorter.min.js lib/sorter.js && esbuild --minify --sourcemap --outfile=web/printer.min.js lib/printer.js && node scripts/bundle-schema-drafts && esbuild --minify --sourcemap --outfile=web/schema-drafts.min.js lib/schema-drafts.js && esbuild --minify --sourcemap --outfile=web/ajv.min.js node_modules/ajv/dist/ajv.bundle.js",
"prepare": "npm run compile:jsonlint && rollup -c && npm run minify && npm run compile:tests",
"compile:jsonlint": "cat.js src/prefix.js.txt src/unicode.js src/custom-parser.js src/pointer.js src/native-parser.js src/configurable-parser.js src/suffix.js.txt > lib/jsonlint.js",
"minify": "esbuild --minify --sourcemap --outfile=web/jsonlint.min.js lib/jsonlint.js && esbuild --minify --sourcemap --outfile=web/validator.min.js lib/validator.js && esbuild --minify --sourcemap --outfile=web/formatter.min.js lib/formatter.js && esbuild --minify --sourcemap --outfile=web/sorter.min.js lib/sorter.js && esbuild --minify --sourcemap --outfile=web/printer.min.js lib/printer.js && esbuild --minify --sourcemap --outfile=web/ajv.min.js node_modules/ajv6/dist/ajv.bundle.js",
"compile:tests": "tsc --moduleResolution node --module es2022 test/types.test.ts && mv.js test/types.test.js test/types.test.mjs",
"test": "npm run lint && c8 node test/types.test.mjs && c8 --no-clean node test/parse1 && c8 --no-clean node test/parse1 --native-parser && c8 --no-clean node test/parse2 && c8 --no-clean node test/parse3 && c8 --no-clean node test/parse4 && c8 --no-clean node test/parse5 && c8 --no-clean node test/portable && c8 --no-clean node test/tokenize && c8 --no-clean node test/print && c8 --no-clean node lib/cli package.json test/recursive && c8 --no-clean node lib/cli -sq test/passes/hasOwnProperty.json && c8 --no-clean node lib/cli -s -e json-schema-draft-04 -V test/passes/3.schema.json test/passes/3.json && c8 --no-clean node lib/cli -C test/passes/comments.txt && c8 --no-clean node lib/cli -pS test/passes/strings.txt && c8 --no-clean node lib/cli -M json5 test/passes/json5.text && c8 --no-clean node lib/cli -v && c8 --no-clean node lib/cli -h && c8 --no-clean node lib/cli -Pc test/fails/10.json || c8 --no-clean node lib/cli -f test/.jsonrc.yml 'test/**/*.json' '!**/fails' && c8 report",
"test": "denolint && c8 node test/types.test.mjs && c8 --no-clean node test/parse1 && c8 --no-clean node test/parse1 --native-parser && c8 --no-clean node test/parse2 && c8 --no-clean node test/parse3 && c8 --no-clean node test/parse4 && c8 --no-clean node test/parse5 && c8 --no-clean node test/portable && c8 --no-clean node test/tokenize && c8 --no-clean node test/print && c8 --no-clean node lib/cli package.json test/recursive && c8 --no-clean node lib/cli -sq test/passes/hasOwnProperty.json && c8 --no-clean node lib/cli -s -e json-schema-draft-04 -V test/passes/schema-04.json test/passes/data-04.json && c8 --no-clean node lib/cli -s -e json-schema-draft-07 -V test/passes/schema-07.json test/passes/data-07.json && c8 --no-clean node lib/cli -C test/passes/comments.txt && c8 --no-clean node lib/cli -pS test/passes/strings.txt && c8 --no-clean node lib/cli -M json5 test/passes/json5.text && c8 --no-clean node lib/cli -v && c8 --no-clean node lib/cli -h && c8 --no-clean node lib/cli -Pc test/fails/10.json || c8 --no-clean node lib/cli -f test/.jsonrc.yml 'test/**/*.json' '!**/fails' && c8 report",
"start": "http-server -c 5",
"web": "npm run web:sync && npm run web:deploy",
"web:clone": "test ! -d ../jsonlint-pages && git clone --single-branch --branch gh-pages `git remote get-url origin` ../jsonlint-pages",
Expand Down Expand Up @@ -75,13 +73,17 @@
]
},
"dependencies": {
"ajv": "6.12.6",
"ajv": "8.12.0",
"ajv6": "npm:ajv@6.12.6",
"commander": "10.0.0",
"cosmiconfig": "8.1.0",
"diff": "5.1.0",
"fast-glob": "3.2.12"
},
"devDependencies": {
"@rollup/plugin-commonjs": "24.0.1",
"@rollup/plugin-json": "6.0.0",
"@rollup/plugin-node-resolve": "15.0.1",
"@semantic-release/changelog": "6.0.2",
"@semantic-release/git": "10.0.1",
"@types/node": "18.14.6",
Expand All @@ -92,6 +94,8 @@
"esbuild": "0.17.11",
"http-server": "14.1.1",
"js-yaml": "4.1.0",
"rollup": "3.18.0",
"rollup-plugin-swc-minify": "1.0.5",
"tehanu": "1.0.1",
"tehanu-repo-coco": "1.0.0",
"tehanu-teru": "1.0.0",
Expand Down
Loading

1 comment on commit 0b9130c

@prantlf
Copy link
Owner Author

@prantlf prantlf commented on 0b9130c Mar 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixes #16.

Please sign in to comment.