diff --git a/CHANGELOG.md b/CHANGELOG.md index 2832560a3..dfce8e10d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ - fix calling return type getter function `@Field(type => Foo)` before finishing module evaluation (allow for extending circular classes using `require`) - fix nullifying other custom method decorators - call the method on target instance, not the stored reference to original function (#247) - fix throwing error when extending non args class in the `@ArgsType()` class +- prevent unnecessary conversion of an object that is already an instance of the requested type (avoid constructor side-effects) ## v0.16.0 ### Features diff --git a/src/helpers/types.ts b/src/helpers/types.ts index 2aa6385c1..13713c423 100644 --- a/src/helpers/types.ts +++ b/src/helpers/types.ts @@ -90,6 +90,10 @@ export function convertToType(Target: any, data?: object): object | undefined { if (simpleTypes.includes(data.constructor)) { return data; } + // skip converting already converted types + if (data instanceof Target) { + return data; + } return Object.assign(new Target(), data); } diff --git a/tests/functional/resolvers.ts b/tests/functional/resolvers.ts index bdf2fc1f7..5abee89cf 100644 --- a/tests/functional/resolvers.ts +++ b/tests/functional/resolvers.ts @@ -1083,6 +1083,7 @@ describe("Resolvers", () => { let queryContext: any; let queryInfo: any; let descriptorEvaluated: boolean; + let sampleObjectConstructorCallCount: number; function DescriptorDecorator(): MethodDecorator { return (obj, methodName, descriptor: any) => { @@ -1119,6 +1120,7 @@ describe("Resolvers", () => { queryContext = undefined; queryInfo = undefined; descriptorEvaluated = false; + sampleObjectConstructorCallCount = 0; }); beforeAll(async () => { @@ -1156,6 +1158,9 @@ describe("Resolvers", () => { isTrue() { return this.TRUE; } + constructor() { + sampleObjectConstructorCallCount++; + } instanceValue = Math.random(); @@ -1427,6 +1432,24 @@ describe("Resolvers", () => { expect(getterFieldResult1).not.toEqual(getterFieldResult2); }); + it("shouldn't create new instance for object type if it's already an instance of its class", async () => { + const query = /* graphql */ ` + query { + sampleQuery { + getterField + methodField + } + } + `; + + const result = await graphql(schema, query); + const getterFieldValue = result.data!.sampleQuery.getterField; + const methodFieldValue = result.data!.sampleQuery.getterField; + + expect(getterFieldValue).toEqual(methodFieldValue); + expect(sampleObjectConstructorCallCount).toBe(1); + }); + it("should use the same instance of resolver class for consecutive queries", async () => { const query = `query { sampleQuery {