Skip to content

Instantly share code, notes, and snippets.

@conartist6
Last active April 26, 2026 18:22
Show Gist options
  • Select an option

  • Save conartist6/5a46f57b741de72b2b88854a7700032d to your computer and use it in GitHub Desktop.

Select an option

Save conartist6/5a46f57b741de72b2b88854a7700032d to your computer and use it in GitHub Desktop.
JSON stream parser for guthib-of-dan
import { StreamGenerator, wait } from '@bablr/stream-iterator';
import { decodeUTF8, readFile } from '@bablr/fs';
let { isArray } = Array;
let arrayLast = (arr) => arr[arr.length - 1];
let maybeWait = (maybePromise, callback) => {
let isPromise = maybePromise instanceof Promise;
return isPromise ? maybePromise.then(callback) : callback(maybePromise);
};
let isPlainObject = (val) => val != null && typeof val === 'object' && !isArray(val);
let isHexDigit = (chr) =>
(chr >= '0' && chr <= '9') || (chr >= 'a' && chr <= 'f') || (chr >= 'A' && chr <= 'F');
let escapables = {
b: '\b',
f: '\f',
n: '\n',
r: '\r',
t: '\t',
};
let keywords = {
t: 'true',
f: 'false',
n: 'null',
};
let Bare = Symbol('Bare');
let Keyword = Symbol('Keyword');
let Escape = Symbol('Escape');
let EscapeCode = Symbol('EscapeCode');
let String_ = Symbol('String');
function* __streamParseJSON(bytes) {
let iter = decodeUTF8(bytes)[Symbol.for('@@streamIterator')]();
let step = iter.next();
let keyword = null;
let key = null;
let value = undefined;
let path = [];
let state = Bare;
for (;;) {
if (step instanceof Promise) step = yield wait(step);
if (step.done) break;
let chr = step.value;
switch (state) {
case Bare: {
switch (chr) {
case ' ':
case '\t':
case '\r':
case '\n':
break;
case ',':
key = isPlainObject(value) ? null : value.length;
break;
case ':':
if (!key) throw new Error();
break;
case '{':
if (value !== undefined) path.push([key, value]);
value = {};
key = null;
break;
case '[':
if (value !== undefined) path.push([key, value]);
value = [];
key = 0;
break;
case ']':
case '}': {
let parent = arrayLast(path)?.[1];
if (parent) {
if (isPlainObject(value) && chr === ']') throw new Error();
let newValue = value;
({ 0: key, 1: value } = path.pop());
value[key] = newValue;
}
break;
}
case '"':
case "'":
if (value !== undefined) path.push([key, value]);
state = String_;
value = '';
break;
case 't':
case 'f':
case 'n':
if (value !== undefined) path.push([key, value]);
keyword = keywords[chr];
state = Keyword;
value = '';
continue;
default:
throw new Error();
}
break;
}
case Keyword: {
if (keyword[value.length] === chr) {
value += chr;
} else throw new Error();
if (value.length === keyword.length) {
switch (keyword) {
case 'true':
value = true;
break;
case 'false':
value = false;
break;
case 'null':
value = null;
break;
default:
throw new Error();
}
let parent = arrayLast(path)[1];
parent[key] = value;
({ 0: key, 1: value } = path.pop());
state = Bare;
}
break;
}
case String_: {
switch (chr) {
case '"':
case "'":
let parent = arrayLast(path)?.[1];
if (parent) {
if (isPlainObject(parent) && !key) {
key = value;
({ 1: value } = path.pop());
} else {
parent[key] = value;
({ 0: key, 1: value } = path.pop());
}
}
state = Bare;
break;
case '\\':
state = Escape;
break;
default:
value = value + chr; // TODO, optimize this
break;
}
break;
}
case Escape: {
switch (chr) {
case '"':
case '\\':
case '//':
case 'b':
case 'f':
case 'n':
case 'r':
case 't':
value = value + escapables[chr] || chr;
let parent = arrayLast(path)?.[1];
if (parent) {
parent[key] = value;
}
state = String_;
break;
case 'u':
path.push([key, value]);
value = [];
state = EscapeCode;
break;
}
break;
}
case EscapeCode: {
if (isHexDigit(chr)) {
value += chr;
} else throw new Error();
if (value.length === 4) {
let code = parseInt(value.join(''), 16);
let parent = arrayLast(path)[1];
state = String_;
({ 0: key, 1: value } = path.pop());
value += String.fromCharCode(code);
}
break;
}
}
step = iter.next();
}
return value;
}
const streamParseJSON = (input) => {
let iter = new StreamGenerator(__streamParseJSON(input));
return maybeWait(iter.next(), (step) => step.value);
};
let obj = await streamParseJSON(readFile('fixture.json'));
debugger;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment