jsonschema-key-compression 1.7.0 → 2.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.
Files changed (46) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +65 -5
  3. package/dist/es/compress.d.ts +3 -1
  4. package/dist/es/compress.js +161 -25
  5. package/dist/es/compress.js.map +1 -1
  6. package/dist/es/create-compressed-json-schema.d.ts +9 -0
  7. package/dist/es/create-compressed-json-schema.js +136 -26
  8. package/dist/es/create-compressed-json-schema.js.map +1 -1
  9. package/dist/es/create-compression-table.d.ts +7 -1
  10. package/dist/es/create-compression-table.js +56 -30
  11. package/dist/es/create-compression-table.js.map +1 -1
  12. package/dist/es/decompress.js +39 -21
  13. package/dist/es/decompress.js.map +1 -1
  14. package/dist/es/enum-compression.d.ts +35 -0
  15. package/dist/es/enum-compression.js +217 -0
  16. package/dist/es/enum-compression.js.map +1 -0
  17. package/dist/es/index.d.ts +1 -1
  18. package/dist/es/index.js.map +1 -1
  19. package/dist/es/types.d.ts +11 -0
  20. package/dist/lib/compress.d.ts +3 -1
  21. package/dist/lib/compress.js +167 -32
  22. package/dist/lib/compress.js.map +1 -1
  23. package/dist/lib/create-compressed-json-schema.d.ts +9 -0
  24. package/dist/lib/create-compressed-json-schema.js +136 -27
  25. package/dist/lib/create-compressed-json-schema.js.map +1 -1
  26. package/dist/lib/create-compression-table.d.ts +7 -1
  27. package/dist/lib/create-compression-table.js +61 -35
  28. package/dist/lib/create-compression-table.js.map +1 -1
  29. package/dist/lib/decompress.js +42 -25
  30. package/dist/lib/decompress.js.map +1 -1
  31. package/dist/lib/enum-compression.d.ts +35 -0
  32. package/dist/lib/enum-compression.js +223 -0
  33. package/dist/lib/enum-compression.js.map +1 -0
  34. package/dist/lib/index.d.ts +1 -1
  35. package/dist/lib/index.js.map +1 -1
  36. package/dist/lib/types.d.ts +11 -0
  37. package/dist/lib/util.js +3 -3
  38. package/dist/lib/util.js.map +1 -1
  39. package/package.json +9 -7
  40. package/src/compress.ts +183 -33
  41. package/src/create-compressed-json-schema.ts +158 -35
  42. package/src/create-compression-table.ts +59 -31
  43. package/src/decompress.ts +40 -27
  44. package/src/enum-compression.ts +240 -0
  45. package/src/index.ts +1 -0
  46. package/src/types.d.ts +11 -0
package/src/compress.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  import type {
2
2
  PlainJsonObject,
3
+ PlainJsonObjectNotArray,
3
4
  CompressionTable,
4
5
  MangoQuery
5
6
  } from './types';
7
+ import { compressEnumValue } from './enum-compression';
6
8
 
7
9
  /**
8
10
  * compress the keys of an object via the compression-table
@@ -13,28 +15,34 @@ export function compressObject(
13
15
  obj: PlainJsonObject
14
16
  ): PlainJsonObject {
15
17
  if (typeof obj !== 'object' || obj === null) return obj;
16
- else if (Array.isArray(obj)) {
18
+ if (Array.isArray(obj)) {
17
19
  // array
18
- return obj
19
- .map(item => compressObject(table, item));
20
- } else {
21
- // object
22
- const ret: PlainJsonObject = {};
23
- const keys = Object.keys(obj);
24
- for (let index = 0; index < keys.length; index++) {
25
- const key = keys[index];
26
- const compressedKey = compressedAndFlaggedKey(
27
- table,
28
- key as any
29
- );
30
- const value = compressObject(
31
- table,
32
- obj[key as any]
33
- );
34
- ret[compressedKey] = value;
20
+ const retArray: PlainJsonObjectNotArray[] = new Array(obj.length);
21
+ for (let index = 0; index < obj.length; index++) {
22
+ const item = obj[index];
23
+ // primitives do not need a recursive call
24
+ retArray[index] = (typeof item === 'object' && item !== null) ? compressObject(table, item) as any : item;
35
25
  }
36
- return ret;
26
+ return retArray;
37
27
  }
28
+ // object
29
+ const ret: PlainJsonObjectNotArray = {};
30
+ const keys = Object.keys(obj);
31
+ const enumCompression = table.enumCompression;
32
+ for (let index = 0; index < keys.length; index++) {
33
+ const key = keys[index] as string;
34
+ const value = (obj as PlainJsonObjectNotArray)[key];
35
+ const enumValues = enumCompression && enumCompression.get(key);
36
+ if (enumValues && (typeof value === 'string' || Array.isArray(value))) {
37
+ ret[compressedAndFlaggedKey(table, key)] = compressEnumValue(enumValues, value);
38
+ } else {
39
+ // primitives do not need a recursive call
40
+ ret[compressedAndFlaggedKey(table, key)] = (typeof value === 'object' && value !== null) ?
41
+ compressObject(table, value) :
42
+ value;
43
+ }
44
+ }
45
+ return ret;
38
46
  }
39
47
 
40
48
  /**
@@ -71,6 +79,26 @@ export function throwErrorIfCompressionFlagUsed(
71
79
  }
72
80
  }
73
81
 
82
+ /**
83
+ * Cache of the flagged compressed keys per table,
84
+ * so that the flag does not have to be concatenated on each use.
85
+ * The cache is keyed by the table object, so a table must not be
86
+ * mutated after it has been used for compression.
87
+ */
88
+ const flaggedKeysCache: WeakMap<CompressionTable, Map<string, string>> = new WeakMap();
89
+ function getFlaggedKeys(table: CompressionTable): Map<string, string> {
90
+ let flaggedKeys = flaggedKeysCache.get(table);
91
+ if (!flaggedKeys) {
92
+ const newFlaggedKeys: Map<string, string> = new Map();
93
+ table.compressedToUncompressed.forEach((compressedKey, key) => {
94
+ newFlaggedKeys.set(key, table.compressionFlag + compressedKey);
95
+ });
96
+ flaggedKeysCache.set(table, newFlaggedKeys);
97
+ flaggedKeys = newFlaggedKeys;
98
+ }
99
+ return flaggedKeys;
100
+ }
101
+
74
102
  export function compressedAndFlaggedKey(
75
103
  table: CompressionTable,
76
104
  key: string
@@ -81,17 +109,22 @@ export function compressedAndFlaggedKey(
81
109
  );
82
110
  /**
83
111
  * keys could be array-accessors like myArray[4]
84
- * we have to split and read the squared brackets value
112
+ * so the part before the squared bracket is looked up
113
+ * and the bracket part is re-added.
114
+ * Most keys have no bracket, so the plain lookup is the fast path.
85
115
  */
86
- const splitSquaredBrackets = key.split('[');
87
- const plainKey = splitSquaredBrackets.shift() as string;
88
- const compressedKey = table.compressedToUncompressed.get(plainKey);
89
- if (!compressedKey) {
116
+ const flaggedKeys = getFlaggedKeys(table);
117
+ const bracketIndex = key.indexOf('[');
118
+ if (bracketIndex === -1) {
119
+ const directFlaggedKey = flaggedKeys.get(key);
120
+ return directFlaggedKey ? directFlaggedKey : key;
121
+ }
122
+ const plainKey = key.slice(0, bracketIndex);
123
+ const flaggedKey = flaggedKeys.get(plainKey);
124
+ if (!flaggedKey) {
90
125
  return key;
91
- } else {
92
- const readdSquared = splitSquaredBrackets.length ? '[' + splitSquaredBrackets.join('[') : '';
93
- return table.compressionFlag + compressedKey + readdSquared;
94
126
  }
127
+ return flaggedKey + key.slice(bracketIndex);
95
128
  }
96
129
 
97
130
 
@@ -161,38 +194,155 @@ export function compressQuery(
161
194
  return ret;
162
195
  }
163
196
 
197
+ /**
198
+ * Operators whose value is a document-value and therefore
199
+ * has to be enum-compressed like the documents themselves.
200
+ * Any other operator, like $type or $mod, gets its value unchanged.
201
+ */
202
+ const ENUM_VALUE_OPERATORS: string[] = [
203
+ '$eq',
204
+ '$ne',
205
+ '$gt',
206
+ '$gte',
207
+ '$lt',
208
+ '$lte',
209
+ '$in',
210
+ '$nin',
211
+ '$all',
212
+ '$not',
213
+ '$elemMatch'
214
+ ];
215
+
216
+ /**
217
+ * Returns the enum of the property that the path points to,
218
+ * or undefined when that property is not enum-compressed.
219
+ */
220
+ function enumValuesOfPath(
221
+ table: CompressionTable,
222
+ path: string
223
+ ): string[] | undefined {
224
+ const enumCompression = table.enumCompression;
225
+ if (!enumCompression) {
226
+ return undefined;
227
+ }
228
+ const splitted = path.split('.');
229
+ let lastKey = splitted[splitted.length - 1] as string;
230
+ const bracketIndex = lastKey.indexOf('[');
231
+ if (bracketIndex !== -1) {
232
+ lastKey = lastKey.slice(0, bracketIndex);
233
+ }
234
+ return enumCompression.get(lastKey);
235
+ }
236
+
237
+ /**
238
+ * A regular expression cannot run on the compressed number of an enum-value,
239
+ * so it is resolved up front into the indexes of the matching enum-values.
240
+ */
241
+ function enumIndexesOfRegex(
242
+ enumValues: string[],
243
+ pattern: any,
244
+ options: any
245
+ ): number[] {
246
+ let source: string;
247
+ let flags: string;
248
+ if (pattern instanceof RegExp) {
249
+ source = pattern.source;
250
+ flags = (pattern.ignoreCase ? 'i' : '') + (pattern.multiline ? 'm' : '');
251
+ } else {
252
+ source = String(pattern);
253
+ flags = '';
254
+ }
255
+ if (typeof options === 'string') {
256
+ flags = options;
257
+ }
258
+ /**
259
+ * The global flags make .test() stateful
260
+ * and are meaningless for a full check of a single value.
261
+ */
262
+ flags = flags.replace(/[gy]/g, '');
263
+ const regex = new RegExp(source, flags);
264
+ const indexes: number[] = [];
265
+ for (let i = 0; i < enumValues.length; i++) {
266
+ if (regex.test(enumValues[i] as string)) {
267
+ indexes.push(i);
268
+ }
269
+ }
270
+ return indexes;
271
+ }
272
+
164
273
  /**
165
274
  * @recursive
275
+ * @param enumValues The enum of the property that the selector belongs to,
276
+ * so that the compared values can be enum-compressed.
166
277
  */
167
278
  export function compressQuerySelector(
168
279
  table: CompressionTable,
169
- selector: any
280
+ selector: any,
281
+ enumValues?: string[]
170
282
  ): any {
171
283
  if (Array.isArray(selector)) {
172
- return selector.map(item => compressQuerySelector(table, item));
284
+ return selector.map(item => compressQuerySelector(table, item, enumValues));
173
285
  } else if (selector instanceof RegExp) {
174
286
  return selector;
175
287
  } else if (typeof selector === 'object' && selector !== null) {
176
288
  const ret: any = {};
289
+ /**
290
+ * $regex and its $options are replaced by a single $in
291
+ * with the indexes of the matching enum-values.
292
+ */
293
+ let regexIndexes: number[] | undefined;
294
+ if (enumValues && typeof selector['$regex'] !== 'undefined') {
295
+ regexIndexes = enumIndexesOfRegex(
296
+ enumValues,
297
+ selector['$regex'],
298
+ selector['$options']
299
+ );
300
+ }
177
301
  Object.keys(selector).forEach(key => {
302
+ if (regexIndexes && (key === '$regex' || key === '$options')) {
303
+ return;
304
+ }
178
305
  let useKey;
306
+ let useEnumValues;
179
307
  if (key.startsWith('$')) {
180
308
  // operator
181
309
  useKey = key;
310
+ useEnumValues = ENUM_VALUE_OPERATORS.indexOf(key) === -1 ? undefined : enumValues;
182
311
  } else {
183
312
  // property path
184
313
  useKey = compressedPath(
185
314
  table,
186
315
  key
187
316
  );
317
+ useEnumValues = enumValuesOfPath(
318
+ table,
319
+ key
320
+ );
321
+ }
322
+ const value = selector[key];
323
+ if (useEnumValues && value instanceof RegExp) {
324
+ ret[useKey] = {
325
+ $in: enumIndexesOfRegex(useEnumValues, value, undefined)
326
+ };
327
+ } else {
328
+ ret[useKey] = compressQuerySelector(
329
+ table,
330
+ value,
331
+ useEnumValues
332
+ );
188
333
  }
189
- ret[useKey] = compressQuerySelector(
190
- table,
191
- selector[key]
192
- );
193
334
  });
335
+ if (regexIndexes) {
336
+ const alreadyIn = ret['$in'];
337
+ ret['$in'] = Array.isArray(alreadyIn) ?
338
+ regexIndexes.filter(index => alreadyIn.includes(index)) :
339
+ regexIndexes;
340
+ }
194
341
  return ret;
195
342
  } else {
343
+ if (enumValues) {
344
+ return compressEnumValue(enumValues, selector);
345
+ }
196
346
  return selector;
197
347
  }
198
348
  }
@@ -1,53 +1,176 @@
1
1
  import type {
2
2
  JsonSchema,
3
+ JsonSchemaTypes,
3
4
  CompressionTable
4
5
  } from './types';
5
6
  import { flatClone } from './util';
6
- import { compressedPath } from './compress';
7
+ import {
8
+ compressedPath,
9
+ compressObject
10
+ } from './compress';
7
11
 
12
+ /**
13
+ * A value that is enum-compressed is stored as the index of the value,
14
+ * so the type of the property changes from string to number.
15
+ */
16
+ function toNumberType(
17
+ type: JsonSchemaTypes | JsonSchemaTypes[] | undefined
18
+ ): JsonSchemaTypes | JsonSchemaTypes[] | undefined {
19
+ if (!type) {
20
+ return type;
21
+ }
22
+ if (Array.isArray(type)) {
23
+ return type.map(single => single === 'string' ? 'number' : single);
24
+ }
25
+ return type === 'string' ? 'number' : type;
26
+ }
27
+
28
+ /**
29
+ * Replaces the enum-values of an enum-compressed property
30
+ * by the indexes that the compressed documents contain.
31
+ * The enum can sit at the property itself or at the items of an array-property.
32
+ * @recursive
33
+ */
34
+ function compressEnumOfSchema(
35
+ schema: JsonSchema,
36
+ enumValues: string[]
37
+ ): JsonSchema {
38
+ let ret = schema;
39
+ if (Array.isArray(schema.enum)) {
40
+ ret = flatClone(schema);
41
+ ret.enum = enumValues.map((_value, index) => index);
42
+ ret.type = toNumberType(schema.type);
43
+ }
44
+ const items = ret.items;
45
+ if (items && !Array.isArray(items) && Array.isArray(items.enum)) {
46
+ if (ret === schema) {
47
+ ret = flatClone(schema);
48
+ }
49
+ ret.items = compressEnumOfSchema(items, enumValues);
50
+ }
51
+ return ret;
52
+ }
53
+
54
+ /**
55
+ * Transforms the schema so that it describes the compressed objects.
56
+ * It recurses into all keywords that can contain sub-schemas,
57
+ * independent of the 'type' field. This mirrors how compressObject() works,
58
+ * which compresses every key that is known to the compression-table,
59
+ * wherever the key occurs in the document. For example a nullable nested object
60
+ * has type: ['object', 'null'] and its properties must still be compressed,
61
+ * and properties inside of oneOf/anyOf/allOf must be compressed as well.
62
+ */
8
63
  export function createCompressedJsonSchema(
9
64
  compressionTable: CompressionTable,
10
65
  schema: JsonSchema
11
66
  ): JsonSchema {
12
- if (schema.type === 'array' && schema.items) {
13
- const cloned = flatClone(schema);
67
+ const hasNested = schema.items ||
68
+ schema.additionalItems ||
69
+ schema.properties ||
70
+ schema.patternProperties ||
71
+ schema.required ||
72
+ schema.allOf ||
73
+ schema.anyOf ||
74
+ schema.oneOf ||
75
+ schema.not ||
76
+ schema.dependencies ||
77
+ schema.definitions ||
78
+ schema.enum;
79
+ if (!hasNested) {
80
+ // no deeper fields in the schema
81
+ return schema;
82
+ }
83
+
84
+ const cloned = flatClone(schema);
85
+ const enumCompression = compressionTable.enumCompression;
86
+ const compressSchema = (subSchema: JsonSchema) => createCompressedJsonSchema(compressionTable, subSchema);
87
+ const compressKey = (key: string) => compressedPath(compressionTable, key);
88
+ const compressSchemaMap = (
89
+ schemaMap: { [k: string]: JsonSchema },
90
+ compressKeys: boolean
91
+ ): { [k: string]: JsonSchema } => {
92
+ const ret: { [k: string]: JsonSchema } = {};
93
+ // do not use Object.entries, it is transpiled shitty
94
+ Object.keys(schemaMap).forEach(key => {
95
+ const useKey = compressKeys ? compressKey(key) : key;
96
+ const subSchema = compressSchema(schemaMap[key] as JsonSchema);
97
+ /**
98
+ * Only real property names can be enum-compressed,
99
+ * the keys of patternProperties and definitions are no property names.
100
+ */
101
+ const enumValues = (compressKeys && enumCompression) ? enumCompression.get(key) : undefined;
102
+ ret[useKey] = enumValues ? compressEnumOfSchema(subSchema, enumValues) : subSchema;
103
+ });
104
+ return ret;
105
+ };
106
+
107
+ if (schema.items) {
14
108
  if (Array.isArray(schema.items)) {
15
- const newItems = schema.items.map(item => createCompressedJsonSchema(
16
- compressionTable,
17
- item
18
- ));
19
- cloned.items = newItems;
109
+ cloned.items = schema.items.map(compressSchema);
20
110
  } else {
21
- const newItems = createCompressedJsonSchema(
22
- compressionTable,
23
- schema.items
24
- );
25
- cloned.items = newItems;
111
+ cloned.items = compressSchema(schema.items);
26
112
  }
27
- return cloned;
28
- } else if (schema.type === 'object' && schema.properties) {
29
- const cloned = flatClone(schema);
30
-
31
- // compress all property names
32
- const newProperties: {
33
- [k: string]: JsonSchema
34
- } = {};
35
- // do not use Object.entries, it is transpiled shitty
36
- Object.keys(schema.properties).forEach(key => {
37
- const property = (schema as any).properties[key];
38
- const compressedKey = compressedPath(compressionTable, key);
39
- newProperties[compressedKey] = createCompressedJsonSchema(compressionTable, property);
113
+ }
114
+ if (schema.additionalItems && typeof schema.additionalItems === 'object') {
115
+ cloned.additionalItems = compressSchema(schema.additionalItems);
116
+ }
117
+
118
+ // compress all property names
119
+ if (schema.properties) {
120
+ cloned.properties = compressSchemaMap(schema.properties, true);
121
+ }
122
+ // the patterns are not property names, only the sub-schemas are compressed
123
+ if (schema.patternProperties) {
124
+ cloned.patternProperties = compressSchemaMap(schema.patternProperties, false);
125
+ }
126
+ // also compress the required array
127
+ if (schema.required) {
128
+ cloned.required = schema.required.map(compressKey);
129
+ }
130
+
131
+ if (schema.allOf) {
132
+ cloned.allOf = schema.allOf.map(compressSchema);
133
+ }
134
+ if (schema.anyOf) {
135
+ cloned.anyOf = schema.anyOf.map(compressSchema);
136
+ }
137
+ if (schema.oneOf) {
138
+ cloned.oneOf = schema.oneOf.map(compressSchema);
139
+ }
140
+ if (schema.not) {
141
+ cloned.not = compressSchema(schema.not);
142
+ }
143
+
144
+ /**
145
+ * dependencies are keyed by property name
146
+ * and contain either a list of property names
147
+ * or a sub-schema
148
+ */
149
+ if (schema.dependencies) {
150
+ const dependencies = schema.dependencies;
151
+ const newDependencies: { [k: string]: JsonSchema | string[] } = {};
152
+ Object.keys(dependencies).forEach(key => {
153
+ const dependency = dependencies[key] as JsonSchema | string[];
154
+ newDependencies[compressKey(key)] = Array.isArray(dependency) ?
155
+ dependency.map(compressKey) :
156
+ compressSchema(dependency);
40
157
  });
41
- cloned.properties = newProperties;
158
+ cloned.dependencies = newDependencies;
159
+ }
42
160
 
43
- // also compress the required array
44
- if (cloned.required) {
45
- cloned.required = cloned.required.map(key => compressedPath(compressionTable, key));
46
- }
161
+ // definition names are not property names, only the sub-schemas are compressed
162
+ if (schema.definitions) {
163
+ cloned.definitions = compressSchemaMap(schema.definitions, false);
164
+ }
47
165
 
48
- return cloned;
49
- } else {
50
- // no deeper fields in the schema
51
- return schema;
166
+ /**
167
+ * enum values are document fragments,
168
+ * so object values contain property names that must be compressed.
169
+ * Primitive values are returned unchanged by compressObject().
170
+ */
171
+ if (schema.enum) {
172
+ cloned.enum = schema.enum.map(value => compressObject(compressionTable, value));
52
173
  }
174
+
175
+ return cloned;
53
176
  }
@@ -7,6 +7,7 @@ import {
7
7
  numberToLetter,
8
8
  alphabeticCompare
9
9
  } from './util';
10
+ import { getEnumCompressionTable } from './enum-compression';
10
11
 
11
12
  /**
12
13
  * Compressed property-names begin with the compression-flag
@@ -16,10 +17,17 @@ import {
16
17
  */
17
18
  export const DEFAULT_COMPRESSION_FLAG = '|';
18
19
 
20
+ /**
21
+ * @param compressEnums If true, the values of string-enums are compressed
22
+ * into the index of the value inside of the enum.
23
+ * This changes the stored format, so it is opt-in
24
+ * to keep already compressed data readable.
25
+ */
19
26
  export function createCompressionTable(
20
27
  schema: JsonSchema,
21
28
  compressionFlag: string = DEFAULT_COMPRESSION_FLAG,
22
- ignoreProperties: string[] = []
29
+ ignoreProperties: string[] = [],
30
+ compressEnums: boolean = false
23
31
  ): CompressionTable {
24
32
  const table = compressedToUncompressedTable(
25
33
  schema,
@@ -35,6 +43,20 @@ export function createCompressionTable(
35
43
  compressionFlag
36
44
  };
37
45
 
46
+ if (compressEnums) {
47
+ const enumCompression = getEnumCompressionTable(
48
+ schema,
49
+ ignoreProperties
50
+ );
51
+ /**
52
+ * The field stays undefined when there is nothing to compress,
53
+ * so that the compression does not have to look it up on each key.
54
+ */
55
+ if (enumCompression.size > 0) {
56
+ compressionTable.enumCompression = enumCompression;
57
+ }
58
+ }
59
+
38
60
  return compressionTable;
39
61
  }
40
62
 
@@ -44,35 +66,37 @@ export function createCompressionTable(
44
66
  */
45
67
  export function getPropertiesOfSchema(schema: JsonSchema): Set<string> {
46
68
  const ret: Set<string> = new Set();
69
+ addPropertiesOfSchema(schema, ret);
70
+ return ret;
71
+ }
47
72
 
48
- function addSchema(innerSchema: JsonSchema) {
49
- const keys = getPropertiesOfSchema(innerSchema);
50
- Array.from(keys).forEach(k => ret.add(k));
51
- }
52
-
53
- if (schema.properties) {
73
+ /**
74
+ * Adds all property names of the schema to the given set.
75
+ * Using a single accumulator set avoids creating
76
+ * a new set and array copy on each nesting level.
77
+ */
78
+ function addPropertiesOfSchema(schema: JsonSchema, ret: Set<string>) {
79
+ const properties = schema.properties;
80
+ if (properties) {
54
81
  // do not use Object.entries, it is transpiled shitty
55
- Object.keys(schema.properties).forEach(property => {
56
- const deepSchema = (schema as any).properties[property];
82
+ const propertyNames = Object.keys(properties);
83
+ for (let i = 0; i < propertyNames.length; i++) {
84
+ const property = propertyNames[i] as string;
57
85
  ret.add(property);
58
- if (!schema.properties) {
59
- return;
60
- }
61
- addSchema(deepSchema);
62
- });
86
+ addPropertiesOfSchema(properties[property] as JsonSchema, ret);
87
+ }
63
88
  }
64
89
 
65
- if (schema.items) {
66
- if (Array.isArray(schema.items)) {
67
- schema.items.forEach(subSchema => {
68
- addSchema(subSchema);
69
- });
90
+ const items = schema.items;
91
+ if (items) {
92
+ if (Array.isArray(items)) {
93
+ for (let i = 0; i < items.length; i++) {
94
+ addPropertiesOfSchema(items[i] as JsonSchema, ret);
95
+ }
70
96
  } else {
71
- addSchema(schema.items);
97
+ addPropertiesOfSchema(items, ret);
72
98
  }
73
99
  }
74
-
75
- return ret;
76
100
  }
77
101
 
78
102
  export function compressedToUncompressedTable(
@@ -83,13 +107,13 @@ export function compressedToUncompressedTable(
83
107
  const schemaKeysSorted: string[] = Array.from(attributes).sort(alphabeticCompare);
84
108
  const table: TableType = new Map();
85
109
  let lastKeyNumber: number = 0;
86
- schemaKeysSorted
87
- .filter(k => k.length > 3 && !ignoreProperties.includes(k))
88
- .forEach(k => {
89
- const compressKey = numberToLetter(lastKeyNumber);
110
+ for (let i = 0; i < schemaKeysSorted.length; i++) {
111
+ const k = schemaKeysSorted[i] as string;
112
+ if (k.length > 3 && !ignoreProperties.includes(k)) {
113
+ table.set(k, numberToLetter(lastKeyNumber));
90
114
  lastKeyNumber++;
91
- table.set(k, compressKey);
92
- });
115
+ }
116
+ }
93
117
  return table;
94
118
  }
95
119
 
@@ -99,9 +123,13 @@ export function uncompressedToCompressedTable(
99
123
  ignoreProperties: string[]
100
124
  ): TableType {
101
125
  const reverseTable: TableType = new Map();
102
- Array.from(table.keys()).forEach(key => {
103
- const value = table.get(key) as string;
104
- if (!ignoreProperties.includes(value)) {
126
+ table.forEach((value, key) => {
127
+ /**
128
+ * The ignored properties are property names,
129
+ * so they must be compared to the key and not to the
130
+ * compressed value which could randomly be equal to an ignored name.
131
+ */
132
+ if (!ignoreProperties.includes(key)) {
105
133
  reverseTable.set(compressionFlag + value, key);
106
134
  }
107
135
  });