@loaders.gl/json
Version:
Framework-independent loader for JSON and streaming JSON formats
114 lines (96 loc) • 3.57 kB
text/typescript
// loaders.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors
import type {Schema, TableBatch} from '@loaders.gl/schema';
import type {JSONLoaderOptions, MetadataBatch, JSONBatch} from '../../json-loader';
import {TableBatchBuilder} from '@loaders.gl/schema';
import {assert, makeTextDecoderIterator} from '@loaders.gl/loader-utils';
import StreamingJSONParser from '../json-parser/streaming-json-parser';
import JSONPath from '../jsonpath/jsonpath';
// TODO - support batch size 0 = no batching/single batch?
// eslint-disable-next-line max-statements, complexity
export async function* parseJSONInBatches(
binaryAsyncIterator: AsyncIterable<ArrayBuffer> | Iterable<ArrayBuffer>,
options: JSONLoaderOptions
): AsyncIterable<TableBatch | MetadataBatch | JSONBatch> {
const asyncIterator = makeTextDecoderIterator(binaryAsyncIterator);
const {metadata} = options;
const {jsonpaths} = options.json || {};
let isFirstChunk: boolean = true;
// @ts-expect-error TODO fix Schema deduction
const schema: Schema = null;
const tableBatchBuilder = new TableBatchBuilder(schema, options);
const parser = new StreamingJSONParser({jsonpaths});
for await (const chunk of asyncIterator) {
const rows = parser.write(chunk);
const jsonpath = rows.length > 0 && parser.getStreamingJsonPathAsString();
if (rows.length > 0 && isFirstChunk) {
if (metadata) {
const initialBatch: TableBatch = {
// Common fields
shape: options?.json?.shape || 'array-row-table',
batchType: 'partial-result',
data: [],
length: 0,
bytesUsed: 0,
// JSON additions
container: parser.getPartialResult(),
jsonpath
};
yield initialBatch;
}
isFirstChunk = false;
// schema = deduceSchema(rows);
}
// Add the row
for (const row of rows) {
tableBatchBuilder.addRow(row);
// If a batch has been completed, emit it
const batch = tableBatchBuilder.getFullBatch({jsonpath});
if (batch) {
yield batch;
}
}
tableBatchBuilder.chunkComplete(chunk);
const batch = tableBatchBuilder.getFullBatch({jsonpath});
if (batch) {
yield batch;
}
}
// yield final batch
const jsonpath = parser.getStreamingJsonPathAsString();
const batch = tableBatchBuilder.getFinalBatch({jsonpath});
if (batch) {
yield batch;
}
if (metadata) {
const finalBatch: JSONBatch = {
shape: 'json',
batchType: 'final-result',
container: parser.getPartialResult(),
jsonpath: parser.getStreamingJsonPathAsString(),
/** Data Just to avoid crashing? */
data: [],
length: 0
// schema: null
};
yield finalBatch;
}
}
export function rebuildJsonObject(batch, data) {
// Last batch will have this special type and will provide all the root object of the parsed file
assert(batch.batchType === 'final-result');
// The streamed JSON data is a top level array (jsonpath = '$'), just return the array of row objects
if (batch.jsonpath === '$') {
return data;
}
// (jsonpath !== '$') The streamed data is not a top level array, so stitch it back in to the top-level object
if (batch.jsonpath && batch.jsonpath.length > 1) {
const topLevelObject = batch.container;
const streamingPath = new JSONPath(batch.jsonpath);
streamingPath.setFieldAtPath(topLevelObject, data);
return topLevelObject;
}
// No jsonpath, in this case nothing was streamed.
return batch.container;
}