src/vs/base/common/jsonSchema.ts

330 LOC · 314 covered · 16 uncovered · 15 ranges · 7477 concepts · 4 introducers · 3932 tests

File neighbourhood

The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.

1 > /*--------------------------------------------------------------------------------------------- jsonSchema.ts ×3
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > export type JSONSchemaType = 'string' | 'number' | 'integer' | 'boolean' | 'null' | 'array' | 'object';
7 >
8 > export interface IJSONSchema {
9 > id?: string;
10 > $id?: string;
11 > $schema?: string;
12 > type?: JSONSchemaType | JSONSchemaType[];
13 > title?: string;
14 > default?: any;
15 > definitions?: IJSONSchemaMap;
16 > description?: string;
17 > properties?: IJSONSchemaMap;
18 > patternProperties?: IJSONSchemaMap;
19 > additionalProperties?: boolean | IJSONSchema;
20 > minProperties?: number;
21 > maxProperties?: number;
22 > dependencies?: IJSONSchemaMap | { [prop: string]: string[] };
23 > items?: IJSONSchema | IJSONSchema[];
24 > minItems?: number;
25 > maxItems?: number;
26 > uniqueItems?: boolean;
27 > additionalItems?: boolean | IJSONSchema;
28 > pattern?: string;
29 > minLength?: number;
30 > maxLength?: number;
31 > minimum?: number;
32 > maximum?: number;
33 > exclusiveMinimum?: boolean | number;
34 > exclusiveMaximum?: boolean | number;
35 > multipleOf?: number;
36 > required?: string[];
37 > $ref?: string;
38 > anyOf?: IJSONSchema[];
39 > allOf?: IJSONSchema[];
40 > oneOf?: IJSONSchema[];
41 > not?: IJSONSchema;
42 > enum?: any[];
43 > format?: string;
44 >
45 > // schema draft 06
46 > const?: any;
47 > contains?: IJSONSchema;
48 > propertyNames?: IJSONSchema;
49 > examples?: any[];
50 >
51 > // schema draft 07
52 > $comment?: string;
53 > if?: IJSONSchema;
54 > then?: IJSONSchema;
55 > else?: IJSONSchema;
56 >
57 > // schema 2019-09
58 > unevaluatedProperties?: boolean | IJSONSchema;
59 > unevaluatedItems?: boolean | IJSONSchema;
60 > minContains?: number;
61 > maxContains?: number;
62 > deprecated?: boolean;
63 > dependentRequired?: { [prop: string]: string[] };
64 > dependentSchemas?: IJSONSchemaMap;
65 > $defs?: { [name: string]: IJSONSchema };
66 > $anchor?: string;
67 > $recursiveRef?: string;
68 > $recursiveAnchor?: string;
69 > $vocabulary?: any;
70 >
71 > // schema 2020-12
72 > prefixItems?: IJSONSchema[];
73 > $dynamicRef?: string;
74 > $dynamicAnchor?: string;
75 >
76 > // VSCode extensions
77 >
78 > defaultSnippets?: IJSONSchemaSnippet[];
79 > errorMessage?: string;
80 > patternErrorMessage?: string;
81 > deprecationMessage?: string;
82 > markdownDeprecationMessage?: string;
83 > enumDescriptions?: string[];
84 > markdownEnumDescriptions?: string[];
85 > markdownDescription?: string;
86 > doNotSuggest?: boolean;
87 > suggestSortText?: string;
88 > allowComments?: boolean;
89 > allowTrailingCommas?: boolean;
90 > secret?: boolean;
91 > }
92 >
93 > export interface IJSONSchemaMap {
94 > [name: string]: IJSONSchema;
95 > }
96 >
97 > export interface IJSONSchemaSnippet {
98 > label?: string;
99 > description?: string;
100 > body?: any; // a object that will be JSON stringified
101 > bodyText?: string; // an already stringified JSON object that can contain new lines (\n) and tabs (\t)
102 > }
103 >
104 > /**
105 > * Converts a basic JSON schema to a TypeScript type.
106 > */
107 > export type TypeFromJsonSchema<T> =
108 > // enum
109 > T extends { enum: infer EnumValues }
110 > ? UnionOf<EnumValues>
111 >
112 > // Object with list of required properties.
113 > // Values are required or optional based on `required` list.
114 > : T extends { type: 'object'; properties: infer P; required: infer RequiredList }
115 > ? {
116 > [K in keyof P]: IsRequired<K, RequiredList> extends true ? TypeFromJsonSchema<P[K]> : TypeFromJsonSchema<P[K]> | undefined;
117 > } & AdditionalPropertiesType<T>
118 >
119 > // Object with no required properties.
120 > // All values are optional
121 > : T extends { type: 'object'; properties: infer P }
122 > ? { [K in keyof P]: TypeFromJsonSchema<P[K]> | undefined } & AdditionalPropertiesType<T>
123 >
124 > // Array
125 > : T extends { type: 'array'; items: infer Items }
126 > ? Items extends [...infer R]
127 > // If items is an array, we treat it like a tuple
128 > ? { [K in keyof R]: TypeFromJsonSchema<Items[K]> }
129 > : Array<TypeFromJsonSchema<Items>>
130 >
131 > // oneOf / anyof
132 > // These are handled the same way as they both represent a union type.
133 > // However at the validation level, they have different semantics.
134 > : T extends { oneOf: infer I }
135 > ? MapSchemaToType<I>
136 > : T extends { anyOf: infer I }
137 > ? MapSchemaToType<I>
138 >
139 > // Primitive types
140 > : T extends { type: infer Type }
141 > // Basic type
142 > ? Type extends 'string' | 'number' | 'integer' | 'boolean' | 'null'
143 > ? SchemaPrimitiveTypeNameToType<Type>
144 > // Union of primitive types
145 > : Type extends [...infer R]
146 > ? UnionOf<{ [K in keyof R]: SchemaPrimitiveTypeNameToType<R[K]> }>
147 > : never
148 >
149 > // Fallthrough
150 > : never;
151 >
152 > type SchemaPrimitiveTypeNameToType<T> =
153 > T extends 'string' ? string :
154 > T extends 'number' | 'integer' ? number :
155 > T extends 'boolean' ? boolean :
156 > T extends 'null' ? null :
157 > never;
158 >
159 > type UnionOf<T> =
160 > T extends [infer First, ...infer Rest]
161 > ? First | UnionOf<Rest>
162 > : never;
163 >
164 > type IsRequired<K, RequiredList> =
165 > RequiredList extends []
166 > ? false
167 >
168 > : RequiredList extends [K, ...infer _]
169 > ? true
170 >
171 > : RequiredList extends [infer _, ...infer R]
172 > ? IsRequired<K, R>
173 >
174 > : false;
175 >
176 > type AdditionalPropertiesType<Schema> =
177 > Schema extends { additionalProperties: infer AP }
178 > ? AP extends false ? {} : { [key: string]: TypeFromJsonSchema<Schema['additionalProperties']> }
179 > : {};
180 >
181 > type MapSchemaToType<T> = T extends [infer First, ...infer Rest]
182 > ? TypeFromJsonSchema<First> | MapSchemaToType<Rest>
183 > : never;
184 >
185 > interface Equals { schemas: IJSONSchema[]; id?: string }
186 >
187 > export function getCompressedContent(schema: IJSONSchema): string {
188 > let hasDups = false; jsonSchema.ts ×10
189 >
190 >
191 > // visit all schema nodes and collect the ones that are equal
192 > const equalsByString = new Map<string, Equals>();
193 > const nodeToEquals = new Map<IJSONSchema, Equals>();
194 > const visitSchemas = (next: IJSONSchema) => {
195 > if (schema === next) {
196 > return true;
197 > }
198 > const val = JSON.stringify(next);
199 > if (val.length < 30) {
200 > // the $ref takes around 25 chars, so we don't save anything
201 > return true;
202 > }
203 > const eq = equalsByString.get(val);
204 > if (!eq) {
205 > const newEq = { schemas: [next] };
206 > equalsByString.set(val, newEq);
207 > nodeToEquals.set(next, newEq);
208 > return true;
209 > }
210 > eq.schemas.push(next);
211 > nodeToEquals.set(next, eq);
212 > hasDups = true;
213 > return false;
214 > };
215 > traverseNodes(schema, visitSchemas);
216 > equalsByString.clear();
217 >
218 > if (!hasDups) {
219 return JSON.stringify(schema);
220 }
222 > let defNodeName = '$defs';
223 > while (schema.hasOwnProperty(defNodeName)) {
224 defNodeName += '_';
225 }
227 > // used to collect all schemas that are later put in `$defs`. The index in the array is the id of the schema.
228 > const definitions: IJSONSchema[] = [];
229 >
230 > function stringify(root: IJSONSchema): string {
231 > return JSON.stringify(root, (_key: string, value: any) => {
232 > if (value !== root) {
233 > const eq = nodeToEquals.get(value);
234 > if (eq && eq.schemas.length > 1) {
235 > if (!eq.id) {
236 > eq.id = `_${definitions.length}`;
237 > definitions.push(eq.schemas[0]);
238 > }
239 > return { $ref: `#/${defNodeName}/${eq.id}` };
240 > }
241 > }
242 > return value;
243 > });
244 > }
245 >
246 > // stringify the schema and replace duplicate subtrees with $ref
247 > // this will add new items to the definitions array
248 > const str = stringify(schema);
249 >
250 > // now stringify the definitions. Each invication of stringify cann add new items to the definitions array, so the length can grow while we iterate
251 > const defStrings: string[] = [];
252 > for (let i = 0; i < definitions.length; i++) {
253 > defStrings.push(`"_${i}":${stringify(definitions[i])}`);
254 > }
255 > if (defStrings.length) {
256 > return `${str.substring(0, str.length - 1)},"${defNodeName}":{${defStrings.join(',')}}}`;
257 > }
258 return str;
259 }
261 > type IJSONSchemaRef = IJSONSchema | boolean;
262 >
263 > function isObject(thing: unknown): thing is object { jsonSchema.ts ×10
264 > return typeof thing === 'object' && thing !== null;
265 > }
267 > /*
268 > * Traverse a JSON schema and visit each schema node
269 > */
270 > function traverseNodes(root: IJSONSchema, visit: (schema: IJSONSchema) => boolean) { jsonSchema.ts ×10
271 > if (!root || typeof root !== 'object') {
272 return;
273 }
274 > const collectEntries = (...entries: (IJSONSchemaRef | undefined)[]) => { jsonSchema.ts ×10
275 > for (const entry of entries) {
276 > if (isObject(entry)) {
277 toWalk.push(entry);
278 }
280 > };
281 > const collectMapEntries = (...maps: (IJSONSchemaMap | undefined)[]) => {
282 > for (const map of maps) {
283 > if (isObject(map)) {
284 > for (const key in map) {
285 > const entry = map[key];
286 > if (isObject(entry)) {
287 > toWalk.push(entry);
288 > }
289 > }
290 > }
291 > }
292 > };
293 > const collectArrayEntries = (...arrays: (IJSONSchemaRef[] | undefined)[]) => {
294 > for (const array of arrays) {
295 > if (Array.isArray(array)) {
296 > for (const entry of array) { jsonSchema.ts ×1
297 > if (isObject(entry)) {
298 > toWalk.push(entry);
299 > }
300 > }
301 > }
303 > };
304 > const collectEntryOrArrayEntries = (items: (IJSONSchemaRef[] | IJSONSchemaRef | undefined)) => {
305 > if (Array.isArray(items)) {
306 for (const entry of items) {
307 if (isObject(entry)) {
308 toWalk.push(entry);
309 }
310 }
311 > } else if (isObject(items)) { jsonSchema.ts ×10
312 > toWalk.push(items); jsonSchema.ts ×1
313 > }
315 >
316 > const toWalk: IJSONSchema[] = [root];
317 >
318 > let next = toWalk.pop();
319 > while (next) {
320 > const visitChildern = visit(next);
321 > if (visitChildern) {
322 > collectEntries(next.additionalItems, next.additionalProperties, next.not, next.contains, next.propertyNames, next.if, next.then, next.else, next.unevaluatedItems, next.unevaluatedProperties);
323 > collectMapEntries(next.definitions, next.$defs, next.properties, next.patternProperties, <IJSONSchemaMap>next.dependencies, next.dependentSchemas);
324 > collectArrayEntries(next.anyOf, next.allOf, next.oneOf, next.prefixItems);
325 > collectEntryOrArrayEntries(next.items);
326 > }
327 > next = toWalk.pop();
328 > }
329 > }
330