Skip to content

Properties passed via the constructor do not always get set. #5116

@hedgybeats

Description

@hedgybeats

Describe the bug

When instantiating new Typescript classes by passing data to constructor, some properties do not get set if that class extends another class. (Only the properties of the extended class get set when passing data via the constructor).

Version used

V14.2.0

To Reproduce

Server side classes:

public class PaginationFilter: BaseFilter
{
    public int PageNumber { get; set; }

    public int PageSize { get; set; } = int.MaxValue;
}

public class BaseFilter
{
    public string Keyword { get; set; }
}

Code generation settings:

        var codeGenerationSettings = new TypeScriptClientGeneratorSettings
        {
            ClassName = "{controller}Client",
            GenerateClientClasses = true,
            Template = TypeScriptTemplate.Angular,
            RxJsVersion = 7.0M,
            UseSingletonProvider = true,
            IncludeHttpContext = false,
            HttpClass = HttpClass.HttpClient,
            WithCredentials = false,
            InjectionTokenType = InjectionTokenType.InjectionToken,
            BaseUrlTokenName = baseUrlTokenName,
            OperationNameGenerator = new MultipleClientsFromOperationIdOperationNameGenerator(),
            ClientBaseClass = string.Empty,
            QueryNullValue = string.Empty,
            GenerateClientInterfaces = false,
            GenerateOptionalParameters = false,
            ExcludedParameterNames = [],
            ExceptionClass = "ApiException",
            WrapDtoExceptions = false,
            WrapResponses = false,
            UseTransformOptionsMethod = false,
            UseTransformResultMethod = false,
            WrapResponseMethods = [],
            GenerateDtoTypes = true,
            ImportRequiredTypes = true
        };

        codeGenerationSettings.TypeScriptGeneratorSettings.ModuleName = moduleName;
        codeGenerationSettings.TypeScriptGeneratorSettings.Namespace = string.Empty;
        codeGenerationSettings.TypeScriptGeneratorSettings.TypeScriptVersion = 4.3M;
        codeGenerationSettings.TypeScriptGeneratorSettings.InlineNamedDictionaries = false;
        codeGenerationSettings.TypeScriptGeneratorSettings.InlineNamedAny = false;
        codeGenerationSettings.TypeScriptGeneratorSettings.ExportTypes = true;
        codeGenerationSettings.TypeScriptGeneratorSettings.TypeStyle = TypeScriptTypeStyle.Class;
        codeGenerationSettings.TypeScriptGeneratorSettings.EnumStyle = TypeScriptEnumStyle.Enum;
        codeGenerationSettings.TypeScriptGeneratorSettings.UseLeafType = false;
        codeGenerationSettings.TypeScriptGeneratorSettings.NullValue = TypeScriptNullValue.Undefined;
        codeGenerationSettings.TypeScriptGeneratorSettings.DateTimeType = TypeScriptDateTimeType.Date;
        codeGenerationSettings.TypeScriptGeneratorSettings.GenerateDefaultValues = true;
        codeGenerationSettings.TypeScriptGeneratorSettings.MarkOptionalProperties = true;
        codeGenerationSettings.TypeScriptGeneratorSettings.GenerateCloneMethod = false;
        codeGenerationSettings.TypeScriptGeneratorSettings.ExcludedTypeNames = [];
        codeGenerationSettings.TypeScriptGeneratorSettings.HandleReferences = false;
        codeGenerationSettings.TypeScriptGeneratorSettings.GenerateConstructorInterface = true;
        codeGenerationSettings.TypeScriptGeneratorSettings.ConvertConstructorInterfaceData = false;
        codeGenerationSettings.TypeScriptGeneratorSettings.ExtendedClasses = [];
        codeGenerationSettings.TypeScriptGeneratorSettings.TemplateDirectory = null;

Auto-generated output:

export class BaseFilter implements IBaseFilter {
    advancedSearch?: Search | undefined;
    keyword?: string;
    advancedFilter?: Filter | undefined;

    constructor(data?: IBaseFilter) {
        if (data) {
            for (var property in data) {
                if (data.hasOwnProperty(property))
                    (<any>this)[property] = (<any>data)[property];
            }
        }
    }

    init(_data?: any) {
        if (_data) {
            this.advancedSearch = _data["advancedSearch"] ? Search.fromJS(_data["advancedSearch"]) : <any>undefined;
            this.keyword = _data["keyword"];
            this.advancedFilter = _data["advancedFilter"] ? Filter.fromJS(_data["advancedFilter"]) : <any>undefined;
        }
    }

    static fromJS(data: any): BaseFilter {
        data = typeof data === 'object' ? data : {};
        let result = new BaseFilter();
        result.init(data);
        return result;
    }

    toJSON(data?: any) {
        data = typeof data === 'object' ? data : {};
        data["advancedSearch"] = this.advancedSearch ? this.advancedSearch.toJSON() : <any>undefined;
        data["keyword"] = this.keyword;
        data["advancedFilter"] = this.advancedFilter ? this.advancedFilter.toJSON() : <any>undefined;
        return data;
    }
}

export interface IBaseFilter {
    advancedSearch?: Search | undefined;
    keyword?: string;
    advancedFilter?: Filter | undefined;
}

export class PaginationFilter extends BaseFilter implements IPaginationFilter {
    pageNumber?: number;
    pageSize?: number;
    orderBy?: string[];

    constructor(data?: IPaginationFilter) {
        super(data);
    }

    override init(_data?: any) {
        super.init(_data);
        if (_data) {
            this.pageNumber = _data["pageNumber"];
            this.pageSize = _data["pageSize"];
            if (Array.isArray(_data["orderBy"])) {
                this.orderBy = [] as any;
                for (let item of _data["orderBy"])
                    this.orderBy!.push(item);
            }
        }
    }

    static override fromJS(data: any): PaginationFilter {
        data = typeof data === 'object' ? data : {};
        let result = new PaginationFilter();
        result.init(data);
        return result;
    }

    override toJSON(data?: any) {
        data = typeof data === 'object' ? data : {};
        data["pageNumber"] = this.pageNumber;
        data["pageSize"] = this.pageSize;
        if (Array.isArray(this.orderBy)) {
            data["orderBy"] = [];
            for (let item of this.orderBy)
                data["orderBy"].push(item);
        }
        super.toJSON(data);
        return data;
    }
}

export interface IPaginationFilter extends IBaseFilter {
    pageNumber?: number;
    pageSize?: number;
    orderBy?: string[];
}

Typescript usage:

const filter = new ApiClient.PaginationFilter({
    keyword: 'Search String',
    pageSize: 10,
    pageNumber: 1
});

console.assert(filter.pageSize === 10); // filter.pageSize stays undefined
console.assert(filter.pageNumber === 1); // filter.pageNumber stays undefined

Expected behavior

I expect that filter.pageSize and filter.pageNumber get assigned the values that are passed via the constructor.

Additional context

While all the data that is initially passed is forwarded to the baseFilter class constructor:

    constructor(data?: IPaginationFilter) {
        super(data);
    }

The IBaseFilter type ends up hiding the 2 properties that would exist in the IPaginationFilter type:

    constructor(data?: IBaseFilter) {
        if (data) {
            for (var property in data) {
                if (data.hasOwnProperty(property))
                    (<any>this)[property] = (<any>data)[property];
            }
        }
    }

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions