@jswalden/streaming-json
Version:
Streaming JSON parsing and stringification for JavaScript/TypeScript
169 lines (168 loc) • 6.92 kB
TypeScript
/**
* A type broadly characterizing all JSON-compatible objects that are not
* arrays.
*/
export interface JSONObject {
[key: string]: JSONValue | undefined;
}
/** A type broadly characterizing all JSON arrays. */
export type JSONArray = JSONValue[];
/**
* A type broadly describing all values that can be serialized to JSON text.
*
* This type doesn't limit objects/arrays compatible with it to not have
* getters/setters, to not be proxies with handler-implemented traps (or that
* have been revoked), etc. It's just moderately nicer than using `any`.
*/
export type JSONValue = number | string | boolean | null | JSONArray | JSONObject;
/**
* The type of the optional `reviver` function that can be passed to
* `StreamingJSONParser.finish`.
*/
export type Reviver<T> = (this: object, prop: string, val: any) => T;
/**
* A JSON parser when you wish to incrementally parse your JSON in fragments,
* rather than from one complete string.
*
* Feed fragments of JSON text to the parser by calling `add(fragment)`. When
* you've fed the entire JSON text to the parser, call `finish()` to get the
* (optionally revived) result.
*
* If the concatenation of added fragments becomes not a prefix of valid JSON
* text, or if the final concatenation of all fragments isn't valid JSON text,
* the applicable `add(fragment)` or `finish()` will throw a `SyntaxError`.
*/
export declare class StreamingJSONParser {
/** The current fragment being parsed. */
private fragment;
/** The index within `this.fragment` of the next unexamined character. */
private current;
/** The length of `this.fragment`. */
private end;
/** Whether all fragments have been added. */
private eof;
/**
* Assuming `this.atEnd()`, wait for another fragment. If the sentinel ""
* fragment is received, return `true`. Otherwise set it as the current
* fragment and return `false` so that it can be parsed.
*/
private atEOF;
/** Whether we've parsed to the end of the current fragment. */
private atEnd;
/**
* Consume whitespace until the current character isn't whitespace (or all
* JSON text has been processed).
*/
private consumeWhitespace;
/** Consume the given keyword starting from the current character. */
private consumeKeyword;
/** Consume and return a JSON string, starting at its leading `"`. */
private jsonString;
/** Consume and return a JSON number, starting at its leading digit or `-`. */
private jsonNumber;
/**
* Consume the (optional whitespace and) colon after a property name in an
* object literal.
*/
private advanceColon;
/**
* Consume the (optional whitespace and) `,` or `}` after a property value in
* an object literal.
*
* @returns `true` iff `}` was consumed and the object has ended
*/
private advanceObjectEnds;
/**
* Consume the (optional whitespace and) `,` or `]` after an element in an
* array literal.
*
* @returns `true` iff `]` was consumed and the array has ended
*/
private advanceArrayEnds;
/** The value of the atomic token consumed by an `advance`. */
private tokenValue;
/** Advance and consume a token, in context where a JSON value is expected. */
private advance;
/**
* A generator for incrementally parsing a JSON text from nonempty fragments.
* (Callers must filter out empty fragments manually if they choose to support
* them.)
*
* After the implicit initial `yield` is passed by calling `.next()`, add the
* nonempty fragments of JSON text using `.next(fragment)`. Indicate that all
* fragments have been added using `.next("")`.
*
* If a `.next(fragment)` causes the concatenated fragments to not be a valid
* prefix of JSON text, or if the concatenated fragments upon `.next("")` form
* invalid JSON text, the pertinent `.next()` throws a `SyntaxError`.
*
* If upon `.next("")` the concatenated fragments form valid JSON text, that
* call returns `{ done: true, value: <result of parsing the JSON text> }`.
*/
private parseJSON;
private parser;
private complete;
constructor();
/**
* Add a `fragment` of additional JSON text to the overall conceptual string
* being parsed.
*
* @throws
* `SyntaxError` if adding this fragment makes the concatenation of all
* fragments not a valid prefix of JSON text.
* @throws
* `Error` (exactly, not a subclass) if fragments can't be added because a
* previous `add(fragment)` already threw or because `finish()` was called.
*/
add(fragment: string): void;
/**
* Stop feeding fragments to this parser and compute and return the final
* parse result.
*
* @throws
* `Error` if parsing was already `done()`.
* @throws
* `SyntaxError` if the concatenation of all added fragments (which could be
* the empty string if no fragments were added) isn't valid JSON. (It must
* be the *prefix* of valid JSON text or a preceding `add(fragment)` would
* have thrown.)
* @returns
* The overall parse result if the concatenated fragments constitute valid
* JSON text.
*/
finish(): JSONValue;
/**
* Stop feeding fragments to this parser, and compute the final parse result
* as the value `unfiltered`.
*
* Then apply `reviver` to `unfiltered` (and recursively to its properties and
* elements) exactly as {@link JSON.parse} would do if it passed `reviver` and
* the concatenation of fragments passed to this parser, and return the value
* {@link JSON.parse} would return (which will be the result returned by the
* outermost call of `reviver`).
*
* @throws
* `Error` if parsing was already `done()`.
* @throws
* `SyntaxError` if the concatenation of all added fragments (which could be
* the empty string if no fragments were added) isn't valid JSON. (It must
* be the *prefix* of valid JSON text or a preceding `add(fragment)` would
* have thrown.)
* @throws
* Any value that `reviver` throws during the reviving process.
* @returns
* The overall parse result if the concatenated fragments constitute valid
* JSON text, as modified by `reviver`.
*/
finish<T>(reviver: Reviver<T>): T;
/**
* Return `true` if more fragments can be added to this parser and `finish()`
* hasn't been called. (Even if the concatenated fragments constitute a
* complete JSON text, fragments containing only whitespace can still be
* added.)
*
* Return `false` if no more fragments can be added (because `finish()` was
* called or because a previously-added fragment caused a JSON syntax error).
*/
done(): boolean;
}