From da1fad1cd083f06b545d25f4b6fb247da29b17fc Mon Sep 17 00:00:00 2001 From: rowan Date: Thu, 22 May 2025 03:35:53 -0500 Subject: [PATCH] prepare to add options --- dist/de/forward.d.ts | 34 +++++ dist/de/forward.js | 134 ++++++++++++++++++++ dist/de/generic.d.ts | 17 +++ dist/de/generic.js | 58 +++++++++ dist/de/impl.d.ts | 105 +-------------- dist/de/impl.js | 220 -------------------------------- dist/de/index.d.ts | 4 + dist/de/index.js | 20 +++ dist/de/interface.d.ts | 50 ++++++++ dist/de/interface.js | 30 +++++ dist/index.d.ts | 1 + dist/index.js | 1 + dist/registry.d.ts | 4 +- dist/ser/decorator.d.ts | 59 +++++++++ dist/ser/decorator.js | 146 +++++++++++++++++++++ dist/ser/impl.d.ts | 50 +------- dist/ser/impl.js | 76 +++-------- dist/ser/index.d.ts | 2 + dist/ser/index.js | 18 +++ dist/ser/interface.d.ts | 45 +++++++ dist/ser/interface.js | 43 +++++++ dist/utils.d.ts | 4 + dist/utils.js | 10 ++ package.json | 8 +- src/de/forward.ts | 118 +++++++++++++++++ src/de/generic.ts | 68 ++++++++++ src/de/impl.ts | 275 +--------------------------------------- src/de/index.ts | 5 + src/de/interface.ts | 85 +++++++++++++ src/index.ts | 2 + src/registry.ts | 4 +- src/ser/decorator.ts | 157 +++++++++++++++++++++++ src/ser/impl.ts | 119 ++++------------- src/ser/index.ts | 3 + src/ser/interface.ts | 82 ++++++++++++ src/utils.ts | 10 ++ 36 files changed, 1262 insertions(+), 805 deletions(-) create mode 100644 dist/de/forward.d.ts create mode 100644 dist/de/forward.js create mode 100644 dist/de/generic.d.ts create mode 100644 dist/de/generic.js create mode 100644 dist/de/index.d.ts create mode 100644 dist/de/index.js create mode 100644 dist/de/interface.d.ts create mode 100644 dist/de/interface.js create mode 100644 dist/ser/decorator.d.ts create mode 100644 dist/ser/decorator.js create mode 100644 dist/ser/index.d.ts create mode 100644 dist/ser/index.js create mode 100644 dist/ser/interface.d.ts create mode 100644 dist/ser/interface.js create mode 100644 src/de/forward.ts create mode 100644 src/de/generic.ts create mode 100644 src/de/index.ts create mode 100644 src/de/interface.ts create mode 100644 src/ser/decorator.ts create mode 100644 src/ser/index.ts create mode 100644 src/ser/interface.ts diff --git a/dist/de/forward.d.ts b/dist/de/forward.d.ts new file mode 100644 index 0000000..69396a3 --- /dev/null +++ b/dist/de/forward.d.ts @@ -0,0 +1,34 @@ +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 { + 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; +} diff --git a/dist/de/forward.js b/dist/de/forward.js new file mode 100644 index 0000000..9afdc72 --- /dev/null +++ b/dist/de/forward.js @@ -0,0 +1,134 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Forward = exports.ForwardIterableAccess = exports.ForwardMapAccess = void 0; +const utils_1 = require("../utils"); +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(); + } + } +} +exports.ForwardMapAccess = ForwardMapAccess; +class ForwardIterableAccess extends interface_1.IterableAccess { + constructor(items) { + super(); + Object.defineProperty(this, "items", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "index", { + enumerable: true, + configurable: true, + writable: true, + value: 0 + }); + this.items = items; + } + nextElement() { + if (this.index < this.items.length) { + return utils_1.IterResult.Next(this.items[this.index++]); + } + else { + return utils_1.IterResult.Done(); + } + } +} +exports.ForwardIterableAccess = ForwardIterableAccess; +class Forward { + constructor(value) { + Object.defineProperty(this, "value", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + this.value = value; + } + static with(value) { + return new this(value); + } + deserializeAny(_visitor) { + throw new Error("Can't forward to deserializeAny"); + } + deserializeBoolean(visitor) { + return visitor.visitBoolean(this.value); + } + deserializeNumber(visitor) { + return visitor.visitNumber(this.value); + } + deserializeBigInt(visitor) { + return visitor.visitBigInt(this.value); + } + deserializeString(visitor) { + return visitor.visitString(this.value); + } + deserializeSymbol(visitor) { + return visitor.visitSymbol(this.value); + } + deserializeNull(visitor) { + return visitor.visitNull(); + } + deserializeObject(visitor) { + return visitor.visitObject(ForwardMapAccess.fromObject(this.value)); + } + deserializeIterable(visitor) { + return visitor.visitIterable(new ForwardIterableAccess(this.value)); + } + deserializeFunction(_visitor) { + throw new Error('Method not implemented.'); + } +} +exports.Forward = Forward; diff --git a/dist/de/generic.d.ts b/dist/de/generic.d.ts new file mode 100644 index 0000000..6b900a1 --- /dev/null +++ b/dist/de/generic.d.ts @@ -0,0 +1,17 @@ +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; +} +export declare class GenericVisitor implements IVisitor { + visitBoolean(value: boolean): T; + visitNumber(value: number): T; + visitBigInt(value: bigint): T; + visitString(value: string): T; + visitSymbol(value: symbol): T; + visitNull(): T; + visitObject(access: IMapAccess): T; + visitIterable(access: IIterableAccess): T; +} diff --git a/dist/de/generic.js b/dist/de/generic.js new file mode 100644 index 0000000..4c0884a --- /dev/null +++ b/dist/de/generic.js @@ -0,0 +1,58 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.GenericVisitor = exports.GenericSeed = void 0; +class GenericSeed { + constructor(visitor = new GenericVisitor()) { + Object.defineProperty(this, "visitor", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + this.visitor = visitor; + } + static deserialize(deserializer, visitor = new GenericVisitor()) { + return deserializer.deserializeAny(visitor); + } + deserialize(deserializer) { + return GenericSeed.deserialize(deserializer, this.visitor); + } +} +exports.GenericSeed = GenericSeed; +class GenericVisitor { + visitBoolean(value) { + return value; + } + visitNumber(value) { + return value; + } + visitBigInt(value) { + return value; + } + visitString(value) { + return value; + } + visitSymbol(value) { + return value; + } + visitNull() { + return null; + } + visitObject(access) { + const result = []; + let entry; + while ((entry = access.nextEntry()) && !entry.done) { + result.push(entry.value); + } + return Object.fromEntries(result); + } + visitIterable(access) { + const result = []; + let element; + while ((element = access.nextElement())) { + result.push(element); + } + return result; + } +} +exports.GenericVisitor = GenericVisitor; diff --git a/dist/de/impl.d.ts b/dist/de/impl.d.ts index d484bac..f09aae8 100644 --- a/dist/de/impl.d.ts +++ b/dist/de/impl.d.ts @@ -1,107 +1,4 @@ import { Registry } from '../registry'; import { Constructor } from '../utils'; -type Nullable = T | undefined; -export declare class IterResult { - static Next(value: T): IteratorResult; - static Done(): IteratorResult; -} -export interface IMapAccess { - nextKeySeed(seed: K): IteratorResult; - nextValueSeed(seed: V): IteratorResult; - nextEntrySeed(kseed: K, vseed: V): IteratorResult<[TK, TV]>; - nextKey(): IteratorResult; - nextValue(): IteratorResult; - nextEntry(): IteratorResult<[K, V]>; - sizeHint?(): Nullable; -} -export declare abstract class MapAccess { - abstract nextKeySeed(seed: K): IteratorResult; - abstract nextValueSeed(seed: V): IteratorResult; - nextEntrySeed(kseed: K, vseed: V): IteratorResult<[TK, TV]>; - abstract nextKey(): IteratorResult; - abstract nextValue(): IteratorResult; - nextEntry(): IteratorResult<[K, V]>; -} -export interface IIterableAccess { - nextElement(): IteratorResult; - sizeHint?(): Nullable; -} -export declare abstract class IterableAccess implements IIterableAccess { - abstract nextElement(): IteratorResult; -} -export interface IVisitor { - visitBoolean(value: boolean): T; - visitNumber(value: number): T; - visitBigInt(value: bigint): T; - visitString(value: string): T; - visitSymbol(value: symbol): T; - visitNull(): T; - visitObject(access: IMapAccess): T; - visitIterable(access: IIterableAccess): T; -} -export declare class GenericSeed { - readonly visitor: IVisitor; - constructor(visitor?: IVisitor); - static deserialize(deserializer: D, visitor?: IVisitor): T; - deserialize(deserializer: D): T; -} -export declare class GenericVisitor implements IVisitor { - visitBoolean(value: boolean): T; - visitNumber(value: number): T; - visitBigInt(value: bigint): T; - visitString(value: string): T; - visitSymbol(value: symbol): T; - visitNull(): T; - visitObject(access: IMapAccess): T; - visitIterable(access: IIterableAccess): T; -} -export interface IDeserializer { - 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; -} -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 { - 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; -} -export interface Deserialize { - (deserializer: IDeserializer): T; -} +import { IDeserializer } from './interface'; export declare function deserialize(deserializer: D, into: Constructor, registry?: Registry): T; -export {}; diff --git a/dist/de/impl.js b/dist/de/impl.js index 87d51f7..c4d4d4c 100644 --- a/dist/de/impl.js +++ b/dist/de/impl.js @@ -1,227 +1,7 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.Forward = exports.ForwardIterableAccess = exports.ForwardMapAccess = exports.GenericVisitor = exports.GenericSeed = exports.IterableAccess = exports.MapAccess = exports.IterResult = void 0; exports.deserialize = deserialize; const registry_1 = require("../registry"); -class IterResult { - static Next(value) { - return { done: false, value }; - } - static Done() { - return { done: true, value: undefined }; - } -} -exports.IterResult = IterResult; -class MapAccess { - nextEntrySeed(kseed, vseed) { - const key = this.nextKeySeed(kseed); - if (!key.done) { - const value = this.nextValueSeed(vseed); - if (!value.done) { - return IterResult.Next([key.value, value.value]); - } - } - return IterResult.Done(); - } - nextEntry() { - const key = this.nextKey(); - if (!key.done) { - const value = this.nextValue(); - if (!value.done) { - return IterResult.Next([key.value, value.value]); - } - } - return IterResult.Done(); - } -} -exports.MapAccess = MapAccess; -class IterableAccess { -} -exports.IterableAccess = IterableAccess; -class GenericSeed { - constructor(visitor = new GenericVisitor()) { - Object.defineProperty(this, "visitor", { - enumerable: true, - configurable: true, - writable: true, - value: void 0 - }); - this.visitor = visitor; - } - static deserialize(deserializer, visitor = new GenericVisitor()) { - return deserializer.deserializeAny(visitor); - } - deserialize(deserializer) { - return GenericSeed.deserialize(deserializer, this.visitor); - } -} -exports.GenericSeed = GenericSeed; -class GenericVisitor { - visitBoolean(value) { - return value; - } - visitNumber(value) { - return value; - } - visitBigInt(value) { - return value; - } - visitString(value) { - return value; - } - visitSymbol(value) { - return value; - } - visitNull() { - return null; - } - visitObject(access) { - const result = []; - let entry; - while ((entry = access.nextEntry()) && !entry.done) { - result.push(entry.value); - } - return Object.fromEntries(result); - } - visitIterable(access) { - const result = []; - let element; - while ((element = access.nextElement())) { - result.push(element); - } - return result; - } -} -exports.GenericVisitor = GenericVisitor; -class ForwardMapAccess extends 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 IterResult.Next(this.keys[this.kindex++]); - } - else { - return IterResult.Done(); - } - } - nextValue() { - if (this.vindex < this.values.length) { - return IterResult.Next(this.values[this.vindex++]); - } - else { - return IterResult.Done(); - } - } -} -exports.ForwardMapAccess = ForwardMapAccess; -class ForwardIterableAccess extends IterableAccess { - constructor(items) { - super(); - Object.defineProperty(this, "items", { - enumerable: true, - configurable: true, - writable: true, - value: void 0 - }); - Object.defineProperty(this, "index", { - enumerable: true, - configurable: true, - writable: true, - value: 0 - }); - this.items = items; - } - nextElement() { - if (this.index < this.items.length) { - return IterResult.Next(this.items[this.index++]); - } - else { - return IterResult.Done(); - } - } -} -exports.ForwardIterableAccess = ForwardIterableAccess; -class Forward { - constructor(value) { - Object.defineProperty(this, "value", { - enumerable: true, - configurable: true, - writable: true, - value: void 0 - }); - this.value = value; - } - static with(value) { - return new this(value); - } - deserializeAny(_visitor) { - throw new Error("Can't forward to deserializeAny"); - } - deserializeBoolean(visitor) { - return visitor.visitBoolean(this.value); - } - deserializeNumber(visitor) { - return visitor.visitNumber(this.value); - } - deserializeBigInt(visitor) { - return visitor.visitBigInt(this.value); - } - deserializeString(visitor) { - return visitor.visitString(this.value); - } - deserializeSymbol(visitor) { - return visitor.visitSymbol(this.value); - } - deserializeNull(visitor) { - return visitor.visitNull(); - } - deserializeObject(visitor) { - return visitor.visitObject(ForwardMapAccess.fromObject(this.value)); - } - deserializeIterable(visitor) { - return visitor.visitIterable(new ForwardIterableAccess(this.value)); - } - deserializeFunction(_visitor) { - throw new Error('Method not implemented.'); - } -} -exports.Forward = Forward; function deserialize(deserializer, into, registry = registry_1.GlobalRegistry) { const de = registry.deserializers.get(into); if (de == null) { diff --git a/dist/de/index.d.ts b/dist/de/index.d.ts new file mode 100644 index 0000000..fefcc75 --- /dev/null +++ b/dist/de/index.d.ts @@ -0,0 +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 new file mode 100644 index 0000000..bfbd240 --- /dev/null +++ b/dist/de/index.js @@ -0,0 +1,20 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +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 new file mode 100644 index 0000000..46fc960 --- /dev/null +++ b/dist/de/interface.d.ts @@ -0,0 +1,50 @@ +import { Nullable } from "../utils"; +export interface IMapAccess { + nextKeySeed(seed: K): IteratorResult; + nextValueSeed(seed: V): IteratorResult; + nextEntrySeed(kseed: K, vseed: V): IteratorResult<[TK, TV]>; + nextKey(): IteratorResult; + nextValue(): IteratorResult; + nextEntry(): IteratorResult<[K, V]>; + sizeHint?(): Nullable; +} +export declare abstract class MapAccess { + abstract nextKeySeed(seed: K): IteratorResult; + abstract nextValueSeed(seed: V): IteratorResult; + nextEntrySeed(kseed: K, vseed: V): IteratorResult<[TK, TV]>; + abstract nextKey(): IteratorResult; + abstract nextValue(): IteratorResult; + nextEntry(): IteratorResult<[K, V]>; +} +export interface IIterableAccess { + nextElement(): IteratorResult; + sizeHint?(): Nullable; +} +export declare abstract class IterableAccess implements IIterableAccess { + abstract nextElement(): IteratorResult; +} +export interface IVisitor { + visitBoolean(value: boolean): T; + visitNumber(value: number): T; + visitBigInt(value: bigint): T; + visitString(value: string): T; + visitSymbol(value: symbol): T; + visitNull(): T; + visitObject(access: IMapAccess): T; + visitIterable(access: IIterableAccess): T; +} +export interface IDeserializer { + 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; +} +export interface Deserialize { + (deserializer: IDeserializer): T; +} diff --git a/dist/de/interface.js b/dist/de/interface.js new file mode 100644 index 0000000..7ff51dc --- /dev/null +++ b/dist/de/interface.js @@ -0,0 +1,30 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.IterableAccess = exports.MapAccess = void 0; +const utils_1 = require("../utils"); +class MapAccess { + nextEntrySeed(kseed, vseed) { + const key = this.nextKeySeed(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(); + } + nextEntry() { + const key = this.nextKey(); + if (!key.done) { + const value = this.nextValue(); + if (!value.done) { + return utils_1.IterResult.Next([key.value, value.value]); + } + } + return utils_1.IterResult.Done(); + } +} +exports.MapAccess = MapAccess; +class IterableAccess { +} +exports.IterableAccess = IterableAccess; diff --git a/dist/index.d.ts b/dist/index.d.ts index 7a2ca30..3dfe828 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -1,3 +1,4 @@ +import '@tsmetadata/polyfill'; export * as ser from './ser/impl'; export * as de from './de/impl'; export * from './case'; diff --git a/dist/index.js b/dist/index.js index a440824..5a99de4 100644 --- a/dist/index.js +++ b/dist/index.js @@ -37,6 +37,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) { }; Object.defineProperty(exports, "__esModule", { value: true }); exports.de = exports.ser = void 0; +require("@tsmetadata/polyfill"); exports.ser = __importStar(require("./ser/impl")); exports.de = __importStar(require("./de/impl")); __exportStar(require("./case"), exports); diff --git a/dist/registry.d.ts b/dist/registry.d.ts index 311bd3f..9037a58 100644 --- a/dist/registry.d.ts +++ b/dist/registry.d.ts @@ -1,5 +1,5 @@ -import { Deserialize } from './de/impl'; -import { Serialize } from './ser/impl'; +import { Deserialize } from './de'; +import { Serialize } from './ser'; export declare class Registry { serializers: Map>; deserializers: Map; diff --git a/dist/ser/decorator.d.ts b/dist/ser/decorator.d.ts new file mode 100644 index 0000000..c77a3b8 --- /dev/null +++ b/dist/ser/decorator.d.ts @@ -0,0 +1,59 @@ +import { CaseConvention } from '../case'; +import { Deserialize } from '../de'; +import { Nullable } from '../utils'; +import { Serialize } from './interface'; +export interface RenameOptions { + serialize?: string; + deserialize?: string; +} +export interface RenameAllOptions { + serialize?: CaseConvention; + deserialize?: CaseConvention; +} +export interface ContainerOptions { + rename?: RenameOptions | string; + renameAll?: RenameAllOptions | CaseConvention; + default?: () => any; + denyUnknownFields?: boolean; + tag?: string; + untagged?: boolean; +} +interface Predicate { + (value: T): boolean; +} +export interface SkipOptions { + serialize?: Predicate | boolean; + deserialize?: Predicate | boolean; +} +export interface PropertyOptions { + alias?: string | string[]; + default?: () => any; + flatten?: boolean; + rename?: RenameOptions | string; + skip?: SkipOptions | Predicate | boolean; + serializeWith?: Serialize; + deserializeWith?: Deserialize; +} +export declare const Stage: Readonly<{ + readonly Serialize: 0; + readonly Deserialize: 1; +}>; +export type Stage = typeof Stage[keyof typeof Stage]; +export declare class SerdeOptions { + readonly target: Nullable; + readonly container: ContainerOptions; + readonly properties: Map; + constructor(target: any, container?: ContainerOptions, properties?: Map); + private getClassName; + getSerializedClassName(defaultName?: string): string | undefined; + getDeserializedClassName(defaultName?: string): string | undefined; + private applyPropertyCase; + private getPropertyName; + getSerializationPropertyName(property: string): string; + getDeserializationPropertyName(property: string): string; + private shouldSkip; + shouldSerialize(property: string, value: any): boolean; + shouldDeserialize(property: string, value: any): boolean; + defaultFor(property: string): any; +} +export {}; diff --git a/dist/ser/decorator.js b/dist/ser/decorator.js new file mode 100644 index 0000000..a30ddf7 --- /dev/null +++ b/dist/ser/decorator.js @@ -0,0 +1,146 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.SerdeOptions = exports.Stage = void 0; +const case_1 = require("../case"); +const utils_1 = require("../utils"); +exports.Stage = Object.freeze({ + Serialize: 0, + Deserialize: 1 +}); +class SerdeOptions { + constructor(target, container = {}, properties = new Map()) { + Object.defineProperty(this, "target", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "container", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + Object.defineProperty(this, "properties", { + enumerable: true, + configurable: true, + writable: true, + value: void 0 + }); + this.target = target; + this.container = container; + this.properties = properties; + } + getClassName(stage, defaultName) { + var _a, _b, _c, _d; + if (defaultName === void 0) { defaultName = (_b = (_a = this.target) === null || _a === void 0 ? void 0 : _a.constructor) === null || _b === void 0 ? void 0 : _b.name; } + if ((0, utils_1.isString)(this.container.rename)) { + return this.container.rename; + } + else if (stage === exports.Stage.Serialize && (0, utils_1.isString)((_c = this.container.rename) === null || _c === void 0 ? void 0 : _c.serialize)) { + return this.container.rename.serialize; + } + else if (stage === exports.Stage.Deserialize && (0, utils_1.isString)((_d = this.container.rename) === null || _d === void 0 ? void 0 : _d.deserialize)) { + return this.container.rename.serialize; + } + else { + return defaultName; + } + } + getSerializedClassName(defaultName) { + var _a, _b; + if (defaultName === void 0) { defaultName = (_b = (_a = this.target) === null || _a === void 0 ? void 0 : _a.constructor) === null || _b === void 0 ? void 0 : _b.name; } + return this.getClassName(exports.Stage.Serialize, defaultName); + } + getDeserializedClassName(defaultName) { + var _a, _b; + if (defaultName === void 0) { defaultName = (_b = (_a = this.target) === null || _a === void 0 ? void 0 : _a.constructor) === null || _b === void 0 ? void 0 : _b.name; } + return this.getClassName(exports.Stage.Deserialize, defaultName); + } + applyPropertyCase(stage, property) { + var _a, _b; + if ((0, utils_1.isNumber)(this.container.renameAll)) { + return (0, case_1.convertCase)(property, this.container.renameAll); + } + else if (stage === exports.Stage.Serialize && (0, utils_1.isNumber)((_a = this.container.renameAll) === null || _a === void 0 ? void 0 : _a.serialize)) { + return (0, case_1.convertCase)(property, this.container.renameAll.serialize); + } + else if (stage === exports.Stage.Deserialize && (0, utils_1.isNumber)((_b = this.container.renameAll) === null || _b === void 0 ? void 0 : _b.deserialize)) { + return (0, case_1.convertCase)(property, this.container.renameAll.deserialize); + } + else { + return property; + } + } + getPropertyName(stage, property) { + var _a, _b; + const options = this.properties.get(property); + if (options == null) { + return property; + } + else if ((0, utils_1.isString)(options.rename)) { + return options.rename; + } + else if (stage == exports.Stage.Serialize && (0, utils_1.isString)((_a = options.rename) === null || _a === void 0 ? void 0 : _a.serialize)) { + return options.rename.serialize; + } + else if (stage == exports.Stage.Deserialize && (0, utils_1.isString)((_b = options.rename) === null || _b === void 0 ? void 0 : _b.deserialize)) { + return options.rename.deserialize; + } + else { + return property; + } + } + getSerializationPropertyName(property) { + const name = this.getPropertyName(exports.Stage.Serialize, property); + return this.applyPropertyCase(exports.Stage.Serialize, name); + } + getDeserializationPropertyName(property) { + const name = this.getPropertyName(exports.Stage.Deserialize, property); + return this.applyPropertyCase(exports.Stage.Deserialize, name); + } + shouldSkip(stage, property, value) { + var _a, _b, _c, _d; + const options = this.properties.get(property); + if (options == null) { + return false; + } + else if (typeof options.skip === 'boolean') { + return options.skip; + } + else if ((0, utils_1.isFunction)(options.skip)) { + return options.skip(value); + } + else if (stage === exports.Stage.Serialize && typeof ((_a = options.skip) === null || _a === void 0 ? void 0 : _a.serialize) === 'boolean') { + return options.skip.serialize; + } + else if (stage === exports.Stage.Serialize && (0, utils_1.isFunction)((_b = options.skip) === null || _b === void 0 ? void 0 : _b.serialize)) { + return options.skip.serialize(value); + } + else if (stage === exports.Stage.Deserialize && typeof ((_c = options.skip) === null || _c === void 0 ? void 0 : _c.deserialize) === 'boolean') { + return options.skip.deserialize; + } + else if (stage === exports.Stage.Deserialize && (0, utils_1.isFunction)((_d = options.skip) === null || _d === void 0 ? void 0 : _d.deserialize)) { + return options.skip.deserialize(value); + } + else { + return false; + } + } + shouldSerialize(property, value) { + return !this.shouldSkip(exports.Stage.Serialize, property, value); + } + shouldDeserialize(property, value) { + return !this.shouldSkip(exports.Stage.Deserialize, property, value); + } + defaultFor(property) { + const options = this.properties.get(property); + if (options != null && (0, utils_1.isFunction)(options.default)) { + return options.default(); + } + else if ((0, utils_1.isFunction)(this.container.default)) { + return this.container.default(); + } + } +} +exports.SerdeOptions = SerdeOptions; diff --git a/dist/ser/impl.d.ts b/dist/ser/impl.d.ts index ee21ca2..985e586 100644 --- a/dist/ser/impl.d.ts +++ b/dist/ser/impl.d.ts @@ -1,46 +1,4 @@ -export interface ISerializeObject { - serializeKey(key: string): void; - serializeValue(value: U): void; - serializeEntry(key: string, value: U): void; - end(): T; -} -export declare abstract class SerializeObject implements ISerializeObject { - abstract serializeKey(key: string): void; - abstract serializeValue(value: U): void; - abstract end(): T; - serializeEntry(key: string, value: U): void; -} -export interface ISerializeIterable { - serializeElement(value: U): void; - end(): T; -} -export declare abstract class SerializeIterable implements ISerializeIterable { - abstract serializeElement(value: U): void; - abstract end(): T; -} -export interface 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; - serializeObject(): ISerializeObject; - serializeClass(name: string): ISerializeObject; -} -export declare class Serializer 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; - serializeObject(): ISerializeObject; - serializeClass(_name: string): ISerializeObject; -} -export interface Serialize { - >(serializer: S, value: T): U; -} -export declare function serialize>(serializer: S, value: V): T; +import { SerdeOptions } from './decorator'; +import { Serializer } from './interface'; +import { Nullable } from '../utils'; +export declare function serialize>(serializer: S, value: V, optionsGetter?: (value: V) => Nullable): T; diff --git a/dist/ser/impl.js b/dist/ser/impl.js index c36abb4..d6fb39c 100644 --- a/dist/ser/impl.js +++ b/dist/ser/impl.js @@ -1,77 +1,41 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.Serializer = exports.SerializeIterable = exports.SerializeObject = void 0; exports.serialize = serialize; -class SerializeObject { - serializeEntry(key, value) { - this.serializeKey(key); - this.serializeValue(value); - } -} -exports.SerializeObject = SerializeObject; -class SerializeIterable { -} -exports.SerializeIterable = SerializeIterable; -class Serializer { - serializeAny(_value) { - throw new Error("Method not implemented."); - } - serializeBoolean(_value) { - throw new Error('Method not implemented.'); - } - serializeNumber(_value) { - throw new Error('Method not implemented.'); - } - serializeBigInt(_value) { - throw new Error('Method not implemented.'); - } - serializeString(_value) { - throw new Error('Method not implemented.'); - } - serializeSymbol(_value) { - throw new Error('Method not implemented.'); - } - serializeNull() { - throw new Error('Method not implemented.'); - } - serializeObject() { - throw new Error('Method not implemented.'); - } - serializeClass(_name) { - throw new Error('Method not implemented.'); - } -} -exports.Serializer = Serializer; -const isPlainObject = (value) => (value === null || value === void 0 ? void 0 : value.constructor) === Object; +const utils_1 = require("../utils"); class UnhandledTypeError extends TypeError { constructor(serializer, value) { - super(`unhandled type: "${typeof value}" for serializer ${serializer.constructor.name}`); + super(`unhandled type: '${typeof value}' for serializer ${serializer.constructor.name}`); } } -function serializeObject(serializer, obj) { +function serializeObject(serializer, obj, options) { for (const key in obj) { serializer.serializeEntry(key, obj[key]); } return serializer.end(); } -function serializeClass(serializer, value) { +function serializeClass(serializer, value, options) { const name = value.constructor.name; const ser = serializer.serializeClass(name); - return serializeObject(ser, value); + return serializeObject(ser, value, options); } -function serialize(serializer, value) { +const defaultGetter = (value) => { + var _a; + return (_a = value[Symbol.metadata]) === null || _a === void 0 ? void 0 : _a.serde; +}; +function serialize(serializer, value, optionsGetter = defaultGetter) { switch (typeof value) { - case "string": return serializer.serializeString(value); - case "number": return serializer.serializeNumber(value); - case "bigint": return serializer.serializeBigInt(value); - case "boolean": return serializer.serializeBoolean(value); - case "symbol": return serializer.serializeSymbol(value); - case "undefined": return serializer.serializeNull(); - case "object": + case 'string': return serializer.serializeString(value); + case 'number': return serializer.serializeNumber(value); + case 'bigint': return serializer.serializeBigInt(value); + case 'boolean': return serializer.serializeBoolean(value); + case 'symbol': return serializer.serializeSymbol(value); + case 'undefined': return serializer.serializeNull(); + case 'object': + const options = optionsGetter(value); switch (true) { case value == null: return serializer.serializeNull(); - case !isPlainObject(value): return serializeClass(serializer, value); - default: return serializeObject(serializer.serializeObject(), value); + case !(0, utils_1.isPlainObject)(value): return serializeClass(serializer, value, options); + default: return serializeObject(serializer.serializeObject(), value, options); } default: throw new UnhandledTypeError(serializer, value); } diff --git a/dist/ser/index.d.ts b/dist/ser/index.d.ts new file mode 100644 index 0000000..52c24f4 --- /dev/null +++ b/dist/ser/index.d.ts @@ -0,0 +1,2 @@ +export * from './impl'; +export * from './interface'; diff --git a/dist/ser/index.js b/dist/ser/index.js new file mode 100644 index 0000000..433cf42 --- /dev/null +++ b/dist/ser/index.js @@ -0,0 +1,18 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +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("./impl"), exports); +__exportStar(require("./interface"), exports); diff --git a/dist/ser/interface.d.ts b/dist/ser/interface.d.ts new file mode 100644 index 0000000..96ab9e5 --- /dev/null +++ b/dist/ser/interface.d.ts @@ -0,0 +1,45 @@ +export interface ISerializeObject { + serializeKey(key: string): void; + serializeValue(value: U): void; + serializeEntry(key: string, value: U): void; + end(): T; +} +export declare abstract class SerializeObject implements ISerializeObject { + abstract serializeKey(key: string): void; + abstract serializeValue(value: U): void; + abstract end(): T; + serializeEntry(key: string, value: U): void; +} +export interface ISerializeIterable { + serializeElement(value: U): void; + end(): T; +} +export declare abstract class SerializeIterable implements ISerializeIterable { + abstract serializeElement(value: U): void; + abstract end(): T; +} +export interface 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; + serializeObject(): ISerializeObject; + serializeClass(name: string): ISerializeObject; +} +export declare class Serializer 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; + serializeObject(): ISerializeObject; + serializeClass(_name: string): ISerializeObject; +} +export interface Serialize { + >(serializer: S, value: T): U; +} diff --git a/dist/ser/interface.js b/dist/ser/interface.js new file mode 100644 index 0000000..0665d89 --- /dev/null +++ b/dist/ser/interface.js @@ -0,0 +1,43 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Serializer = exports.SerializeIterable = exports.SerializeObject = void 0; +class SerializeObject { + serializeEntry(key, value) { + this.serializeKey(key); + this.serializeValue(value); + } +} +exports.SerializeObject = SerializeObject; +class SerializeIterable { +} +exports.SerializeIterable = SerializeIterable; +class Serializer { + serializeAny(_value) { + throw new Error("Method not implemented."); + } + serializeBoolean(_value) { + throw new Error('Method not implemented.'); + } + serializeNumber(_value) { + throw new Error('Method not implemented.'); + } + serializeBigInt(_value) { + throw new Error('Method not implemented.'); + } + serializeString(_value) { + throw new Error('Method not implemented.'); + } + serializeSymbol(_value) { + throw new Error('Method not implemented.'); + } + serializeNull() { + throw new Error('Method not implemented.'); + } + serializeObject() { + throw new Error('Method not implemented.'); + } + serializeClass(_name) { + throw new Error('Method not implemented.'); + } +} +exports.Serializer = Serializer; diff --git a/dist/utils.d.ts b/dist/utils.d.ts index 3db7207..d5dfc33 100644 --- a/dist/utils.d.ts +++ b/dist/utils.d.ts @@ -13,3 +13,7 @@ export declare function isIterable(value: any): value is Iterable; export declare function isString(value: any): value is string; export declare function isNumber(value: any): value is number; export type Constructor = new (...args: any[]) => T; +export declare class IterResult { + static Next(value: T): IteratorResult; + static Done(): IteratorResult; +} diff --git a/dist/utils.js b/dist/utils.js index fe888ab..8dbf243 100644 --- a/dist/utils.js +++ b/dist/utils.js @@ -1,5 +1,6 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); +exports.IterResult = void 0; exports.staticImplements = staticImplements; exports.isPlainObject = isPlainObject; exports.isFunction = isFunction; @@ -24,3 +25,12 @@ function isString(value) { function isNumber(value) { return !isNaN(value); } +class IterResult { + static Next(value) { + return { done: false, value }; + } + static Done() { + return { done: true, value: undefined }; + } +} +exports.IterResult = IterResult; diff --git a/package.json b/package.json index e93e89d..0b9b61f 100644 --- a/package.json +++ b/package.json @@ -9,12 +9,12 @@ "default": "./dist/index.js" }, "./ser": { - "types": "./dist/ser/impl.d.ts", - "default": "./dist/ser/impl.js" + "types": "./dist/ser/index.d.ts", + "default": "./dist/ser/index.js" }, "./de": { - "types": "./dist/de/impl.d.ts", - "default": "./dist/de/impl.js" + "types": "./dist/de/index.d.ts", + "default": "./dist/de/index.js" }, "./utils": { "types": "./dist/utils.d.ts", diff --git a/src/de/forward.ts b/src/de/forward.ts new file mode 100644 index 0000000..6ba63bf --- /dev/null +++ b/src/de/forward.ts @@ -0,0 +1,118 @@ +import { IterResult } from '../utils' +import { Deserialize, IDeserializer, IterableAccess, IVisitor, MapAccess } from './interface' + +export class ForwardMapAccess extends MapAccess { + private readonly keys: string[] + private readonly values: any[] + + private kindex: number = 0 + private vindex: number = 0 + + constructor(keys: string[], values: any[]) { + super() + this.keys = keys + this.values = values + } + + static fromObject(obj: object): ForwardMapAccess { + return new ForwardMapAccess( + Object.keys(obj), + Object.values(obj) + ) + } + + nextKeySeed(_seed: K): IteratorResult { + return this.nextKey() + } + + nextValueSeed(_seed: V): IteratorResult { + return this.nextValue() + } + + nextKey(): IteratorResult { + if (this.kindex < this.keys.length) { + return IterResult.Next(this.keys[this.kindex++]) as IteratorResult + } else { + return IterResult.Done() + } + } + + nextValue(): IteratorResult { + if (this.vindex < this.values.length) { + return IterResult.Next(this.values[this.vindex++]) as IteratorResult + } else { + return IterResult.Done() + } + } +} + +export class ForwardIterableAccess extends IterableAccess { + private readonly items: any[] + private index: number = 0 + + constructor(items: any[]) { + super() + this.items = items + } + + nextElement(): IteratorResult { + if (this.index < this.items.length) { + return IterResult.Next(this.items[this.index++]) as IteratorResult + } else { + return IterResult.Done() + } + } +} + +export class Forward implements IDeserializer { + private readonly value: any + + constructor(value: any) { + this.value = value + } + + static with(value: any): Forward { + return new this(value) + } + + deserializeAny>(_visitor: V): T { + throw new Error("Can't forward to deserializeAny") + } + + deserializeBoolean>(visitor: V): T { + return visitor.visitBoolean(this.value) + } + + deserializeNumber>(visitor: V): T { + return visitor.visitNumber(this.value) + } + + deserializeBigInt>(visitor: V): T { + return visitor.visitBigInt(this.value) + } + + deserializeString>(visitor: V): T { + return visitor.visitString(this.value) + } + + deserializeSymbol>(visitor: V): T { + return visitor.visitSymbol(this.value) + } + + deserializeNull>(visitor: V): T { + return visitor.visitNull() + } + + deserializeObject>(visitor: V): T { + return visitor.visitObject(ForwardMapAccess.fromObject(this.value)) + } + + deserializeIterable>(visitor: V): T { + return visitor.visitIterable(new ForwardIterableAccess(this.value)) + } + + deserializeFunction>(_visitor: V): T { + throw new Error('Method not implemented.') + } +} + diff --git a/src/de/generic.ts b/src/de/generic.ts new file mode 100644 index 0000000..0ddd333 --- /dev/null +++ b/src/de/generic.ts @@ -0,0 +1,68 @@ +import { IDeserializer, IIterableAccess, IMapAccess, IVisitor } from './interface' + +export class GenericSeed { + readonly visitor: IVisitor + + constructor(visitor: IVisitor = new GenericVisitor()) { + this.visitor = visitor + } + + static deserialize(deserializer: D, visitor: IVisitor = new GenericVisitor()): T { + return deserializer.deserializeAny(visitor) + } + + deserialize(deserializer: D): T { + return GenericSeed.deserialize(deserializer, this.visitor) + } +} + +export class GenericVisitor implements IVisitor { + visitBoolean(value: boolean): T { + return value as T + } + + visitNumber(value: number): T { + return value as T + } + + visitBigInt(value: bigint): T { + return value as T + } + + visitString(value: string): T { + return value as T + } + + visitSymbol(value: symbol): T { + return value as T + } + + visitNull(): T { + return null as T + } + + visitObject(access: IMapAccess): T { + const result = [] + let entry + + while ((entry = access.nextEntry()) && !entry.done) { + result.push(entry.value) + } + + return Object.fromEntries(result) + } + + visitIterable(access: IIterableAccess): T { + const result = [] + let element + + while ((element = access.nextElement())) { + result.push(element) + } + + return result as T + } +} + + + diff --git a/src/de/impl.ts b/src/de/impl.ts index 392a1fe..fc65614 100644 --- a/src/de/impl.ts +++ b/src/de/impl.ts @@ -1,279 +1,6 @@ import { GlobalRegistry, Registry } from '../registry' import { Constructor } from '../utils' - -type Nullable = T | undefined - -export class IterResult { - static Next(value: T): IteratorResult { - return { done: false, value } - } - - static Done(): IteratorResult { - return { done: true, value: undefined } - } -} - -export interface IMapAccess { - nextKeySeed(seed: K): IteratorResult - nextValueSeed(seed: V): IteratorResult - nextEntrySeed(kseed: K, vseed: V): IteratorResult<[TK, TV]> - nextKey(): IteratorResult - nextValue(): IteratorResult - nextEntry(): IteratorResult<[K, V]> - sizeHint?(): Nullable -} - -export abstract class MapAccess { - abstract nextKeySeed(seed: K): IteratorResult - abstract nextValueSeed(seed: V): IteratorResult - - nextEntrySeed(kseed: K, vseed: V): IteratorResult<[TK, TV]> { - const key = this.nextKeySeed(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() - } - - abstract nextKey(): IteratorResult - abstract nextValue(): IteratorResult - - nextEntry(): IteratorResult<[K, V]> { - const key = this.nextKey() as IteratorResult - - if (!key.done) { - const value = this.nextValue() as IteratorResult - - if (!value.done) { - return IterResult.Next([key.value, value.value]) - } - } - - return IterResult.Done() - } -} - -export interface IIterableAccess { - nextElement(): IteratorResult - sizeHint?(): Nullable -} - -export abstract class IterableAccess implements IIterableAccess { - abstract nextElement(): IteratorResult -} - -export interface IVisitor { - visitBoolean(value: boolean): T - visitNumber(value: number): T - visitBigInt(value: bigint): T - visitString(value: string): T - visitSymbol(value: symbol): T - visitNull(): T - visitObject(access: IMapAccess): T - visitIterable(access: IIterableAccess): T -} - -export class GenericSeed { - readonly visitor: IVisitor - - constructor(visitor: IVisitor = new GenericVisitor()) { - this.visitor = visitor - } - - static deserialize(deserializer: D, visitor: IVisitor = new GenericVisitor()): T { - return deserializer.deserializeAny(visitor) - } - - deserialize(deserializer: D): T { - return GenericSeed.deserialize(deserializer, this.visitor) - } -} - -export class GenericVisitor implements IVisitor { - visitBoolean(value: boolean): T { - return value as T - } - - visitNumber(value: number): T { - return value as T - } - - visitBigInt(value: bigint): T { - return value as T - } - - visitString(value: string): T { - return value as T - } - - visitSymbol(value: symbol): T { - return value as T - } - - visitNull(): T { - return null as T - } - - visitObject(access: IMapAccess): T { - const result = [] - let entry - - while ((entry = access.nextEntry()) && !entry.done) { - result.push(entry.value) - } - - return Object.fromEntries(result) - } - - visitIterable(access: IIterableAccess): T { - const result = [] - let element - - while ((element = access.nextElement())) { - result.push(element) - } - - return result as T - } -} - -export interface IDeserializer { - 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 -} - -export class ForwardMapAccess extends MapAccess { - private readonly keys: string[] - private readonly values: any[] - - private kindex: number = 0 - private vindex: number = 0 - - constructor(keys: string[], values: any[]) { - super() - this.keys = keys - this.values = values - } - - static fromObject(obj: object): ForwardMapAccess { - return new ForwardMapAccess( - Object.keys(obj), - Object.values(obj) - ) - } - - nextKeySeed(_seed: K): IteratorResult { - return this.nextKey() - } - - nextValueSeed(_seed: V): IteratorResult { - return this.nextValue() - } - - nextKey(): IteratorResult { - if (this.kindex < this.keys.length) { - return IterResult.Next(this.keys[this.kindex++]) as IteratorResult - } else { - return IterResult.Done() - } - } - - nextValue(): IteratorResult { - if (this.vindex < this.values.length) { - return IterResult.Next(this.values[this.vindex++]) as IteratorResult - } else { - return IterResult.Done() - } - } -} - -export class ForwardIterableAccess extends IterableAccess { - private readonly items: any[] - private index: number = 0 - - constructor(items: any[]) { - super() - this.items = items - } - - nextElement(): IteratorResult { - if (this.index < this.items.length) { - return IterResult.Next(this.items[this.index++]) as IteratorResult - } else { - return IterResult.Done() - } - } -} - -export class Forward implements IDeserializer { - private readonly value: any - - constructor(value: any) { - this.value = value - } - - static with(value: any): Forward { - return new this(value) - } - - deserializeAny>(_visitor: V): T { - throw new Error("Can't forward to deserializeAny") - } - - deserializeBoolean>(visitor: V): T { - return visitor.visitBoolean(this.value) - } - - deserializeNumber>(visitor: V): T { - return visitor.visitNumber(this.value) - } - - deserializeBigInt>(visitor: V): T { - return visitor.visitBigInt(this.value) - } - - deserializeString>(visitor: V): T { - return visitor.visitString(this.value) - } - - deserializeSymbol>(visitor: V): T { - return visitor.visitSymbol(this.value) - } - - deserializeNull>(visitor: V): T { - return visitor.visitNull() - } - - deserializeObject>(visitor: V): T { - return visitor.visitObject(ForwardMapAccess.fromObject(this.value)) - } - - deserializeIterable>(visitor: V): T { - return visitor.visitIterable(new ForwardIterableAccess(this.value)) - } - - deserializeFunction>(_visitor: V): T { - throw new Error('Method not implemented.') - } -} - -export interface Deserialize { - (deserializer: IDeserializer): T -} +import { IDeserializer } from './interface' export function deserialize(deserializer: D, into: Constructor, registry: Registry = GlobalRegistry): T { const de = registry.deserializers.get(into) diff --git a/src/de/index.ts b/src/de/index.ts new file mode 100644 index 0000000..bf995e2 --- /dev/null +++ b/src/de/index.ts @@ -0,0 +1,5 @@ +export * from './forward' +export * from './generic' +export * from './impl' +export * from './interface' + diff --git a/src/de/interface.ts b/src/de/interface.ts new file mode 100644 index 0000000..934e803 --- /dev/null +++ b/src/de/interface.ts @@ -0,0 +1,85 @@ +import { IterResult, Nullable } from "../utils" + +export interface IMapAccess { + nextKeySeed(seed: K): IteratorResult + nextValueSeed(seed: V): IteratorResult + nextEntrySeed(kseed: K, vseed: V): IteratorResult<[TK, TV]> + nextKey(): IteratorResult + nextValue(): IteratorResult + nextEntry(): IteratorResult<[K, V]> + sizeHint?(): Nullable +} + +export abstract class MapAccess { + abstract nextKeySeed(seed: K): IteratorResult + abstract nextValueSeed(seed: V): IteratorResult + + nextEntrySeed(kseed: K, vseed: V): IteratorResult<[TK, TV]> { + const key = this.nextKeySeed(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() + } + + abstract nextKey(): IteratorResult + abstract nextValue(): IteratorResult + + nextEntry(): IteratorResult<[K, V]> { + const key = this.nextKey() as IteratorResult + + if (!key.done) { + const value = this.nextValue() as IteratorResult + + if (!value.done) { + return IterResult.Next([key.value, value.value]) + } + } + + return IterResult.Done() + } +} + +export interface IIterableAccess { + nextElement(): IteratorResult + sizeHint?(): Nullable +} + +export abstract class IterableAccess implements IIterableAccess { + abstract nextElement(): IteratorResult +} + +export interface IVisitor { + visitBoolean(value: boolean): T + visitNumber(value: number): T + visitBigInt(value: bigint): T + visitString(value: string): T + visitSymbol(value: symbol): T + visitNull(): T + visitObject(access: IMapAccess): T + visitIterable(access: IIterableAccess): T +} + +export interface IDeserializer { + 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 +} + +export interface Deserialize { + (deserializer: IDeserializer): T +} + diff --git a/src/index.ts b/src/index.ts index 1df7235..9dd5a0f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,5 @@ +import '@tsmetadata/polyfill' + export * as ser from './ser/impl' export * as de from './de/impl' export * from './case' diff --git a/src/registry.ts b/src/registry.ts index 46e2bda..8530f84 100644 --- a/src/registry.ts +++ b/src/registry.ts @@ -1,5 +1,5 @@ -import { Deserialize } from './de/impl' -import { Serialize } from './ser/impl' +import { Deserialize } from './de' +import { Serialize } from './ser' export class Registry { serializers: Map> = new Map() diff --git a/src/ser/decorator.ts b/src/ser/decorator.ts new file mode 100644 index 0000000..ef173b3 --- /dev/null +++ b/src/ser/decorator.ts @@ -0,0 +1,157 @@ +import { CaseConvention, convertCase } from '../case' +import { Deserialize } from '../de' +import { isFunction, isNumber, isString, Nullable } from '../utils' +import { Serialize } from './interface' + +export interface RenameOptions { + serialize?: string + deserialize?: string +} + +export interface RenameAllOptions { + serialize?: CaseConvention + deserialize?: CaseConvention +} + +export interface ContainerOptions { + rename?: RenameOptions | string + renameAll?: RenameAllOptions | CaseConvention + default?: () => any + denyUnknownFields?: boolean + tag?: string + untagged?: boolean +} + +interface Predicate { + (value: T): boolean +} + +export interface SkipOptions { + serialize?: Predicate | boolean + deserialize?: Predicate | boolean +} +export interface PropertyOptions { + alias?: string | string[] + default?: () => any + flatten?: boolean + rename?: RenameOptions | string + skip?: SkipOptions | Predicate | boolean + serializeWith?: Serialize + deserializeWith?: Deserialize +} + +export const Stage = Object.freeze({ + Serialize: 0, + Deserialize: 1 +} as const) + +export type Stage = typeof Stage[keyof typeof Stage] + +export class SerdeOptions { + readonly target: Nullable + readonly container: ContainerOptions + readonly properties: Map + + constructor(target: any, container: ContainerOptions = {}, properties: Map = new Map()) { + this.target = target + this.container = container + this.properties = properties + } + + private getClassName(stage: Stage, defaultName: string = this.target?.constructor?.name) { + if (isString(this.container.rename)) { + return this.container.rename + } else if (stage === Stage.Serialize && isString(this.container.rename?.serialize)) { + return this.container.rename.serialize + } else if (stage === Stage.Deserialize && isString(this.container.rename?.deserialize)) { + return this.container.rename.serialize + } else { + return defaultName + } + } + + getSerializedClassName(defaultName: string = this.target?.constructor?.name) { + return this.getClassName(Stage.Serialize, defaultName) + } + + getDeserializedClassName(defaultName: string = this.target?.constructor?.name) { + return this.getClassName(Stage.Deserialize, defaultName) + } + + private applyPropertyCase(stage: Stage, property: string) { + if (isNumber(this.container.renameAll)) { + return convertCase(property, this.container.renameAll) + } else if (stage === Stage.Serialize && isNumber(this.container.renameAll?.serialize)) { + return convertCase(property, this.container.renameAll.serialize) + } else if (stage === Stage.Deserialize && isNumber(this.container.renameAll?.deserialize)) { + return convertCase(property, this.container.renameAll.deserialize) + } else { + return property + } + } + + private getPropertyName(stage: Stage, property: string) { + const options = this.properties.get(property) + + if (options == null) { + return property + } else if (isString(options.rename)) { + return options.rename + } else if (stage == Stage.Serialize && isString(options.rename?.serialize)) { + return options.rename.serialize + } else if (stage == Stage.Deserialize && isString(options.rename?.deserialize)) { + return options.rename.deserialize + } else { + return property + } + } + + getSerializationPropertyName(property: string) { + const name = this.getPropertyName(Stage.Serialize, property) + return this.applyPropertyCase(Stage.Serialize, name) + } + + getDeserializationPropertyName(property: string) { + const name = this.getPropertyName(Stage.Deserialize, property) + return this.applyPropertyCase(Stage.Deserialize, name) + } + + private shouldSkip(stage: Stage, property: string, value: any) { + const options = this.properties.get(property) + if (options == null) { + return false + } else if (typeof options.skip === 'boolean') { + return options.skip + } else if (isFunction(options.skip)) { + return options.skip(value) + } else if (stage === Stage.Serialize && typeof options.skip?.serialize === 'boolean') { + return options.skip.serialize + } else if (stage === Stage.Serialize && isFunction(options.skip?.serialize)) { + return options.skip.serialize(value) + } else if (stage === Stage.Deserialize && typeof options.skip?.deserialize === 'boolean') { + return options.skip.deserialize + } else if (stage === Stage.Deserialize && isFunction(options.skip?.deserialize)) { + return options.skip.deserialize(value) + } else { + return false + } + } + + shouldSerialize(property: string, value: any) { + return !this.shouldSkip(Stage.Serialize, property, value) + } + + shouldDeserialize(property: string, value: any) { + return !this.shouldSkip(Stage.Deserialize, property, value) + } + + defaultFor(property: string) { + const options = this.properties.get(property) + if (options != null && isFunction(options.default)) { + return options.default() + } else if (isFunction(this.container.default)) { + return this.container.default() + } + } +} + diff --git a/src/ser/impl.ts b/src/ser/impl.ts index 7705b24..0c3545b 100644 --- a/src/ser/impl.ts +++ b/src/ser/impl.ts @@ -1,94 +1,14 @@ -export interface ISerializeObject { - serializeKey(key: string): void - serializeValue(value: U): void - serializeEntry(key: string, value: U): void - end(): T -} - -export abstract class SerializeObject implements ISerializeObject { - abstract serializeKey(key: string): void - abstract serializeValue(value: U): void - abstract end(): T - - serializeEntry(key: string, value: U): void { - this.serializeKey(key) - this.serializeValue(value) - } -} - -export interface ISerializeIterable { - serializeElement(value: U): void - end(): T -} - -export abstract class SerializeIterable implements ISerializeIterable { - abstract serializeElement(value: U): void - abstract end(): T -} - -export interface 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 - serializeObject(): ISerializeObject - serializeClass(name: string): ISerializeObject -} - -export class Serializer implements ISerializer { - serializeAny(_value: any): T { - throw new Error("Method not implemented.") - } - - serializeBoolean(_value: boolean): T { - throw new Error('Method not implemented.') - } - - serializeNumber(_value: number): T { - throw new Error('Method not implemented.') - } - - serializeBigInt(_value: bigint): T { - throw new Error('Method not implemented.') - } - - serializeString(_value: string): T { - throw new Error('Method not implemented.') - } - - serializeSymbol(_value: symbol): T { - throw new Error('Method not implemented.') - } - - serializeNull(): T { - throw new Error('Method not implemented.') - } - - serializeObject(): ISerializeObject { - throw new Error('Method not implemented.') - } - - serializeClass(_name: string): ISerializeObject { - throw new Error('Method not implemented.') - } -} - -export interface Serialize { - >(serializer: S, value: T): U -} - -const isPlainObject = (value: any): value is object => value?.constructor === Object +import { SerdeOptions } from './decorator' +import { ISerializeObject, Serializer } from './interface' +import { isPlainObject, Nullable } from '../utils' class UnhandledTypeError extends TypeError { constructor(serializer: Serializer, value: any) { - super(`unhandled type: "${typeof value}" for serializer ${serializer.constructor.name}`) + super(`unhandled type: '${typeof value}' for serializer ${serializer.constructor.name}`) } } -function serializeObject>(serializer: S, obj: V): T { +function serializeObject>(serializer: S, obj: V, options?: SerdeOptions): T { for (const key in obj) { serializer.serializeEntry(key, obj[key]) } @@ -96,25 +16,30 @@ function serializeObject>(ser return serializer.end() } -function serializeClass>(serializer: S, value: V): T { +function serializeClass>(serializer: S, value: V, options?: SerdeOptions): T { const name = value.constructor.name const ser = serializer.serializeClass(name) - return serializeObject(ser, value) + return serializeObject(ser, value, options) } -export function serialize>(serializer: S, value: V): T { +const defaultGetter = (value: any): Nullable => { + return value[Symbol.metadata]?.serde +} + +export function serialize>(serializer: S, value: V, optionsGetter: (value: V) => Nullable = defaultGetter): T { switch (typeof value) { - case "string": return serializer.serializeString(value) - case "number": return serializer.serializeNumber(value) - case "bigint": return serializer.serializeBigInt(value) - case "boolean": return serializer.serializeBoolean(value) - case "symbol": return serializer.serializeSymbol(value) - case "undefined": return serializer.serializeNull() - case "object": + case 'string': return serializer.serializeString(value) + case 'number': return serializer.serializeNumber(value) + case 'bigint': return serializer.serializeBigInt(value) + case 'boolean': return serializer.serializeBoolean(value) + case 'symbol': return serializer.serializeSymbol(value) + case 'undefined': return serializer.serializeNull() + case 'object': + const options = optionsGetter(value) switch (true) { case value == null: return serializer.serializeNull() - case !isPlainObject(value): return serializeClass(serializer, value) - default: return serializeObject(serializer.serializeObject(), value) + case !isPlainObject(value): return serializeClass(serializer, value, options) + default: return serializeObject(serializer.serializeObject(), value, options) } default: throw new UnhandledTypeError(serializer, value) } diff --git a/src/ser/index.ts b/src/ser/index.ts new file mode 100644 index 0000000..6265425 --- /dev/null +++ b/src/ser/index.ts @@ -0,0 +1,3 @@ +export * from './impl' +export * from './interface' + diff --git a/src/ser/interface.ts b/src/ser/interface.ts new file mode 100644 index 0000000..da53b4c --- /dev/null +++ b/src/ser/interface.ts @@ -0,0 +1,82 @@ +export interface ISerializeObject { + serializeKey(key: string): void + serializeValue(value: U): void + serializeEntry(key: string, value: U): void + end(): T +} + +export abstract class SerializeObject implements ISerializeObject { + abstract serializeKey(key: string): void + abstract serializeValue(value: U): void + abstract end(): T + + serializeEntry(key: string, value: U): void { + this.serializeKey(key) + this.serializeValue(value) + } +} + +export interface ISerializeIterable { + serializeElement(value: U): void + end(): T +} + +export abstract class SerializeIterable implements ISerializeIterable { + abstract serializeElement(value: U): void + abstract end(): T +} + +export interface 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 + serializeObject(): ISerializeObject + serializeClass(name: string): ISerializeObject +} + +export class Serializer implements ISerializer { + serializeAny(_value: any): T { + throw new Error("Method not implemented.") + } + + serializeBoolean(_value: boolean): T { + throw new Error('Method not implemented.') + } + + serializeNumber(_value: number): T { + throw new Error('Method not implemented.') + } + + serializeBigInt(_value: bigint): T { + throw new Error('Method not implemented.') + } + + serializeString(_value: string): T { + throw new Error('Method not implemented.') + } + + serializeSymbol(_value: symbol): T { + throw new Error('Method not implemented.') + } + + serializeNull(): T { + throw new Error('Method not implemented.') + } + + serializeObject(): ISerializeObject { + throw new Error('Method not implemented.') + } + + serializeClass(_name: string): ISerializeObject { + throw new Error('Method not implemented.') + } +} + +export interface Serialize { + >(serializer: S, value: T): U +} + diff --git a/src/utils.ts b/src/utils.ts index e4df467..cc8f44a 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -36,3 +36,13 @@ export function isNumber(value: any): value is number { export type Constructor = new (...args: any[]) => T +export class IterResult { + static Next(value: T): IteratorResult { + return { done: false, value } + } + + static Done(): IteratorResult { + return { done: true, value: undefined } + } +} +