Skip to content

Commit 715a6b0

Browse files
committed
fix: resolve toJSON in getModelReference()
1 parent e151d2e commit 715a6b0

File tree

4 files changed

+61
-26
lines changed

4 files changed

+61
-26
lines changed

src/metadataGeneration/typeResolver.ts

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -254,29 +254,6 @@ export class TypeResolver {
254254
return stringMetaType;
255255
}
256256

257-
const type = this.current.typeChecker.getTypeFromTypeNode(this.typeNode);
258-
const toJSON = this.current.typeChecker.getPropertyOfType(type, 'toJSON');
259-
if (toJSON) {
260-
const declaration = toJSON.declarations[0] as ts.MethodDeclaration;
261-
262-
let nodeType = declaration.type;
263-
if (!nodeType) {
264-
const typeChecker = this.current.typeChecker;
265-
const signature = typeChecker.getSignatureFromDeclaration(declaration);
266-
const implicitType = typeChecker.getReturnTypeOfSignature(signature!);
267-
nodeType = typeChecker.typeToTypeNode(implicitType) as ts.TypeNode;
268-
}
269-
const type = new TypeResolver(nodeType, this.current).resolve();
270-
const referenceType: Tsoa.ReferenceType = {
271-
refName: typeReference.getText(),
272-
dataType: 'refAlias',
273-
type,
274-
validators: {},
275-
};
276-
this.current.AddReferenceType(referenceType);
277-
return referenceType;
278-
}
279-
280257
if (this.context[typeReference.typeName.text]) {
281258
return new TypeResolver(this.context[typeReference.typeName.text], this.current, this.parentNode, this.context).resolve();
282259
}
@@ -478,15 +455,45 @@ export class TypeResolver {
478455
}
479456

480457
private getModelReference(modelType: ts.InterfaceDeclaration | ts.ClassDeclaration, name: string) {
458+
const example = this.getNodeExample(modelType);
459+
const description = this.getNodeDescription(modelType);
460+
461+
// Handle toJSON methods
462+
let toJSON: ts.ClassElement | ts.TypeElement | undefined;
463+
if (ts.isClassDeclaration(modelType)) {
464+
toJSON = modelType.members.find(member => member.name && member.name.getText() === 'toJSON');
465+
} else {
466+
toJSON = modelType.members.find(member => member.name && member.name.getText() === 'toJSON');
467+
}
468+
469+
if (toJSON && (ts.isMethodDeclaration(toJSON) || ts.isMethodSignature(toJSON))) {
470+
let nodeType = toJSON.type;
471+
if (!nodeType) {
472+
const typeChecker = this.current.typeChecker;
473+
const signature = typeChecker.getSignatureFromDeclaration(toJSON);
474+
const implicitType = typeChecker.getReturnTypeOfSignature(signature!);
475+
nodeType = typeChecker.typeToTypeNode(implicitType) as ts.TypeNode;
476+
}
477+
const type = new TypeResolver(nodeType, this.current).resolve();
478+
const referenceType: Tsoa.ReferenceType = {
479+
refName: this.getRefTypeName(name),
480+
dataType: 'refAlias',
481+
description,
482+
type,
483+
validators: {},
484+
...(example && { example }),
485+
};
486+
return referenceType;
487+
}
488+
481489
const properties = this.getModelProperties(modelType);
482490
const additionalProperties = this.getModelAdditionalProperties(modelType);
483491
const inheritedProperties = this.getModelInheritedProperties(modelType) || [];
484-
const example = this.getNodeExample(modelType);
485492

486493
const referenceType: Tsoa.ReferenceType = {
487494
additionalProperties,
488495
dataType: 'refObject',
489-
description: this.getNodeDescription(modelType),
496+
description,
490497
properties: inheritedProperties,
491498
refName: this.getRefTypeName(name),
492499
...(example && { example }),

tests/fixtures/controllers/getController.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Readable } from 'stream';
22
import { Controller, Example, Get, OperationId, Query, Request, Route, SuccessResponse, Tags } from '../../../src';
33
import '../duplicateTestModel';
4-
import { GenericModel, GetterClass, TestClassModel, TestModel, TestSubModel } from '../testModel';
4+
import { GenericModel, GetterClass, TestClassModel, TestModel, TestSubModel, GetterInterface } from '../testModel';
55
import { ModelService } from './../services/modelService';
66

77
@Route('GetTest')
@@ -63,6 +63,11 @@ export class GetTestController extends Controller {
6363
return new GetterClass();
6464
}
6565

66+
@Get('GetterInterface')
67+
public async getGetterInterface(): Promise<GetterInterface> {
68+
return {} as GetterInterface;
69+
}
70+
6671
@Get('Multi')
6772
public async getMultipleModels(): Promise<TestModel[]> {
6873
return [new ModelService().getModel(), new ModelService().getModel(), new ModelService().getModel()];

tests/fixtures/testModel.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -634,6 +634,10 @@ export class GetterClass {
634634
}
635635
}
636636

637+
export interface GetterInterface {
638+
toJSON(): { foo: string };
639+
}
640+
637641
export interface GenericModel<T = string> {
638642
result: T;
639643
union?: T | string;

tests/unit/swagger/schemaDetails3.spec.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -864,6 +864,25 @@ describe('Definition generation for OpenAPI 3.0.0', () => {
864864
type: 'object',
865865
});
866866

867+
const getterInterface = getComponentSchema('GetterInterface', currentSpec);
868+
expect(getterInterface).to.deep.eq({
869+
properties: {
870+
foo: {
871+
type: 'string',
872+
description: undefined,
873+
example: undefined,
874+
format: undefined,
875+
default: undefined,
876+
},
877+
},
878+
required: ['foo'],
879+
type: 'object',
880+
default: undefined,
881+
example: undefined,
882+
format: undefined,
883+
description: undefined,
884+
});
885+
867886
const omit = getComponentSchema('Omit_ErrorResponseModel.status_', currentSpec);
868887
expect(omit).to.deep.eq(
869888
{

0 commit comments

Comments
 (0)