From bbaea7cc9e6021b7ab263033c9dd9d2836bfbbf4 Mon Sep 17 00:00:00 2001 From: rowan Date: Sun, 25 May 2025 10:54:38 -0500 Subject: [PATCH] clean up interfaces; merge nextSeed/next methods in accessors --- dist/de/forward.d.ts | 45 ++++-------- dist/de/forward.js | 153 +++++++++++++++++++++-------------------- dist/de/generic.d.ts | 8 +-- dist/de/generic.js | 20 +----- dist/de/index.d.ts | 1 + dist/de/index.js | 1 + dist/de/interface.d.ts | 37 ++++------ dist/de/interface.js | 41 ++--------- dist/registry.d.ts | 2 + dist/registry.js | 73 ++++++++++++++++++++ dist/ser/identity.d.ts | 13 ++++ dist/ser/identity.js | 80 +++++++++++++++++++++ dist/ser/impl.js | 15 ++-- dist/ser/index.d.ts | 1 + dist/ser/index.js | 1 + dist/utils.d.ts | 1 + dist/utils.js | 11 ++- src/de/forward.ts | 132 +++++++++++++++++++++++++++++++++++ src/de/generic.ts | 18 +---- src/de/index.ts | 1 + src/de/interface.ts | 85 +++++------------------ src/registry.ts | 97 +++++++++++++++++++++++++- src/ser/identity.ts | 73 ++++++++++++++++++++ src/ser/impl.ts | 22 +++--- src/ser/index.ts | 1 + src/utils.ts | 11 ++- 26 files changed, 650 insertions(+), 293 deletions(-) create mode 100644 dist/ser/identity.d.ts create mode 100644 dist/ser/identity.js create mode 100644 src/de/forward.ts create mode 100644 src/ser/identity.ts diff --git a/dist/de/forward.d.ts b/dist/de/forward.d.ts index dbfd7ec..3b58572 100644 --- a/dist/de/forward.d.ts +++ b/dist/de/forward.d.ts @@ -1,34 +1,17 @@ -import { Deserialize, IDeserializer, IterableAccess, IVisitor, MapAccess } from './interface'; -export declare class ForwardMapAccess extends MapAccess { - private readonly _keys; - private readonly _values; - private kindex; - private vindex; - constructor(keys: string[], values: any[]); - static fromObject(obj: object): ForwardMapAccess; - nextKeySeed>(_seed: K): IteratorResult; - nextValueSeed>(_seed: V): IteratorResult; - nextKey(): IteratorResult; - nextValue(): IteratorResult; -} -export declare class ForwardIterableAccess extends IterableAccess { - private readonly items; - private index; - constructor(items: any[]); - nextElement(): IteratorResult; -} -export declare class Forward implements IDeserializer { +import { IDeserializer, IVisitor } from './interface'; +import { Registry } from '../registry'; +export declare function forward(value: any, into: any, registry?: Registry): unknown; +export declare class Forwarder implements IDeserializer { private readonly value; constructor(value: any); - static with(value: any): Forward; - deserializeAny>(_visitor: V): T; - deserializeBoolean>(visitor: V): T; - deserializeNumber>(visitor: V): T; - deserializeBigInt>(visitor: V): T; - deserializeString>(visitor: V): T; - deserializeSymbol>(visitor: V): T; - deserializeNull>(visitor: V): T; - deserializeObject>(visitor: V): T; - deserializeIterable>(visitor: V): T; - deserializeFunction>(_visitor: V): T; + deserializeAny(visitor: IVisitor): T; + deserializeBoolean(visitor: IVisitor): T; + deserializeNumber(visitor: IVisitor): T; + deserializeBigInt(visitor: IVisitor): T; + deserializeString(visitor: IVisitor): T; + deserializeSymbol(visitor: IVisitor): T; + deserializeNull(visitor: IVisitor): T; + deserializeObject(visitor: IVisitor): T; + deserializeIterable(visitor: IVisitor): T; + deserializeFunction(_visitor: IVisitor): T; } diff --git a/dist/de/forward.js b/dist/de/forward.js index a2d7b94..3ee8305 100644 --- a/dist/de/forward.js +++ b/dist/de/forward.js @@ -1,69 +1,19 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.Forward = exports.ForwardIterableAccess = exports.ForwardMapAccess = void 0; -const utils_1 = require("../utils"); +exports.Forwarder = void 0; +exports.forward = forward; +const impl_1 = require("./impl"); const interface_1 = require("./interface"); -class ForwardMapAccess extends interface_1.MapAccess { - constructor(keys, values) { - super(); - Object.defineProperty(this, "_keys", { - enumerable: true, - configurable: true, - writable: true, - value: void 0 - }); - Object.defineProperty(this, "_values", { - enumerable: true, - configurable: true, - writable: true, - value: void 0 - }); - Object.defineProperty(this, "kindex", { - enumerable: true, - configurable: true, - writable: true, - value: 0 - }); - Object.defineProperty(this, "vindex", { - enumerable: true, - configurable: true, - writable: true, - value: 0 - }); - this._keys = keys; - this._values = values; - } - static fromObject(obj) { - return new ForwardMapAccess(Object.keys(obj), Object.values(obj)); - } - nextKeySeed(_seed) { - return this.nextKey(); - } - nextValueSeed(_seed) { - return this.nextValue(); - } - nextKey() { - if (this.kindex < this.keys.length) { - return utils_1.IterResult.Next(this._keys[this.kindex++]); - } - else { - return utils_1.IterResult.Done(); - } - } - nextValue() { - if (this.vindex < this.values.length) { - return utils_1.IterResult.Next(this._values[this.vindex++]); - } - else { - return utils_1.IterResult.Done(); - } - } +const registry_1 = require("../registry"); +const utils_1 = require("../utils"); +function forward(value, into, registry = registry_1.GlobalRegistry) { + const forwarder = new Forwarder(value); + return (0, impl_1.deserialize)(forwarder, into, registry); } -exports.ForwardMapAccess = ForwardMapAccess; -class ForwardIterableAccess extends interface_1.IterableAccess { - constructor(items) { +class ForwardMapAccess extends interface_1.MapAccess { + constructor(entries) { super(); - Object.defineProperty(this, "items", { + Object.defineProperty(this, "_entries", { enumerable: true, configurable: true, writable: true, @@ -73,21 +23,65 @@ class ForwardIterableAccess extends interface_1.IterableAccess { enumerable: true, configurable: true, writable: true, - value: 0 + value: -1 }); - this.items = items; + this._entries = entries; } - nextElement() { - if (this.index < this.items.length) { - return utils_1.IterResult.Next(this.items[this.index++]); + static fromObject(value) { + return new this(Object.entries(value)); + } + nextKey(seed) { + this.index += 1; + if (this.index >= this._entries.length) { + return utils_1.IterResult.Done(); } else { + const key = this._entries[this.index][0]; + const value = seed != null ? seed(new Forwarder(key)) : key; + return utils_1.IterResult.Next(value); + } + } + nextValue(seed) { + if (this.index >= this._entries.length) { return utils_1.IterResult.Done(); } + else { + const value = this._entries[this.index][1]; + const deser = seed != null ? seed(value) : value; + return utils_1.IterResult.Next(deser); + } } } -exports.ForwardIterableAccess = ForwardIterableAccess; -class Forward { +class ForwardIterableAccess extends interface_1.IterableAccess { + constructor(elements) { + super(); + Object.defineProperty(this, "elements", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "index", { + enumerable: true, + configurable: true, + writable: true, + value: -1 + }); + this.elements = elements; + } + nextElement(seed) { + this.index += 1; + if (this.index >= this.elements.length) { + return utils_1.IterResult.Done(); + } + else { + const element = this.elements[this.index]; + const deser = seed != null ? seed(new Forwarder(element)) : element; + return utils_1.IterResult.Next(deser); + } + } +} +class Forwarder { constructor(value) { Object.defineProperty(this, "value", { enumerable: true, @@ -97,11 +91,22 @@ class Forward { }); this.value = value; } - static with(value) { - return new this(value); - } - deserializeAny(_visitor) { - throw new Error("Can't forward to deserializeAny"); + deserializeAny(visitor) { + switch (typeof this.value) { + case 'string': return this.deserializeString(visitor); + case 'number': return this.deserializeNumber(visitor); + case 'bigint': return this.deserializeBigInt(visitor); + case 'boolean': return this.deserializeBoolean(visitor); + case 'symbol': return this.deserializeSymbol(visitor); + case 'undefined': return this.deserializeNull(visitor); + case 'object': { + switch (true) { + case Array.isArray(this.value): return this.deserializeIterable(visitor); + default: return this.deserializeObject(visitor); + } + } + case 'function': return this.deserializeFunction(visitor); + } } deserializeBoolean(visitor) { return visitor.visitBoolean(this.value); @@ -131,4 +136,4 @@ class Forward { throw new Error('Method not implemented.'); } } -exports.Forward = Forward; +exports.Forwarder = Forwarder; diff --git a/dist/de/generic.d.ts b/dist/de/generic.d.ts index 1163e6b..61b0043 100644 --- a/dist/de/generic.d.ts +++ b/dist/de/generic.d.ts @@ -1,10 +1,4 @@ -import { IDeserializer, IIterableAccess, IMapAccess, IVisitor } from './interface'; -export declare class GenericSeed { - readonly visitor: IVisitor; - constructor(visitor?: IVisitor); - static deserialize(deserializer: D, visitor?: IVisitor): T; - deserialize(deserializer: D): T; -} +import { IIterableAccess, IMapAccess, IVisitor } from './interface'; export declare class Visitor implements IVisitor { private overrides?; constructor(overrides?: Partial>); diff --git a/dist/de/generic.js b/dist/de/generic.js index ff2db0e..0e93e1d 100644 --- a/dist/de/generic.js +++ b/dist/de/generic.js @@ -1,26 +1,8 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.Visitor = exports.GenericSeed = void 0; +exports.Visitor = void 0; const utils_1 = require("../utils"); const interface_1 = require("./interface"); -class GenericSeed { - constructor(visitor = new Visitor()) { - Object.defineProperty(this, "visitor", { - enumerable: true, - configurable: true, - writable: true, - value: void 0 - }); - this.visitor = visitor; - } - static deserialize(deserializer, visitor = new Visitor()) { - return deserializer.deserializeAny(visitor); - } - deserialize(deserializer) { - return GenericSeed.deserialize(deserializer, this.visitor); - } -} -exports.GenericSeed = GenericSeed; class Visitor { constructor(overrides) { Object.defineProperty(this, "overrides", { diff --git a/dist/de/index.d.ts b/dist/de/index.d.ts index 7d961fa..fefcc75 100644 --- a/dist/de/index.d.ts +++ b/dist/de/index.d.ts @@ -1,3 +1,4 @@ +export * from './forward'; export * from './generic'; export * from './impl'; export * from './interface'; diff --git a/dist/de/index.js b/dist/de/index.js index 94c91a2..bfbd240 100644 --- a/dist/de/index.js +++ b/dist/de/index.js @@ -14,6 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) { for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); }; Object.defineProperty(exports, "__esModule", { value: true }); +__exportStar(require("./forward"), exports); __exportStar(require("./generic"), exports); __exportStar(require("./impl"), exports); __exportStar(require("./interface"), exports); diff --git a/dist/de/interface.d.ts b/dist/de/interface.d.ts index ffd531f..7ab225a 100644 --- a/dist/de/interface.d.ts +++ b/dist/de/interface.d.ts @@ -1,40 +1,31 @@ import { Nullable } from '../utils'; export interface IMapAccess { - nextKeySeed>(seed: K): IteratorResult; - nextValueSeed>(seed: V): IteratorResult; - nextEntrySeed, V extends Deserialize>(kseed: K, vseed: V): IteratorResult<[TK, TV]>; - nextKey(): IteratorResult; - nextValue(): IteratorResult; - nextEntry(): IteratorResult<[K, V]>; + nextKey(seed?: Deserialize): IteratorResult; + nextValue(seed?: Deserialize): IteratorResult; + nextEntry(kseed?: Deserialize, vseed?: Deserialize): IteratorResult<[K, V]>; sizeHint?(): Nullable; - entries>(seed?: K): Iterator; - values>(seed?: V): Iterator; - entries, V extends Deserialize>(kseed: K, vseed: V): Iterator<[TK, TV]>; + entries(seed?: Deserialize): Iterator; + values(seed?: Deserialize): Iterator; + entries(kseed: Deserialize, vseed: Deserialize): Iterator<[K, V]>; [Symbol.iterator](): Iterator<[K, V]>; } export declare abstract class MapAccess { - abstract nextKeySeed>(seed: K): IteratorResult; - abstract nextValueSeed>(seed: V): IteratorResult; - private orDefaultSeed; - nextEntrySeed, V extends Deserialize>(kseed: K, vseed: V): IteratorResult<[TK, TV]>; - nextKey(): IteratorResult; - nextValue(): IteratorResult; - nextEntry(): IteratorResult<[K, V]>; + abstract nextKey(seed?: Deserialize): IteratorResult; + abstract nextValue(seed?: Deserialize): IteratorResult; + nextEntry(kseed?: Deserialize, vseed?: Deserialize): IteratorResult<[K, V]>; private generate; - keys>(seed?: K): Iterator; - values>(seed?: V): Iterator; - entries, V extends Deserialize>(kseed?: K, vseed?: V): Iterator<[TK, TV]>; + keys(seed?: Deserialize): Iterator; + values(seed?: Deserialize): Iterator; + entries(kseed?: Deserialize, vseed?: Deserialize): Iterator<[K, V]>; [Symbol.iterator](): Iterator; } export interface IIterableAccess { - nextElementSeed>(seed: D): IteratorResult; - nextElement(): IteratorResult; + nextElement(seed: Deserialize): IteratorResult; sizeHint?(): Nullable; [Symbol.iterator](): Iterator; } export declare abstract class IterableAccess implements IIterableAccess { - abstract nextElementSeed>(seed: D): IteratorResult; - nextElement(): IteratorResult; + abstract nextElement(seed: Deserialize): IteratorResult; [Symbol.iterator](): Iterator; } export declare function isVisitor(visitor: any): visitor is IVisitor; diff --git a/dist/de/interface.js b/dist/de/interface.js index d6a2a9d..5110317 100644 --- a/dist/de/interface.js +++ b/dist/de/interface.js @@ -3,31 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.IterableAccess = exports.MapAccess = void 0; exports.isVisitor = isVisitor; const utils_1 = require("../utils"); -const generic_1 = require("./generic"); class MapAccess { - orDefaultSeed(seed) { - return seed || generic_1.GenericSeed.deserialize; - } - nextEntrySeed(kseed, vseed) { - const key = this.nextKeySeed(kseed); + nextEntry(kseed, vseed) { + const key = this.nextKey(kseed); if (!key.done) { - const value = this.nextValueSeed(vseed); - if (!value.done) { - return utils_1.IterResult.Next([key.value, value.value]); - } - } - return utils_1.IterResult.Done(); - } - nextKey() { - return this.nextKeySeed(generic_1.GenericSeed.deserialize); - } - nextValue() { - return this.nextValueSeed(generic_1.GenericSeed.deserialize); - } - nextEntry() { - const key = this.nextKey(); - if (!key.done) { - const value = this.nextValue(); + const value = this.nextValue(vseed); if (!value.done) { return utils_1.IterResult.Next([key.value, value.value]); } @@ -41,19 +21,13 @@ class MapAccess { } } keys(seed) { - return this.generate(seed == null ? - this.nextKey.bind(this) : - this.nextKeySeed.bind(this, seed)); + return this.generate(this.nextKey.bind(this, seed)); } values(seed) { - return this.generate(seed == null ? - this.nextValue.bind(this) : - this.nextValueSeed.bind(this, seed)); + return this.generate(this.nextValue.bind(this, seed)); } entries(kseed, vseed) { - return this.generate(kseed == null && vseed == null ? - this.nextEntry.bind(this) : - this.nextEntrySeed.bind(this, this.orDefaultSeed(kseed), this.orDefaultSeed(vseed))); + return this.generate(this.nextEntry.bind(this, kseed, vseed)); } [Symbol.iterator]() { return this.entries(); @@ -61,9 +35,6 @@ class MapAccess { } exports.MapAccess = MapAccess; class IterableAccess { - nextElement() { - return this.nextElementSeed(generic_1.GenericSeed.deserialize); - } [Symbol.iterator]() { return { next: this.nextElement.bind(this) diff --git a/dist/registry.d.ts b/dist/registry.d.ts index 1cbd5c7..1540f48 100644 --- a/dist/registry.d.ts +++ b/dist/registry.d.ts @@ -9,3 +9,5 @@ export declare class Registry { export declare const GlobalRegistry: Registry; export declare const registerSerialize: (ctor: Function, serialize: Serialize) => void; export declare const registerDeserialize: (ctor: Function, deserialize: Deserialize) => void; +export declare function getSerialize(value: U, fallback: Serialize, registry: Registry): Serialize; +export declare function getDeserialize(value: U, fallback: Deserialize, registry: Registry): Deserialize; diff --git a/dist/registry.js b/dist/registry.js index d7a4879..cc8c995 100644 --- a/dist/registry.js +++ b/dist/registry.js @@ -1,6 +1,9 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.registerDeserialize = exports.registerSerialize = exports.GlobalRegistry = exports.Registry = void 0; +exports.getSerialize = getSerialize; +exports.getDeserialize = getDeserialize; +const utils_1 = require("./utils"); class Registry { constructor() { Object.defineProperty(this, "serializers", { @@ -27,3 +30,73 @@ exports.Registry = Registry; exports.GlobalRegistry = new Registry(); exports.registerSerialize = exports.GlobalRegistry.registerSerialize.bind(exports.GlobalRegistry); exports.registerDeserialize = exports.GlobalRegistry.registerDeserialize.bind(exports.GlobalRegistry); +function getFrom(map, value) { + return map.get((0, utils_1.type)(value)); +} +function getSerialize(value, fallback, registry) { + return getFrom(registry.serializers, value) || fallback; +} +function getDeserialize(value, fallback, registry) { + return getFrom(registry.deserializers, value) || fallback; +} +(0, exports.registerSerialize)(Boolean, (ser, value) => ser.serializeBoolean(value)); +(0, exports.registerSerialize)(String, (ser, value) => ser.serializeString(value)); +(0, exports.registerSerialize)(Number, (ser, value) => ser.serializeNumber(value)); +(0, exports.registerSerialize)(BigInt, (ser, value) => ser.serializeBigInt(value)); +(0, exports.registerSerialize)(Symbol, (ser, value) => ser.serializeSymbol(value)); +(0, exports.registerSerialize)(utils_1.Null, (ser, _value) => ser.serializeNull()); +(0, exports.registerSerialize)(Object, (ser, value) => { + const obj = Object.entries(value); + const serObj = ser.serializeObject(obj.length); + obj.forEach(([key, value]) => serObj.serializeEntry(key, value)); + return serObj.end(); +}); +(0, exports.registerSerialize)(Array, (ser, value) => { + const arr = value; + const iter = ser.serializeIterable(arr.length); + arr.forEach((value) => iter.serializeElement(value)); + return iter.end(); +}); +(0, exports.registerDeserialize)(Boolean, (de) => de.deserializeBoolean({ + visitBoolean(value) { + return value; + } +})); +(0, exports.registerDeserialize)(String, (de) => de.deserializeString({ + visitString(value) { + return value; + } +})); +(0, exports.registerDeserialize)(Number, (de) => de.deserializeNumber({ + visitNumber(value) { + return value; + } +})); +(0, exports.registerDeserialize)(BigInt, (de) => de.deserializeBigInt({ + visitBigInt(value) { + return value; + } +})); +(0, exports.registerDeserialize)(Symbol, (de) => de.deserializeSymbol({ + visitSymbol(value) { + return value; + } +})); +(0, exports.registerDeserialize)(Object, (de) => de.deserializeObject({ + visitObject(access) { + let result = {}; + for (const [key, value] of access) { + result[key] = value; + } + return result; + } +})); +(0, exports.registerDeserialize)(Array, (de) => de.deserializeIterable({ + visitIterable(access) { + let result = []; + for (const value of access) { + result.push(value); + } + return result; + } +})); diff --git a/dist/ser/identity.d.ts b/dist/ser/identity.d.ts new file mode 100644 index 0000000..1816636 --- /dev/null +++ b/dist/ser/identity.d.ts @@ -0,0 +1,13 @@ +import { ISerializeIterable, ISerializeObject, ISerializer } from './interface'; +export declare class IdentitySerializer implements ISerializer { + serializeAny?(value: any): T; + serializeBoolean(value: boolean): T; + serializeNumber(value: number): T; + serializeBigInt(value: bigint): T; + serializeString(value: string): T; + serializeSymbol(value: symbol): T; + serializeNull(): T; + serializeIterable(_len?: number): ISerializeIterable; + serializeObject(_len?: number): ISerializeObject; + serializeClass(_name: string, _len?: number): ISerializeObject; +} diff --git a/dist/ser/identity.js b/dist/ser/identity.js new file mode 100644 index 0000000..5bfb849 --- /dev/null +++ b/dist/ser/identity.js @@ -0,0 +1,80 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.IdentitySerializer = void 0; +const interface_1 = require("./interface"); +class IdentityMap extends interface_1.SerializeObject { + constructor() { + super(...arguments); + Object.defineProperty(this, "value", { + enumerable: true, + configurable: true, + writable: true, + value: {} + }); + Object.defineProperty(this, "currentKey", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + } + serializeKey(key) { + this.currentKey = key; + } + serializeValue(value) { + this.value[this.currentKey] = value; + } + end() { + return this.value; + } +} +class IdentityIterable extends interface_1.SerializeIterable { + constructor() { + super(...arguments); + Object.defineProperty(this, "value", { + enumerable: true, + configurable: true, + writable: true, + value: [] + }); + } + serializeElement(value) { + this.value.push(value); + } + end() { + return this.value; + } +} +class IdentitySerializer { + serializeAny(value) { + return value; + } + serializeBoolean(value) { + return value; + } + serializeNumber(value) { + return value; + } + serializeBigInt(value) { + return value; + } + serializeString(value) { + return value; + } + serializeSymbol(value) { + return value; + } + serializeNull() { + return null; + } + serializeIterable(_len) { + return new IdentityIterable(); + } + serializeObject(_len) { + return new IdentityMap(); + } + serializeClass(_name, _len) { + return new IdentityMap(); + } +} +exports.IdentitySerializer = IdentitySerializer; diff --git a/dist/ser/impl.js b/dist/ser/impl.js index 53f2a02..fc3fd79 100644 --- a/dist/ser/impl.js +++ b/dist/ser/impl.js @@ -15,17 +15,17 @@ function serializeObject(serializer, obj) { } return serializer.end(); } +function serializeIterable(serializer, iter) { + for (const item of iter) { + serializer.serializeElement(item); + } + return serializer.end(); +} function serializeClass(serializer, value) { const name = value.constructor.name; const ser = serializer.serializeClass(name); return serializeObject(ser, value); } -function getSerialize(value, registry) { - if ((0, utils_1.isObject)(value)) { - return registry.serializers.get(value.constructor); - } - return registry.serializers.get(utils_1.PrimitivePrototype[typeof value]) || defaultSerialize; -} function defaultSerialize(serializer, value) { switch (typeof value) { case 'string': return serializer.serializeString(value); @@ -37,6 +37,7 @@ function defaultSerialize(serializer, value) { case 'object': switch (true) { case value == null: return serializer.serializeNull(); + case Array.isArray(value): return serializeIterable(serializer.serializeIterable(value.length), value); case !(0, utils_1.isPlainObject)(value): return serializeClass(serializer, value); default: return serializeObject(serializer.serializeObject(), value); } @@ -44,6 +45,6 @@ function defaultSerialize(serializer, value) { } } function serialize(serializer, value, registry = registry_1.GlobalRegistry) { - const ser = getSerialize(value, registry); + const ser = (0, registry_1.getSerialize)(value, defaultSerialize, registry); return ser(serializer, value); } diff --git a/dist/ser/index.d.ts b/dist/ser/index.d.ts index 52c24f4..0588d51 100644 --- a/dist/ser/index.d.ts +++ b/dist/ser/index.d.ts @@ -1,2 +1,3 @@ +export * from './identity'; export * from './impl'; export * from './interface'; diff --git a/dist/ser/index.js b/dist/ser/index.js index 433cf42..cf58fe1 100644 --- a/dist/ser/index.js +++ b/dist/ser/index.js @@ -14,5 +14,6 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) { for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); }; Object.defineProperty(exports, "__esModule", { value: true }); +__exportStar(require("./identity"), exports); __exportStar(require("./impl"), exports); __exportStar(require("./interface"), exports); diff --git a/dist/utils.d.ts b/dist/utils.d.ts index da362b0..17a857c 100644 --- a/dist/utils.d.ts +++ b/dist/utils.d.ts @@ -28,3 +28,4 @@ export declare const PrimitivePrototype: Readonly<{ readonly object: ObjectConstructor; readonly function: FunctionConstructor; }>; +export declare function type(value: any): Function | ObjectConstructor | SymbolConstructor | ArrayConstructor | BooleanConstructor | NumberConstructor | BigIntConstructor | StringConstructor | FunctionConstructor; diff --git a/dist/utils.js b/dist/utils.js index fcb3628..ee7671c 100644 --- a/dist/utils.js +++ b/dist/utils.js @@ -8,8 +8,9 @@ exports.isIterable = isIterable; exports.isString = isString; exports.isNumber = isNumber; exports.Null = Null; +exports.type = type; function isObject(value) { - return typeof value === 'object'; + return typeof value === 'object' && !Array.isArray(value); } function isPlainObject(value) { return Object.getPrototypeOf(value) === Object.prototype; @@ -48,3 +49,11 @@ exports.PrimitivePrototype = Object.freeze({ object: Object, function: Function }); +function type(value) { + switch (true) { + case Array.isArray(value): return Array; + case isPlainObject(value): return Object; + case isObject(value): return value.constructor; + default: return exports.PrimitivePrototype[typeof value]; + } +} diff --git a/src/de/forward.ts b/src/de/forward.ts new file mode 100644 index 0000000..5e55201 --- /dev/null +++ b/src/de/forward.ts @@ -0,0 +1,132 @@ +import { deserialize } from './impl' +import { Deserialize, IDeserializer, IMapAccess, IterableAccess, IVisitor, MapAccess } from './interface' +import { GlobalRegistry, Registry } from '../registry' +import { IterResult } from '../utils' + +export function forward(value: any, into: any, registry: Registry = GlobalRegistry) { + const forwarder = new Forwarder(value) + return deserialize(forwarder, into, registry) +} + +type Entry = [PropertyKey, any] + +class ForwardMapAccess extends MapAccess { + private readonly _entries: Entry[] + private index: number = -1 + + constructor(entries: Entry[]) { + super() + this._entries = entries + } + + static fromObject(value: Record) { + return new this(Object.entries(value)) + } + + nextKey>(seed?: K): IteratorResult { + this.index += 1 + + if (this.index >= this._entries.length) { + return IterResult.Done() + } else { + const key = this._entries[this.index][0] + const value = seed != null ? seed(new Forwarder(key)) : key as T + return IterResult.Next(value) + } + } + + nextValue>(seed?: V): IteratorResult { + if (this.index >= this._entries.length) { + return IterResult.Done() + } else { + const value = this._entries[this.index][1] + const deser = seed != null ? seed(value) : value + return IterResult.Next(deser) + } + } +} + +class ForwardIterableAccess extends IterableAccess { + private readonly elements: T[] + private index: number = -1 + + constructor(elements: T[]) { + super() + this.elements = elements + } + + nextElement>(seed?: D): IteratorResult { + this.index += 1 + + if (this.index >= this.elements.length) { + return IterResult.Done() + } else { + const element = this.elements[this.index] + const deser = seed != null ? seed(new Forwarder(element)) : element + return IterResult.Next(deser as T) + } + } +} + +export class Forwarder implements IDeserializer { + private readonly value: any + + constructor(value: any) { + this.value = value + } + + deserializeAny(visitor: IVisitor): T { + switch (typeof this.value) { + case 'string': return this.deserializeString(visitor) + case 'number': return this.deserializeNumber(visitor) + case 'bigint': return this.deserializeBigInt(visitor) + case 'boolean': return this.deserializeBoolean(visitor) + case 'symbol': return this.deserializeSymbol(visitor) + case 'undefined': return this.deserializeNull(visitor) + case 'object': { + switch (true) { + case Array.isArray(this.value): return this.deserializeIterable(visitor) + default: return this.deserializeObject(visitor) + } + } + case 'function': return this.deserializeFunction(visitor) + } + } + + deserializeBoolean(visitor: IVisitor): T { + return visitor.visitBoolean(this.value) + } + + deserializeNumber(visitor: IVisitor): T { + return visitor.visitNumber(this.value) + } + + deserializeBigInt(visitor: IVisitor): T { + return visitor.visitBigInt(this.value) + } + + deserializeString(visitor: IVisitor): T { + return visitor.visitString(this.value) + } + + deserializeSymbol(visitor: IVisitor): T { + return visitor.visitSymbol(this.value) + } + + deserializeNull(visitor: IVisitor): T { + return visitor.visitNull() + } + + deserializeObject(visitor: IVisitor): T { + return visitor.visitObject(ForwardMapAccess.fromObject(this.value) as IMapAccess) + } + + deserializeIterable(visitor: IVisitor): T { + return visitor.visitIterable(new ForwardIterableAccess(this.value)) + } + + deserializeFunction(_visitor: IVisitor): T { + throw new Error('Method not implemented.') + } +} + diff --git a/src/de/generic.ts b/src/de/generic.ts index 7981412..10067ec 100644 --- a/src/de/generic.ts +++ b/src/de/generic.ts @@ -1,21 +1,5 @@ import { isFunction } from '../utils' -import { IDeserializer, IIterableAccess, IMapAccess, isVisitor, IVisitor } from './interface' - -export class GenericSeed { - readonly visitor: IVisitor - - constructor(visitor: IVisitor = new Visitor()) { - this.visitor = visitor - } - - static deserialize(deserializer: D, visitor: IVisitor = new Visitor()): T { - return deserializer.deserializeAny(visitor) - } - - deserialize(deserializer: D): T { - return GenericSeed.deserialize(deserializer, this.visitor) - } -} +import { IIterableAccess, IMapAccess, isVisitor, IVisitor } from './interface' export class Visitor implements IVisitor { private overrides?: Partial> diff --git a/src/de/index.ts b/src/de/index.ts index 61be4af..bf995e2 100644 --- a/src/de/index.ts +++ b/src/de/index.ts @@ -1,3 +1,4 @@ +export * from './forward' export * from './generic' export * from './impl' export * from './interface' diff --git a/src/de/interface.ts b/src/de/interface.ts index c659d3c..c209c58 100644 --- a/src/de/interface.ts +++ b/src/de/interface.ts @@ -1,55 +1,25 @@ import { IterResult, Nullable } from '../utils' -import { GenericSeed } from './generic' export interface IMapAccess { - nextKeySeed>(seed: K): IteratorResult - nextValueSeed>(seed: V): IteratorResult - nextEntrySeed, V extends Deserialize>(kseed: K, vseed: V): IteratorResult<[TK, TV]> - nextKey(): IteratorResult - nextValue(): IteratorResult - nextEntry(): IteratorResult<[K, V]> + nextKey(seed?: Deserialize): IteratorResult + nextValue(seed?: Deserialize): IteratorResult + nextEntry(kseed?: Deserialize, vseed?: Deserialize): IteratorResult<[K, V]> sizeHint?(): Nullable - entries>(seed?: K): Iterator - values>(seed?: V): Iterator - entries, V extends Deserialize>(kseed: K, vseed: V): Iterator<[TK, TV]> + entries(seed?: Deserialize): Iterator + values(seed?: Deserialize): Iterator + entries(kseed: Deserialize, vseed: Deserialize): Iterator<[K, V]> [Symbol.iterator](): Iterator<[K, V]> } export abstract class MapAccess { - abstract nextKeySeed>(seed: K): IteratorResult - abstract nextValueSeed>(seed: V): IteratorResult + abstract nextKey(seed?: Deserialize): IteratorResult + abstract nextValue(seed?: Deserialize): IteratorResult - private orDefaultSeed(seed?: Deserialize): Deserialize { - return seed || GenericSeed.deserialize - } - - nextEntrySeed, V extends Deserialize>(kseed: K, vseed: V): IteratorResult<[TK, TV]> { - const key = this.nextKeySeed(kseed) as IteratorResult + nextEntry(kseed?: Deserialize, vseed?: Deserialize): IteratorResult<[K, V]> { + const key = this.nextKey(kseed) as IteratorResult if (!key.done) { - const value = this.nextValueSeed(vseed) as IteratorResult - - if (!value.done) { - return IterResult.Next([key.value, value.value]) - } - } - - return IterResult.Done() - } - - nextKey(): IteratorResult { - return this.nextKeySeed(GenericSeed.deserialize) - } - - nextValue(): IteratorResult { - return this.nextValueSeed(GenericSeed.deserialize) - } - - nextEntry(): IteratorResult<[K, V]> { - const key = this.nextKey() as IteratorResult - - if (!key.done) { - const value = this.nextValue() as IteratorResult + const value = this.nextValue(vseed) as IteratorResult if (!value.done) { return IterResult.Next([key.value, value.value]) @@ -67,28 +37,16 @@ export abstract class MapAccess { } } - keys>(seed?: K): Iterator { - return this.generate( - seed == null ? - this.nextKey.bind(this) : - this.nextKeySeed.bind(this, seed) as any - ) + keys(seed?: Deserialize): Iterator { + return this.generate(this.nextKey.bind(this, seed) as any) } - values>(seed?: V): Iterator { - return this.generate( - seed == null ? - this.nextValue.bind(this) : - this.nextValueSeed.bind(this, seed) as any - ) + values(seed?: Deserialize): Iterator { + return this.generate(this.nextValue.bind(this, seed) as any) } - entries, V extends Deserialize>(kseed?: K, vseed?: V): Iterator<[TK, TV]> { - return this.generate( - kseed == null && vseed == null ? - this.nextEntry.bind(this) : - this.nextEntrySeed.bind(this, this.orDefaultSeed(kseed), this.orDefaultSeed(vseed)) as any - ) + entries(kseed?: Deserialize, vseed?: Deserialize): Iterator<[K, V]> { + return this.generate(this.nextEntry.bind(this, kseed, vseed) as any) } [Symbol.iterator](): Iterator { @@ -97,18 +55,13 @@ export abstract class MapAccess { } export interface IIterableAccess { - nextElementSeed>(seed: D): IteratorResult - nextElement(): IteratorResult + nextElement(seed: Deserialize): IteratorResult sizeHint?(): Nullable [Symbol.iterator](): Iterator } export abstract class IterableAccess implements IIterableAccess { - abstract nextElementSeed>(seed: D): IteratorResult - - nextElement(): IteratorResult { - return this.nextElementSeed(GenericSeed.deserialize) - } + abstract nextElement(seed: Deserialize): IteratorResult [Symbol.iterator](): Iterator { return { diff --git a/src/registry.ts b/src/registry.ts index 6c69392..b0e2c82 100644 --- a/src/registry.ts +++ b/src/registry.ts @@ -1,5 +1,6 @@ -import { Deserialize } from './de' -import { Serialize } from './ser' +import { Deserialize, IDeserializer, IIterableAccess, IMapAccess } from './de' +import { ISerializer, Serialize } from './ser' +import { isObject, Null, PrimitivePrototype, type } from './utils' export class Registry { serializers: Map> = new Map() @@ -18,3 +19,95 @@ export const GlobalRegistry = new Registry() export const registerSerialize = GlobalRegistry.registerSerialize.bind(GlobalRegistry) export const registerDeserialize = GlobalRegistry.registerDeserialize.bind(GlobalRegistry) +function getFrom(map: Map, value: V): T { + return map.get(type(value)) as T +} + +export function getSerialize(value: U, fallback: Serialize, registry: Registry): Serialize { + return getFrom(registry.serializers, value) || fallback +} + +export function getDeserialize(value: U, fallback: Deserialize, registry: Registry): Deserialize { + return getFrom(registry.deserializers, value) || fallback +} + +registerSerialize(Boolean, (ser: ISerializer, value: U) => ser.serializeBoolean(value as boolean)) + +registerSerialize(String, (ser: ISerializer, value: U) => ser.serializeString(value as string)) + +registerSerialize(Number, (ser: ISerializer, value: U) => ser.serializeNumber(value as number)) + +registerSerialize(BigInt, (ser: ISerializer, value: U) => ser.serializeBigInt(value as bigint)) + +registerSerialize(Symbol, (ser: ISerializer, value: U) => ser.serializeSymbol(value as symbol)) + +registerSerialize(Null, (ser: ISerializer, _value: U) => ser.serializeNull()) + +registerSerialize(Object, (ser: ISerializer, value: U) => { + const obj = Object.entries(value as object) + const serObj = ser.serializeObject(obj.length) + obj.forEach(([key, value]) => serObj.serializeEntry(key, value)) + return serObj.end() +}) + +registerSerialize(Array, (ser: ISerializer, value: U) => { + const arr = value as any[] + const iter = ser.serializeIterable(arr.length) + arr.forEach((value: any) => iter.serializeElement(value)) + return iter.end() +}) + + +registerDeserialize(Boolean, (de: IDeserializer) => de.deserializeBoolean({ + visitBoolean(value: boolean) { + return value + } +})) + +registerDeserialize(String, (de: IDeserializer) => de.deserializeString({ + visitString(value: string) { + return value + } +})) + +registerDeserialize(Number, (de: IDeserializer) => de.deserializeNumber({ + visitNumber(value: number) { + return value + } +})) + +registerDeserialize(BigInt, (de: IDeserializer) => de.deserializeBigInt({ + visitBigInt(value: bigint) { + return value + } +})) + +registerDeserialize(Symbol, (de: IDeserializer) => de.deserializeSymbol({ + visitSymbol(value: symbol) { + return value + } +})) + +registerDeserialize(Object, (de: IDeserializer) => de.deserializeObject({ + visitObject(access: IMapAccess) { + let result = {} as any + + for (const [key, value] of access) { + result[key] = value + } + + return result + } +})) + +registerDeserialize(Array, (de: IDeserializer) => de.deserializeIterable({ + visitIterable(access: IIterableAccess) { + let result = [] + + for (const value of access) { + result.push(value) + } + + return result + } +})) diff --git a/src/ser/identity.ts b/src/ser/identity.ts new file mode 100644 index 0000000..93bf012 --- /dev/null +++ b/src/ser/identity.ts @@ -0,0 +1,73 @@ +import { ISerializeIterable, ISerializeObject, ISerializer, SerializeIterable, SerializeObject } from './interface' + +class IdentityMap extends SerializeObject { + private value: Record = {} + private currentKey?: string + + serializeKey(key: string): void { + this.currentKey = key + } + + serializeValue(value: U): void { + this.value[this.currentKey!] = value + } + + end(): T { + return this.value + } +} + +class IdentityIterable extends SerializeIterable { + private value: any[] = [] + + serializeElement(value: U): void { + this.value.push(value) + } + + end(): T { + return this.value as T + } +} + +export class IdentitySerializer implements ISerializer { + serializeAny?(value: any): T { + return value as T + } + + serializeBoolean(value: boolean): T { + return value as T + } + + serializeNumber(value: number): T { + return value as T + } + + serializeBigInt(value: bigint): T { + return value as T + } + + serializeString(value: string): T { + return value as T + } + + serializeSymbol(value: symbol): T { + return value as T + } + + serializeNull(): T { + return null as T + } + + serializeIterable(_len?: number): ISerializeIterable { + return new IdentityIterable() + } + + serializeObject(_len?: number): ISerializeObject { + return new IdentityMap() + } + + serializeClass(_name: string, _len?: number): ISerializeObject { + return new IdentityMap() + } +} + diff --git a/src/ser/impl.ts b/src/ser/impl.ts index d62f24e..34c023d 100644 --- a/src/ser/impl.ts +++ b/src/ser/impl.ts @@ -1,6 +1,6 @@ -import { ISerializeObject, ISerializer, Serialize } from './interface' +import { ISerializeIterable, ISerializeObject, ISerializer, Serialize } from './interface' import { isObject, isPlainObject, PrimitivePrototype } from '../utils' -import { GlobalRegistry, Registry } from '../registry' +import { getSerialize, GlobalRegistry, Registry } from '../registry' class UnhandledTypeError extends TypeError { constructor(serializer: ISerializer, value: any) { @@ -17,19 +17,20 @@ function serializeObject>(ser return serializer.end() } +function serializeIterable, S extends ISerializeIterable>(serializer: S, iter: U): T { + for (const item of iter) { + serializer.serializeElement(item) + } + + return serializer.end() +} + function serializeClass>(serializer: S, value: U): T { const name = value.constructor.name const ser = serializer.serializeClass(name) return serializeObject(ser, value) } -function getSerialize(value: U, registry: Registry): Serialize { - if (isObject(value)) { - return registry.serializers.get(value.constructor) as Serialize - } - - return registry.serializers.get(PrimitivePrototype[typeof value]) || defaultSerialize as Serialize -} function defaultSerialize>(serializer: S, value: U): T { switch (typeof value) { case 'string': return serializer.serializeString(value) @@ -41,6 +42,7 @@ function defaultSerialize>(serializer: S, value: case 'object': switch (true) { case value == null: return serializer.serializeNull() + case Array.isArray(value): return serializeIterable(serializer.serializeIterable(value.length), value) case !isPlainObject(value): return serializeClass(serializer, value) default: return serializeObject(serializer.serializeObject(), value) } @@ -49,7 +51,7 @@ function defaultSerialize>(serializer: S, value: } export function serialize>(serializer: S, value: U, registry: Registry = GlobalRegistry): T { - const ser = getSerialize(value, registry) + const ser = getSerialize(value, defaultSerialize, registry) return ser(serializer, value) } diff --git a/src/ser/index.ts b/src/ser/index.ts index 6265425..e72fc8c 100644 --- a/src/ser/index.ts +++ b/src/ser/index.ts @@ -1,3 +1,4 @@ +export * from './identity' export * from './impl' export * from './interface' diff --git a/src/utils.ts b/src/utils.ts index 877fd86..8976307 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -11,7 +11,7 @@ export interface ToString { } export function isObject(value: any): value is object { - return typeof value === 'object' + return typeof value === 'object' && !Array.isArray(value) } export function isPlainObject(value: any): value is object { @@ -61,3 +61,12 @@ export const PrimitivePrototype = Object.freeze({ function: Function } as const) +export function type(value: any) { + switch (true) { + case Array.isArray(value): return Array + case isPlainObject(value): return Object + case isObject(value): return value.constructor + default: return PrimitivePrototype[typeof value] + } +} +