UNPKG

16 kBMarkdownView Raw
1# JSON Schema Infer
2
3> Infers JSON Schemas from example JSON. Powers the schema inference of [jsonhero.io](https://jsonhero.io)
4
5![Coverage lines](./badges/badge-lines.svg)
6![Tests](https://github.com/jsonhero-io/schema-infer/actions/workflows/test.yml/badge.svg?branch=main)
7[![Downloads](https://img.shields.io/npm/dm/%40jsonhero%2Fschema-infer.svg)](https://npmjs.com/@jsonhero/schema-infer)
8[![Install size](https://packagephobia.com/badge?p=%40jsonhero%2Fschema-infer)](https://packagephobia.com/result?p=@jsonhero/schema-infer)
9
10## Features
11
12- Written in typescript
13- Inspired by [jtd-infer](https://jsontypedef.com/docs/jtd-infer/)
14- Generates valid 2020-12 JSON schema documents from example data
15- Supports most string formats through [json-infer-types](https://github.com/jsonhero-io/json-infer-types)
16 - Date and times
17 - URIs
18 - Email Addresses
19 - Hostnames
20 - IP Addresses
21 - uuids
22- Supports snapshotting and restoring inference sessions
23- Handles nullable values and required/optional properties
24
25## Usage
26
27Install `schema-infer`:
28
29```bash
30npm install --save @jsonhero/schema-infer
31```
32
33To produce a JSON Schema document, pass in the example JSON to `inferSchema` and call `toJSONSchema` on the result:
34
35```ts
36import { inferSchema } from "@jsonhero/schema-infer";
37
38inferSchema({
39 id: "abeb8b52-e960-44dc-9e09-57bb00d6b441",
40 name: "Eric",
41 emailAddress: "eric@jsonhero.io",
42 website: "https://github.com/ericallam",
43 joined: "2022-01-01",
44}).toJSONSchema();
45```
46
47Infers the following JSON schema:
48
49```json
50{
51 "$schema": "https://json-schema.org/draft/2020-12/schema",
52 "type": "object",
53 "properties": {
54 "id": { "type": "string", "format": "uuid" },
55 "name": { "type": "string" },
56 "emailAddress": { "type": "string", "format": "email" },
57 "website": { "type": "string", "format": "uri" },
58 "joined": { "type": "string", "format": "date" }
59 },
60 "required": ["id", "name", "emailAddress", "website", "joined"]
61}
62```
63
64Inferring an array of objects, with some properties being optional:
65
66```ts
67inferSchema([
68 { rank: 1, name: "Eric", winner: true },
69 { rank: 2, name: "Matt" },
70]).toJSONSchema();
71```
72
73Produces the following schema:
74
75```json
76{
77 "items": {
78 "properties": {
79 "name": {
80 "type": "string"
81 },
82 "rank": {
83 "type": "integer"
84 },
85 "winner": {
86 "type": "boolean"
87 }
88 },
89 "required": ["rank", "name"],
90 "type": "object"
91 },
92 "type": "array"
93}
94```
95
96You can produce better results by inferring from more than 1 example JSON, like so:
97
98```ts
99let inference = inferSchema({ name: "Eric" });
100inference = inferSchema({ name: "James", age: 87 });
101
102inference.toJSONSchema();
103```
104
105Produces:
106
107```json
108{
109 "type": "object",
110 "properties": {
111 "name": { "type": "string" },
112 "age": { "type": "integer" }
113 },
114 "required": ["name"]
115}
116```
117
118If you need to save the inference session for later use you can use the `toSnapshot` and `restoreSnapshot` functions:
119
120```ts
121let inference = inferSchema({ name: "Eric" });
122let snapshot = inference.toSnapshot();
123
124await writeFile("./inference.json", JSON.stringify(snapshot));
125
126// Later:
127let snapshot = JSON.parse(await readFile("./inference.json"));
128inferSchema({ email: "eric@jsonhero.io" }, restoreSnapshot(snapshot));
129```
130
131This library makes use of `anyOf` to handle a value that can be multiple conflicting types:
132
133```ts
134inferSchema([1, "three"]).toJSONSchema();
135```
136
137Will produce
138
139```json
140{
141 "type": "array",
142 "items": {
143 "anyOf": [{ "type": "integer" }, { "type": "string" }]
144 }
145}
146```
147
148## Examples
149
150### Airtable API
151
152<details>
153 <summary>JSON</summary>
154
155 ```json
156 [
157 {
158 "id": "rec3SDRbI5izJ0ENy",
159 "fields": {
160 "Link": "www.examplelink.com",
161 "Name": "Ikrore chair",
162 "Settings": [
163 "Office",
164 "Bedroom",
165 "Living room"
166 ],
167 "Vendor": [
168 "reczC9ifQTdJpMZcx"
169 ],
170 "Color": [
171 "Grey",
172 "Green",
173 "Red",
174 "White",
175 "Blue purple"
176 ],
177 "Designer": [
178 "recJ76rS7fEJi03wW"
179 ],
180 "Type": "Chairs",
181 "Images": [
182 {
183 "id": "atten0ycxONEmeKfu",
184 "width": 501,
185 "height": 750,
186 "url": "https://dl.airtable.com/.attachments/e13d90aafb01450314538eee5398abb3/ea5e6e6f/pexels-photo-1166406.jpegautocompresscstinysrgbh750w1260",
187 "filename": "pexels-photo-1166406.jpeg?auto=compress&cs=tinysrgb&h=750&w=1260",
188 "size": 33496,
189 "type": "image/jpeg",
190 "thumbnails": {
191 "small": {
192 "url": "https://dl.airtable.com/.attachmentThumbnails/ff3db1021522f6100afa7e09ab42b187/9bc0dc81",
193 "width": 24,
194 "height": 36
195 },
196 "large": {
197 "url": "https://dl.airtable.com/.attachmentThumbnails/15421f668579a7d75c506253b61668d6/f7c14834",
198 "width": 501,
199 "height": 750
200 },
201 "full": {
202 "url": "https://dl.airtable.com/.attachmentThumbnails/bd297cad0f2acb7da5d63e0692934def/3053bea3",
203 "width": 3000,
204 "height": 3000
205 }
206 }
207 }
208 ],
209 "Description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
210 "Materials": [
211 "Tech suede",
212 "Light wood"
213 ],
214 "Size (WxLxH)": "40x32x19",
215 "Unit cost": 1300.5,
216 "Total units sold": 0,
217 "Gross sales": 0
218 },
219 "createdTime": "2015-01-27T20:16:05.000Z"
220 },
221 {
222 "id": "rec4gR4daG7FLbTss",
223 "fields": {
224 "Link": "www.examplelink.com",
225 "Name": "Angular pendant",
226 "Settings": [
227 "Office"
228 ],
229 "Vendor": [
230 "reczC9ifQTdJpMZcx"
231 ],
232 "Color": [
233 "Silver",
234 "Black",
235 "White",
236 "Gold"
237 ],
238 "Designer": [
239 "recoh9S9UjHVUpcPy"
240 ],
241 "In stock": true,
242 "Type": "Lighting",
243 "Orders": [
244 "recspa0dTuVfr5Tji"
245 ],
246 "Images": [
247 {
248 "id": "attViFaKwjE6WJ3iD",
249 "width": 1000,
250 "height": 1500,
251 "url": "https://dl.airtable.com/.attachments/ce5d081b96ad1d4ef7aa3003c77fb761/4e9b68ae/photo-1546902172-146006dcd1e6ixlibrb-1.2.1ixideyJhcHBfaWQiOjEyMDd9autoformatfitcropw1000q80",
252 "filename": "photo-1546902172-146006dcd1e6?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1000&q=80",
253 "size": 163784,
254 "type": "image/jpeg",
255 "thumbnails": {
256 "small": {
257 "url": "https://dl.airtable.com/.attachmentThumbnails/ffa7089696c170c6be567d5f34b4ed66/e1046fbc",
258 "width": 24,
259 "height": 36
260 },
261 "large": {
262 "url": "https://dl.airtable.com/.attachmentThumbnails/e66162154bfa7eacd377d40266f57316/39fb0eac",
263 "width": 512,
264 "height": 768
265 },
266 "full": {
267 "url": "https://dl.airtable.com/.attachmentThumbnails/7070d3cb16ad9d18e4fa5bbedb4e740b/460fd6c4",
268 "width": 3000,
269 "height": 3000
270 }
271 }
272 }
273 ],
274 "Description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
275 "Materials": [
276 "Steel"
277 ],
278 "Size (WxLxH)": "7.5 x 12.75, 10.5 x 17.5 ",
279 "Unit cost": 295,
280 "Total units sold": 2,
281 "Gross sales": 590
282 },
283 "createdTime": "2015-01-27T19:14:59.000Z"
284 },
285 {
286 "id": "rec4rIuzOPQA07c3M",
287 "fields": {
288 "Link": "www.examplelink.com",
289 "Name": "Madrid chair",
290 "Settings": [
291 "Living room",
292 "Office"
293 ],
294 "Vendor": [
295 "reczC9ifQTdJpMZcx"
296 ],
297 "Color": [
298 "White",
299 "Brown",
300 "Black"
301 ],
302 "Designer": [
303 "recqx2njQY1QqkcaV"
304 ],
305 "In stock": true,
306 "Type": "Chairs",
307 "Orders": [
308 "rec0jJArKIPxTddSX",
309 "rec3mEIxLONBSab4Y"
310 ],
311 "Images": [
312 {
313 "id": "attYAf0fLp3H3OdGk",
314 "width": 1000,
315 "height": 477,
316 "url": "https://dl.airtable.com/.attachments/c717b870174222c61991d81d32e6faa4/1ef6556a/photo-1505843490538-5133c6c7d0e1ixlibrb-1.2.1ixideyJhcHBfaWQiOjEyMDd9autoformatfitcropw1000q80",
317 "filename": "photo-1505843490538-5133c6c7d0e1?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1000&q=80",
318 "size": 17498,
319 "type": "image/jpeg",
320 "thumbnails": {
321 "small": {
322 "url": "https://dl.airtable.com/.attachmentThumbnails/c3e8f6f2189b0d9eb14cb58b9c653f42/3b76d95a",
323 "width": 75,
324 "height": 36
325 },
326 "large": {
327 "url": "https://dl.airtable.com/.attachmentThumbnails/e222fd421eddb24f9b5171a25adaa9ec/3cf86de6",
328 "width": 1000,
329 "height": 477
330 },
331 "full": {
332 "url": "https://dl.airtable.com/.attachmentThumbnails/4cae754b4adc96820e98a79ca8ebdcbd/09040841",
333 "width": 3000,
334 "height": 3000
335 }
336 }
337 }
338 ],
339 "Description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
340 "Materials": [
341 "Light wood",
342 "Metal"
343 ],
344 "Size (WxLxH)": "3x1x5",
345 "Unit cost": 5429,
346 "Total units sold": 36,
347 "Gross sales": 195444
348 },
349 "createdTime": "2014-09-24T05:48:20.000Z"
350 }
351]
352 ```
353</details>
354
355<details>
356 <summary>Inferred Schema</summary>
357
358 ```json
359{
360 "type": "object",
361 "properties": {
362 "id": {
363 "type": "string"
364 },
365 "fields": {
366 "type": "object",
367 "properties": {
368 "Link": {
369 "type": "string",
370 "format": "hostname"
371 },
372 "Name": {
373 "type": "string"
374 },
375 "Settings": {
376 "type": "array",
377 "items": {
378 "type": "string"
379 }
380 },
381 "Vendor": {
382 "type": "array",
383 "items": {
384 "type": "string"
385 }
386 },
387 "Color": {
388 "type": "array",
389 "items": {
390 "type": "string"
391 }
392 },
393 "Designer": {
394 "type": "array",
395 "items": {
396 "type": "string"
397 }
398 },
399 "Type": {
400 "type": "string"
401 },
402 "Images": {
403 "type": "array",
404 "items": {
405 "type": "object",
406 "properties": {
407 "id": {
408 "type": "string"
409 },
410 "width": {
411 "type": "integer"
412 },
413 "height": {
414 "type": "integer"
415 },
416 "url": {
417 "type": "string",
418 "format": "uri"
419 },
420 "filename": {
421 "type": "string"
422 },
423 "size": {
424 "type": "integer"
425 },
426 "type": {
427 "type": "string"
428 },
429 "thumbnails": {
430 "type": "object",
431 "properties": {
432 "small": {
433 "type": "object",
434 "properties": {
435 "url": {
436 "type": "string",
437 "format": "uri"
438 },
439 "width": {
440 "type": "integer"
441 },
442 "height": {
443 "type": "integer"
444 }
445 },
446 "required": [
447 "url",
448 "width",
449 "height"
450 ]
451 },
452 "large": {
453 "type": "object",
454 "properties": {
455 "url": {
456 "type": "string",
457 "format": "uri"
458 },
459 "width": {
460 "type": "integer"
461 },
462 "height": {
463 "type": "integer"
464 }
465 },
466 "required": [
467 "url",
468 "width",
469 "height"
470 ]
471 },
472 "full": {
473 "type": "object",
474 "properties": {
475 "url": {
476 "type": "string",
477 "format": "uri"
478 },
479 "width": {
480 "type": "integer"
481 },
482 "height": {
483 "type": "integer"
484 }
485 },
486 "required": [
487 "url",
488 "width",
489 "height"
490 ]
491 }
492 },
493 "required": [
494 "small",
495 "large",
496 "full"
497 ]
498 }
499 },
500 "required": [
501 "id",
502 "width",
503 "height",
504 "url",
505 "filename",
506 "size",
507 "type",
508 "thumbnails"
509 ]
510 }
511 },
512 "Description": {
513 "type": "string"
514 },
515 "Materials": {
516 "type": "array",
517 "items": {
518 "type": "string"
519 }
520 },
521 "Size (WxLxH)": {
522 "type": "string"
523 },
524 "Unit cost": {
525 "type": "number"
526 },
527 "Total units sold": {
528 "type": "integer"
529 },
530 "Gross sales": {
531 "type": "integer"
532 },
533 "In stock": {
534 "type": "boolean"
535 },
536 "Orders": {
537 "type": "array",
538 "items": {
539 "type": "string"
540 }
541 }
542 },
543 "required": [
544 "Link",
545 "Name",
546 "Settings",
547 "Vendor",
548 "Color",
549 "Designer",
550 "Type",
551 "Images",
552 "Description",
553 "Materials",
554 "Size (WxLxH)",
555 "Unit cost",
556 "Total units sold",
557 "Gross sales"
558 ]
559 },
560 "createdTime": {
561 "type": "string",
562 "format": "date-time"
563 }
564 },
565 "required": [
566 "id",
567 "fields",
568 "createdTime"
569 ]
570}
571 ```
572</details>
573
574## Roadmap
575
576- Add support for hints for discriminators (tagged unions), value-only schemas, and enums
577- Add support for [JSON Typedefs](https://jsontypedef.com)
578- Add "verbose" mode to include `$id`, `examples`, etc.
579
\No newline at end of file