@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.
- package/dist/dist.dev.js +260 -24
- package/dist/dist.min.js +11 -11
- package/dist/geojson-loader.d.ts.map +1 -1
- package/dist/geojson-loader.js +4 -2
- package/dist/geojson-loader.js.map +1 -0
- package/dist/geojson-worker.js +239 -22
- package/dist/geojson-writer.js +1 -0
- package/dist/geojson-writer.js.map +1 -0
- package/dist/index.cjs +222 -30
- package/dist/index.cjs.map +4 -4
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -0
- package/dist/json-loader.js +2 -1
- package/dist/json-loader.js.map +1 -0
- package/dist/json-writer.js +1 -0
- package/dist/json-writer.js.map +1 -0
- package/dist/lib/clarinet/clarinet.js +1 -0
- package/dist/lib/clarinet/clarinet.js.map +1 -0
- package/dist/lib/encoder-utils/encode-table-row.js +1 -0
- package/dist/lib/encoder-utils/encode-table-row.js.map +1 -0
- package/dist/lib/encoder-utils/encode-utils.js +1 -0
- package/dist/lib/encoder-utils/encode-utils.js.map +1 -0
- package/dist/lib/encoder-utils/utf8-encoder.d.ts +1 -1
- package/dist/lib/encoder-utils/utf8-encoder.d.ts.map +1 -1
- package/dist/lib/encoder-utils/utf8-encoder.js +3 -1
- package/dist/lib/encoder-utils/utf8-encoder.js.map +1 -0
- package/dist/lib/encoders/geojson-encoder.js +1 -0
- package/dist/lib/encoders/geojson-encoder.js.map +1 -0
- package/dist/lib/encoders/json-encoder.js +1 -0
- package/dist/lib/encoders/json-encoder.js.map +1 -0
- package/dist/lib/json-parser/json-parser.js +1 -0
- package/dist/lib/json-parser/json-parser.js.map +1 -0
- package/dist/lib/json-parser/streaming-json-parser.js +1 -0
- package/dist/lib/json-parser/streaming-json-parser.js.map +1 -0
- package/dist/lib/jsonpath/jsonpath.d.ts.map +1 -1
- package/dist/lib/jsonpath/jsonpath.js +213 -18
- package/dist/lib/jsonpath/jsonpath.js.map +1 -0
- package/dist/lib/parsers/parse-json-in-batches.d.ts +1 -1
- package/dist/lib/parsers/parse-json-in-batches.d.ts.map +1 -1
- package/dist/lib/parsers/parse-json-in-batches.js +5 -4
- package/dist/lib/parsers/parse-json-in-batches.js.map +1 -0
- package/dist/lib/parsers/parse-json.js +1 -0
- package/dist/lib/parsers/parse-json.js.map +1 -0
- package/dist/lib/parsers/parse-ndjson-in-batches.d.ts +1 -1
- package/dist/lib/parsers/parse-ndjson-in-batches.d.ts.map +1 -1
- package/dist/lib/parsers/parse-ndjson-in-batches.js +4 -3
- package/dist/lib/parsers/parse-ndjson-in-batches.js.map +1 -0
- package/dist/lib/parsers/parse-ndjson.js +1 -0
- package/dist/lib/parsers/parse-ndjson.js.map +1 -0
- package/dist/ndgeoson-loader.js +2 -1
- package/dist/ndgeoson-loader.js.map +1 -0
- package/dist/ndjson-loader.js +2 -1
- package/dist/ndjson-loader.js.map +1 -0
- package/dist/workers/geojson-worker.js +1 -0
- package/dist/workers/geojson-worker.js.map +1 -0
- package/package.json +10 -7
- package/src/geojson-loader.ts +2 -1
- package/src/json-loader.ts +3 -1
- package/src/lib/encoder-utils/utf8-encoder.ts +4 -2
- package/src/lib/jsonpath/jsonpath.ts +268 -21
- package/src/lib/parsers/parse-json-in-batches.ts +7 -5
- 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
|
|
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:
|
|
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
|
|
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:
|
|
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
|
|