Skip to content

Adding @Alias decorator and allow multiple @Expose decorators #1192

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,55 @@ export class User {
}
```

If you need to rename the property from or to a plain instance,
you can use the `from` and `to` options of `@Alias`:

```typescript
import { Expose } from 'class-transformer';

export class User {
@Expose({ name: 'uid' })
id: string;

firstName: string;

lastName: string;

@Alias({ from: 'secretKey', to: '__password' })
password: string;

@Alias({ to: 'fullName' })
getFullName() {
return this.firstName + ' ' + this.lastName;
}
}
```

Using this configuration, the `User` object would accept
the following plain object for `plainToInstance(User, ...)`:

```json
{
"uid": "1234",
"firstName": "John",
"lastName": "Doe",
"secretKey": "allYourBaseAreBelongToUs"
}
```

Subsequently `instanceToPlain(...)` would then produce the
following plain object:

```json
{
"uid": "1234",
"firstName": "John",
"lastName": "Doe",
"__password": "allYourBaseAreBelongToUs",
"fullName": "John Doe"
}
```

## Skipping specific properties[⬆](#table-of-contents)

Sometimes you want to skip some properties during transformation.
Expand Down
136 changes: 101 additions & 35 deletions src/MetadataStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,43 +11,58 @@ export class MetadataStorage {

private _typeMetadatas = new Map<Function, Map<string, TypeMetadata>>();
private _transformMetadatas = new Map<Function, Map<string, TransformMetadata[]>>();
private _exposeMetadatas = new Map<Function, Map<string, ExposeMetadata>>();
private _excludeMetadatas = new Map<Function, Map<string, ExcludeMetadata>>();
private _exposeMetadatas = new Map<Function, Map<string | undefined, ExposeMetadata[]>>();
private _excludeMetadatas = new Map<Function, Map<string | undefined, ExcludeMetadata>>();
private _ancestorsMap = new Map<Function, Function[]>();

// -------------------------------------------------------------------------
// Adder Methods
// -------------------------------------------------------------------------

addTypeMetadata(metadata: TypeMetadata): void {
if (!this._typeMetadatas.has(metadata.target)) {
this._typeMetadatas.set(metadata.target, new Map<string, TypeMetadata>());
let properties = this._typeMetadatas.get(metadata.target);
if (!properties) {
properties = new Map<string, TypeMetadata>();
this._typeMetadatas.set(metadata.target, properties);
}
this._typeMetadatas.get(metadata.target).set(metadata.propertyName, metadata);
properties.set(metadata.propertyName, metadata);
}

addTransformMetadata(metadata: TransformMetadata): void {
if (!this._transformMetadatas.has(metadata.target)) {
this._transformMetadatas.set(metadata.target, new Map<string, TransformMetadata[]>());
let properties = this._transformMetadatas.get(metadata.target);
if (!properties) {
properties = new Map<string, TransformMetadata[]>();
this._transformMetadatas.set(metadata.target, properties);
}
if (!this._transformMetadatas.get(metadata.target).has(metadata.propertyName)) {
this._transformMetadatas.get(metadata.target).set(metadata.propertyName, []);
let metadatas = properties.get(metadata.propertyName);
if (!metadatas) {
metadatas = [];
properties.set(metadata.propertyName, metadatas);
}
this._transformMetadatas.get(metadata.target).get(metadata.propertyName).push(metadata);
metadatas.push(metadata);
}

addExposeMetadata(metadata: ExposeMetadata): void {
if (!this._exposeMetadatas.has(metadata.target)) {
this._exposeMetadatas.set(metadata.target, new Map<string, ExposeMetadata>());
let properties = this._exposeMetadatas.get(metadata.target);
if (!properties) {
properties = new Map<string, ExposeMetadata[]>();
this._exposeMetadatas.set(metadata.target, properties);
}
this._exposeMetadatas.get(metadata.target).set(metadata.propertyName, metadata);
let values = properties.get(metadata.propertyName);
if (!values) {
values = [];
properties.set(metadata.propertyName, values);
}
values.push(metadata);
}

addExcludeMetadata(metadata: ExcludeMetadata): void {
if (!this._excludeMetadatas.has(metadata.target)) {
this._excludeMetadatas.set(metadata.target, new Map<string, ExcludeMetadata>());
let properties = this._excludeMetadatas.get(metadata.target);
if (!properties) {
properties = new Map<string, ExcludeMetadata>();
this._excludeMetadatas.set(metadata.target, properties);
}
this._excludeMetadatas.get(metadata.target).set(metadata.propertyName, metadata);
properties.set(metadata.propertyName, metadata);
}

// -------------------------------------------------------------------------
Expand Down Expand Up @@ -77,21 +92,44 @@ export class MetadataStorage {
});
}

findExcludeMetadata(target: Function, propertyName: string): ExcludeMetadata {
findExcludeMetadata(target: Function, propertyName: string): ExcludeMetadata | undefined {
return this.findMetadata(this._excludeMetadatas, target, propertyName);
}

findExposeMetadata(target: Function, propertyName: string): ExposeMetadata {
return this.findMetadata(this._exposeMetadatas, target, propertyName);
findExposeMetadata(
target: Function,
propertyName: string,
transformationType: TransformationType
): ExposeMetadata | undefined {
return this.findMetadatas(this._exposeMetadatas, target, propertyName).find(metadata => {
if (!metadata.options) return true;
if (metadata.options.toClassOnly === true && metadata.options.toPlainOnly === true) return true;

if (metadata.options.toClassOnly === true) {
return (
transformationType === TransformationType.CLASS_TO_CLASS ||
transformationType === TransformationType.PLAIN_TO_CLASS
);
}
if (metadata.options.toPlainOnly === true) {
return transformationType === TransformationType.CLASS_TO_PLAIN;
}

return true;
});
}

findExposeMetadataByCustomName(target: Function, name: string): ExposeMetadata {
findExposeMetadataByCustomName(
target: Function,
name: string,
transformType: TransformationType
): ExposeMetadata | undefined {
return this.getExposedMetadatas(target).find(metadata => {
return metadata.options && metadata.options.name === name;
});
}

findTypeMetadata(target: Function, propertyName: string): TypeMetadata {
findTypeMetadata(target: Function, propertyName: string): TypeMetadata | undefined {
return this.findMetadata(this._typeMetadatas, target, propertyName);
}

Expand All @@ -105,7 +143,7 @@ export class MetadataStorage {
}

getExposedMetadatas(target: Function): ExposeMetadata[] {
return this.getMetadata(this._exposeMetadatas, target);
return this.getMetadatas(this._exposeMetadatas, target);
}

getExcludedMetadatas(target: Function): ExcludeMetadata[] {
Expand All @@ -115,6 +153,7 @@ export class MetadataStorage {
getExposedProperties(target: Function, transformationType: TransformationType): string[] {
return this.getExposedMetadatas(target)
.filter(metadata => {
if (!metadata.propertyName) return false;
if (!metadata.options) return true;
if (metadata.options.toClassOnly === true && metadata.options.toPlainOnly === true) return true;

Expand All @@ -130,12 +169,13 @@ export class MetadataStorage {

return true;
})
.map(metadata => metadata.propertyName);
.map(metadata => metadata.propertyName as string);
}

getExcludedProperties(target: Function, transformationType: TransformationType): string[] {
return this.getExcludedMetadatas(target)
.filter(metadata => {
if (!metadata.propertyName) return false;
if (!metadata.options) return true;
if (metadata.options.toClassOnly === true && metadata.options.toPlainOnly === true) return true;

Expand All @@ -151,7 +191,7 @@ export class MetadataStorage {

return true;
})
.map(metadata => metadata.propertyName);
.map(metadata => metadata.propertyName as string);
}

clear(): void {
Expand All @@ -165,12 +205,12 @@ export class MetadataStorage {
// Private Methods
// -------------------------------------------------------------------------

private getMetadata<T extends { target: Function; propertyName: string }>(
metadatas: Map<Function, Map<string, T>>,
private getMetadata<T extends { target: Function; propertyName: string | undefined }>(
metadatas: Map<Function, Map<string | undefined, T>>,
target: Function
): T[] {
const metadataFromTargetMap = metadatas.get(target);
let metadataFromTarget: T[];
let metadataFromTarget: T[] | undefined = undefined;
if (metadataFromTargetMap) {
metadataFromTarget = Array.from(metadataFromTargetMap.values()).filter(meta => meta.propertyName !== undefined);
}
Expand All @@ -187,11 +227,37 @@ export class MetadataStorage {
return metadataFromAncestors.concat(metadataFromTarget || []);
}

private findMetadata<T extends { target: Function; propertyName: string }>(
metadatas: Map<Function, Map<string, T>>,
private getMetadatas<T extends { target: Function; propertyName: string | undefined }>(
metadatas: Map<Function, Map<string | undefined, T[]>>,
target: Function
): T[] {
const metadataFromTargetMap = metadatas.get(target);
let metadataFromTarget: T[] | undefined = undefined;
if (metadataFromTargetMap) {
metadataFromTarget = Array.from(metadataFromTargetMap.values()).reduce(
(acc, values) => acc.concat(values.filter(meta => meta.propertyName !== undefined)),
[]
);
}
const metadataFromAncestors: T[] = [];
for (const ancestor of this.getAncestors(target)) {
const ancestorMetadataMap = metadatas.get(ancestor);
if (ancestorMetadataMap) {
const metadataFromAncestor = Array.from(ancestorMetadataMap.values()).reduce(
(acc, values) => acc.concat(values.filter(meta => meta.propertyName !== undefined)),
[]
);
metadataFromAncestors.push(...metadataFromAncestor);
}
}
return metadataFromAncestors.concat(metadataFromTarget || []);
}

private findMetadata<T extends { target: Function; propertyName: string | undefined }>(
metadatas: Map<Function, Map<string | undefined, T>>,
target: Function,
propertyName: string
): T {
): T | undefined {
const metadataFromTargetMap = metadatas.get(target);
if (metadataFromTargetMap) {
const metadataFromTarget = metadataFromTargetMap.get(propertyName);
Expand All @@ -211,13 +277,13 @@ export class MetadataStorage {
return undefined;
}

private findMetadatas<T extends { target: Function; propertyName: string }>(
metadatas: Map<Function, Map<string, T[]>>,
private findMetadatas<T extends { target: Function; propertyName: string | undefined }>(
metadatas: Map<Function, Map<string | undefined, T[]>>,
target: Function,
propertyName: string
): T[] {
const metadataFromTargetMap = metadatas.get(target);
let metadataFromTarget: T[];
let metadataFromTarget: T[] | undefined = undefined;
if (metadataFromTargetMap) {
metadataFromTarget = metadataFromTargetMap.get(propertyName);
}
Expand All @@ -226,7 +292,7 @@ export class MetadataStorage {
const ancestorMetadataMap = metadatas.get(ancestor);
if (ancestorMetadataMap) {
if (ancestorMetadataMap.has(propertyName)) {
metadataFromAncestorsTarget.push(...ancestorMetadataMap.get(propertyName));
metadataFromAncestorsTarget.push(...(ancestorMetadataMap.get(propertyName) as T[]));
}
}
}
Expand All @@ -249,6 +315,6 @@ export class MetadataStorage {
}
this._ancestorsMap.set(target, ancestors);
}
return this._ancestorsMap.get(target);
return this._ancestorsMap.get(target) || [];
}
}
26 changes: 17 additions & 9 deletions src/TransformOperationExecutor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,20 +170,28 @@ export class TransformOperationExecutor {
}

const valueKey = key;
let newValueKey = key,
propertyName = key;
let newValueKey = key;
let propertyName = key;
if (!this.options.ignoreDecorators && targetType) {
if (this.transformationType === TransformationType.PLAIN_TO_CLASS) {
const exposeMetadata = defaultMetadataStorage.findExposeMetadataByCustomName(targetType as Function, key);
if (exposeMetadata) {
const exposeMetadata = defaultMetadataStorage.findExposeMetadataByCustomName(
targetType as Function,
key,
this.transformationType
);
if (exposeMetadata?.propertyName) {
propertyName = exposeMetadata.propertyName;
newValueKey = exposeMetadata.propertyName;
}
} else if (
this.transformationType === TransformationType.CLASS_TO_PLAIN ||
this.transformationType === TransformationType.CLASS_TO_CLASS
) {
const exposeMetadata = defaultMetadataStorage.findExposeMetadata(targetType as Function, key);
const exposeMetadata = defaultMetadataStorage.findExposeMetadata(
targetType as Function,
key,
this.transformationType
);
if (exposeMetadata && exposeMetadata.options && exposeMetadata.options.name) {
newValueKey = exposeMetadata.options.name;
}
Expand Down Expand Up @@ -461,7 +469,7 @@ export class TransformOperationExecutor {
let exposedProperties = defaultMetadataStorage.getExposedProperties(target, this.transformationType);
if (this.transformationType === TransformationType.PLAIN_TO_CLASS) {
exposedProperties = exposedProperties.map(key => {
const exposeMetadata = defaultMetadataStorage.findExposeMetadata(target, key);
const exposeMetadata = defaultMetadataStorage.findExposeMetadata(target, key, this.transformationType);
if (exposeMetadata && exposeMetadata.options && exposeMetadata.options.name) {
return exposeMetadata.options.name;
}
Expand All @@ -486,7 +494,7 @@ export class TransformOperationExecutor {
// apply versioning options
if (this.options.version !== undefined) {
keys = keys.filter(key => {
const exposeMetadata = defaultMetadataStorage.findExposeMetadata(target, key);
const exposeMetadata = defaultMetadataStorage.findExposeMetadata(target, key, this.transformationType);
if (!exposeMetadata || !exposeMetadata.options) return true;

return this.checkVersion(exposeMetadata.options.since, exposeMetadata.options.until);
Expand All @@ -496,14 +504,14 @@ export class TransformOperationExecutor {
// apply grouping options
if (this.options.groups && this.options.groups.length) {
keys = keys.filter(key => {
const exposeMetadata = defaultMetadataStorage.findExposeMetadata(target, key);
const exposeMetadata = defaultMetadataStorage.findExposeMetadata(target, key, this.transformationType);
if (!exposeMetadata || !exposeMetadata.options) return true;

return this.checkGroups(exposeMetadata.options.groups);
});
} else {
keys = keys.filter(key => {
const exposeMetadata = defaultMetadataStorage.findExposeMetadata(target, key);
const exposeMetadata = defaultMetadataStorage.findExposeMetadata(target, key, this.transformationType);
return (
!exposeMetadata ||
!exposeMetadata.options ||
Expand Down
Loading