you know what fuck all the options

This commit is contained in:
Rowan 2025-05-24 16:21:05 -05:00
parent ac02e19606
commit 62e9563b8a
18 changed files with 139 additions and 706 deletions

22
dist/de/generic.d.ts vendored
View file

@ -1,4 +1,3 @@
import { SerdeOptions } from '../option';
import { IDeserializer, IIterableAccess, IMapAccess, IVisitor } from './interface'; import { IDeserializer, IIterableAccess, IMapAccess, IVisitor } from './interface';
export declare class GenericSeed<T> { export declare class GenericSeed<T> {
readonly visitor: IVisitor<T>; readonly visitor: IVisitor<T>;
@ -6,10 +5,10 @@ export declare class GenericSeed<T> {
static deserialize<T, D extends IDeserializer>(deserializer: D, visitor?: IVisitor<T>): T; static deserialize<T, D extends IDeserializer>(deserializer: D, visitor?: IVisitor<T>): T;
deserialize<D extends IDeserializer>(deserializer: D): T; deserialize<D extends IDeserializer>(deserializer: D): T;
} }
export declare class ProxyVisitor<T> implements IVisitor<T> { export declare class Visitor<T> implements IVisitor<T> {
private overrides?; private overrides?;
private options?; constructor(overrides?: Partial<IVisitor<T>>);
constructor(overrides?: Partial<IVisitor<T>>, options?: SerdeOptions); static from<T>(visitor: Partial<IVisitor<T>>): IVisitor<T>;
visitBoolean(value: boolean): T; visitBoolean(value: boolean): T;
visitNumber(value: number): T; visitNumber(value: number): T;
visitBigInt(value: bigint): T; visitBigInt(value: bigint): T;
@ -19,18 +18,3 @@ export declare class ProxyVisitor<T> implements IVisitor<T> {
visitObject(access: IMapAccess): T; visitObject(access: IMapAccess): T;
visitIterable(access: IIterableAccess): T; visitIterable(access: IIterableAccess): T;
} }
export declare class ProxyDeserializer implements IDeserializer {
private readonly deserializer;
private readonly options?;
constructor(deserializer: IDeserializer, options?: SerdeOptions);
deserializeAny<T, V extends IVisitor<T>>(visitor: V): T;
deserializeBoolean<T, V extends IVisitor<T>>(visitor: V): T;
deserializeNumber<T, V extends IVisitor<T>>(visitor: V): T;
deserializeBigInt<T, V extends IVisitor<T>>(visitor: V): T;
deserializeString<T, V extends IVisitor<T>>(visitor: V): T;
deserializeSymbol<T, V extends IVisitor<T>>(visitor: V): T;
deserializeNull<T, V extends IVisitor<T>>(visitor: V): T;
deserializeObject<T, V extends IVisitor<T>>(visitor: V): T;
deserializeIterable<T, V extends IVisitor<T>>(visitor: V): T;
deserializeFunction<T, V extends IVisitor<T>>(visitor: V): T;
}

191
dist/de/generic.js vendored
View file

@ -1,9 +1,10 @@
"use strict"; "use strict";
Object.defineProperty(exports, "__esModule", { value: true }); 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 utils_1 = require("../utils");
const interface_1 = require("./interface");
class GenericSeed { class GenericSeed {
constructor(visitor = new ProxyVisitor()) { constructor(visitor = new Visitor()) {
Object.defineProperty(this, "visitor", { Object.defineProperty(this, "visitor", {
enumerable: true, enumerable: true,
configurable: true, configurable: true,
@ -12,7 +13,7 @@ class GenericSeed {
}); });
this.visitor = visitor; this.visitor = visitor;
} }
static deserialize(deserializer, visitor = new ProxyVisitor()) { static deserialize(deserializer, visitor = new Visitor()) {
return deserializer.deserializeAny(visitor); return deserializer.deserializeAny(visitor);
} }
deserialize(deserializer) { deserialize(deserializer) {
@ -20,127 +21,21 @@ class GenericSeed {
} }
} }
exports.GenericSeed = GenericSeed; exports.GenericSeed = GenericSeed;
class ProxyMapAccess { class Visitor {
constructor(access, options) { constructor(overrides) {
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) {
Object.defineProperty(this, "overrides", { Object.defineProperty(this, "overrides", {
enumerable: true, enumerable: true,
configurable: true, configurable: true,
writable: true, writable: true,
value: void 0 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.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) { visitBoolean(value) {
var _a, _b, _c; var _a, _b, _c;
@ -168,14 +63,12 @@ class ProxyVisitor {
} }
visitObject(access) { visitObject(access) {
var _a, _b; 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)) { 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 = []; const result = [];
let entry; for (const entry of access) {
while ((entry = proxy.nextEntry()) && !entry.done) { result.push(entry);
result.push(entry.value);
} }
return Object.fromEntries(result); return Object.fromEntries(result);
} }
@ -185,60 +78,10 @@ class ProxyVisitor {
return (_b = this.overrides) === null || _b === void 0 ? void 0 : _b.visitIterable(access); return (_b = this.overrides) === null || _b === void 0 ? void 0 : _b.visitIterable(access);
} }
const result = []; const result = [];
let element; for (const element of access) {
while ((element = access.nextElement())) {
result.push(element); result.push(element);
} }
return result; return result;
} }
} }
exports.ProxyVisitor = ProxyVisitor; exports.Visitor = Visitor;
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;

4
dist/de/impl.js vendored
View file

@ -2,14 +2,12 @@
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
exports.deserialize = deserialize; exports.deserialize = deserialize;
const registry_1 = require("../registry"); const registry_1 = require("../registry");
const generic_1 = require("./generic");
function deserialize(deserializer, into, registry = registry_1.GlobalRegistry) { function deserialize(deserializer, into, registry = registry_1.GlobalRegistry) {
var _a;
const de = registry.deserializers.get(into); const de = registry.deserializers.get(into);
if (de == null) { if (de == null) {
throw new ReferenceError(`No deserializer for ${into.name}`); throw new ReferenceError(`No deserializer for ${into.name}`);
} }
else { 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);
} }
} }

View file

@ -28,10 +28,13 @@ export declare abstract class MapAccess {
export interface IIterableAccess { export interface IIterableAccess {
nextElement<T>(): IteratorResult<T>; nextElement<T>(): IteratorResult<T>;
sizeHint?(): Nullable<number>; sizeHint?(): Nullable<number>;
[Symbol.iterator]<T>(): Iterator<T>;
} }
export declare abstract class IterableAccess implements IIterableAccess { export declare abstract class IterableAccess implements IIterableAccess {
abstract nextElement<T>(): IteratorResult<T>; abstract nextElement<T>(): IteratorResult<T>;
[Symbol.iterator]<T>(): Iterator<T>;
} }
export declare function isVisitor<T>(visitor: any): visitor is IVisitor<T>;
export interface IVisitor<T> { export interface IVisitor<T> {
visitBoolean(value: boolean): T; visitBoolean(value: boolean): T;
visitNumber(value: number): T; visitNumber(value: number): T;

21
dist/de/interface.js vendored
View file

@ -1,6 +1,7 @@
"use strict"; "use strict";
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
exports.IterableAccess = exports.MapAccess = void 0; exports.IterableAccess = exports.MapAccess = void 0;
exports.isVisitor = isVisitor;
const utils_1 = require("../utils"); const utils_1 = require("../utils");
class MapAccess { class MapAccess {
nextEntrySeed(kseed, vseed) { nextEntrySeed(kseed, vseed) {
@ -25,7 +26,7 @@ class MapAccess {
} }
*generate(next) { *generate(next) {
let item; let item;
while ((item = next())) { while ((item = next()) && !item.done) {
yield item.value; yield item.value;
} }
} }
@ -50,5 +51,23 @@ class MapAccess {
} }
exports.MapAccess = MapAccess; exports.MapAccess = MapAccess;
class IterableAccess { class IterableAccess {
*[Symbol.iterator]() {
return {
next: this.nextElement
};
}
} }
exports.IterableAccess = IterableAccess; 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);
}

8
dist/index.d.ts vendored
View file

@ -1,8 +1,4 @@
import '@tsmetadata/polyfill'; export * as ser from './ser/index';
export * as ser from './ser/impl'; export * as de from './de/index';
export * as de from './de/impl';
export * from './decorator';
export * from './option';
export * from './case';
export * from './registry'; export * from './registry';
export * from './utils'; export * from './utils';

8
dist/index.js vendored
View file

@ -37,11 +37,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
}; };
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
exports.de = exports.ser = void 0; exports.de = exports.ser = void 0;
require("@tsmetadata/polyfill"); exports.ser = __importStar(require("./ser/index"));
exports.ser = __importStar(require("./ser/impl")); exports.de = __importStar(require("./de/index"));
exports.de = __importStar(require("./de/impl"));
__exportStar(require("./decorator"), exports);
__exportStar(require("./option"), exports);
__exportStar(require("./case"), exports);
__exportStar(require("./registry"), exports); __exportStar(require("./registry"), exports);
__exportStar(require("./utils"), exports); __exportStar(require("./utils"), exports);

17
dist/option.d.ts vendored
View file

@ -1,5 +1,5 @@
import { Deserialize } from './de'; import { IDeserializer } from './de';
import { Serialize } from './ser'; import { ISerializer } from './ser';
import { CaseConvention } from './case'; import { CaseConvention } from './case';
import { Nullable } from './utils'; import { Nullable } from './utils';
export interface RenameOptions { export interface RenameOptions {
@ -25,14 +25,19 @@ export interface SkipOptions {
serialize?: Predicate | boolean; serialize?: Predicate | boolean;
deserialize?: Predicate | boolean; deserialize?: Predicate | boolean;
} }
type CustomSerializer = <T, U, S extends ISerializer<T>>(value: U, serializer: S) => T;
type CustomDeserializer = <T, D extends IDeserializer>(deserializer: D) => T;
export interface CustomSerdeOptions {
serialize?: CustomSerializer;
deserialize?: CustomDeserializer;
}
export interface PropertyOptions { export interface PropertyOptions {
alias?: string | string[]; alias?: string | string[];
default?: () => any; default?: () => any;
flatten?: boolean; flatten?: boolean;
rename?: RenameOptions | string; rename?: RenameOptions | string;
skip?: SkipOptions | Predicate | boolean; skip?: SkipOptions | Predicate | boolean;
serializeWith?: Serialize<any>; with?: CustomSerdeOptions;
deserializeWith?: Deserialize<any>;
} }
export declare const Stage: Readonly<{ export declare const Stage: Readonly<{
readonly Serialize: 0; readonly Serialize: 0;
@ -67,5 +72,9 @@ export declare class SerdeOptions {
shouldSkipSerialization(property: string, value: any): boolean; shouldSkipSerialization(property: string, value: any): boolean;
shouldSkipDeserialization(property: string, value: any): boolean; shouldSkipDeserialization(property: string, value: any): boolean;
defaultFor(property: string): any; defaultFor(property: string): any;
hasCustomSerializer(property: string): boolean;
hasCustomDeserializer(property: string): boolean;
useCustomSerializer<T, U, S extends ISerializer<U>>(serializer: S, property: string, value: T): Nullable<U>;
useCustomDeserializer<T, U, D extends IDeserializer>(deserializer: D, property: string): Nullable<U>;
} }
export {}; export {};

20
dist/option.js vendored
View file

@ -205,5 +205,25 @@ class SerdeOptions {
return this.container.default(); 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; exports.SerdeOptions = SerdeOptions;

4
dist/ser/impl.d.ts vendored
View file

@ -1,4 +1,2 @@
import { ISerializer } from './interface'; import { ISerializer } from './interface';
import { Nullable } from '../utils'; export declare function serialize<T, V, S extends ISerializer<T>>(serializer: S, value: V): T;
import { SerdeOptions } from '../option';
export declare function serialize<T, V, S extends ISerializer<T>>(serializer: S, value: V, optionsGetter?: (value: V) => Nullable<SerdeOptions>): T;

22
dist/ser/impl.js vendored
View file

@ -7,26 +7,19 @@ class UnhandledTypeError extends TypeError {
super(`unhandled type: '${typeof value}' for serializer ${serializer.constructor.name}`); super(`unhandled type: '${typeof value}' for serializer ${serializer.constructor.name}`);
} }
} }
function serializeObject(serializer, obj, options) { function serializeObject(serializer, obj) {
for (const key in obj) { for (const key in obj) {
const value = obj[key]; const value = obj[key];
if (!(options === null || options === void 0 ? void 0 : options.shouldSkipSerialization(key, value))) { serializer.serializeEntry(key, value);
const name = (options === null || options === void 0 ? void 0 : options.getSerializationPropertyName(key)) || key;
serializer.serializeEntry(name, value);
}
} }
return serializer.end(); return serializer.end();
} }
function serializeClass(serializer, value, options) { function serializeClass(serializer, value) {
const name = value.constructor.name; const name = value.constructor.name;
const ser = serializer.serializeClass(name); const ser = serializer.serializeClass(name);
return serializeObject(ser, value, options); return serializeObject(ser, value);
} }
const defaultGetter = (value) => { function serialize(serializer, value) {
var _a;
return (_a = value.constructor[Symbol.metadata]) === null || _a === void 0 ? void 0 : _a.serde;
};
function serialize(serializer, value, optionsGetter = defaultGetter) {
switch (typeof value) { switch (typeof value) {
case 'string': return serializer.serializeString(value); case 'string': return serializer.serializeString(value);
case 'number': return serializer.serializeNumber(value); case 'number': return serializer.serializeNumber(value);
@ -35,11 +28,10 @@ function serialize(serializer, value, optionsGetter = defaultGetter) {
case 'symbol': return serializer.serializeSymbol(value); case 'symbol': return serializer.serializeSymbol(value);
case 'undefined': return serializer.serializeNull(); case 'undefined': return serializer.serializeNull();
case 'object': case 'object':
const options = optionsGetter(value);
switch (true) { switch (true) {
case value == null: return serializer.serializeNull(); case value == null: return serializer.serializeNull();
case !(0, utils_1.isPlainObject)(value): return serializeClass(serializer, value, options); case !(0, utils_1.isPlainObject)(value): return serializeClass(serializer, value);
default: return serializeObject(serializer.serializeObject(), value, options); default: return serializeObject(serializer.serializeObject(), value);
} }
default: throw new UnhandledTypeError(serializer, value); default: throw new UnhandledTypeError(serializer, value);
} }

View file

@ -1,15 +1,14 @@
import { SerdeOptions } from '../option' import { isFunction } from '../utils'
import { isFunction, isString, IterResult, Nullable } from '../utils' import { IDeserializer, IIterableAccess, IMapAccess, isVisitor, IVisitor } from './interface'
import { Deserialize, IDeserializer, IIterableAccess, IMapAccess, IVisitor } from './interface'
export class GenericSeed<T> { export class GenericSeed<T> {
readonly visitor: IVisitor<T> readonly visitor: IVisitor<T>
constructor(visitor: IVisitor<T> = new ProxyVisitor()) { constructor(visitor: IVisitor<T> = new Visitor()) {
this.visitor = visitor this.visitor = visitor
} }
static deserialize<T, D extends IDeserializer>(deserializer: D, visitor: IVisitor<T> = new ProxyVisitor()): T { static deserialize<T, D extends IDeserializer>(deserializer: D, visitor: IVisitor<T> = new Visitor()): T {
return deserializer.deserializeAny(visitor) return deserializer.deserializeAny(visitor)
} }
@ -18,131 +17,19 @@ export class GenericSeed<T> {
} }
} }
class ProxyMapAccess implements IMapAccess { export class Visitor<T> implements IVisitor<T> {
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<T, I extends IteratorResult<T> = IteratorResult<T>>(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<K, V>(entry: IteratorResult<[K, V]>) {
return this.options?.shouldSkipDeserialization(entry.value[0] as string, entry.value[1])
}
nextKeySeed<T, K extends Deserialize<T>>(seed: K): IteratorResult<T> {
return this.wrapResponse<T>(this.access.nextKeySeed(seed))
}
nextValueSeed<T, V extends Deserialize<T>>(seed: V): IteratorResult<T> {
return this.access.nextValueSeed(seed)
}
nextEntrySeed<TK, TV, K extends Deserialize<TK>, V extends Deserialize<TV>>(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<T>(): IteratorResult<T> {
return this.wrapResponse<T>(this.access.nextKey())
}
nextValue<V>(): IteratorResult<V> {
return this.access.nextValue()
}
nextEntry<K, V>(): 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<number> {
return this.access.sizeHint?.()
}
private *generate<T>(next: () => IteratorResult<T>): Iterator<T> {
let item: IteratorResult<T>
while ((item = next()) && !item.done) {
yield item.value
}
}
keys<T, K extends Deserialize<T>>(seed?: K): Iterator<T> {
return this.generate(
seed == null ?
this.nextKey.bind(this) :
this.nextKeySeed.bind(this, seed) as any
)
}
values<T, V extends Deserialize<T>>(seed?: V): Iterator<T> {
return this.generate(
seed == null ?
this.nextValue.bind(this) :
this.nextValueSeed.bind(this, seed) as any
)
}
entries<TK, TV, K extends Deserialize<TK>, V extends Deserialize<TV>>(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]<K, V>(): Iterator<[K, V]> {
return this.entries() as Iterator<[K, V]>
}
}
export class ProxyVisitor<T> implements IVisitor<T> {
private overrides?: Partial<IVisitor<T>> private overrides?: Partial<IVisitor<T>>
private options?: SerdeOptions
constructor(overrides?: Partial<IVisitor<T>>, options?: SerdeOptions) { constructor(overrides?: Partial<IVisitor<T>>) {
if (overrides instanceof ProxyVisitor) { this.overrides = overrides
return overrides }
static from<T>(visitor: Partial<IVisitor<T>>): IVisitor<T> {
if (visitor instanceof Visitor || isVisitor(visitor)) {
return visitor
} }
this.overrides = overrides return new this(visitor)
this.options = options
} }
visitBoolean(value: boolean): T { visitBoolean(value: boolean): T {
@ -170,17 +57,14 @@ export class ProxyVisitor<T> implements IVisitor<T> {
} }
visitObject(access: IMapAccess): T { visitObject(access: IMapAccess): T {
const proxy = new ProxyMapAccess(access, this.options)
if (isFunction(this.overrides?.visitObject)) { if (isFunction(this.overrides?.visitObject)) {
return this.overrides?.visitObject(proxy) return this.overrides?.visitObject(access)
} }
const result = [] const result = []
let entry
while ((entry = proxy.nextEntry()) && !entry.done) { for (const entry of access) {
result.push(entry.value) result.push(entry)
} }
return Object.fromEntries(result) return Object.fromEntries(result)
@ -192,9 +76,8 @@ export class ProxyVisitor<T> implements IVisitor<T> {
} }
const result = [] const result = []
let element
while ((element = access.nextElement())) { for (const element of access) {
result.push(element) result.push(element)
} }
@ -202,53 +85,3 @@ export class ProxyVisitor<T> implements IVisitor<T> {
} }
} }
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<T, V extends IVisitor<T>>(visitor: V): T {
return this.deserializer.deserializeAny(new ProxyVisitor(visitor, this.options))
}
deserializeBoolean<T, V extends IVisitor<T>>(visitor: V): T {
return this.deserializer.deserializeBoolean(new ProxyVisitor(visitor, this.options))
}
deserializeNumber<T, V extends IVisitor<T>>(visitor: V): T {
return this.deserializer.deserializeNumber(new ProxyVisitor(visitor, this.options))
}
deserializeBigInt<T, V extends IVisitor<T>>(visitor: V): T {
return this.deserializer.deserializeBigInt(new ProxyVisitor(visitor, this.options))
}
deserializeString<T, V extends IVisitor<T>>(visitor: V): T {
return this.deserializer.deserializeString(new ProxyVisitor(visitor, this.options))
}
deserializeSymbol<T, V extends IVisitor<T>>(visitor: V): T {
return this.deserializer.deserializeSymbol(new ProxyVisitor(visitor, this.options))
}
deserializeNull<T, V extends IVisitor<T>>(visitor: V): T {
return this.deserializer.deserializeNull(new ProxyVisitor(visitor, this.options))
}
deserializeObject<T, V extends IVisitor<T>>(visitor: V): T {
return this.deserializer.deserializeObject(new ProxyVisitor(visitor, this.options))
}
deserializeIterable<T, V extends IVisitor<T>>(visitor: V): T {
return this.deserializer.deserializeIterable(new ProxyVisitor(visitor, this.options))
}
deserializeFunction<T, V extends IVisitor<T>>(visitor: V): T {
return this.deserializer.deserializeFunction(new ProxyVisitor(visitor, this.options))
}
}

View file

@ -1,6 +1,5 @@
import { GlobalRegistry, Registry } from '../registry' import { GlobalRegistry, Registry } from '../registry'
import { Constructor } from '../utils' import { Constructor } from '../utils'
import { ProxyDeserializer } from './generic'
import { IDeserializer } from './interface' import { IDeserializer } from './interface'
export function deserialize<T, D extends IDeserializer>(deserializer: D, into: Constructor<T>, registry: Registry = GlobalRegistry): T { export function deserialize<T, D extends IDeserializer>(deserializer: D, into: Constructor<T>, registry: Registry = GlobalRegistry): T {
@ -9,7 +8,7 @@ export function deserialize<T, D extends IDeserializer>(deserializer: D, into: C
if (de == null) { if (de == null) {
throw new ReferenceError(`No deserializer for ${into.name}`) throw new ReferenceError(`No deserializer for ${into.name}`)
} else { } else {
return de(new ProxyDeserializer(deserializer, (into as any)?.[Symbol.metadata]?.serde)) return de(deserializer)
} }
} }

View file

@ -52,12 +52,11 @@ export abstract class MapAccess {
private *generate<T>(next: () => IteratorResult<T>): Iterator<T> { private *generate<T>(next: () => IteratorResult<T>): Iterator<T> {
let item let item
while ((item = next())) { while ((item = next()) && !item.done) {
yield item.value yield item.value
} }
} }
keys<T, K extends Deserialize<T>>(seed?: K): Iterator<T> { keys<T, K extends Deserialize<T>>(seed?: K): Iterator<T> {
return this.generate( return this.generate(
seed == null ? seed == null ?
@ -90,12 +89,33 @@ export abstract class MapAccess {
export interface IIterableAccess { export interface IIterableAccess {
nextElement<T>(): IteratorResult<T> nextElement<T>(): IteratorResult<T>
sizeHint?(): Nullable<number> sizeHint?(): Nullable<number>
[Symbol.iterator]<T>(): Iterator<T>
} }
export abstract class IterableAccess implements IIterableAccess { export abstract class IterableAccess implements IIterableAccess {
abstract nextElement<T>(): IteratorResult<T> abstract nextElement<T>(): IteratorResult<T>
*[Symbol.iterator]<T>(): Iterator<T> {
return {
next: this.nextElement
}
}
} }
const VisitorMethods = Object.freeze([
'visitBoolean',
'visitNumber',
'visitBigInt',
'visitString',
'visitSymbol',
'visitNull',
'visitObject',
'visitIterable'
] as const)
export function isVisitor<T>(visitor: any): visitor is IVisitor<T> {
return VisitorMethods.every(method => method in visitor)
}
export interface IVisitor<T> { export interface IVisitor<T> {
visitBoolean(value: boolean): T visitBoolean(value: boolean): T
visitNumber(value: number): T visitNumber(value: number): T

View file

@ -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<SerdeOptions>
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
}
}
}

View file

@ -1,10 +1,5 @@
import '@tsmetadata/polyfill' export * as ser from './ser/index'
export * as de from './de/index'
export * as ser from './ser/impl'
export * as de from './de/impl'
export * from './decorator'
export * from './option'
export * from './case'
export * from './registry' export * from './registry'
export * from './utils' export * from './utils'

View file

@ -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 {
<T>(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<any>
deserializeWith?: Deserialize<any>
}
export const Stage = Object.freeze({
Serialize: 0,
Deserialize: 1
} as const)
export class PropertyMap extends Map<string, PropertyOptions> {
private readonly options: SerdeOptions
private readonly aliases: Map<string, string> = new Map()
constructor(options: SerdeOptions, iterable?: Iterable<any>) {
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<string>, 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<any>
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()
}
}
}

View file

@ -1,6 +1,5 @@
import { ISerializeObject, ISerializer } from './interface' import { ISerializeObject, ISerializer } from './interface'
import { isPlainObject, Nullable } from '../utils' import { isPlainObject } from '../utils'
import { SerdeOptions } from '../option'
class UnhandledTypeError extends TypeError { class UnhandledTypeError extends TypeError {
constructor(serializer: ISerializer<unknown>, value: any) { constructor(serializer: ISerializer<unknown>, value: any) {
@ -8,29 +7,22 @@ class UnhandledTypeError extends TypeError {
} }
} }
function serializeObject<T, V extends object, S extends ISerializeObject<T>>(serializer: S, obj: V, options?: SerdeOptions): T { function serializeObject<T, V extends object, S extends ISerializeObject<T>>(serializer: S, obj: V): T {
for (const key in obj) { for (const key in obj) {
const value = obj[key] const value = obj[key]
if (!options?.shouldSkipSerialization(key, value)) { serializer.serializeEntry(key, value)
const name = options?.getSerializationPropertyName(key) || key
serializer.serializeEntry(name, value)
}
} }
return serializer.end() return serializer.end()
} }
function serializeClass<T, V extends object, S extends ISerializer<T>>(serializer: S, value: V, options?: SerdeOptions): T { function serializeClass<T, V extends object, S extends ISerializer<T>>(serializer: S, value: V): T {
const name = value.constructor.name const name = value.constructor.name
const ser = serializer.serializeClass(name) const ser = serializer.serializeClass(name)
return serializeObject(ser, value, options) return serializeObject(ser, value)
} }
const defaultGetter = (value: any): Nullable<SerdeOptions> => { export function serialize<T, V, S extends ISerializer<T>>(serializer: S, value: V): T {
return value.constructor[Symbol.metadata]?.serde
}
export function serialize<T, V, S extends ISerializer<T>>(serializer: S, value: V, optionsGetter: (value: V) => Nullable<SerdeOptions> = defaultGetter): T {
switch (typeof value) { switch (typeof value) {
case 'string': return serializer.serializeString(value) case 'string': return serializer.serializeString(value)
case 'number': return serializer.serializeNumber(value) case 'number': return serializer.serializeNumber(value)
@ -39,11 +31,10 @@ export function serialize<T, V, S extends ISerializer<T>>(serializer: S, value:
case 'symbol': return serializer.serializeSymbol(value) case 'symbol': return serializer.serializeSymbol(value)
case 'undefined': return serializer.serializeNull() case 'undefined': return serializer.serializeNull()
case 'object': case 'object':
const options = optionsGetter(value)
switch (true) { switch (true) {
case value == null: return serializer.serializeNull() case value == null: return serializer.serializeNull()
case !isPlainObject(value): return serializeClass(serializer, value, options) case !isPlainObject(value): return serializeClass(serializer, value)
default: return serializeObject(serializer.serializeObject(), value, options) default: return serializeObject(serializer.serializeObject(), value)
} }
default: throw new UnhandledTypeError(serializer, value) default: throw new UnhandledTypeError(serializer, value)
} }