@v4fire/core
Version:
V4Fire core library
243 lines (202 loc) • 5.15 kB
text/typescript
/*!
* V4Fire Core
* https://github.com/V4Fire/Core
*
* Released under the MIT license
* https://github.com/V4Fire/Core/blob/master/LICENSE
*/
/**
* [[include:core/json/stream/assembler/README.md]]
* @packageDocumentation
*/
import symbolGenerator from 'core/symbol';
import type { Token, TokenProcessor } from 'core/json/stream/parser';
import { NULL } from 'core/json/stream/assembler/const';
import type { AssemblerOptions } from 'core/json/stream/assembler/interface';
export * from 'core/json/stream/assembler/interface';
export const
$$ = symbolGenerator();
export default class Assembler<T = unknown> implements TokenProcessor<T> {
/**
* Property key of the active assembling value
*/
key: string | null = null;
/**
* A value of the active assembled item.
* If it is a container (object or array), all new assembled values will be added to it.
*/
value: unknown = NULL;
/**
* Indicates that the active value is fully assembled
*/
get isValueAssembled(): boolean {
return this[$$.isValueAssembled] ?? false;
}
/**
* Sets the value assembling status
* @param value
*/
protected set isValueAssembled(value: boolean) {
this[$$.isValueAssembled] = value;
}
/**
* Depth of the assembling structure
*/
get depth(): number {
// To ignore keys from the stack divide length by two
return (Math.floor(this.stack.length / 2)) + (this.isValueAssembled ? 0 : 1);
}
/**
* Stack of nested assembled items and keys contained within the active assembling value
*/
protected stack: unknown[] = [];
/**
* Handler to process an object start
*/
protected startObject: AnyFunction = this.createStartObjectHandler(Object);
/**
* Handler to process an array start
*/
protected startArray: AnyFunction = this.createStartObjectHandler(Array);
/**
* Function to transform a value after assembling.
* Its API is identical to the reviver from `JSON.parse`.
*
* @param key
* @param value
*/
protected readonly reviver?: JSONCb;
/**
* @param [opts] - additional options
*/
constructor(opts: AssemblerOptions = {}) {
if (Object.isFunction(opts.reviver)) {
this.reviver = opts.reviver;
}
if (opts.numberAsString) {
// eslint-disable-next-line @typescript-eslint/unbound-method
this.numberValue = this.stringValue;
}
}
/**
* Processes the passed JSON token and yields the assembled values
*/
*processToken(chunk: Token): Generator<T> {
this[chunk.name]?.(chunk.value);
if (this.isValueAssembled) {
yield Object.cast(this.value);
this.value = NULL;
this.isValueAssembled = false;
}
}
/**
* Creates a handler to process starting of an object or array
* @param Constr - constructor to create a structure
*/
protected createStartObjectHandler(Constr: ObjectConstructor | ArrayConstructor): AnyFunction {
return () => {
if (this.isValueAssembled) {
this.isValueAssembled = false;
}
if (this.value !== NULL) {
this.stack.push(this.value, this.key);
}
this.value = Object.cast(new Constr());
this.key = null;
};
}
/**
* Handler to process an object key value
* @param value
*/
protected keyValue(value: string): void {
this.key = value;
}
/**
* Handler to process a string value
* @param value
*/
protected stringValue(value: string): void {
this.endPrimitive();
this.saveValue(value);
}
/**
* Handler to process a number value
* @param value
*/
protected numberValue(value: string): void {
this.endPrimitive();
this.saveValue(parseFloat(value));
}
/**
* Handler to process nullish values
*/
protected nullValue(): void {
this.endPrimitive();
this.saveValue(null);
}
/**
* Handler to process a truly boolean value
*/
protected trueValue(): void {
this.endPrimitive();
this.saveValue(true);
}
/**
* Handler to process a falsy boolean value
*/
protected falseValue(): void {
this.endPrimitive();
this.saveValue(false);
}
/**
* Handler to process an object end
*/
protected endObject(): void {
if (this.stack.length > 0) {
const
{value} = this;
this.key = Object.cast(this.stack.pop());
this.value = this.stack.pop() ?? null;
this.saveValue(value);
} else {
this.isValueAssembled = true;
}
}
/**
* Handler to process an array end
*/
protected endArray(): void {
this.endObject();
}
/**
* Handler to process ending of primitive values
*/
protected endPrimitive(): void {
if (this.value === NULL) {
this.isValueAssembled = true;
}
}
/**
* Saves an assembled value into the internal structure
* @param value
*/
protected saveValue(value: unknown): void {
if (this.isValueAssembled) {
this.value = this.reviver?.('', value) ?? value;
} else if (Object.isArray(this.value)) {
const
val = this.reviver?.(String(this.value.length), value) ?? value;
if (val !== undefined) {
this.value.push(val);
}
} else if (Object.isDictionary(this.value) && Object.isString(this.key)) {
const
val = this.reviver?.(this.key, value) ?? value;
if (val !== undefined) {
this.value[this.key] = val;
}
this.key = null;
}
}
}