add rename options for ser/de

This commit is contained in:
Rowan 2025-05-23 21:39:46 -05:00
parent da1fad1cd0
commit b4c7a9ba2b
28 changed files with 981 additions and 124 deletions

View file

@ -1,13 +1,13 @@
import { Deserialize, IDeserializer, IterableAccess, IVisitor, MapAccess } from './interface';
export declare class ForwardMapAccess extends MapAccess {
private readonly keys;
private readonly values;
private readonly _keys;
private readonly _values;
private kindex;
private vindex;
constructor(keys: string[], values: any[]);
static fromObject(obj: object): ForwardMapAccess;
nextKeySeed<T, K extends Deserialize>(_seed: K): IteratorResult<T>;
nextValueSeed<T, V extends Deserialize>(_seed: V): IteratorResult<T>;
nextKeySeed<T, K extends Deserialize<T>>(_seed: K): IteratorResult<T>;
nextValueSeed<T, V extends Deserialize<T>>(_seed: V): IteratorResult<T>;
nextKey<T>(): IteratorResult<T>;
nextValue<V>(): IteratorResult<V>;
}

12
dist/de/forward.js vendored
View file

@ -6,13 +6,13 @@ const interface_1 = require("./interface");
class ForwardMapAccess extends interface_1.MapAccess {
constructor(keys, values) {
super();
Object.defineProperty(this, "keys", {
Object.defineProperty(this, "_keys", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "values", {
Object.defineProperty(this, "_values", {
enumerable: true,
configurable: true,
writable: true,
@ -30,8 +30,8 @@ class ForwardMapAccess extends interface_1.MapAccess {
writable: true,
value: 0
});
this.keys = keys;
this.values = values;
this._keys = keys;
this._values = values;
}
static fromObject(obj) {
return new ForwardMapAccess(Object.keys(obj), Object.values(obj));
@ -44,7 +44,7 @@ class ForwardMapAccess extends interface_1.MapAccess {
}
nextKey() {
if (this.kindex < this.keys.length) {
return utils_1.IterResult.Next(this.keys[this.kindex++]);
return utils_1.IterResult.Next(this._keys[this.kindex++]);
}
else {
return utils_1.IterResult.Done();
@ -52,7 +52,7 @@ class ForwardMapAccess extends interface_1.MapAccess {
}
nextValue() {
if (this.vindex < this.values.length) {
return utils_1.IterResult.Next(this.values[this.vindex++]);
return utils_1.IterResult.Next(this._values[this.vindex++]);
}
else {
return utils_1.IterResult.Done();

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

@ -1,3 +1,4 @@
import { SerdeOptions } from '../option';
import { IDeserializer, IIterableAccess, IMapAccess, IVisitor } from './interface';
export declare class GenericSeed<T> {
readonly visitor: IVisitor<T>;
@ -5,7 +6,10 @@ export declare class GenericSeed<T> {
static deserialize<T, D extends IDeserializer>(deserializer: D, visitor?: IVisitor<T>): T;
deserialize<D extends IDeserializer>(deserializer: D): T;
}
export declare class GenericVisitor<T> implements IVisitor<T> {
export declare class ProxyVisitor<T> implements IVisitor<T> {
private overrides?;
private options?;
constructor(overrides?: Partial<IVisitor<T>>, options?: SerdeOptions);
visitBoolean(value: boolean): T;
visitNumber(value: number): T;
visitBigInt(value: bigint): T;
@ -15,3 +19,18 @@ export declare class GenericVisitor<T> implements IVisitor<T> {
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<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;
}

196
dist/de/generic.js vendored
View file

@ -1,8 +1,9 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.GenericVisitor = exports.GenericSeed = void 0;
exports.ProxyDeserializer = exports.ProxyVisitor = exports.GenericSeed = void 0;
const utils_1 = require("../utils");
class GenericSeed {
constructor(visitor = new GenericVisitor()) {
constructor(visitor = new ProxyVisitor()) {
Object.defineProperty(this, "visitor", {
enumerable: true,
configurable: true,
@ -11,7 +12,7 @@ class GenericSeed {
});
this.visitor = visitor;
}
static deserialize(deserializer, visitor = new GenericVisitor()) {
static deserialize(deserializer, visitor = new ProxyVisitor()) {
return deserializer.deserializeAny(visitor);
}
deserialize(deserializer) {
@ -19,34 +20,156 @@ class GenericSeed {
}
}
exports.GenericSeed = GenericSeed;
class GenericVisitor {
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;
if (result.done) {
return result;
}
else if ((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);
}
else if (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
]);
}
else {
return result;
}
}
nextKeySeed(seed) {
return this.wrapResponse(this.access.nextKeySeed(seed));
}
nextValueSeed(seed) {
return this.access.nextValueSeed(seed);
}
nextEntrySeed(kseed, vseed) {
return this.wrapResponse(this.access.nextEntrySeed(kseed, vseed));
}
nextKey() {
return this.wrapResponse(this.access.nextKey());
}
nextValue() {
return this.access.nextValue();
}
nextEntry() {
return this.wrapResponse(this.access.nextEntry());
}
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", {
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;
}
visitBoolean(value) {
return value;
var _a, _b, _c;
return (_c = (_b = (_a = this.overrides) === null || _a === void 0 ? void 0 : _a.visitBoolean) === null || _b === void 0 ? void 0 : _b.call(_a, value)) !== null && _c !== void 0 ? _c : value;
}
visitNumber(value) {
return value;
var _a, _b, _c;
return (_c = (_b = (_a = this.overrides) === null || _a === void 0 ? void 0 : _a.visitNumber) === null || _b === void 0 ? void 0 : _b.call(_a, value)) !== null && _c !== void 0 ? _c : value;
}
visitBigInt(value) {
return value;
var _a, _b, _c;
return (_c = (_b = (_a = this.overrides) === null || _a === void 0 ? void 0 : _a.visitBigInt) === null || _b === void 0 ? void 0 : _b.call(_a, value)) !== null && _c !== void 0 ? _c : value;
}
visitString(value) {
return value;
var _a, _b, _c;
return (_c = (_b = (_a = this.overrides) === null || _a === void 0 ? void 0 : _a.visitString) === null || _b === void 0 ? void 0 : _b.call(_a, value)) !== null && _c !== void 0 ? _c : value;
}
visitSymbol(value) {
return value;
var _a, _b, _c;
return (_c = (_b = (_a = this.overrides) === null || _a === void 0 ? void 0 : _a.visitSymbol) === null || _b === void 0 ? void 0 : _b.call(_a, value)) !== null && _c !== void 0 ? _c : value;
}
visitNull() {
return null;
var _a, _b, _c;
return (_c = (_b = (_a = this.overrides) === null || _a === void 0 ? void 0 : _a.visitNull) === null || _b === void 0 ? void 0 : _b.call(_a)) !== null && _c !== void 0 ? _c : null;
}
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);
}
const result = [];
let entry;
while ((entry = access.nextEntry()) && !entry.done) {
while ((entry = proxy.nextEntry()) && !entry.done) {
result.push(entry.value);
}
return Object.fromEntries(result);
}
visitIterable(access) {
var _a, _b;
if ((0, utils_1.isFunction)((_a = this.overrides) === null || _a === void 0 ? void 0 : _a.visitIterable)) {
return (_b = this.overrides) === null || _b === void 0 ? void 0 : _b.visitIterable(access);
}
const result = [];
let element;
while ((element = access.nextElement())) {
@ -55,4 +178,53 @@ class GenericVisitor {
return result;
}
}
exports.GenericVisitor = GenericVisitor;
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;

4
dist/de/impl.js vendored
View file

@ -2,12 +2,14 @@
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(deserializer);
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));
}
}

View file

@ -1,20 +1,29 @@
import { Nullable } from "../utils";
import { Nullable } from '../utils';
export interface IMapAccess {
nextKeySeed<T, K extends Deserialize>(seed: K): IteratorResult<T>;
nextValueSeed<T, V extends Deserialize>(seed: V): IteratorResult<T>;
nextEntrySeed<TK, TV, K extends Deserialize, V extends Deserialize>(kseed: K, vseed: V): IteratorResult<[TK, TV]>;
nextKeySeed<T, K extends Deserialize<T>>(seed: K): IteratorResult<T>;
nextValueSeed<T, V extends Deserialize<T>>(seed: V): IteratorResult<T>;
nextEntrySeed<TK, TV, K extends Deserialize<TK>, V extends Deserialize<TV>>(kseed: K, vseed: V): IteratorResult<[TK, TV]>;
nextKey<T>(): IteratorResult<T>;
nextValue<V>(): IteratorResult<V>;
nextEntry<K, V>(): IteratorResult<[K, V]>;
sizeHint?(): Nullable<number>;
entries<T, K extends Deserialize<T>>(seed?: K): Iterator<T>;
values<T, V extends Deserialize<T>>(seed?: V): Iterator<T>;
entries<TK, TV, K extends Deserialize<TK>, V extends Deserialize<TV>>(kseed: K, vseed: V): Iterator<[TK, TV]>;
[Symbol.iterator]<K, V>(): Iterator<[K, V]>;
}
export declare abstract class MapAccess {
abstract nextKeySeed<T, K extends Deserialize>(seed: K): IteratorResult<T>;
abstract nextValueSeed<T, V extends Deserialize>(seed: V): IteratorResult<T>;
nextEntrySeed<TK, TV, K extends Deserialize, V extends Deserialize>(kseed: K, vseed: V): IteratorResult<[TK, TV]>;
abstract nextKeySeed<T, K extends Deserialize<T>>(seed: K): IteratorResult<T>;
abstract nextValueSeed<T, V extends Deserialize<T>>(seed: V): IteratorResult<T>;
nextEntrySeed<TK, TV, K extends Deserialize<TK>, V extends Deserialize<TV>>(kseed: K, vseed: V): IteratorResult<[TK, TV]>;
abstract nextKey<T>(): IteratorResult<T>;
abstract nextValue<V>(): IteratorResult<V>;
nextEntry<K, V>(): IteratorResult<[K, V]>;
private generate;
keys<T, K extends Deserialize<T>>(seed?: K): Iterator<T>;
values<T, V extends Deserialize<T>>(seed?: V): Iterator<T>;
entries<TK, TV, K extends Deserialize<TK>, V extends Deserialize<TV>>(kseed?: K, vseed?: V): Iterator<[TK, TV]>;
[Symbol.iterator]<K, V>(): Iterator<K, V>;
}
export interface IIterableAccess {
nextElement<T>(): IteratorResult<T>;
@ -34,17 +43,17 @@ export interface IVisitor<T> {
visitIterable(access: IIterableAccess): T;
}
export interface IDeserializer {
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;
deserializeAny<T>(visitor: Partial<IVisitor<T>>): T;
deserializeBoolean<T>(visitor: Partial<IVisitor<T>>): T;
deserializeNumber<T>(visitor: Partial<IVisitor<T>>): T;
deserializeBigInt<T>(visitor: Partial<IVisitor<T>>): T;
deserializeString<T>(visitor: Partial<IVisitor<T>>): T;
deserializeSymbol<T>(visitor: Partial<IVisitor<T>>): T;
deserializeNull<T>(visitor: Partial<IVisitor<T>>): T;
deserializeObject<T>(visitor: Partial<IVisitor<T>>): T;
deserializeIterable<T>(visitor: Partial<IVisitor<T>>): T;
deserializeFunction<T>(visitor: Partial<IVisitor<T>>): T;
}
export interface Deserialize {
<T>(deserializer: IDeserializer): T;
export interface Deserialize<T> {
(deserializer: IDeserializer): T;
}

24
dist/de/interface.js vendored
View file

@ -23,6 +23,30 @@ class MapAccess {
}
return utils_1.IterResult.Done();
}
*generate(next) {
let item;
while ((item = next())) {
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();
}
}
exports.MapAccess = MapAccess;
class IterableAccess {

2
dist/decorator.d.ts vendored Normal file
View file

@ -0,0 +1,2 @@
import { ContainerOptions, PropertyOptions } from './option';
export declare function serde(options: ContainerOptions | PropertyOptions): (target: any, context: DecoratorContext) => void;

34
dist/decorator.js vendored Normal file
View file

@ -0,0 +1,34 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.serde = serde;
const option_1 = require("./option");
const ContextKind = Object.freeze({
Class: 'class',
Method: 'method',
Getter: 'getter',
Setter: 'setter',
Field: 'field',
Accessor: 'accessor'
});
function decorateClass(target, context, options) {
const serde = context.metadata.serde;
context.metadata.serde = new option_1.SerdeOptions(target, options, serde === null || serde === void 0 ? void 0 : serde.properties);
}
function decorateField(context, options) {
const serde = context.metadata.serde || new option_1.SerdeOptions(undefined);
serde.properties.set(context.name, options);
context.metadata.serde = serde;
}
function serde(options) {
return function (target, context) {
switch (context.kind) {
case ContextKind.Class:
decorateClass(target, context, options);
break;
case ContextKind.Field:
decorateField(context, options);
default:
break;
}
};
}

2
dist/index.d.ts vendored
View file

@ -1,6 +1,8 @@
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 * from './registry';
export * from './utils';

2
dist/index.js vendored
View file

@ -40,6 +40,8 @@ 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);
__exportStar(require("./registry"), exports);
__exportStar(require("./utils"), exports);

71
dist/option.d.ts vendored Normal file
View file

@ -0,0 +1,71 @@
import { Deserialize } from './de';
import { Serialize } from './ser';
import { CaseConvention } from './case';
import { 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 declare const Stage: Readonly<{
readonly Serialize: 0;
readonly Deserialize: 1;
}>;
export declare class PropertyMap extends Map<string, PropertyOptions> {
private readonly options;
private readonly aliases;
constructor(options: SerdeOptions, iterable?: Iterable<any>);
private setAliasesFromOptions;
set(key: string, value: PropertyOptions): this;
addAlias(alias: string | Iterable<string>, key: string): void;
getFieldFromAlias(alias: string): string | undefined;
private buildAliasMap;
}
export type Stage = typeof Stage[keyof typeof Stage];
export declare class SerdeOptions {
readonly target: Nullable<any>;
readonly container: ContainerOptions;
readonly properties: PropertyMap;
constructor(target: any, container?: ContainerOptions, properties?: PropertyMap);
private getClassName;
getSerializedClassName(defaultName?: string): string | undefined;
getDeserializedClassName(defaultName?: string): string | undefined;
getCaseConvention(stage: Stage): CaseConvention | undefined;
applyCaseConvention(stage: Stage, property: string): string;
getPropertyRename(stage: Stage, property: string): string | undefined;
getSerializationPropertyName(property: string): string;
getNameFromAlias(alias: string): string | undefined;
getDeserializationPropertyName(property: string): string;
shouldSkip(stage: Stage, property: string, value: any): boolean;
shouldSerialize(property: string, value: any): boolean;
shouldDeserialize(property: string, value: any): boolean;
defaultFor(property: string): any;
}
export {};

209
dist/option.js vendored Normal file
View file

@ -0,0 +1,209 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SerdeOptions = exports.PropertyMap = exports.Stage = void 0;
const case_1 = require("./case");
const utils_1 = require("./utils");
exports.Stage = Object.freeze({
Serialize: 0,
Deserialize: 1
});
class PropertyMap extends Map {
constructor(options, iterable) {
super(iterable);
Object.defineProperty(this, "options", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "aliases", {
enumerable: true,
configurable: true,
writable: true,
value: new Map()
});
this.options = options;
this.buildAliasMap();
}
setAliasesFromOptions(key, options) {
this.aliases.set(key, key);
if (options.alias != null) {
this.addAlias(options.alias, key);
}
if ((0, utils_1.isString)(options.rename)) {
this.aliases.set(options.rename, key);
}
else if ((0, utils_1.isNumber)(this.options.container.renameAll)) {
this.aliases.set(this.options.applyCaseConvention(exports.Stage.Deserialize, key), key);
}
}
set(key, value) {
super.set(key, value);
this.setAliasesFromOptions(key, value);
return this;
}
addAlias(alias, key) {
if ((0, utils_1.isString)(alias)) {
this.aliases.set(alias, key);
}
else if ((0, utils_1.isIterable)(alias)) {
for (const a of alias) {
this.addAlias(a, key);
}
}
}
getFieldFromAlias(alias) {
return this.aliases.get(alias);
}
buildAliasMap() {
for (const [key, value] of this.entries()) {
if (value.alias != null) {
this.addAlias(value.alias, key);
}
}
}
}
exports.PropertyMap = PropertyMap;
class SerdeOptions {
constructor(target, container = {}, properties = new PropertyMap(this)) {
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);
}
getCaseConvention(stage) {
var _a, _b;
if ((0, utils_1.isNumber)(this.container.renameAll)) {
if (stage === exports.Stage.Serialize) {
return this.container.renameAll;
}
else {
return case_1.CaseConvention.CamelCase;
}
}
else if (stage === exports.Stage.Serialize && (0, utils_1.isNumber)((_a = this.container.renameAll) === null || _a === void 0 ? void 0 : _a.serialize)) {
return 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 this.container.renameAll.deserialize;
}
}
applyCaseConvention(stage, property) {
const convention = this.getCaseConvention(stage);
return convention != null ? (0, case_1.convertCase)(property, convention) : property;
}
getPropertyRename(stage, property) {
var _a, _b;
const options = this.properties.get(property);
if (options == null) {
return;
}
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;
}
}
getSerializationPropertyName(property) {
return this.getPropertyRename(exports.Stage.Serialize, property) ||
this.applyCaseConvention(exports.Stage.Serialize, property);
}
getNameFromAlias(alias) {
return this.properties.getFieldFromAlias(alias);
}
getDeserializationPropertyName(property) {
return this.getNameFromAlias(property) ||
this.applyCaseConvention(exports.Stage.Deserialize, property);
}
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;

6
dist/registry.d.ts vendored
View file

@ -2,10 +2,10 @@ import { Deserialize } from './de';
import { Serialize } from './ser';
export declare class Registry {
serializers: Map<Function, Serialize<any>>;
deserializers: Map<Function, Deserialize>;
deserializers: Map<Function, Deserialize<any>>;
registerSerializer<T>(ctor: Function, serialize: Serialize<T>): void;
registerDeserializer(ctor: Function, deserialize: Deserialize): void;
registerDeserializer<T>(ctor: Function, deserialize: Deserialize<T>): void;
}
export declare const GlobalRegistry: Registry;
export declare const registerSerializer: <T>(ctor: Function, serialize: Serialize<T>) => void;
export declare const registerDeserializer: (ctor: Function, deserialize: Deserialize) => void;
export declare const registerDeserializer: <T>(ctor: Function, deserialize: Deserialize<T>) => void;

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

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

5
dist/ser/impl.js vendored
View file

@ -9,7 +9,8 @@ class UnhandledTypeError extends TypeError {
}
function serializeObject(serializer, obj, options) {
for (const key in obj) {
serializer.serializeEntry(key, obj[key]);
const name = (options === null || options === void 0 ? void 0 : options.getSerializationPropertyName(key)) || key;
serializer.serializeEntry(name, obj[key]);
}
return serializer.end();
}
@ -20,7 +21,7 @@ function serializeClass(serializer, value, options) {
}
const defaultGetter = (value) => {
var _a;
return (_a = value[Symbol.metadata]) === null || _a === void 0 ? void 0 : _a.serde;
return (_a = value.constructor[Symbol.metadata]) === null || _a === void 0 ? void 0 : _a.serde;
};
function serialize(serializer, value, optionsGetter = defaultGetter) {
switch (typeof value) {

1
dist/utils.d.ts vendored
View file

@ -6,7 +6,6 @@ export type Primitive = string | number | boolean | symbol | bigint | null | und
export interface ToString {
toString(): string;
}
export declare function staticImplements<T>(): <U extends T>(constructor: U) => void;
export declare function isPlainObject(value: any): boolean;
export declare function isFunction(value: any): value is Function;
export declare function isIterable(value: any): value is Iterable<any>;

4
dist/utils.js vendored
View file

@ -1,15 +1,11 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.IterResult = void 0;
exports.staticImplements = staticImplements;
exports.isPlainObject = isPlainObject;
exports.isFunction = isFunction;
exports.isIterable = isIterable;
exports.isString = isString;
exports.isNumber = isNumber;
function staticImplements() {
return (constructor) => { constructor; };
}
function isPlainObject(value) {
return Object.getPrototypeOf(value) === Object.prototype;
}

View file

@ -2,16 +2,16 @@ 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 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
this._keys = keys
this._values = values
}
static fromObject(obj: object): ForwardMapAccess {
@ -21,17 +21,17 @@ export class ForwardMapAccess extends MapAccess {
)
}
nextKeySeed<T, K extends Deserialize>(_seed: K): IteratorResult<T> {
nextKeySeed<T, K extends Deserialize<T>>(_seed: K): IteratorResult<T> {
return this.nextKey()
}
nextValueSeed<T, V extends Deserialize>(_seed: V): IteratorResult<T> {
nextValueSeed<T, V extends Deserialize<T>>(_seed: V): IteratorResult<T> {
return this.nextValue()
}
nextKey<T>(): IteratorResult<T> {
if (this.kindex < this.keys.length) {
return IterResult.Next(this.keys[this.kindex++]) as IteratorResult<T>
return IterResult.Next(this._keys[this.kindex++]) as IteratorResult<T>
} else {
return IterResult.Done()
}
@ -39,7 +39,7 @@ export class ForwardMapAccess extends MapAccess {
nextValue<V>(): IteratorResult<V> {
if (this.vindex < this.values.length) {
return IterResult.Next(this.values[this.vindex++]) as IteratorResult<V>
return IterResult.Next(this._values[this.vindex++]) as IteratorResult<V>
} else {
return IterResult.Done()
}

View file

@ -1,13 +1,15 @@
import { IDeserializer, IIterableAccess, IMapAccess, IVisitor } from './interface'
import { SerdeOptions } from '../option'
import { isFunction, isString, IterResult, Nullable } from '../utils'
import { Deserialize, IDeserializer, IIterableAccess, IMapAccess, IVisitor } from './interface'
export class GenericSeed<T> {
readonly visitor: IVisitor<T>
constructor(visitor: IVisitor<T> = new GenericVisitor()) {
constructor(visitor: IVisitor<T> = new ProxyVisitor()) {
this.visitor = visitor
}
static deserialize<T, D extends IDeserializer>(deserializer: D, visitor: IVisitor<T> = new GenericVisitor()): T {
static deserialize<T, D extends IDeserializer>(deserializer: D, visitor: IVisitor<T> = new ProxyVisitor()): T {
return deserializer.deserializeAny(visitor)
}
@ -16,36 +18,150 @@ export class GenericSeed<T> {
}
}
export class GenericVisitor<T> implements IVisitor<T> {
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<T, I extends IteratorResult<T> = IteratorResult<T>>(result: I): I {
if (result.done) {
return result
} else if (isString(result.value)) {
const key = this.options?.getDeserializationPropertyName(result.value) ?? result.value
return IterResult.Next(key) as I
} else if (Array.isArray(result.value)) {
const [alias, value] = result.value
const key = this.options?.getDeserializationPropertyName(alias) ?? alias
return IterResult.Next([
key,
value
]) as I
} else {
return result
}
}
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]> {
return this.wrapResponse<[TK, TV]>(this.access.nextEntrySeed(kseed, vseed))
}
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]> {
return this.wrapResponse<[K, V]>(this.access.nextEntry())
}
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 options?: SerdeOptions
constructor(overrides?: Partial<IVisitor<T>>, options?: SerdeOptions) {
if (overrides instanceof ProxyVisitor) {
return overrides
}
this.overrides = overrides
this.options = options
}
visitBoolean(value: boolean): T {
return value as T
return this.overrides?.visitBoolean?.(value) ?? value as T
}
visitNumber(value: number): T {
return value as T
return this.overrides?.visitNumber?.(value) ?? value as T
}
visitBigInt(value: bigint): T {
return value as T
return this.overrides?.visitBigInt?.(value) ?? value as T
}
visitString(value: string): T {
return value as T
return this.overrides?.visitString?.(value) ?? value as T
}
visitSymbol(value: symbol): T {
return value as T
return this.overrides?.visitSymbol?.(value) ?? value as T
}
visitNull(): T {
return null as T
return this.overrides?.visitNull?.() ?? null as T
}
visitObject(access: IMapAccess): T {
const proxy = new ProxyMapAccess(access, this.options)
if (isFunction(this.overrides?.visitObject)) {
return this.overrides?.visitObject(proxy)
}
const result = []
let entry
while ((entry = access.nextEntry()) && !entry.done) {
while ((entry = proxy.nextEntry()) && !entry.done) {
result.push(entry.value)
}
@ -53,6 +169,10 @@ export class GenericVisitor<T> implements IVisitor<T> {
}
visitIterable(access: IIterableAccess): T {
if (isFunction(this.overrides?.visitIterable)) {
return this.overrides?.visitIterable(access)
}
const result = []
let element
@ -64,5 +184,53 @@ export class GenericVisitor<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,5 +1,6 @@
import { GlobalRegistry, Registry } from '../registry'
import { Constructor } from '../utils'
import { ProxyDeserializer } from './generic'
import { IDeserializer } from './interface'
export function deserialize<T, D extends IDeserializer>(deserializer: D, into: Constructor<T>, registry: Registry = GlobalRegistry): T {
@ -8,7 +9,7 @@ export function deserialize<T, D extends IDeserializer>(deserializer: D, into: C
if (de == null) {
throw new ReferenceError(`No deserializer for ${into.name}`)
} else {
return de(deserializer)
return de(new ProxyDeserializer(deserializer, (into as any)?.[Symbol.metadata]?.serde))
}
}

View file

@ -1,20 +1,24 @@
import { IterResult, Nullable } from "../utils"
import { IterResult, Nullable } from '../utils'
export interface IMapAccess {
nextKeySeed<T, K extends Deserialize>(seed: K): IteratorResult<T>
nextValueSeed<T, V extends Deserialize>(seed: V): IteratorResult<T>
nextEntrySeed<TK, TV, K extends Deserialize, V extends Deserialize>(kseed: K, vseed: V): IteratorResult<[TK, TV]>
nextKeySeed<T, K extends Deserialize<T>>(seed: K): IteratorResult<T>
nextValueSeed<T, V extends Deserialize<T>>(seed: V): IteratorResult<T>
nextEntrySeed<TK, TV, K extends Deserialize<TK>, V extends Deserialize<TV>>(kseed: K, vseed: V): IteratorResult<[TK, TV]>
nextKey<T>(): IteratorResult<T>
nextValue<V>(): IteratorResult<V>
nextEntry<K, V>(): IteratorResult<[K, V]>
sizeHint?(): Nullable<number>
entries<T, K extends Deserialize<T>>(seed?: K): Iterator<T>
values<T, V extends Deserialize<T>>(seed?: V): Iterator<T>
entries<TK, TV, K extends Deserialize<TK>, V extends Deserialize<TV>>(kseed: K, vseed: V): Iterator<[TK, TV]>
[Symbol.iterator]<K, V>(): Iterator<[K, V]>
}
export abstract class MapAccess {
abstract nextKeySeed<T, K extends Deserialize>(seed: K): IteratorResult<T>
abstract nextValueSeed<T, V extends Deserialize>(seed: V): IteratorResult<T>
abstract nextKeySeed<T, K extends Deserialize<T>>(seed: K): IteratorResult<T>
abstract nextValueSeed<T, V extends Deserialize<T>>(seed: V): IteratorResult<T>
nextEntrySeed<TK, TV, K extends Deserialize, V extends Deserialize>(kseed: K, vseed: V): IteratorResult<[TK, TV]> {
nextEntrySeed<TK, TV, K extends Deserialize<TK>, V extends Deserialize<TV>>(kseed: K, vseed: V): IteratorResult<[TK, TV]> {
const key = this.nextKeySeed(kseed) as IteratorResult<TK>
if (!key.done) {
@ -44,6 +48,43 @@ export abstract class MapAccess {
return IterResult.Done()
}
private *generate<T>(next: () => IteratorResult<T>): Iterator<T> {
let item
while ((item = next())) {
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 interface IIterableAccess {
@ -67,19 +108,19 @@ export interface IVisitor<T> {
}
export interface IDeserializer {
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
deserializeAny<T>(visitor: Partial<IVisitor<T>>): T
deserializeBoolean<T>(visitor: Partial<IVisitor<T>>): T
deserializeNumber<T>(visitor: Partial<IVisitor<T>>): T
deserializeBigInt<T>(visitor: Partial<IVisitor<T>>): T
deserializeString<T>(visitor: Partial<IVisitor<T>>): T
deserializeSymbol<T>(visitor: Partial<IVisitor<T>>): T
deserializeNull<T>(visitor: Partial<IVisitor<T>>): T
deserializeObject<T>(visitor: Partial<IVisitor<T>>): T
deserializeIterable<T>(visitor: Partial<IVisitor<T>>): T
deserializeFunction<T>(visitor: Partial<IVisitor<T>>): T
}
export interface Deserialize {
<T>(deserializer: IDeserializer): T
export interface Deserialize<T> {
(deserializer: IDeserializer): T
}

44
src/decorator.ts Normal file
View file

@ -0,0 +1,44 @@
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

@ -2,6 +2,8 @@ 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 * from './registry'
export * from './utils'

View file

@ -1,7 +1,7 @@
import { CaseConvention, convertCase } from '../case'
import { Deserialize } from '../de'
import { isFunction, isNumber, isString, Nullable } from '../utils'
import { Serialize } from './interface'
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
@ -37,7 +37,7 @@ export interface PropertyOptions {
rename?: RenameOptions | string
skip?: SkipOptions | Predicate | boolean
serializeWith?: Serialize<any>
deserializeWith?: Deserialize
deserializeWith?: Deserialize<any>
}
export const Stage = Object.freeze({
@ -45,14 +45,67 @@ export const Stage = Object.freeze({
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: Map<string, PropertyOptions>
readonly properties: PropertyMap
constructor(target: any, container: ContainerOptions = {}, properties: Map<string, PropertyOptions> = new Map()) {
constructor(target: any, container: ContainerOptions = {}, properties: PropertyMap = new PropertyMap(this)) {
this.target = target
this.container = container
this.properties = properties
@ -78,45 +131,54 @@ export class SerdeOptions {
return this.getClassName(Stage.Deserialize, defaultName)
}
private applyPropertyCase(stage: Stage, property: string) {
getCaseConvention(stage: Stage) {
if (isNumber(this.container.renameAll)) {
return convertCase(property, 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 convertCase(property, this.container.renameAll.serialize)
return this.container.renameAll.serialize
} else if (stage === Stage.Deserialize && isNumber(this.container.renameAll?.deserialize)) {
return convertCase(property, this.container.renameAll.deserialize)
} else {
return property
return this.container.renameAll.deserialize
}
}
private getPropertyName(stage: Stage, property: string) {
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 property
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
} else {
return property
}
}
getSerializationPropertyName(property: string) {
const name = this.getPropertyName(Stage.Serialize, property)
return this.applyPropertyCase(Stage.Serialize, name)
return this.getPropertyRename(Stage.Serialize, property) ||
this.applyCaseConvention(Stage.Serialize, property)
}
getNameFromAlias(alias: string) {
return this.properties.getFieldFromAlias(alias)
}
getDeserializationPropertyName(property: string) {
const name = this.getPropertyName(Stage.Deserialize, property)
return this.applyPropertyCase(Stage.Deserialize, name)
return this.getNameFromAlias(property) ||
this.applyCaseConvention(Stage.Deserialize, property)
}
private shouldSkip(stage: Stage, property: string, value: any) {
shouldSkip(stage: Stage, property: string, value: any) {
const options = this.properties.get(property)
if (options == null) {
return false

View file

@ -3,13 +3,13 @@ import { Serialize } from './ser'
export class Registry {
serializers: Map<Function, Serialize<any>> = new Map()
deserializers: Map<Function, Deserialize> = new Map()
deserializers: Map<Function, Deserialize<any>> = new Map()
registerSerializer<T>(ctor: Function, serialize: Serialize<T>) {
this.serializers.set(ctor, serialize)
}
registerDeserializer(ctor: Function, deserialize: Deserialize) {
registerDeserializer<T>(ctor: Function, deserialize: Deserialize<T>) {
this.deserializers.set(ctor, deserialize)
}
}

View file

@ -1,6 +1,6 @@
import { SerdeOptions } from './decorator'
import { ISerializeObject, Serializer } from './interface'
import { isPlainObject, Nullable } from '../utils'
import { SerdeOptions } from '../option'
class UnhandledTypeError extends TypeError {
constructor(serializer: Serializer<unknown>, value: any) {
@ -10,7 +10,8 @@ class UnhandledTypeError extends TypeError {
function serializeObject<T, V extends object, S extends ISerializeObject<T>>(serializer: S, obj: V, options?: SerdeOptions): T {
for (const key in obj) {
serializer.serializeEntry(key, obj[key])
const name = options?.getSerializationPropertyName(key) || key
serializer.serializeEntry(name, obj[key])
}
return serializer.end()
@ -23,7 +24,7 @@ function serializeClass<T, V extends object, S extends Serializer<T>>(serializer
}
const defaultGetter = (value: any): Nullable<SerdeOptions> => {
return value[Symbol.metadata]?.serde
return value.constructor[Symbol.metadata]?.serde
}
export function serialize<T, V, S extends Serializer<T>>(serializer: S, value: V, optionsGetter: (value: V) => Nullable<SerdeOptions> = defaultGetter): T {

View file

@ -10,10 +10,6 @@ export interface ToString {
toString(): string
}
export function staticImplements<T>() {
return <U extends T>(constructor: U) => { constructor }
}
export function isPlainObject(value: any): boolean {
return Object.getPrototypeOf(value) === Object.prototype
}