@loaders.gl/json 4.4.0-alpha.1 → 4.4.0-alpha.9

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 (62) hide show
  1. package/dist/dist.dev.js +260 -24
  2. package/dist/dist.min.js +11 -11
  3. package/dist/geojson-loader.d.ts.map +1 -1
  4. package/dist/geojson-loader.js +4 -2
  5. package/dist/geojson-loader.js.map +1 -0
  6. package/dist/geojson-worker.js +239 -22
  7. package/dist/geojson-writer.js +1 -0
  8. package/dist/geojson-writer.js.map +1 -0
  9. package/dist/index.cjs +222 -30
  10. package/dist/index.cjs.map +4 -4
  11. package/dist/index.js +1 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/json-loader.js +2 -1
  14. package/dist/json-loader.js.map +1 -0
  15. package/dist/json-writer.js +1 -0
  16. package/dist/json-writer.js.map +1 -0
  17. package/dist/lib/clarinet/clarinet.js +1 -0
  18. package/dist/lib/clarinet/clarinet.js.map +1 -0
  19. package/dist/lib/encoder-utils/encode-table-row.js +1 -0
  20. package/dist/lib/encoder-utils/encode-table-row.js.map +1 -0
  21. package/dist/lib/encoder-utils/encode-utils.js +1 -0
  22. package/dist/lib/encoder-utils/encode-utils.js.map +1 -0
  23. package/dist/lib/encoder-utils/utf8-encoder.d.ts +1 -1
  24. package/dist/lib/encoder-utils/utf8-encoder.d.ts.map +1 -1
  25. package/dist/lib/encoder-utils/utf8-encoder.js +3 -1
  26. package/dist/lib/encoder-utils/utf8-encoder.js.map +1 -0
  27. package/dist/lib/encoders/geojson-encoder.js +1 -0
  28. package/dist/lib/encoders/geojson-encoder.js.map +1 -0
  29. package/dist/lib/encoders/json-encoder.js +1 -0
  30. package/dist/lib/encoders/json-encoder.js.map +1 -0
  31. package/dist/lib/json-parser/json-parser.js +1 -0
  32. package/dist/lib/json-parser/json-parser.js.map +1 -0
  33. package/dist/lib/json-parser/streaming-json-parser.js +1 -0
  34. package/dist/lib/json-parser/streaming-json-parser.js.map +1 -0
  35. package/dist/lib/jsonpath/jsonpath.d.ts.map +1 -1
  36. package/dist/lib/jsonpath/jsonpath.js +213 -18
  37. package/dist/lib/jsonpath/jsonpath.js.map +1 -0
  38. package/dist/lib/parsers/parse-json-in-batches.d.ts +1 -1
  39. package/dist/lib/parsers/parse-json-in-batches.d.ts.map +1 -1
  40. package/dist/lib/parsers/parse-json-in-batches.js +5 -4
  41. package/dist/lib/parsers/parse-json-in-batches.js.map +1 -0
  42. package/dist/lib/parsers/parse-json.js +1 -0
  43. package/dist/lib/parsers/parse-json.js.map +1 -0
  44. package/dist/lib/parsers/parse-ndjson-in-batches.d.ts +1 -1
  45. package/dist/lib/parsers/parse-ndjson-in-batches.d.ts.map +1 -1
  46. package/dist/lib/parsers/parse-ndjson-in-batches.js +4 -3
  47. package/dist/lib/parsers/parse-ndjson-in-batches.js.map +1 -0
  48. package/dist/lib/parsers/parse-ndjson.js +1 -0
  49. package/dist/lib/parsers/parse-ndjson.js.map +1 -0
  50. package/dist/ndgeoson-loader.js +2 -1
  51. package/dist/ndgeoson-loader.js.map +1 -0
  52. package/dist/ndjson-loader.js +2 -1
  53. package/dist/ndjson-loader.js.map +1 -0
  54. package/dist/workers/geojson-worker.js +1 -0
  55. package/dist/workers/geojson-worker.js.map +1 -0
  56. package/package.json +10 -7
  57. package/src/geojson-loader.ts +2 -1
  58. package/src/json-loader.ts +3 -1
  59. package/src/lib/encoder-utils/utf8-encoder.ts +4 -2
  60. package/src/lib/jsonpath/jsonpath.ts +268 -21
  61. package/src/lib/parsers/parse-json-in-batches.ts +7 -5
  62. package/src/lib/parsers/parse-ndjson-in-batches.ts +7 -4
@@ -2,6 +2,8 @@
2
2
  // SPDX-License-Identifier: MIT
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
+ /* eslint-disable no-continue */
6
+
5
7
  /**
6
8
  * A parser for a minimal subset of the jsonpath standard
7
9
  * Full JSON path parsers for JS exist but are quite large (bundle size)
@@ -14,26 +16,7 @@ export default class JSONPath {
14
16
  path: string[];
15
17
 
16
18
  constructor(path: JSONPath | string[] | string | null = null) {
17
- this.path = ['$'];
18
-
19
- if (path instanceof JSONPath) {
20
- // @ts-ignore
21
- this.path = [...path.path];
22
- return;
23
- }
24
-
25
- if (Array.isArray(path)) {
26
- this.path.push(...path);
27
- return;
28
- }
29
-
30
- // Parse a string as a JSONPath
31
- if (typeof path === 'string') {
32
- this.path = path.split('.');
33
- if (this.path[0] !== '$') {
34
- throw new Error('JSONPaths must start with $');
35
- }
36
- }
19
+ this.path = parseJsonPath(path);
37
20
  }
38
21
 
39
22
  clone(): JSONPath {
@@ -41,7 +24,7 @@ export default class JSONPath {
41
24
  }
42
25
 
43
26
  toString(): string {
44
- return this.path.join('.');
27
+ return formatJsonPath(this.path);
45
28
  }
46
29
 
47
30
  push(name: string): void {
@@ -103,3 +86,267 @@ export default class JSONPath {
103
86
  return object[field];
104
87
  }
105
88
  }
89
+
90
+ type BracketSegment =
91
+ | {type: 'property'; value: string; nextIndex: number}
92
+ | {type: 'array-selector'; nextIndex: number};
93
+
94
+ function parseJsonPath(path: JSONPath | string[] | string | null): string[] {
95
+ if (path instanceof JSONPath) {
96
+ return [...path.path];
97
+ }
98
+
99
+ if (Array.isArray(path)) {
100
+ return ['$'].concat(path);
101
+ }
102
+
103
+ if (typeof path === 'string') {
104
+ return parseJsonPathString(path);
105
+ }
106
+
107
+ return ['$'];
108
+ }
109
+
110
+ // eslint-disable-next-line complexity, max-statements
111
+ function parseJsonPathString(pathString: string): string[] {
112
+ const trimmedPath = pathString.trim();
113
+ if (!trimmedPath.startsWith('$')) {
114
+ throw new Error('JSONPath must start with $');
115
+ }
116
+
117
+ const segments: string[] = ['$'];
118
+ let index = 1;
119
+ let arrayElementSelectorEncountered = false;
120
+
121
+ while (index < trimmedPath.length) {
122
+ const character = trimmedPath[index];
123
+ if (character === '.') {
124
+ if (arrayElementSelectorEncountered) {
125
+ throw new Error('JSONPath cannot select fields after array element selectors');
126
+ }
127
+ index += 1;
128
+ if (trimmedPath[index] === '.') {
129
+ throw new Error('JSONPath descendant selectors (..) are not supported');
130
+ }
131
+ const {value, nextIndex, isWildcard} = parseDotSegment(trimmedPath, index);
132
+ if (isWildcard) {
133
+ if (nextIndex < trimmedPath.length) {
134
+ throw new Error('JSONPath wildcard selectors must terminate the path');
135
+ }
136
+ arrayElementSelectorEncountered = true;
137
+ index = nextIndex;
138
+ continue;
139
+ }
140
+ segments.push(value);
141
+ index = nextIndex;
142
+ continue;
143
+ }
144
+
145
+ if (character === '[') {
146
+ const parsedSegment = parseBracketSegment(trimmedPath, index);
147
+ if (parsedSegment.type === 'property') {
148
+ if (arrayElementSelectorEncountered) {
149
+ throw new Error('JSONPath cannot select fields after array element selectors');
150
+ }
151
+ segments.push(parsedSegment.value);
152
+ } else {
153
+ arrayElementSelectorEncountered = true;
154
+ }
155
+ index = parsedSegment.nextIndex;
156
+ continue;
157
+ }
158
+
159
+ if (character === '@') {
160
+ throw new Error('JSONPath current node selector (@) is not supported');
161
+ }
162
+
163
+ if (character.trim() === '') {
164
+ index += 1;
165
+ continue;
166
+ }
167
+
168
+ throw new Error(`Unexpected character "${character}" in JSONPath`);
169
+ }
170
+
171
+ return segments;
172
+ }
173
+
174
+ function parseDotSegment(
175
+ pathString: string,
176
+ startIndex: number
177
+ ): {
178
+ value: string;
179
+ nextIndex: number;
180
+ isWildcard: boolean;
181
+ } {
182
+ if (startIndex >= pathString.length) {
183
+ throw new Error('JSONPath cannot end with a period');
184
+ }
185
+
186
+ if (pathString[startIndex] === '*') {
187
+ return {value: '*', nextIndex: startIndex + 1, isWildcard: true};
188
+ }
189
+
190
+ const firstCharacter = pathString[startIndex];
191
+ if (firstCharacter === '@') {
192
+ throw new Error('JSONPath current node selector (@) is not supported');
193
+ }
194
+ if (!isIdentifierStartCharacter(firstCharacter)) {
195
+ throw new Error('JSONPath property names after period must start with a letter, $ or _');
196
+ }
197
+
198
+ let endIndex = startIndex + 1;
199
+ while (endIndex < pathString.length && isIdentifierCharacter(pathString[endIndex])) {
200
+ endIndex++;
201
+ }
202
+
203
+ if (endIndex === startIndex) {
204
+ throw new Error('JSONPath is missing a property name after period');
205
+ }
206
+
207
+ return {
208
+ value: pathString.slice(startIndex, endIndex),
209
+ nextIndex: endIndex,
210
+ isWildcard: false
211
+ };
212
+ }
213
+
214
+ function parseBracketSegment(pathString: string, startIndex: number): BracketSegment {
215
+ const contentStartIndex = startIndex + 1;
216
+ if (contentStartIndex >= pathString.length) {
217
+ throw new Error('JSONPath has unterminated bracket');
218
+ }
219
+
220
+ const firstCharacter = pathString[contentStartIndex];
221
+ if (firstCharacter === "'" || firstCharacter === '"') {
222
+ const {value, nextIndex} = parseBracketProperty(pathString, contentStartIndex);
223
+ return {type: 'property', value, nextIndex};
224
+ }
225
+
226
+ const closingBracketIndex = pathString.indexOf(']', contentStartIndex);
227
+ if (closingBracketIndex === -1) {
228
+ throw new Error('JSONPath has unterminated bracket');
229
+ }
230
+
231
+ const content = pathString.slice(contentStartIndex, closingBracketIndex).trim();
232
+ const unsupportedSelectorMessage = getUnsupportedBracketSelectorMessage(content);
233
+ if (unsupportedSelectorMessage) {
234
+ throw new Error(unsupportedSelectorMessage);
235
+ }
236
+ if (content === '*') {
237
+ return {type: 'array-selector', nextIndex: closingBracketIndex + 1};
238
+ }
239
+
240
+ if (/^\d+$/.test(content)) {
241
+ throw new Error('JSONPath array index selectors are not supported');
242
+ }
243
+
244
+ if (/^\d*\s*:\s*\d*(\s*:\s*\d*)?$/.test(content)) {
245
+ return {type: 'array-selector', nextIndex: closingBracketIndex + 1};
246
+ }
247
+
248
+ throw new Error(`Unsupported bracket selector "[${content}]" in JSONPath`);
249
+ }
250
+
251
+ function getUnsupportedBracketSelectorMessage(content: string): string | null {
252
+ if (!content.length) {
253
+ return 'JSONPath bracket selectors cannot be empty';
254
+ }
255
+ if (content.startsWith('(')) {
256
+ return 'JSONPath script selectors are not supported';
257
+ }
258
+ if (content.startsWith('?')) {
259
+ return 'JSONPath filter selectors are not supported';
260
+ }
261
+ if (content.includes(',')) {
262
+ return 'JSONPath union selectors are not supported';
263
+ }
264
+ if (content.startsWith('@') || content.includes('@.')) {
265
+ return 'JSONPath current node selector (@) is not supported';
266
+ }
267
+ return null;
268
+ }
269
+
270
+ // eslint-disable-next-line complexity, max-statements
271
+ function parseBracketProperty(
272
+ pathString: string,
273
+ startIndex: number
274
+ ): {
275
+ value: string;
276
+ nextIndex: number;
277
+ } {
278
+ const quoteCharacter = pathString[startIndex];
279
+ let index = startIndex + 1;
280
+ let value = '';
281
+ let terminated = false;
282
+
283
+ while (index < pathString.length) {
284
+ const character = pathString[index];
285
+ if (character === '\\') {
286
+ index += 1;
287
+ if (index >= pathString.length) {
288
+ break;
289
+ }
290
+ value += pathString[index];
291
+ index += 1;
292
+ continue;
293
+ }
294
+
295
+ if (character === quoteCharacter) {
296
+ terminated = true;
297
+ index += 1;
298
+ break;
299
+ }
300
+
301
+ value += character;
302
+ index += 1;
303
+ }
304
+
305
+ if (!terminated) {
306
+ throw new Error('JSONPath string in bracket property selector is unterminated');
307
+ }
308
+
309
+ while (index < pathString.length && pathString[index].trim() === '') {
310
+ index += 1;
311
+ }
312
+
313
+ if (pathString[index] !== ']') {
314
+ throw new Error('JSONPath property selectors must end with ]');
315
+ }
316
+
317
+ if (!value.length) {
318
+ throw new Error('JSONPath property selectors cannot be empty');
319
+ }
320
+
321
+ return {value, nextIndex: index + 1};
322
+ }
323
+
324
+ function isIdentifierCharacter(character: string): boolean {
325
+ return /[a-zA-Z0-9$_]/.test(character);
326
+ }
327
+
328
+ function isIdentifierStartCharacter(character: string): boolean {
329
+ return /[a-zA-Z_$]/.test(character);
330
+ }
331
+
332
+ function isIdentifierSegment(segment: string): boolean {
333
+ return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(segment);
334
+ }
335
+
336
+ function formatJsonPath(path: string[]): string {
337
+ return path
338
+ .map((segment, index) => {
339
+ if (index === 0) {
340
+ return segment;
341
+ }
342
+ if (segment === '*') {
343
+ return '.*';
344
+ }
345
+ if (isIdentifierSegment(segment)) {
346
+ return `.${segment}`;
347
+ }
348
+ const escapedSegment = segment.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
349
+ return `['${escapedSegment}']`;
350
+ })
351
+ .join('');
352
+ }
@@ -6,26 +6,28 @@ import type {Schema, TableBatch} from '@loaders.gl/schema';
6
6
  import type {JSONLoaderOptions, MetadataBatch, JSONBatch} from '../../json-loader';
7
7
 
8
8
  import {TableBatchBuilder} from '@loaders.gl/schema-utils';
9
- import {assert, makeTextDecoderIterator} from '@loaders.gl/loader-utils';
9
+ import {assert, makeTextDecoderIterator, toArrayBufferIterator} from '@loaders.gl/loader-utils';
10
10
  import StreamingJSONParser from '../json-parser/streaming-json-parser';
11
11
  import JSONPath from '../jsonpath/jsonpath';
12
12
 
13
13
  // TODO - support batch size 0 = no batching/single batch?
14
14
  // eslint-disable-next-line max-statements, complexity
15
15
  export async function* parseJSONInBatches(
16
- binaryAsyncIterator: AsyncIterable<ArrayBuffer> | Iterable<ArrayBuffer>,
16
+ binaryAsyncIterator:
17
+ | AsyncIterable<ArrayBufferLike | ArrayBufferView>
18
+ | Iterable<ArrayBufferLike | ArrayBufferView>,
17
19
  options: JSONLoaderOptions
18
20
  ): AsyncIterable<TableBatch | MetadataBatch | JSONBatch> {
19
- const asyncIterator = makeTextDecoderIterator(binaryAsyncIterator);
21
+ const asyncIterator = makeTextDecoderIterator(toArrayBufferIterator(binaryAsyncIterator));
20
22
 
21
- const {metadata} = options;
23
+ const metadata = Boolean(options?.core?.metadata || (options as any)?.metadata);
22
24
  const {jsonpaths} = options.json || {};
23
25
 
24
26
  let isFirstChunk: boolean = true;
25
27
 
26
28
  // @ts-expect-error TODO fix Schema deduction
27
29
  const schema: Schema = null;
28
- const tableBatchBuilder = new TableBatchBuilder(schema, options);
30
+ const tableBatchBuilder = new TableBatchBuilder(schema, options?.core);
29
31
 
30
32
  const parser = new StreamingJSONParser({jsonpaths});
31
33
 
@@ -8,14 +8,17 @@ import {
8
8
  LoaderOptions,
9
9
  makeLineIterator,
10
10
  makeNumberedLineIterator,
11
- makeTextDecoderIterator
11
+ makeTextDecoderIterator,
12
+ toArrayBufferIterator
12
13
  } from '@loaders.gl/loader-utils';
13
14
 
14
15
  export async function* parseNDJSONInBatches(
15
- binaryAsyncIterator: AsyncIterable<ArrayBuffer> | Iterable<ArrayBuffer>,
16
+ binaryAsyncIterator:
17
+ | AsyncIterable<ArrayBufferLike | ArrayBufferView>
18
+ | Iterable<ArrayBufferLike | ArrayBufferView>,
16
19
  options?: LoaderOptions
17
20
  ): AsyncIterable<TableBatch> {
18
- const textIterator = makeTextDecoderIterator(binaryAsyncIterator);
21
+ const textIterator = makeTextDecoderIterator(toArrayBufferIterator(binaryAsyncIterator));
19
22
  const lineIterator = makeLineIterator(textIterator);
20
23
  const numberedLineIterator = makeNumberedLineIterator(lineIterator);
21
24
 
@@ -23,7 +26,7 @@ export async function* parseNDJSONInBatches(
23
26
  const shape = 'row-table';
24
27
  // @ts-ignore
25
28
  const tableBatchBuilder = new TableBatchBuilder(schema, {
26
- ...options,
29
+ ...(options?.core || options),
27
30
  shape
28
31
  });
29
32