@sapientpro/json-stream
Version:
A JSON stream parser
313 lines (312 loc) • 12.3 kB
JavaScript
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var _JsonStream_instances, _JsonStream_observers, _JsonStream_resolveDesc;
Object.defineProperty(exports, "__esModule", { value: true });
exports.JsonStream = exports.Rest = exports.Any = void 0;
const node_stream_1 = require("node:stream");
const rxjs_1 = require("rxjs");
exports.Any = Symbol('Any');
exports.Rest = Symbol('Rest');
class JsonStream extends node_stream_1.Writable {
constructor(start = '', collectJson = false) {
let buffer = '';
let pos = 0;
let continuation = Promise.withResolvers();
let process = Promise.withResolvers();
const next = async (shift = 0, callback) => {
while (pos + shift >= buffer.length) {
callback?.();
const { resolve } = continuation;
continuation = Promise.withResolvers();
resolve();
await process.promise;
}
//Cleanup buffer if it's too big
if (!collectJson && pos > 512) {
buffer = buffer.substring(pos);
pos = 0;
}
return this.writable || pos < buffer.length;
};
const waitStart = async () => {
const length = start?.length ?? 0;
if (!length)
return;
while (await next(length)) {
const startPos = buffer.indexOf(start, pos);
if (startPos >= 0) {
pos = startPos + start.length;
await skipSpaces();
buffer = buffer.substring(pos);
pos = 0;
break;
}
pos = buffer.length - length + 1;
}
};
const skipSpaces = async () => {
while (await next()) {
if (buffer.at(pos).trim() === '') {
++pos;
continue;
}
break;
}
};
const parse = async (path = []) => {
await skipSpaces();
let value;
switch (buffer.at(pos)) {
case '{': {
pos++;
value = {};
while (await next()) {
await skipSpaces();
if (buffer.at(pos) === '}') {
++pos;
break;
}
const name = await parseString();
await skipSpaces();
if (buffer.at(pos) !== ':') {
throw new SyntaxError('Json syntax error at ' + pos);
}
++pos;
value[name] = await parse([...path, name]);
await skipSpaces();
if (buffer.at(pos) === ',') {
++pos;
}
}
break;
}
case '[': {
++pos;
let index = 0;
value = [];
while (await next()) {
await skipSpaces();
if (buffer.at(pos) === ']') {
++pos;
break;
}
value.push(await parse([...path, index]));
++index;
await skipSpaces();
if (buffer.at(pos) === ',') {
++pos;
}
}
break;
}
case '"':
value = await parseString(path);
break;
case "t":
await next(3);
if (buffer.substring(pos, pos + 4) !== 'true') {
throw new SyntaxError('Json syntax error at ' + pos);
}
value = true;
pos += 4;
break;
case "f":
await next(4);
if (buffer.substring(pos, pos + 5) !== 'false') {
throw new SyntaxError('Json syntax error at ' + pos);
}
value = false;
pos += 5;
break;
case "n":
await next(3);
if (buffer.substring(pos, pos + 4) !== 'null') {
throw new SyntaxError('Json syntax error at ' + pos);
}
value = null;
pos += 4;
break;
default:
do {
const str = buffer.substring(pos);
const match = str.match(/^(-?\d+(\.\d+)?([eE][+-]?\d+)?)([^.eE])?/);
if ((this.writable || this.writableLength > 0) && match && match[4] === void 0) {
await next(str.length + 1);
continue;
}
if (!match) {
throw new SyntaxError('Json syntax error at ' + pos);
}
value = Number(match[1]);
pos += match[1].length;
} while (false);
}
pushValue(__classPrivateFieldGet(this, _JsonStream_observers, "f"), path, value);
return value;
};
const parseString = async (path) => {
let value = '';
let chunk = '';
++pos;
const stream = path && __classPrivateFieldGet(this, _JsonStream_instances, "m", _JsonStream_resolveDesc).call(this, path, false)?.stream;
loop: while (await next(0, () => {
value += chunk;
stream?.push(chunk);
chunk = '';
})) {
switch (buffer.at(pos)) {
case '"':
++pos;
break loop;
case '\\':
++pos;
await next();
switch (buffer.at(pos)) {
case 't':
chunk += '\t';
++pos;
break;
case 'r':
chunk += '\r';
++pos;
break;
case 'n':
chunk += '\n';
++pos;
break;
case 'b':
chunk += '\b';
++pos;
break;
case 'f':
chunk += '\f';
++pos;
break;
case 'u':
await next(4);
chunk += String.fromCharCode(parseInt(buffer.substring(pos + 1, pos + 5), 16));
pos += 5;
break;
default:
chunk += buffer.at(pos);
++pos;
break;
}
break;
default:
chunk += buffer.at(pos);
++pos;
break;
}
}
if (chunk) {
value += chunk;
stream?.push(chunk);
}
stream?.push(null);
return value;
};
const pushValue = (observers, path, value, originalPath = [...path]) => {
if (path.length === 0) {
observers.observer?.next({
path: originalPath,
value
});
return;
}
const key = path.shift();
if (observers.children[key]) {
pushValue(observers.children[key], path, value, originalPath);
}
if (observers.children[exports.Any]) {
pushValue(observers.children[exports.Any], path, value, originalPath);
}
if (observers.children[exports.Rest]) {
pushValue(observers.children[exports.Rest], [], value, originalPath);
}
};
const cleanup = (observer) => {
observer.stream?.push(null);
observer.observer?.complete();
for (const child of Object.values(observer.children)) {
cleanup(child);
}
};
super({
defaultEncoding: 'utf-8',
construct(callback) {
waitStart()
.then(() => parse())
.then(async (value) => {
this.emit('value', value);
continuation.resolve();
})
.catch((e) => {
this.emit('error', e);
});
callback();
},
async write(chunk, encoding, callback) {
buffer += chunk.toString('utf-8');
continuation.promise.then(() => {
callback();
});
const { resolve } = process;
process = Promise.withResolvers();
resolve();
},
final(callback) {
continuation.promise.then(() => {
callback();
}, callback);
},
destroy(error, callback) {
cleanup(__classPrivateFieldGet(this, _JsonStream_observers, "f"));
callback(error);
}
});
_JsonStream_instances.add(this);
_JsonStream_observers.set(this, { children: {} });
}
observe(path = []) {
var _a;
return ((_a = __classPrivateFieldGet(this, _JsonStream_instances, "m", _JsonStream_resolveDesc).call(this, path)).observer ?? (_a.observer = new rxjs_1.Subject()));
}
stream(path) {
if (__classPrivateFieldGet(this, _JsonStream_instances, "m", _JsonStream_resolveDesc).call(this, path).stream) {
throw new Error('Stream already exists');
}
return (__classPrivateFieldGet(this, _JsonStream_instances, "m", _JsonStream_resolveDesc).call(this, path).stream = new node_stream_1.Readable({
encoding: 'utf-8',
read() {
}
}));
}
async value(path = []) {
const { value } = await (0, rxjs_1.firstValueFrom)(this.observe(path));
return value;
}
}
exports.JsonStream = JsonStream;
_JsonStream_observers = new WeakMap(), _JsonStream_instances = new WeakSet(), _JsonStream_resolveDesc = function _JsonStream_resolveDesc(path, create = true) {
if (typeof path === 'string') {
path = path.split('.');
}
let observer = __classPrivateFieldGet(this, _JsonStream_observers, "f");
for (const key of path) {
if (Object.hasOwn(observer.children, key)) {
observer = observer.children[key];
}
else if (create) {
observer = observer.children[key] = { children: {} };
}
else {
return null;
}
}
return observer;
};
;