UNPKG

@v4fire/core

Version:
243 lines (202 loc) 5.15 kB
/*! * 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; } } }