Skip to content

Commit b9fcd2e

Browse files
committed
feat: Add validation using JSON Schema
1 parent 50e06aa commit b9fcd2e

File tree

5 files changed

+138
-31
lines changed

5 files changed

+138
-31
lines changed

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
This is a fork of the original package with the following enhancements:
1111

1212
* Validates with both [JSON] and [JSON5] standards.
13+
* Supports [JSON Schema] drafts 04, 06 and 07.
1314
* Optionally recognizes JavaScript-style comments and single quoted strings.
1415
* Optionally ignores trailing commas and reports duplicate object keys as an error.
1516
* Prefers using the 8x faster native JSON parser, if possible.
@@ -78,6 +79,21 @@ Options can be passed as keys in an object to the `jsonlint` function. The follo
7879
* `indent`, the value passed to `JSON.stringify`, it can be the number of spaces, or string like "\t"
7980
* `sortKeys`, when `true` keys of objects in the output JSON will be sorted alphabetically (`format` has to be set to `true` too)
8081

82+
#### Schema Validation
83+
84+
You can validate JSON files using JSON Schema drafts 04, 06 or 07, if you specify the schema in addition to other options:
85+
86+
jsonlint({
87+
schema: {
88+
src: 'some/manifest-schema.json',
89+
environment: 'json-schema-draft-04'
90+
}
91+
})
92+
93+
* `schema`, when set the source file will be validated using ae JSON Schema in addition to the syntax checks
94+
* `src`, when filled with a file path, the file will be used as a source of the JSON Schema
95+
* `environment`, can specify the version of the JSON Schema draft to use for validation: "json-schema-draft-04", "json-schema-draft-06" or "json-schema-draft-07" (if not set, the schema draft version will be inferred automatically)
96+
8197
### jsonlint.reporter(customReporter)
8298

8399
#### customReporter(file)
@@ -121,3 +137,4 @@ Licensed under the [MIT License].
121137
[`jsonlint`]: https://prantlf.github.io/jsonlint/
122138
[JSON]: https://tools.ietf.org/html/rfc8259
123139
[JSON5]: https://spec.json5.org
140+
[JSON Schema]: https://json-schema.org

index.js

Lines changed: 76 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,107 @@
11
'use strict';
22

3+
var fs = require('fs');
34
var mapStream = require('map-stream');
45
var colors = require('ansi-colors');
56
var jsonlint = require('@prantlf/jsonlint');
7+
var validator = require('@prantlf/jsonlint/lib/validator');
68
var sorter = require('@prantlf/jsonlint/lib/sorter');
79
var through = require('through2');
810
var PluginError = require('plugin-error');
911
var log = require('fancy-log');
1012

11-
var formatOutput = function (msg) {
12-
var output = {};
13-
14-
if (msg) { output.message = msg; }
15-
16-
output.success = msg ? false : true;
17-
18-
return output;
19-
};
20-
2113
var jsonLintPlugin = function (options) {
2214
options = Object.assign({
2315
mode: 'json',
2416
ignoreComments: false,
2517
ignoreTrailingCommas: false,
2618
allowSingleQuotedStrings: false,
2719
allowDuplicateObjectKeys: true,
20+
schema: {},
2821
format: false,
2922
indent: 2,
3023
sortKeys: false
3124
}, options);
25+
var schema = options.schema;
26+
var parserOptions = {
27+
mode: options.mode,
28+
ignoreComments: options.ignoreComments || options.cjson ||
29+
options.mode === 'cjson' || options.mode === 'json5',
30+
ignoreTrailingCommas: options.ignoreTrailingCommas || options.mode === 'json5',
31+
allowSingleQuotedStrings: options.allowSingleQuotedStrings || options.mode === 'json5',
32+
allowDuplicateObjectKeys: options.allowDuplicateObjectKeys,
33+
environment: schema.environment
34+
};
35+
var schemaContent;
36+
37+
function createResult (message) {
38+
var result = {};
39+
if (message) {
40+
result.message = message;
41+
result.success = false;
42+
} else {
43+
result.success = true;
44+
}
45+
return result;
46+
}
47+
48+
function formatOutput (parsedData, file) {
49+
if (options.format) {
50+
if (options.sortKeys) {
51+
parsedData = sorter.sortObject(parsedData);
52+
}
53+
var formatted = JSON.stringify(parsedData, null, options.indent) + '\n';
54+
file.contents = new Buffer(formatted);
55+
}
56+
}
57+
58+
function validateSchema (parsedData, file, finish) {
59+
var errorMessage;
60+
try {
61+
var validate = validator.compile(schemaContent, parserOptions);
62+
validate(parsedData);
63+
formatOutput(parsedData, file);
64+
}
65+
catch (error) {
66+
errorMessage = error.message;
67+
}
68+
finish(errorMessage);
69+
}
70+
71+
function loadAndValidateSchema (parsedData, file, finish) {
72+
if (schemaContent) {
73+
validateSchema(parsedData, finish);
74+
} else {
75+
fs.readFile(schema.src, 'utf-8', function(error, fileContent) {
76+
if (error) {
77+
finish(error.message);
78+
} else {
79+
schemaContent = fileContent;
80+
validateSchema(parsedData, file, finish);
81+
}
82+
});
83+
}
84+
}
3285

3386
return mapStream(function (file, cb) {
34-
var errorMessage = '';
35-
36-
var parserOptions = {
37-
mode: options.mode,
38-
ignoreComments: options.ignoreComments || options.cjson ||
39-
options.mode === 'cjson' || options.mode === 'json5',
40-
ignoreTrailingCommas: options.ignoreTrailingCommas || options.mode === 'json5',
41-
allowSingleQuotedStrings: options.allowSingleQuotedStrings || options.mode === 'json5',
42-
allowDuplicateObjectKeys: options.allowDuplicateObjectKeys
43-
};
87+
var errorMessage;
88+
function finish (errorMessage) {
89+
file.jsonlint = createResult(errorMessage);
90+
cb(null, file);
91+
}
92+
4493
try {
4594
var parsedData = jsonlint.parse(String(file.contents), parserOptions);
46-
if (options.format) {
47-
if (options.sortKeys) {
48-
parsedData = sorter.sortObject(parsedData);
49-
}
50-
var formatted = JSON.stringify(parsedData, null, options.indent) + '\n';
51-
file.contents = new Buffer(formatted);
95+
if (schema.src) {
96+
loadAndValidateSchema(parsedData, file, finish);
97+
return;
5298
}
99+
formatOutput(parsedData, file);
53100
}
54-
catch (err) {
55-
errorMessage = err.message;
101+
catch (error) {
102+
errorMessage = error.message;
56103
}
57-
file.jsonlint = formatOutput(errorMessage);
58-
59-
cb(null, file);
104+
finish(errorMessage);
60105
});
61106
};
62107

test/fixtures/data.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"key": "value"
3+
}

test/fixtures/schema.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-04/schema#",
3+
"type": "object",
4+
"title": "Schema for data.json",
5+
"properties": {
6+
"key": {
7+
"type": "string",
8+
"title": "Required property"
9+
}
10+
},
11+
"required": [
12+
"key"
13+
]
14+
}

test/main.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,34 @@ describe('gulp-jsonlint', function () {
188188
});
189189
});
190190

191+
it('succeeds validation using JSON Schema', function (done) {
192+
parseValidFile('fixtures/data.json', done, {
193+
schema: {
194+
src: 'test/fixtures/schema.json'
195+
}
196+
});
197+
});
198+
199+
it('fails validation using JSON Schema', function (done) {
200+
var file = getFile('fixtures/valid.json');
201+
var stream = jsonLintPlugin({
202+
schema: {
203+
src: 'test/fixtures/schema.json'
204+
}
205+
});
206+
207+
stream.on('data', function (f) {
208+
should.exist(f.jsonlint.success);
209+
f.jsonlint.success.should.equal(false);
210+
should.exist(f.jsonlint.message);
211+
f.jsonlint.message.should.match(/should have required property/);
212+
});
213+
214+
stream.once('end', done);
215+
stream.write(file);
216+
stream.end();
217+
});
218+
191219
it('can format the output', function (done) {
192220
formatValidFile('fixtures/json5.json', done, {
193221
mode: 'json5',

0 commit comments

Comments
 (0)