Skip to content
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

feat: add instanceToPlainAsync method to handle promise values #1490

Open
wants to merge 2 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
24 changes: 24 additions & 0 deletions src/ClassTransformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { TransformOperationExecutor } from './TransformOperationExecutor';
import { TransformationType } from './enums';
import { ClassConstructor } from './interfaces';
import { defaultOptions } from './constants/default-options.constant';
import { instanceToPlainAsync } from './utils';

export class ClassTransformer {
// -------------------------------------------------------------------------
Expand All @@ -25,6 +26,29 @@ export class ClassTransformer {
return executor.transform(undefined, object, undefined, undefined, undefined, undefined);
}

/**
* Converts class (constructor) object to plain (literal) object with support to promise values resolution
*/

instanceToPlainAsync<T extends Record<string, any>>(object: T, options?: ClassTransformOptions): Record<string, any>;
instanceToPlainAsync<T extends Record<string, any>>(
object: T[],
options?: ClassTransformOptions
): Record<string, any>[];
instanceToPlainAsync<T extends Record<string, any>>(
object: T | T[],
options?: ClassTransformOptions
): Record<string, any> | Record<string, any>[] {
const executor = new TransformOperationExecutor(TransformationType.CLASS_TO_PLAIN, {
...defaultOptions,
...options,
});
return instanceToPlainAsync(
executor.transform(undefined, object, undefined, undefined, undefined, undefined),
this.instanceToPlain
);
}

/**
* Converts class (constructor) object to plain (literal) object.
* Uses given plain object as source object (it means fills given plain object with data from class object).
Expand Down
12 changes: 12 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,18 @@ export function instanceToPlain<T>(
return classTransformer.instanceToPlain(object, options);
}

/**
* Converts class (constructor) object to plain (literal) object with support to promise values resolution
*/
export function instanceToPlainAsync<T>(object: T, options?: ClassTransformOptions): Record<string, any>;
export function instanceToPlainAsync<T>(object: T[], options?: ClassTransformOptions): Record<string, any>[];
export function instanceToPlainAsync<T>(
object: T | T[],
options?: ClassTransformOptions
): Record<string, any> | Record<string, any>[] {
return classTransformer.instanceToPlainAsync(object, options);
}

/**
* Converts class (constructor) object to plain (literal) object.
* Uses given plain object as source object (it means fills given plain object with data from class object).
Expand Down
1 change: 1 addition & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './get-global.util';
export * from './is-promise.util';
export * from './promise-values.util';
56 changes: 56 additions & 0 deletions src/utils/promise-values.util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
function promisesTracker(value: any, promises: Promise<any>[]) {
if (Array.isArray(value)) {
for (let i = 0; i < value.length; i++) {
if (value[i] instanceof Promise) {
promises.push(value[i]);
} else {
promisesTracker(value, promises);
}
}
}

if (value instanceof Object) {
for (const key of Object.keys(value)) {
if (value[key] instanceof Promise) {
promises.push(value[key]);
} else {
promisesTracker(value[key], promises);
}
}
}

return value;
}

async function resolvePromises(value: any) {
if (Array.isArray(value)) {
for (let i = 0; i < value.length; i++) {
if (value[i] instanceof Promise) {
value[i] = await value[i];
} else {
value[i] = await resolvePromises(value[i]);
}
}
}

if (value instanceof Object) {
for (const key of Object.keys(value)) {
if (value[key] instanceof Promise) {
value[key] = await value[key];
} else {
value[key] = await resolvePromises(value[key]);
}
}
}

return value;
}

export async function instanceToPlainAsync(value: any, instanceToPlainMethod: any): Promise<Record<string, any>> {
const valuesPromises: Promise<any>[] = [];

promisesTracker(instanceToPlainMethod(value), valuesPromises);
await Promise.all(valuesPromises);

return resolvePromises(value);
}
21 changes: 21 additions & 0 deletions test/functional/transformer-async.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import 'reflect-metadata';
import { defaultMetadataStorage } from '../../src/storage';
import { Expose } from '../../src/decorators';
import { instanceToPlainAsync } from '../../src/index';

describe('applying a transformation at a class that contains promise value', () => {
beforeEach(() => defaultMetadataStorage.clear());
afterEach(() => defaultMetadataStorage.clear());

it('should resolve the promise value', async () => {
class User {
@Expose()
name: Promise<string> = new Promise(resolve => {
resolve('Jonathan');
});
}

const plainUser = await instanceToPlainAsync(new User());
expect(plainUser.name).toEqual('Jonathan');
});
});