Skip to content

Commit 932074d

Browse files
author
Tom Eklöf
committed
Initial commit
0 parents  commit 932074d

File tree

5 files changed

+647
-0
lines changed

5 files changed

+647
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.idea
2+
node_modules

README.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# json-from-schema
2+
3+
json-from-schema generates random JSON based on [JSON Schema draft v4 schemas](http://json-schema.org).
4+
5+
# Usage
6+
7+
```javascript
8+
var jfs = require('json-from-schema');
9+
var schema1 = {
10+
id: 'http://www.example.com/herp#'
11+
, type: "object"
12+
, properties: {
13+
someString: {type: 'string', pattern: 'bl(a){1,10}h'}
14+
, someInt: {type: 'integer', minimum: 23, maximum: 42}
15+
, someEnum: {$ref: '#/definitions/blaEnum'}
16+
, someEnumArray: {type: 'array', items: {$ref: '#/definitions/blaEnum'}, minItems: 5, maxItems: 8}
17+
, someObject: {
18+
type: 'object'
19+
, properties: {
20+
derp: {type: 'string', minLength:1, maxLength:5}
21+
, herp: {type: 'string', minLength:5, maxLength:10}
22+
}
23+
24+
, patternProperties: {
25+
'pat-\\d+': {type: 'string', pattern: 'patStr-\\w{1,20}'}
26+
}
27+
28+
, additionalProperties: true
29+
, required: ['derp']
30+
}
31+
}
32+
33+
, additionalProperties: false
34+
, required: ['someString', 'someObject']
35+
, definitions: {
36+
blaEnum: {enum: ['bla', 'dohoi', 666]}
37+
}
38+
};
39+
40+
var schema2 = {
41+
id: 'http://www.example.com/derp#'
42+
, type: "object"
43+
, properties: {
44+
herps: {type: "array", items: {$ref: 'http://www.example.com/herp'}}
45+
}
46+
};
47+
48+
var gen = new jfs.JsonFromSchema([schema1, schema2])
49+
var sampleDerp = gen.generate('http://www.example.com/derp'); // note: no hash at the end
50+
51+
var sampleHerp = gen.generate('http://www.example.com/herp');
52+
53+
54+
```

json-from-schema.js

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
/**
2+
* Created by teklof on 27.8.14.
3+
*/
4+
5+
var _ = require('lodash');
6+
var ptr = require('json-ptr');
7+
var RandExpr = require('randexp');
8+
9+
function JsonFromSchema(schemas) {
10+
this._schemas = _.reduce(schemas, function (acc, schema) {
11+
if(!schema.id) {
12+
throw new Error("All schemas need ids");
13+
}
14+
var idLen = schema.id.length;
15+
var id = schema.id[idLen - 1] === '#' ? schema.id.substring(0, idLen - 1) : schema.id;
16+
acc[id] = _.cloneDeep(schema);
17+
return acc;
18+
}, {});
19+
var self = this;
20+
_.each(this._schemas, function (schema) {
21+
self._resolveRefs(schema, self._schemas);
22+
});
23+
}
24+
25+
// JS uses double precision floats (52 bits in the mantissa), so the maximum representable integer is 2^52
26+
var MAX_INT = Math.pow(2, 52);
27+
28+
JsonFromSchema.prototype._resolveRefs = exports._resolveRefs = function _resolveRefs(schema, schemasByIds, topSchema) {
29+
function isLocal(ref) {
30+
return ref[0] === '#';
31+
}
32+
33+
topSchema = topSchema || schema;
34+
var pointed;
35+
var $ref = schema.$ref;
36+
if ($ref) {
37+
if (isLocal($ref)) { // JSON pointer
38+
pointed = ptr.create($ref).get(topSchema);
39+
} else { // not a JSON pointer so blindly assume it's an ID
40+
pointed = schemasByIds[$ref];
41+
}
42+
43+
if (!pointed) {
44+
throw new ReferenceError("Pointer " + $ref + " didn't point to anything?");
45+
}
46+
47+
if(pointed.$ref) {
48+
/* if the schema being pointed to isn't the one we started in, topSchema needs to be set to the schema being
49+
pointed to so its JSON pointers can be dereferenced properly */
50+
_resolveRefs(pointed, schemasByIds, isLocal(pointed.$ref) ? topSchema : pointed);
51+
}
52+
53+
delete schema.$ref;
54+
_.assign(schema, pointed);
55+
}
56+
57+
// this schema didn't have a reference, so go through all subschemas
58+
_.each(schema, function (subSchema, key) {
59+
if(_.isPlainObject(subSchema)) {
60+
_resolveRefs(subSchema, schemasByIds, topSchema);
61+
}
62+
});
63+
64+
};
65+
66+
JsonFromSchema.prototype._generators = {
67+
'_randomNumber': function _randomNumber(schema, options) {
68+
options = options || {};
69+
var integer = schema.type === 'integer'
70+
, minimum = schema.minimum || (integer ? -MAX_INT : -MAX_INT*0.671)// note: just random constants to make float generation work
71+
, maximum = schema.maximum || (integer ? MAX_INT : MAX_INT*0.5663);
72+
73+
if (options.exclusiveMinimum && integer) { // TODO: floats
74+
minimum += 1;
75+
}
76+
77+
if (options.exclusiveMaximum && integer) { // TODO: floats
78+
maximum -= 1;
79+
}
80+
81+
return _.random(minimum, maximum, schema.type === 'number');
82+
}
83+
84+
, 'boolean': function () {
85+
return !!_.random(1);
86+
}
87+
88+
, 'string': function(schema, options) {
89+
options = options || {};
90+
schema = schema || {};
91+
var minCharCode = options.minCharCode || 32
92+
, maxCharCode = options.maxCharCode || 126
93+
, minLength = schema.minLength || 0
94+
, maxLength = schema.maxLength || 32;
95+
96+
if (schema.enum) {
97+
return this.enum(schema);
98+
}
99+
100+
if (schema.pattern) {
101+
var re = new RandExpr(schema.pattern);
102+
re.anyRandChar = function () {
103+
return String.fromCharCode(_.random(minCharCode, maxCharCode));
104+
};
105+
// FIXME: randexp's max doesn't work as I expected; this needs a fix so it doesn't generate strings that go over maxLength
106+
re.max = maxLength;
107+
return re.gen();
108+
}
109+
110+
var charCodes = _.times(_.random(minLength, maxLength), function () {
111+
return _.random(minCharCode, maxCharCode);
112+
});
113+
return String.fromCharCode.apply(null, charCodes);
114+
}
115+
116+
, 'number': function(schema, options) {
117+
schema = schema || {type: 'number'};
118+
return this._randomNumber(schema, options);
119+
}
120+
121+
, 'integer': function (schema, options) {
122+
schema = schema || {type: 'integer'};
123+
return this._randomNumber(schema, options);
124+
}
125+
126+
, 'enum': function(schema) {
127+
return _.sample(schema.enum);
128+
}
129+
130+
, 'array': function(schema, options) {
131+
options = options || {};
132+
schema = schema || {};
133+
var itemSchema = schema.items || {type: 'string'}
134+
, itemType = itemSchema.type || ('enum' in itemSchema && 'enum')
135+
, minItems = schema.minItems || 0
136+
, maxItems = schema.maxItems || 10
137+
, len = _.random(minItems, maxItems);
138+
139+
var self = this;
140+
return _.times(len, function () {
141+
return self[itemType](itemSchema, options);
142+
});
143+
}
144+
145+
, '_randomObject': function(options) {
146+
var numKeys = _.random(options.minRandomKeys || 0, options.maxRandomKeys || 10);
147+
var self = this;
148+
149+
var gens = [
150+
_.partial(this.array, {items: {type: 'integer'}})
151+
, _.partial(this.array, {items: {type: 'number'}})
152+
, _.partial(this.array, {items: {type: 'string'}})
153+
, this.string
154+
, this.integer
155+
, this.number
156+
];
157+
158+
return _(_.times(numKeys, function () {
159+
return self.string();
160+
})).reduce(function (acc, key) {
161+
acc[key] = _.sample(gens).apply(self);
162+
return acc;
163+
}, {}).valueOf();
164+
}
165+
166+
, 'object': function(schema, options) {
167+
168+
options = options || {};
169+
schema = schema || {};
170+
171+
var self = this
172+
, required = schema.required || []
173+
, props = schema.properties && Object.keys(schema.properties) || []
174+
, patternProps = schema.patternProperties && Object.keys(schema.patternProperties) || []
175+
, additionals = "additionalProperties" in schema ? !!schema.additionalProperties : true
176+
, maxPatternProps = options.maxPatternProps || 10
177+
, nonRequiredProps = _.difference(props, required)
178+
// generate all required properties plus a random amount of non-required properties
179+
, propsToGenerate = _.union(required, _.sample(nonRequiredProps, _.random(nonRequiredProps.length)));
180+
181+
var obj = _.reduce(propsToGenerate, function(acc, propName) {
182+
var propSchema = schema.properties[propName];
183+
184+
var type = propSchema.type || propSchema.enum && 'enum';
185+
acc[propName] = self[type](propSchema, options);
186+
return acc;
187+
}, {});
188+
189+
if(patternProps.length) {
190+
var nPats = _.random(maxPatternProps);
191+
192+
var ppObj = _(_.times(nPats, function () {
193+
return _.sample(patternProps);
194+
})).reduce(function(acc, propPattern) {
195+
var propSchema = schema.patternProperties[propPattern];
196+
var propName = self.string({pattern: propPattern});
197+
var type = propSchema.type || propSchema.enum && 'enum';
198+
acc[propName] = self[type](propSchema, options);
199+
return acc;
200+
}, {}).valueOf();
201+
_.defaults(obj, ppObj);
202+
}
203+
204+
if(additionals) { // if additionalProperties is true, add some random properties to the object
205+
_.defaults(obj, this._randomObject(options));
206+
}
207+
208+
209+
return obj;
210+
}
211+
};
212+
213+
JsonFromSchema.prototype.generate = function generate(schemaId, options) {
214+
var schema = this._schemas[schemaId];
215+
216+
if(!schema) {
217+
throw new ReferenceError("No schema with ID " + schemaId + " registered");
218+
}
219+
220+
var type = schema.type || schema.enum && 'enum';
221+
return this._generators[type](schema, options);
222+
};
223+
224+
exports.JsonFromSchema = JsonFromSchema;

package.json

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"name": "json-from-schema",
3+
"version": "1.0.0",
4+
"description": "Generates random JSON using a JSON schema",
5+
"main": "json_from_schema.js",
6+
"scripts": {
7+
"test": "mocha --reporter spec --require chai --ui bdd --recursive --colors --timeout 2000"
8+
},
9+
10+
"repository": {
11+
"type": "git",
12+
"url": "git://github.com/ORBAT/json-from-schema.git"
13+
},
14+
15+
"homepage": "https://github.com/ORBAT/json-from-schema",
16+
17+
"keywords": [
18+
"json",
19+
"schema",
20+
"random"
21+
],
22+
"author": "Tom Eklöf (http://github.com/ORBAT/)",
23+
"license": "MIT",
24+
25+
"dependencies": {
26+
"json-ptr": "0.1.1",
27+
"randexp": "0.3.4",
28+
"lodash": "~2.4"
29+
},
30+
31+
"devDependencies": {
32+
"mocha": "^1.18",
33+
"chai": "^1.9",
34+
"z-schema": "~3.0"
35+
}
36+
}

0 commit comments

Comments
 (0)