186
187
export function getCompressedContent(schema: IJSONSchema): string {
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
}