Skip to content

Instantly share code, notes, and snippets.

@Droogans
Last active August 21, 2021 01:34
Show Gist options
  • Save Droogans/88a150817886d6dba827aa74fe26747d to your computer and use it in GitHub Desktop.
Save Droogans/88a150817886d6dba827aa74fe26747d to your computer and use it in GitHub Desktop.
yieldable-json in typescript
import fs from 'fs';
import path from 'path';
import {parseAsync} from './yieldable-json';
(async () => {
const jsonFileLocation: string = process.argv.slice(-1)[0] as string;
const jsonFilePath = path.resolve(process.cwd(), jsonFileLocation);
let result;
for await (const fragment of parseAsync(await fs.promises.readFile(jsonFilePath))) {
if (!Array.isArray(fragment.property)) {
continue;
}
result = fragment.property;
break;
}
// return otherFn(result, ...)
})();
// @ts-nocheck
/* **************************************************************************
*
* (c) Copyright IBM Corp. 2017
* http://www.opensource.org/licenses/apache2.0.php
*
* Contributors:
* Multiple authors (IBM Corp.) - initial implementation and documentation
***************************************************************************/
/*
github.com/DefinitelyTyped/DefinitelyTyped/discussions/55269
https://www.npmjs.com/package/yieldable-json doesn't have typescript types
**/
const validateIntensity = (intensity: number): number => {
intensity = Math.round(intensity);
if (intensity > 0 && intensity <= 32)
return intensity;
else if (intensity <= 0)
return 1;
else
return 32;
};
/*
https://stackoverflow.com/a/48342359/881224
**/
class ParseError extends Error {
constructor(message?: string) {
super(message);
Object.setPrototypeOf(this, new.target.prototype);
}
get [Symbol.toStringTag]() { return 'ParseError'; }
static get [Symbol.species]() { return ParseError; }
}
class ParseWrapper {
text: string;
reviver: any;
intensity: number;
counter: number;
keyN: number;
parseStr: string;
at: number;
ch: string;
word: string;
constructor(text: Buffer, reviver: any | any[], intensity: number) {
this.text = text.toString();
this.reviver = reviver;
this.counter = 0;
this.intensity = intensity;
this.keyN = 0;
this.parseStr = this.text;
this.at = 0;
this.ch = ' ';
this.word = '';
}
seek() {
do {
this.ch = this.parseStr.charAt(this.at);
this.at++;
} while (this.ch <= ' ');
return this.ch;
};
unseek() {
this.ch = this.parseStr.charAt(--this.at);
}
wordCheck() {
this.word = '';
do {
this.word += this.ch;
this.seek();
} while (this.ch.match(/[a-z]/i));
this.parseStr = this.parseStr.slice(this.at - 1);
this.at = 0;
return this.word;
};
normalizeUnicodedString() {
let inQuotes = ' ';
let tempIndex = this.at;
let index = 0;
let slash = 0;
let c = '"';
while (c) {
index = this.parseStr.indexOf('"', tempIndex + 1);
tempIndex = index;
this.ch = this.parseStr.charAt(tempIndex - 1);
while (this.ch === '\\') {
slash++;
this.ch = this.parseStr.charAt(tempIndex - (slash + 1));
}
if (slash % 2 === 0) {
inQuotes = this.parseStr.substring(this.at, index);
this.parseStr = this.parseStr.slice(++index);
slash = 0;
break;
} else
slash = 0;
}
// When parsing string values, look for " and \ characters.
index = inQuotes.indexOf('\\');
while (index >= 0) {
let escapee = {
'"': '"',
'\'': '\'',
'/': '/',
'\\': '\\',
b: '\b',
f: '\f',
n: '\n',
r: '\r',
t: '\t',
} as { [key: string]: string };
let hex = 0;
let i = 0;
let uffff = 0;
this.at = index;
this.ch = inQuotes.charAt(++this.at);
if (this.ch === 'u') {
uffff = 0;
for (i = 0; i < 4; i += 1) {
hex = parseInt(this.ch = inQuotes.charAt(++this.at), 16);
if (!isFinite(hex)) {
break;
}
uffff = uffff * 16 + hex;
}
inQuotes = inQuotes.slice(0, index) +
String.fromCharCode(uffff) + inQuotes.slice(index + 6);
this.at = index;
} else if (typeof escapee[this.ch] === 'string') {
inQuotes = inQuotes.slice(0, index) +
escapee[this.ch] + inQuotes.slice(index + 2);
this.at = index + 1;
} else
break;
index = inQuotes.indexOf('\\', this.at);
}
this.at = 0;
return inQuotes;
}
async* [Symbol.asyncIterator]() {
const yielded = await this.parseYield();
console.log(typeof yielded);
yield yielded
}
async parseYield(): Promise<any> {
let key = '';
let returnObj: { [key: string]: any } = {};
let returnArr: string[] = [];
let v = '';
let inQuotes = '';
let num = 0;
let numHolder = '';
const addup = () => {
numHolder += this.ch;
this.seek();
};
// Handle premitive types. eg: JSON.parse(21)
if (typeof this.parseStr === 'number' || typeof this.parseStr === 'boolean' ||
this.parseStr === null) {
this.parseStr = '';
return this.text;
} else if (typeof this.parseStr === 'undefined') {
return this.text;
} else if (this.parseStr.charAt(0) === '[' && this.parseStr.charAt(1) === ']') {
this.parseStr = '';
return [];
} else if (this.parseStr.charAt(0) === '{' && this.parseStr.charAt(1) === '}') {
this.parseStr = '';
return {};
} else {
// Common case: non-premitive types.
if (this.keyN !== 1)
this.seek();
switch (this.ch) {
case '{':
// Object case
this.seek();
// "this condition will always return false"
// due to an unstable, self-modifying class instance state for `this.ch`
if (this.ch === '}') {
this.parseStr = this.parseStr.slice(this.at);
this.at = 0;
return returnObj;
}
do {
if (this.ch !== '"')
this.seek();
this.keyN = 1;
key = await this.parseYield();
this.keyN = 0;
this.seek();
returnObj[key] = await this.parseYield();
this.seek();
if (this.ch === '}') {
this.parseStr = this.parseStr.slice(this.at);
this.at = 0;
return returnObj;
}
} while (this.ch === ',');
return new ParseError('Bad object');
case '[':
// Array case
this.seek();
if (this.ch === ']') {
this.parseStr = this.parseStr.slice(this.at);
this.at = 0;
return returnArr;
}
this.unseek();
do {
v = await this.parseYield();
returnArr.push(v);
this.seek();
if (this.ch === ']') {
this.parseStr = this.parseStr.slice(this.at);
this.at = 0;
return returnArr;
}
} while (this.ch === ',');
return new ParseError('Bad array');
case '"':
this.parseStr = this.parseStr.slice(this.at - 1);
this.at = 0;
if (this.parseStr.charAt(0) === '"' && this.parseStr.charAt(1) === '"') {
this.parseStr = this.parseStr.slice(2);
this.at = 0;
return inQuotes;
} else {
this.seek();
return this.normalizeUnicodedString();
}
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '-':
if (this.ch === '-') addup();
do {
addup();
if (this.ch === '.' || this.ch === 'e' || this.ch === 'E' ||
this.ch === '-' || this.ch === '+' ||
(this.ch >= String.fromCharCode(65) &&
this.ch <= String.fromCharCode(70)))
addup();
} while (this.ch === '-' || this.ch === '+' || (isFinite(this.ch) && this.ch !== ''));
num = Number(numHolder);
this.parseStr = this.parseStr.slice(this.at - 1);
this.at = 0;
return num;
case 't':
this.word = this.wordCheck();
if (this.word === 'true')
return true;
else return new ParseError('Unexpected character');
case 'f':
this.word = this.wordCheck();
if (this.word === 'false')
return false;
else return new ParseError('Unexpected character');
case 'n':
this.word = this.wordCheck();
if (this.word === 'null')
return null;
else return new ParseError('Unexpected character');
default:
return new ParseError('Unexpected character');
}
}
}
revive(yieldedObject: any, key: string) {
let k = '';
let v = '';
let val = yieldedObject[key];
if (val && typeof val === 'object') {
for (k in val) {
if (Object.prototype.hasOwnProperty.call(val, k)) {
v = this.revive(val, k);
if (v !== undefined)
val[k] = v;
else
delete val[k];
}
}
}
return this.reviver.call(yieldedObject, key, val);
};
}
export const parseAsync = (data: Buffer | string, reviver?: any | number[], intensity = 1): ParseWrapper => {
return new ParseWrapper(data as Buffer, reviver, validateIntensity(intensity));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment