diff --git a/dist/de/generic.d.ts b/dist/de/generic.d.ts index 5e507e6..1163e6b 100644 --- a/dist/de/generic.d.ts +++ b/dist/de/generic.d.ts @@ -1,4 +1,3 @@ -import { SerdeOptions } from '../option'; import { IDeserializer, IIterableAccess, IMapAccess, IVisitor } from './interface'; export declare class GenericSeed { readonly visitor: IVisitor; @@ -6,10 +5,10 @@ export declare class GenericSeed { static deserialize(deserializer: D, visitor?: IVisitor): T; deserialize(deserializer: D): T; } -export declare class ProxyVisitor implements IVisitor { +export declare class Visitor implements IVisitor { private overrides?; - private options?; - constructor(overrides?: Partial>, options?: SerdeOptions); + constructor(overrides?: Partial>); + static from(visitor: Partial>): IVisitor; visitBoolean(value: boolean): T; visitNumber(value: number): T; visitBigInt(value: bigint): T; @@ -19,18 +18,3 @@ export declare class ProxyVisitor implements IVisitor { visitObject(access: IMapAccess): T; visitIterable(access: IIterableAccess): T; } -export declare class ProxyDeserializer implements IDeserializer { - private readonly deserializer; - private readonly options?; - constructor(deserializer: IDeserializer, options?: SerdeOptions); - 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/generic.js b/dist/de/generic.js index 2f61de7..ff2db0e 100644 --- a/dist/de/generic.js +++ b/dist/de/generic.js @@ -1,9 +1,10 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.ProxyDeserializer = exports.ProxyVisitor = exports.GenericSeed = void 0; +exports.Visitor = exports.GenericSeed = void 0; const utils_1 = require("../utils"); +const interface_1 = require("./interface"); class GenericSeed { - constructor(visitor = new ProxyVisitor()) { + constructor(visitor = new Visitor()) { Object.defineProperty(this, "visitor", { enumerable: true, configurable: true, @@ -12,7 +13,7 @@ class GenericSeed { }); this.visitor = visitor; } - static deserialize(deserializer, visitor = new ProxyVisitor()) { + static deserialize(deserializer, visitor = new Visitor()) { return deserializer.deserializeAny(visitor); } deserialize(deserializer) { @@ -20,127 +21,21 @@ class GenericSeed { } } exports.GenericSeed = GenericSeed; -class ProxyMapAccess { - constructor(access, options) { - Object.defineProperty(this, "access", { - enumerable: true, - configurable: true, - writable: true, - value: void 0 - }); - Object.defineProperty(this, "options", { - enumerable: true, - configurable: true, - writable: true, - value: void 0 - }); - if (access instanceof ProxyMapAccess) { - return this; - } - this.access = access; - this.options = options; - } - wrapResponse(result) { - var _a, _b, _c, _d; - switch (true) { - default: - case result.done: return result; - case (0, utils_1.isString)(result.value): { - const key = (_b = (_a = this.options) === null || _a === void 0 ? void 0 : _a.getDeserializationPropertyName(result.value)) !== null && _b !== void 0 ? _b : result.value; - return utils_1.IterResult.Next(key); - } - case Array.isArray(result.value): { - const [alias, value] = result.value; - const key = (_d = (_c = this.options) === null || _c === void 0 ? void 0 : _c.getDeserializationPropertyName(alias)) !== null && _d !== void 0 ? _d : alias; - return utils_1.IterResult.Next([ - key, - value - ]); - } - } - } - shouldSkipEntry(entry) { - var _a; - return (_a = this.options) === null || _a === void 0 ? void 0 : _a.shouldSkipDeserialization(entry.value[0], entry.value[1]); - } - nextKeySeed(seed) { - return this.wrapResponse(this.access.nextKeySeed(seed)); - } - nextValueSeed(seed) { - return this.access.nextValueSeed(seed); - } - nextEntrySeed(kseed, vseed) { - const response = this.wrapResponse(this.access.nextEntrySeed(kseed, vseed)); - if (!response.done && this.shouldSkipEntry(response)) { - return this.nextEntrySeed(kseed, vseed); - } - else { - return response; - } - } - nextKey() { - return this.wrapResponse(this.access.nextKey()); - } - nextValue() { - return this.access.nextValue(); - } - nextEntry() { - const response = this.wrapResponse(this.access.nextEntry()); - if (!response.done && this.shouldSkipEntry(response)) { - return this.nextEntry(); - } - else { - return response; - } - } - sizeHint() { - var _a, _b; - return (_b = (_a = this.access).sizeHint) === null || _b === void 0 ? void 0 : _b.call(_a); - } - *generate(next) { - let item; - while ((item = next()) && !item.done) { - yield item.value; - } - } - keys(seed) { - return this.generate(seed == null ? - this.nextKey.bind(this) : - this.nextKeySeed.bind(this, seed)); - } - values(seed) { - return this.generate(seed == null ? - this.nextValue.bind(this) : - this.nextValueSeed.bind(this, seed)); - } - entries(kseed, vseed) { - return this.generate(kseed == null || vseed == null ? - this.nextEntry.bind(this) : - this.nextEntrySeed.bind(this, kseed, vseed)); - } - [Symbol.iterator]() { - return this.entries(); - } -} -class ProxyVisitor { - constructor(overrides, options) { +class Visitor { + constructor(overrides) { Object.defineProperty(this, "overrides", { enumerable: true, configurable: true, writable: true, value: void 0 }); - Object.defineProperty(this, "options", { - enumerable: true, - configurable: true, - writable: true, - value: void 0 - }); - if (overrides instanceof ProxyVisitor) { - return overrides; - } this.overrides = overrides; - this.options = options; + } + static from(visitor) { + if (visitor instanceof Visitor || (0, interface_1.isVisitor)(visitor)) { + return visitor; + } + return new this(visitor); } visitBoolean(value) { var _a, _b, _c; @@ -168,14 +63,12 @@ class ProxyVisitor { } visitObject(access) { var _a, _b; - const proxy = new ProxyMapAccess(access, this.options); if ((0, utils_1.isFunction)((_a = this.overrides) === null || _a === void 0 ? void 0 : _a.visitObject)) { - return (_b = this.overrides) === null || _b === void 0 ? void 0 : _b.visitObject(proxy); + return (_b = this.overrides) === null || _b === void 0 ? void 0 : _b.visitObject(access); } const result = []; - let entry; - while ((entry = proxy.nextEntry()) && !entry.done) { - result.push(entry.value); + for (const entry of access) { + result.push(entry); } return Object.fromEntries(result); } @@ -185,60 +78,10 @@ class ProxyVisitor { return (_b = this.overrides) === null || _b === void 0 ? void 0 : _b.visitIterable(access); } const result = []; - let element; - while ((element = access.nextElement())) { + for (const element of access) { result.push(element); } return result; } } -exports.ProxyVisitor = ProxyVisitor; -class ProxyDeserializer { - constructor(deserializer, options) { - Object.defineProperty(this, "deserializer", { - enumerable: true, - configurable: true, - writable: true, - value: void 0 - }); - Object.defineProperty(this, "options", { - enumerable: true, - configurable: true, - writable: true, - value: void 0 - }); - this.deserializer = deserializer; - this.options = options; - } - deserializeAny(visitor) { - return this.deserializer.deserializeAny(new ProxyVisitor(visitor, this.options)); - } - deserializeBoolean(visitor) { - return this.deserializer.deserializeBoolean(new ProxyVisitor(visitor, this.options)); - } - deserializeNumber(visitor) { - return this.deserializer.deserializeNumber(new ProxyVisitor(visitor, this.options)); - } - deserializeBigInt(visitor) { - return this.deserializer.deserializeBigInt(new ProxyVisitor(visitor, this.options)); - } - deserializeString(visitor) { - return this.deserializer.deserializeString(new ProxyVisitor(visitor, this.options)); - } - deserializeSymbol(visitor) { - return this.deserializer.deserializeSymbol(new ProxyVisitor(visitor, this.options)); - } - deserializeNull(visitor) { - return this.deserializer.deserializeNull(new ProxyVisitor(visitor, this.options)); - } - deserializeObject(visitor) { - return this.deserializer.deserializeObject(new ProxyVisitor(visitor, this.options)); - } - deserializeIterable(visitor) { - return this.deserializer.deserializeIterable(new ProxyVisitor(visitor, this.options)); - } - deserializeFunction(visitor) { - return this.deserializer.deserializeFunction(new ProxyVisitor(visitor, this.options)); - } -} -exports.ProxyDeserializer = ProxyDeserializer; +exports.Visitor = Visitor; diff --git a/dist/de/impl.js b/dist/de/impl.js index 2ce8d5b..c4d4d4c 100644 --- a/dist/de/impl.js +++ b/dist/de/impl.js @@ -2,14 +2,12 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.deserialize = deserialize; const registry_1 = require("../registry"); -const generic_1 = require("./generic"); function deserialize(deserializer, into, registry = registry_1.GlobalRegistry) { - var _a; const de = registry.deserializers.get(into); if (de == null) { throw new ReferenceError(`No deserializer for ${into.name}`); } else { - return de(new generic_1.ProxyDeserializer(deserializer, (_a = into === null || into === void 0 ? void 0 : into[Symbol.metadata]) === null || _a === void 0 ? void 0 : _a.serde)); + return de(deserializer); } } diff --git a/dist/de/interface.d.ts b/dist/de/interface.d.ts index b18158b..ea715c9 100644 --- a/dist/de/interface.d.ts +++ b/dist/de/interface.d.ts @@ -28,10 +28,13 @@ export declare abstract class MapAccess { export interface IIterableAccess { nextElement(): IteratorResult; sizeHint?(): Nullable; + [Symbol.iterator](): Iterator; } export declare abstract class IterableAccess implements IIterableAccess { abstract nextElement(): IteratorResult; + [Symbol.iterator](): Iterator; } +export declare function isVisitor(visitor: any): visitor is IVisitor; export interface IVisitor { visitBoolean(value: boolean): T; visitNumber(value: number): T; diff --git a/dist/de/interface.js b/dist/de/interface.js index 5ce1694..0b95303 100644 --- a/dist/de/interface.js +++ b/dist/de/interface.js @@ -1,6 +1,7 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.IterableAccess = exports.MapAccess = void 0; +exports.isVisitor = isVisitor; const utils_1 = require("../utils"); class MapAccess { nextEntrySeed(kseed, vseed) { @@ -25,7 +26,7 @@ class MapAccess { } *generate(next) { let item; - while ((item = next())) { + while ((item = next()) && !item.done) { yield item.value; } } @@ -50,5 +51,23 @@ class MapAccess { } exports.MapAccess = MapAccess; class IterableAccess { + *[Symbol.iterator]() { + return { + next: this.nextElement + }; + } } exports.IterableAccess = IterableAccess; +const VisitorMethods = Object.freeze([ + 'visitBoolean', + 'visitNumber', + 'visitBigInt', + 'visitString', + 'visitSymbol', + 'visitNull', + 'visitObject', + 'visitIterable' +]); +function isVisitor(visitor) { + return VisitorMethods.every(method => method in visitor); +} diff --git a/dist/index.d.ts b/dist/index.d.ts index b787498..001b449 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -1,8 +1,4 @@ -import '@tsmetadata/polyfill'; -export * as ser from './ser/impl'; -export * as de from './de/impl'; -export * from './decorator'; -export * from './option'; -export * from './case'; +export * as ser from './ser/index'; +export * as de from './de/index'; export * from './registry'; export * from './utils'; diff --git a/dist/index.js b/dist/index.js index 8927ae9..93de231 100644 --- a/dist/index.js +++ b/dist/index.js @@ -37,11 +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("./decorator"), exports); -__exportStar(require("./option"), exports); -__exportStar(require("./case"), exports); +exports.ser = __importStar(require("./ser/index")); +exports.de = __importStar(require("./de/index")); __exportStar(require("./registry"), exports); __exportStar(require("./utils"), exports); diff --git a/dist/option.d.ts b/dist/option.d.ts index 1f5a3fd..bf253c2 100644 --- a/dist/option.d.ts +++ b/dist/option.d.ts @@ -1,5 +1,5 @@ -import { Deserialize } from './de'; -import { Serialize } from './ser'; +import { IDeserializer } from './de'; +import { ISerializer } from './ser'; import { CaseConvention } from './case'; import { Nullable } from './utils'; export interface RenameOptions { @@ -25,14 +25,19 @@ export interface SkipOptions { serialize?: Predicate | boolean; deserialize?: Predicate | boolean; } +type CustomSerializer = >(value: U, serializer: S) => T; +type CustomDeserializer = (deserializer: D) => T; +export interface CustomSerdeOptions { + serialize?: CustomSerializer; + deserialize?: CustomDeserializer; +} export interface PropertyOptions { alias?: string | string[]; default?: () => any; flatten?: boolean; rename?: RenameOptions | string; skip?: SkipOptions | Predicate | boolean; - serializeWith?: Serialize; - deserializeWith?: Deserialize; + with?: CustomSerdeOptions; } export declare const Stage: Readonly<{ readonly Serialize: 0; @@ -67,5 +72,9 @@ export declare class SerdeOptions { shouldSkipSerialization(property: string, value: any): boolean; shouldSkipDeserialization(property: string, value: any): boolean; defaultFor(property: string): any; + hasCustomSerializer(property: string): boolean; + hasCustomDeserializer(property: string): boolean; + useCustomSerializer>(serializer: S, property: string, value: T): Nullable; + useCustomDeserializer(deserializer: D, property: string): Nullable; } export {}; diff --git a/dist/option.js b/dist/option.js index 2f6b048..587b3e7 100644 --- a/dist/option.js +++ b/dist/option.js @@ -205,5 +205,25 @@ class SerdeOptions { return this.container.default(); } } + hasCustomSerializer(property) { + var _a; + const options = this.properties.get(property); + return (0, utils_1.isFunction)((_a = options === null || options === void 0 ? void 0 : options.with) === null || _a === void 0 ? void 0 : _a.serialize); + } + hasCustomDeserializer(property) { + var _a; + const options = this.properties.get(property); + return (0, utils_1.isFunction)((_a = options === null || options === void 0 ? void 0 : options.with) === null || _a === void 0 ? void 0 : _a.deserialize); + } + useCustomSerializer(serializer, property, value) { + var _a, _b; + const options = this.properties.get(property); + return (_b = (_a = options === null || options === void 0 ? void 0 : options.with) === null || _a === void 0 ? void 0 : _a.serialize) === null || _b === void 0 ? void 0 : _b.call(_a, value, serializer); + } + useCustomDeserializer(deserializer, property) { + var _a, _b; + const options = this.properties.get(property); + return (_b = (_a = options === null || options === void 0 ? void 0 : options.with) === null || _a === void 0 ? void 0 : _a.deserialize) === null || _b === void 0 ? void 0 : _b.call(_a, deserializer); + } } exports.SerdeOptions = SerdeOptions; diff --git a/dist/ser/impl.d.ts b/dist/ser/impl.d.ts index 0ad864e..75539f5 100644 --- a/dist/ser/impl.d.ts +++ b/dist/ser/impl.d.ts @@ -1,4 +1,2 @@ import { ISerializer } from './interface'; -import { Nullable } from '../utils'; -import { SerdeOptions } from '../option'; -export declare function serialize>(serializer: S, value: V, optionsGetter?: (value: V) => Nullable): T; +export declare function serialize>(serializer: S, value: V): T; diff --git a/dist/ser/impl.js b/dist/ser/impl.js index 95e40ea..0c90628 100644 --- a/dist/ser/impl.js +++ b/dist/ser/impl.js @@ -7,26 +7,19 @@ class UnhandledTypeError extends TypeError { super(`unhandled type: '${typeof value}' for serializer ${serializer.constructor.name}`); } } -function serializeObject(serializer, obj, options) { +function serializeObject(serializer, obj) { for (const key in obj) { const value = obj[key]; - if (!(options === null || options === void 0 ? void 0 : options.shouldSkipSerialization(key, value))) { - const name = (options === null || options === void 0 ? void 0 : options.getSerializationPropertyName(key)) || key; - serializer.serializeEntry(name, value); - } + serializer.serializeEntry(key, value); } return serializer.end(); } -function serializeClass(serializer, value, options) { +function serializeClass(serializer, value) { const name = value.constructor.name; const ser = serializer.serializeClass(name); - return serializeObject(ser, value, options); + return serializeObject(ser, value); } -const defaultGetter = (value) => { - var _a; - return (_a = value.constructor[Symbol.metadata]) === null || _a === void 0 ? void 0 : _a.serde; -}; -function serialize(serializer, value, optionsGetter = defaultGetter) { +function serialize(serializer, value) { switch (typeof value) { case 'string': return serializer.serializeString(value); case 'number': return serializer.serializeNumber(value); @@ -35,11 +28,10 @@ function serialize(serializer, value, optionsGetter = defaultGetter) { 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 !(0, utils_1.isPlainObject)(value): return serializeClass(serializer, value, options); - default: return serializeObject(serializer.serializeObject(), value, options); + case !(0, utils_1.isPlainObject)(value): return serializeClass(serializer, value); + default: return serializeObject(serializer.serializeObject(), value); } default: throw new UnhandledTypeError(serializer, value); } diff --git a/src/de/generic.ts b/src/de/generic.ts index 6de8eb9..7981412 100644 --- a/src/de/generic.ts +++ b/src/de/generic.ts @@ -1,15 +1,14 @@ -import { SerdeOptions } from '../option' -import { isFunction, isString, IterResult, Nullable } from '../utils' -import { Deserialize, IDeserializer, IIterableAccess, IMapAccess, IVisitor } from './interface' +import { isFunction } from '../utils' +import { IDeserializer, IIterableAccess, IMapAccess, isVisitor, IVisitor } from './interface' export class GenericSeed { readonly visitor: IVisitor - constructor(visitor: IVisitor = new ProxyVisitor()) { + constructor(visitor: IVisitor = new Visitor()) { this.visitor = visitor } - static deserialize(deserializer: D, visitor: IVisitor = new ProxyVisitor()): T { + static deserialize(deserializer: D, visitor: IVisitor = new Visitor()): T { return deserializer.deserializeAny(visitor) } @@ -18,131 +17,19 @@ export class GenericSeed { } } -class ProxyMapAccess implements IMapAccess { - private access!: IMapAccess - private options?: SerdeOptions - - constructor(access: IMapAccess, options?: SerdeOptions) { - if (access instanceof ProxyMapAccess) { - return this - } - - this.access = access - this.options = options - } - - private wrapResponse = IteratorResult>(result: I): I { - switch (true) { - default: - case result.done: return result - - case isString(result.value): { - const key = this.options?.getDeserializationPropertyName(result.value) ?? result.value - return IterResult.Next(key) as I - } - case Array.isArray(result.value): { - const [alias, value] = result.value - const key = this.options?.getDeserializationPropertyName(alias) ?? alias - return IterResult.Next([ - key, - value - ]) as I - } - } - } - - shouldSkipEntry(entry: IteratorResult<[K, V]>) { - return this.options?.shouldSkipDeserialization(entry.value[0] as string, entry.value[1]) - } - - nextKeySeed>(seed: K): IteratorResult { - return this.wrapResponse(this.access.nextKeySeed(seed)) - } - - nextValueSeed>(seed: V): IteratorResult { - return this.access.nextValueSeed(seed) - } - - nextEntrySeed, V extends Deserialize>(kseed: K, vseed: V): IteratorResult<[TK, TV]> { - const response = this.wrapResponse<[TK, TV]>(this.access.nextEntrySeed(kseed, vseed)) - - if (!response.done && this.shouldSkipEntry(response)) { - return this.nextEntrySeed(kseed, vseed) - } else { - return response - } - } - - nextKey(): IteratorResult { - return this.wrapResponse(this.access.nextKey()) - } - - nextValue(): IteratorResult { - return this.access.nextValue() - } - - nextEntry(): IteratorResult<[K, V]> { - const response = this.wrapResponse<[K, V]>(this.access.nextEntry()) - - if (!response.done && this.shouldSkipEntry(response)) { - return this.nextEntry() - } else { - return response - } - } - - sizeHint?(): Nullable { - return this.access.sizeHint?.() - } - - private *generate(next: () => IteratorResult): Iterator { - let item: IteratorResult - - while ((item = next()) && !item.done) { - yield item.value - } - } - - keys>(seed?: K): Iterator { - return this.generate( - seed == null ? - this.nextKey.bind(this) : - this.nextKeySeed.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 - ) - } - - 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, kseed, vseed) as any - ) - } - - [Symbol.iterator](): Iterator<[K, V]> { - return this.entries() as Iterator<[K, V]> - } -} - -export class ProxyVisitor implements IVisitor { +export class Visitor implements IVisitor { private overrides?: Partial> - private options?: SerdeOptions - constructor(overrides?: Partial>, options?: SerdeOptions) { - if (overrides instanceof ProxyVisitor) { - return overrides + constructor(overrides?: Partial>) { + this.overrides = overrides + } + + static from(visitor: Partial>): IVisitor { + if (visitor instanceof Visitor || isVisitor(visitor)) { + return visitor } - this.overrides = overrides - this.options = options + return new this(visitor) } visitBoolean(value: boolean): T { @@ -170,17 +57,14 @@ export class ProxyVisitor implements IVisitor { } visitObject(access: IMapAccess): T { - const proxy = new ProxyMapAccess(access, this.options) - if (isFunction(this.overrides?.visitObject)) { - return this.overrides?.visitObject(proxy) + return this.overrides?.visitObject(access) } const result = [] - let entry - while ((entry = proxy.nextEntry()) && !entry.done) { - result.push(entry.value) + for (const entry of access) { + result.push(entry) } return Object.fromEntries(result) @@ -192,9 +76,8 @@ export class ProxyVisitor implements IVisitor { } const result = [] - let element - while ((element = access.nextElement())) { + for (const element of access) { result.push(element) } @@ -202,53 +85,3 @@ export class ProxyVisitor implements IVisitor { } } -export class ProxyDeserializer implements IDeserializer { - private readonly deserializer: IDeserializer - private readonly options?: SerdeOptions - - constructor(deserializer: IDeserializer, options?: SerdeOptions) { - this.deserializer = deserializer - this.options = options - } - - deserializeAny>(visitor: V): T { - return this.deserializer.deserializeAny(new ProxyVisitor(visitor, this.options)) - } - - deserializeBoolean>(visitor: V): T { - return this.deserializer.deserializeBoolean(new ProxyVisitor(visitor, this.options)) - } - - deserializeNumber>(visitor: V): T { - return this.deserializer.deserializeNumber(new ProxyVisitor(visitor, this.options)) - } - - deserializeBigInt>(visitor: V): T { - return this.deserializer.deserializeBigInt(new ProxyVisitor(visitor, this.options)) - } - - deserializeString>(visitor: V): T { - return this.deserializer.deserializeString(new ProxyVisitor(visitor, this.options)) - } - - deserializeSymbol>(visitor: V): T { - return this.deserializer.deserializeSymbol(new ProxyVisitor(visitor, this.options)) - } - - deserializeNull>(visitor: V): T { - return this.deserializer.deserializeNull(new ProxyVisitor(visitor, this.options)) - } - - deserializeObject>(visitor: V): T { - return this.deserializer.deserializeObject(new ProxyVisitor(visitor, this.options)) - } - - deserializeIterable>(visitor: V): T { - return this.deserializer.deserializeIterable(new ProxyVisitor(visitor, this.options)) - } - - deserializeFunction>(visitor: V): T { - return this.deserializer.deserializeFunction(new ProxyVisitor(visitor, this.options)) - } -} - diff --git a/src/de/impl.ts b/src/de/impl.ts index 287e1b1..fc65614 100644 --- a/src/de/impl.ts +++ b/src/de/impl.ts @@ -1,6 +1,5 @@ import { GlobalRegistry, Registry } from '../registry' import { Constructor } from '../utils' -import { ProxyDeserializer } from './generic' import { IDeserializer } from './interface' export function deserialize(deserializer: D, into: Constructor, registry: Registry = GlobalRegistry): T { @@ -9,7 +8,7 @@ export function deserialize(deserializer: D, into: C if (de == null) { throw new ReferenceError(`No deserializer for ${into.name}`) } else { - return de(new ProxyDeserializer(deserializer, (into as any)?.[Symbol.metadata]?.serde)) + return de(deserializer) } } diff --git a/src/de/interface.ts b/src/de/interface.ts index a574891..ef437ec 100644 --- a/src/de/interface.ts +++ b/src/de/interface.ts @@ -52,12 +52,11 @@ export abstract class MapAccess { private *generate(next: () => IteratorResult): Iterator { let item - while ((item = next())) { + while ((item = next()) && !item.done) { yield item.value } } - keys>(seed?: K): Iterator { return this.generate( seed == null ? @@ -90,12 +89,33 @@ export abstract class MapAccess { export interface IIterableAccess { nextElement(): IteratorResult sizeHint?(): Nullable + [Symbol.iterator](): Iterator } export abstract class IterableAccess implements IIterableAccess { abstract nextElement(): IteratorResult + + *[Symbol.iterator](): Iterator { + return { + next: this.nextElement + } + } } +const VisitorMethods = Object.freeze([ + 'visitBoolean', + 'visitNumber', + 'visitBigInt', + 'visitString', + 'visitSymbol', + 'visitNull', + 'visitObject', + 'visitIterable' +] as const) + +export function isVisitor(visitor: any): visitor is IVisitor { + return VisitorMethods.every(method => method in visitor) +} export interface IVisitor { visitBoolean(value: boolean): T visitNumber(value: number): T diff --git a/src/decorator.ts b/src/decorator.ts deleted file mode 100644 index 23b858d..0000000 --- a/src/decorator.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { ContainerOptions, PropertyOptions, SerdeOptions } from './option' -import { Nullable } from './utils' - -const ContextKind = Object.freeze({ - Class: 'class', - Method: 'method', - Getter: 'getter', - Setter: 'setter', - Field: 'field', - Accessor: 'accessor' -} as const) - -function decorateClass(target: Function, context: DecoratorContext, options: ContainerOptions) { - const serde = context.metadata.serde as Nullable - - context.metadata.serde = new SerdeOptions( - target, - options, - serde?.properties - ) -} - -function decorateField(context: DecoratorContext, options: PropertyOptions) { - const serde = (context.metadata.serde as SerdeOptions) || new SerdeOptions(undefined) - serde.properties.set(context.name as string, options) - context.metadata.serde = serde -} - -export function serde(options: ContainerOptions | PropertyOptions) { - return function(target: any, context: DecoratorContext) { - switch (context.kind) { - case ContextKind.Class: - decorateClass(target, context, options) - break - - case ContextKind.Field: - decorateField(context, options) - - default: - break - } - } -} - diff --git a/src/index.ts b/src/index.ts index 6b0679e..aa46d94 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,5 @@ -import '@tsmetadata/polyfill' - -export * as ser from './ser/impl' -export * as de from './de/impl' -export * from './decorator' -export * from './option' -export * from './case' +export * as ser from './ser/index' +export * as de from './de/index' export * from './registry' export * from './utils' diff --git a/src/option.ts b/src/option.ts deleted file mode 100644 index 08eba05..0000000 --- a/src/option.ts +++ /dev/null @@ -1,219 +0,0 @@ -import { Deserialize } from './de' -import { Serialize } from './ser' -import { CaseConvention, convertCase } from './case' -import { isFunction, isIterable, isNumber, isString, Nullable } from './utils' - -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 class PropertyMap extends Map { - private readonly options: SerdeOptions - private readonly aliases: Map = new Map() - - constructor(options: SerdeOptions, iterable?: Iterable) { - super(iterable) - this.options = options - this.buildAliasMap() - } - - private setAliasesFromOptions(key: string, options: PropertyOptions) { - this.aliases.set(key, key) - - if (options.alias != null) { - this.addAlias(options.alias, key) - } - - if (isString(options.rename)) { - this.aliases.set(options.rename, key) - } else if (isNumber(this.options.container.renameAll)) { - this.aliases.set(this.options.applyCaseConvention(Stage.Deserialize, key), key) - } - } - - set(key: string, value: PropertyOptions): this { - super.set(key, value) - this.setAliasesFromOptions(key, value) - return this - } - - addAlias(alias: string | Iterable, key: string) { - if (isString(alias)) { - this.aliases.set(alias, key) - } else if (isIterable(alias)) { - for (const a of alias) { - this.addAlias(a, key) - } - } - } - - getFieldFromAlias(alias: string) { - return this.aliases.get(alias) - } - - private buildAliasMap() { - for (const [key, value] of this.entries()) { - if (value.alias != null) { - this.addAlias(value.alias, key) - } - } - } -} - -export type Stage = typeof Stage[keyof typeof Stage] - -export class SerdeOptions { - readonly target: Nullable - readonly container: ContainerOptions - readonly properties: PropertyMap - - constructor(target: any, container: ContainerOptions = {}, properties: PropertyMap = new PropertyMap(this)) { - 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) - } - - getCaseConvention(stage: Stage) { - if (isNumber(this.container.renameAll)) { - if (stage === Stage.Serialize) { - return this.container.renameAll - } else { - return CaseConvention.CamelCase - } - } else if (stage === Stage.Serialize && isNumber(this.container.renameAll?.serialize)) { - return this.container.renameAll.serialize - } else if (stage === Stage.Deserialize && isNumber(this.container.renameAll?.deserialize)) { - return this.container.renameAll.deserialize - } - } - - applyCaseConvention(stage: Stage, property: string) { - const convention = this.getCaseConvention(stage) - return convention != null ? convertCase(property, convention) : property - } - - getPropertyRename(stage: Stage, property: string) { - const options = this.properties.get(property) - - if (options == null) { - return - } 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 - } - } - - getSerializationPropertyName(property: string) { - return this.getPropertyRename(Stage.Serialize, property) || - this.applyCaseConvention(Stage.Serialize, property) - } - - getNameFromAlias(alias: string) { - return this.properties.getFieldFromAlias(alias) - } - - getDeserializationPropertyName(property: string) { - return this.getNameFromAlias(property) || - this.applyCaseConvention(Stage.Deserialize, property) - } - - 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 - } - } - - shouldSkipSerialization(property: string, value: any) { - return this.shouldSkip(Stage.Serialize, property, value) - } - - shouldSkipDeserialization(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 cf2a002..400e5db 100644 --- a/src/ser/impl.ts +++ b/src/ser/impl.ts @@ -1,6 +1,5 @@ import { ISerializeObject, ISerializer } from './interface' -import { isPlainObject, Nullable } from '../utils' -import { SerdeOptions } from '../option' +import { isPlainObject } from '../utils' class UnhandledTypeError extends TypeError { constructor(serializer: ISerializer, value: any) { @@ -8,29 +7,22 @@ class UnhandledTypeError extends TypeError { } } -function serializeObject>(serializer: S, obj: V, options?: SerdeOptions): T { +function serializeObject>(serializer: S, obj: V): T { for (const key in obj) { const value = obj[key] - if (!options?.shouldSkipSerialization(key, value)) { - const name = options?.getSerializationPropertyName(key) || key - serializer.serializeEntry(name, value) - } + serializer.serializeEntry(key, value) } return serializer.end() } -function serializeClass>(serializer: S, value: V, options?: SerdeOptions): T { +function serializeClass>(serializer: S, value: V): T { const name = value.constructor.name const ser = serializer.serializeClass(name) - return serializeObject(ser, value, options) + return serializeObject(ser, value) } -const defaultGetter = (value: any): Nullable => { - return value.constructor[Symbol.metadata]?.serde -} - -export function serialize>(serializer: S, value: V, optionsGetter: (value: V) => Nullable = defaultGetter): T { +export function serialize>(serializer: S, value: V): T { switch (typeof value) { case 'string': return serializer.serializeString(value) case 'number': return serializer.serializeNumber(value) @@ -39,11 +31,10 @@ export function serialize>(serializer: S, 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, options) - default: return serializeObject(serializer.serializeObject(), value, options) + case !isPlainObject(value): return serializeClass(serializer, value) + default: return serializeObject(serializer.serializeObject(), value) } default: throw new UnhandledTypeError(serializer, value) }