@lowdep/json-schema-gen 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Rushabh Shah
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,145 @@
1
+ # json-schema-gen
2
+
3
+ ![Zero dependencies](https://img.shields.io/badge/dependencies-0-brightgreen) ![Node](https://img.shields.io/badge/node-%3E%3D14-blue) ![License: MIT](https://img.shields.io/badge/license-MIT-green) ![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-lightgrey)
4
+
5
+
6
+ Generate JSON Schema (draft-07) from sample JSON data. Detects string formats (email, URI, UUID, date-time), merges schemas from multiple samples, and outputs a clean schema ready for validation. Zero dependencies.
7
+
8
+ Like `quicktype` but needs no installation — and `genson` (Python) or online tools are overkill when you just need a schema.
9
+
10
+ ---
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ npm install -g json-schema-gen
16
+ ```
17
+
18
+ Or without installing:
19
+
20
+ ```bash
21
+ npx json-schema-gen data.json
22
+ ```
23
+
24
+ ---
25
+
26
+ ## Example
27
+
28
+ Given `user.json`:
29
+ ```json
30
+ {
31
+ "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
32
+ "name": "Alice Johnson",
33
+ "email": "alice@example.com",
34
+ "age": 32,
35
+ "active": true,
36
+ "createdAt": "2024-01-15T09:30:00Z",
37
+ "tags": ["admin", "user"],
38
+ "address": {
39
+ "street": "123 Main St",
40
+ "city": "Springfield",
41
+ "zip": "62701"
42
+ }
43
+ }
44
+ ```
45
+
46
+ Running `json-schema-gen user.json --required` outputs:
47
+
48
+ ```json
49
+ {
50
+ "$schema": "https://json-schema.org/draft-07/schema",
51
+ "type": "object",
52
+ "properties": {
53
+ "id": { "type": "string", "format": "uuid" },
54
+ "name": { "type": "string" },
55
+ "email": { "type": "string", "format": "email" },
56
+ "age": { "type": "integer" },
57
+ "active": { "type": "boolean" },
58
+ "createdAt": { "type": "string", "format": "date-time" },
59
+ "tags": { "type": "array", "items": { "type": "string" } },
60
+ "address": {
61
+ "type": "object",
62
+ "properties": {
63
+ "street": { "type": "string" },
64
+ "city": { "type": "string" },
65
+ "zip": { "type": "string" }
66
+ },
67
+ "required": ["street", "city", "zip"],
68
+ "additionalProperties": false
69
+ }
70
+ },
71
+ "required": ["id", "name", "email", "age", "active", "createdAt", "tags", "address"],
72
+ "additionalProperties": false
73
+ }
74
+ ```
75
+
76
+ ---
77
+
78
+ ## Usage
79
+
80
+ ```bash
81
+ json-schema-gen user.json # Basic schema
82
+ json-schema-gen user.json --required # Mark all fields as required
83
+ json-schema-gen user.json --title "User" --id "/schemas/user"
84
+ json-schema-gen user.json --out schema.json # Write to file
85
+ json-schema-gen s1.json s2.json s3.json # Merge from multiple samples
86
+ cat data.json | json-schema-gen - # Read from stdin
87
+ ```
88
+
89
+ ---
90
+
91
+ ## Detected Formats
92
+
93
+ | Format | Example |
94
+ |---|---|
95
+ | `date-time` | `"2024-01-15T09:30:00Z"` |
96
+ | `date` | `"2024-01-15"` |
97
+ | `time` | `"09:30:00"` |
98
+ | `email` | `"user@example.com"` |
99
+ | `uri` | `"https://example.com"` |
100
+ | `uuid` | `"a1b2c3d4-e5f6-7890-abcd-ef1234567890"` |
101
+ | `ipv4` | `"192.168.1.1"` |
102
+
103
+ ---
104
+
105
+ ## Multi-Sample Merging
106
+
107
+ Pass multiple JSON files to infer a schema that covers all of them. Fields present in only some samples will not be marked `required`:
108
+
109
+ ```bash
110
+ json-schema-gen user1.json user2.json user3.json
111
+ ```
112
+
113
+ ---
114
+
115
+ ## Options
116
+
117
+ | Flag | Description |
118
+ |---|---|
119
+ | `--required` | Mark all non-null fields as required |
120
+ | `--examples` | Include example values for string fields |
121
+ | `--title <name>` | Add `title` to the schema |
122
+ | `--id <uri>` | Add `$id` to the schema |
123
+ | `--out <file>` | Write output to a file |
124
+
125
+ ---
126
+
127
+ ## License
128
+
129
+ MIT
130
+
131
+ ---
132
+
133
+ ## Keywords
134
+
135
+ `json schema generator` · `json to schema` · `quicktype alternative` · `genson alternative` · `infer schema` · `draft-07` · `generate schema` · `json schema` · `zero dependencies` · `cli`
136
+
137
+ ---
138
+
139
+ <div align="center">
140
+
141
+ **Built to solve, shared to help — Rushabh Shah 🛠️✨**
142
+
143
+ <sub>One of 40+ zero-dependency developer CLI tools — no <code>node_modules</code>, ever.</sub>
144
+
145
+ </div>
@@ -0,0 +1,268 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+
7
+ const VERSION = '1.0.0';
8
+
9
+ // ─── ANSI ─────────────────────────────────────────────────────────────────────
10
+ const isTTY = process.stdout.isTTY;
11
+ const c = (code, t) => isTTY ? `\x1b[${code}m${t}\x1b[0m` : t;
12
+ const bold = t => c('1', t);
13
+ const dim = t => c('2', t);
14
+ const red = t => c('31', t);
15
+ const green = t => c('32', t);
16
+ const cyan = t => c('36', t);
17
+ const yellow = t => c('33', t);
18
+
19
+ // ─── Format detectors ─────────────────────────────────────────────────────────
20
+ const FORMAT_DETECTORS = [
21
+ { format: 'date-time', test: v => /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/.test(v) },
22
+ { format: 'date', test: v => /^\d{4}-\d{2}-\d{2}$/.test(v) },
23
+ { format: 'time', test: v => /^\d{2}:\d{2}(:\d{2})?$/.test(v) },
24
+ { format: 'email', test: v => /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(v) },
25
+ { format: 'uri', test: v => /^https?:\/\//.test(v) },
26
+ { format: 'uuid', test: v => /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(v) },
27
+ { format: 'ipv4', test: v => /^(\d{1,3}\.){3}\d{1,3}$/.test(v) },
28
+ ];
29
+
30
+ function detectFormat(s) {
31
+ for (const { format, test } of FORMAT_DETECTORS) {
32
+ if (test(s)) return format;
33
+ }
34
+ return null;
35
+ }
36
+
37
+ // ─── Schema inference ─────────────────────────────────────────────────────────
38
+ function inferSchema(value, opts = {}) {
39
+ const { allRequired = false, examples = false } = opts;
40
+
41
+ if (value === null) return { type: 'null' };
42
+
43
+ if (typeof value === 'boolean') return { type: 'boolean' };
44
+
45
+ if (typeof value === 'number') {
46
+ return Number.isInteger(value) ? { type: 'integer' } : { type: 'number' };
47
+ }
48
+
49
+ if (typeof value === 'string') {
50
+ const schema = { type: 'string' };
51
+ if (value.length > 0) {
52
+ const fmt = detectFormat(value);
53
+ if (fmt) schema.format = fmt;
54
+ if (examples && !fmt) schema.examples = [value];
55
+ }
56
+ return schema;
57
+ }
58
+
59
+ if (Array.isArray(value)) {
60
+ if (value.length === 0) return { type: 'array', items: {} };
61
+ // Merge schemas of all items
62
+ const itemSchemas = value.map(v => inferSchema(v, opts));
63
+ const merged = mergeSchemas(itemSchemas);
64
+ return { type: 'array', items: merged };
65
+ }
66
+
67
+ if (typeof value === 'object') {
68
+ const properties = {};
69
+ const required = [];
70
+
71
+ for (const [key, val] of Object.entries(value)) {
72
+ properties[key] = inferSchema(val, opts);
73
+ if (allRequired && val !== null && val !== undefined) {
74
+ required.push(key);
75
+ }
76
+ }
77
+
78
+ const schema = {
79
+ type: 'object',
80
+ properties,
81
+ additionalProperties: false,
82
+ };
83
+ if (required.length > 0) schema.required = required;
84
+ return schema;
85
+ }
86
+
87
+ return {};
88
+ }
89
+
90
+ // ─── Merge multiple schemas (for array items from multiple samples) ───────────
91
+ function mergeSchemas(schemas) {
92
+ if (schemas.length === 0) return {};
93
+ if (schemas.length === 1) return schemas[0];
94
+
95
+ // Check if all the same type
96
+ const types = [...new Set(schemas.map(s => s.type))];
97
+ if (types.length > 1) {
98
+ // Multiple types — use anyOf
99
+ const uniqueSchemas = deduplicateSchemas(schemas);
100
+ if (uniqueSchemas.length === 1) return uniqueSchemas[0];
101
+ return { anyOf: uniqueSchemas };
102
+ }
103
+
104
+ const type = types[0];
105
+
106
+ if (type === 'object') {
107
+ // Merge object schemas: union all keys, mark keys missing in some as non-required
108
+ const allKeys = new Set(schemas.flatMap(s => Object.keys(s.properties || {})));
109
+ const properties = {};
110
+ const alwaysPresent = new Set([...allKeys]);
111
+
112
+ for (const key of allKeys) {
113
+ const keySchemas = schemas.filter(s => s.properties && key in s.properties).map(s => s.properties[key]);
114
+ if (keySchemas.length < schemas.length) alwaysPresent.delete(key);
115
+ properties[key] = mergeSchemas(keySchemas.length > 0 ? keySchemas : [{}]);
116
+ }
117
+
118
+ const merged = { type: 'object', properties, additionalProperties: false };
119
+ if (alwaysPresent.size > 0) merged.required = [...alwaysPresent];
120
+ return merged;
121
+ }
122
+
123
+ if (type === 'array') {
124
+ const itemSchemas = schemas.map(s => s.items).filter(Boolean);
125
+ return { type: 'array', items: itemSchemas.length > 0 ? mergeSchemas(itemSchemas) : {} };
126
+ }
127
+
128
+ // Primitive: just return first (they're all the same type)
129
+ const first = schemas[0];
130
+ // If formats differ, drop the format
131
+ const formats = [...new Set(schemas.map(s => s.format).filter(Boolean))];
132
+ if (formats.length === 1) return first;
133
+ const { format, ...rest } = first;
134
+ return rest;
135
+ }
136
+
137
+ function deduplicateSchemas(schemas) {
138
+ const seen = new Set();
139
+ return schemas.filter(s => {
140
+ const key = JSON.stringify(s);
141
+ if (seen.has(key)) return false;
142
+ seen.add(key);
143
+ return true;
144
+ });
145
+ }
146
+
147
+ // ─── Merge schemas from multiple sample files ─────────────────────────────────
148
+ function mergeFromSamples(samples, opts) {
149
+ const schemas = samples.map(v => inferSchema(v, opts));
150
+ return mergeSchemas(schemas);
151
+ }
152
+
153
+ // ─── CLI ──────────────────────────────────────────────────────────────────────
154
+ const args = process.argv.slice(2);
155
+
156
+ // Flags that consume the next argument as their value
157
+ const VALUE_FLAGS = new Set(['--id', '--title', '--out']);
158
+
159
+ function getFlag(f) {
160
+ const i = args.indexOf(f);
161
+ return i !== -1 ? args[i + 1] : null;
162
+ }
163
+ function hasFlag(f) { return args.includes(f); }
164
+
165
+ // Positional args: skip flags and their values
166
+ const positional = [];
167
+ for (let i = 0; i < args.length; i++) {
168
+ if (args[i].startsWith('-')) {
169
+ if (VALUE_FLAGS.has(args[i])) i++; // skip the next value too
170
+ } else {
171
+ positional.push(args[i]);
172
+ }
173
+ }
174
+
175
+ if (hasFlag('--version') || hasFlag('-v')) {
176
+ console.log(`json-schema-gen v${VERSION}`); process.exit(0);
177
+ }
178
+
179
+ if (hasFlag('--help') || hasFlag('-h') || !positional.length) {
180
+ console.log(`
181
+ ${bold('json-schema-gen')} — Generate JSON Schema from sample JSON, zero dependencies
182
+
183
+ ${bold('USAGE')}
184
+ json-schema-gen <file.json> [file2.json ...] [options]
185
+
186
+ ${bold('OPTIONS')}
187
+ --required Mark all non-null fields as required
188
+ --examples Include example values for string fields
189
+ --id <uri> Add $id to the schema
190
+ --title <name> Add title to the schema
191
+ --out <file> Write schema to file instead of stdout
192
+ --pretty Pretty-print even when piped (default: always pretty)
193
+ --version Show version
194
+
195
+ ${bold('EXAMPLES')}
196
+ json-schema-gen user.json
197
+ json-schema-gen user.json --required --title "User"
198
+ json-schema-gen sample1.json sample2.json # Merge multiple samples
199
+ json-schema-gen data.json --out schema.json
200
+ cat data.json | json-schema-gen - # Read from stdin
201
+ `);
202
+ process.exit(positional.length ? 0 : 1);
203
+ }
204
+
205
+ const allRequired = hasFlag('--required');
206
+ const showExamples = hasFlag('--examples');
207
+ const schemaId = getFlag('--id');
208
+ const schemaTitle = getFlag('--title');
209
+ const outFile = getFlag('--out');
210
+
211
+ const opts = { allRequired, examples: showExamples };
212
+
213
+ // Read all input files
214
+ const samples = [];
215
+ for (const p of positional) {
216
+ let raw;
217
+ if (p === '-') {
218
+ raw = fs.readFileSync('/dev/stdin', 'utf8');
219
+ } else {
220
+ const fullPath = path.resolve(p);
221
+ if (!fs.existsSync(fullPath)) {
222
+ console.error(red(`\nFile not found: ${fullPath}\n`)); process.exit(1);
223
+ }
224
+ raw = fs.readFileSync(fullPath, 'utf8');
225
+ }
226
+ try {
227
+ samples.push(JSON.parse(raw));
228
+ } catch (e) {
229
+ console.error(red(`\nInvalid JSON in ${p}: ${e.message}\n`)); process.exit(1);
230
+ }
231
+ }
232
+
233
+ // Generate schema
234
+ const schema = samples.length === 1
235
+ ? inferSchema(samples[0], opts)
236
+ : mergeFromSamples(samples, opts);
237
+
238
+ // Add meta fields
239
+ const output = {
240
+ $schema: 'https://json-schema.org/draft-07/schema',
241
+ ...(schemaId ? { $id: schemaId } : {}),
242
+ ...(schemaTitle ? { title: schemaTitle } : {}),
243
+ ...schema,
244
+ };
245
+
246
+ const jsonStr = JSON.stringify(output, null, 2);
247
+
248
+ if (outFile) {
249
+ fs.writeFileSync(path.resolve(outFile), jsonStr + '\n');
250
+ const props = countProperties(output);
251
+ if (!isTTY || outFile) {
252
+ process.stderr.write(`\n${bold('json-schema-gen')} ${dim(outFile)}\n`);
253
+ process.stderr.write(dim(` ${props} propert${props === 1 ? 'y' : 'ies'} · ${positional.length} sample${positional.length > 1 ? 's' : ''}\n\n`));
254
+ }
255
+ } else {
256
+ console.log(jsonStr);
257
+ }
258
+
259
+ function countProperties(schema, depth = 0) {
260
+ if (!schema || typeof schema !== 'object') return 0;
261
+ if (schema.type === 'object' && schema.properties) {
262
+ return Object.keys(schema.properties).reduce((sum, k) => sum + 1 + countProperties(schema.properties[k], depth + 1), 0);
263
+ }
264
+ if (schema.type === 'array' && schema.items) {
265
+ return countProperties(schema.items, depth);
266
+ }
267
+ return 0;
268
+ }
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@lowdep/json-schema-gen",
3
+ "version": "1.0.0",
4
+ "description": "Generate JSON Schema (draft-07) from sample JSON data — format detection, multi-sample merging, zero dependencies",
5
+ "bin": {
6
+ "json-schema-gen": "bin/json-schema-gen.js"
7
+ },
8
+ "keywords": [
9
+ "json",
10
+ "json-schema",
11
+ "schema",
12
+ "generator",
13
+ "cli",
14
+ "validation",
15
+ "zero-dependencies",
16
+ "json schema generator",
17
+ "json to schema",
18
+ "quicktype alternative",
19
+ "genson alternative",
20
+ "infer schema",
21
+ "draft-07",
22
+ "generate schema",
23
+ "json schema",
24
+ "zero dependencies"
25
+ ],
26
+ "author": "Rushabh Shah",
27
+ "license": "MIT",
28
+ "engines": {
29
+ "node": ">=14"
30
+ },
31
+ "files": [
32
+ "bin/"
33
+ ],
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "git+https://github.com/Rushabh5000/json-schema-gen.git"
37
+ },
38
+ "bugs": {
39
+ "url": "https://github.com/Rushabh5000/json-schema-gen/issues"
40
+ },
41
+ "homepage": "https://github.com/Rushabh5000/json-schema-gen#readme",
42
+ "publishConfig": {
43
+ "access": "public"
44
+ }
45
+ }