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

Using Open API, the TypeScript client creates a default child model if the (optional) property is undefined in the response #1910

Closed
cjzzz opened this issue Jan 22, 2019 · 1 comment

Comments

@cjzzz
Copy link

cjzzz commented Jan 22, 2019

Using NSwagStudio (v12.0.12.0) to generate a TypeScript client using the default settings with an OpenAPI specification, a default child instance is created for any objects with optional children not defined in the API's response. This does not happen when using a Swagger specification.

Swagger spec:

{
  "swagger": "2.0",
  "info": {
    "title": "API",
    "version": "v1"
  },
  "paths": {
    "/test": {
      "get": {
        "tags": [
          "Test"
        ],
        "operationId": "Test",
        "responses": {
          "200": {
            "description": "Success",
            "schema": {
              "$ref": "#/components/schemas/ParentDto"
            }
          }
        }
      }
    }
  },
  "definitions": {
    "ParentDto": {
      "properties": {
        "child": {
          "$ref": "#/definitions/ChildDto"
        }
      }
    },
    "ChildDto": {
      "properties": {
        "property": {
          "type": "string"
        }
      }
    }
  }
}

OpenAPI spec:

{
  "openapi": "3.0.1",
  "info": {
    "title": "API",
    "version": "v1"
  },
  "paths": {
    "/test": {
      "get": {
        "tags": [
          "Test"
        ],
        "operationId": "Test",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ParentDto"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "ParentDto": {
        "type": "object",
        "properties": {
          "child": {
            "$ref": "#/components/schemas/ChildDto"
          }
        }
      },
      "ChildDto": {
        "type": "object",
        "properties": {
          "property": {
            "type": "string"
          }
        }
      }
    }
  }
}

TypeScript client generated by Swagger spec:

/* tslint:disable */
//----------------------
// <auto-generated>
//     Generated using the NSwag toolchain v12.0.12.0 (NJsonSchema v9.13.15.0 (Newtonsoft.Json v11.0.0.0)) (http://NSwag.org)
// </auto-generated>
//----------------------
// ReSharper disable InconsistentNaming

export class Client {
    private http: { fetch(url: RequestInfo, init?: RequestInit): Promise<Response> };
    private baseUrl: string;
    protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined;

    constructor(baseUrl?: string, http?: { fetch(url: RequestInfo, init?: RequestInit): Promise<Response> }) {
        this.http = http ? http : <any>window;
        this.baseUrl = baseUrl ? baseUrl : "";
    }

    /**
     * @return Success
     */
    test(): Promise<ParentDto> {
        let url_ = this.baseUrl + "/test";
        url_ = url_.replace(/[?&]$/, "");

        let options_ = <RequestInit>{
            method: "GET",
            headers: {
                "Accept": "application/json"
            }
        };

        return this.http.fetch(url_, options_).then((_response: Response) => {
            return this.processTest(_response);
        });
    }

    protected processTest(response: Response): Promise<ParentDto> {
        const status = response.status;
        let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
        if (status === 200) {
            return response.text().then((_responseText) => {
            let result200: any = null;
            let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
            result200 = resultData200 ? ParentDto.fromJS(resultData200) : new ParentDto();
            return result200;
            });
        } else if (status !== 200 && status !== 204) {
            return response.text().then((_responseText) => {
            return throwException("An unexpected server error occurred.", status, _responseText, _headers);
            });
        }
        return Promise.resolve<ParentDto>(<any>null);
    }
}

export class ParentDto implements IParentDto {
    child?: ChildDto | undefined;

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

    init(data?: any) {
        if (data) {
            this.child = data["child"] ? ChildDto.fromJS(data["child"]) : <any>undefined;
        }
    }

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

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

export interface IParentDto {
    child?: ChildDto | undefined;
}

export class ChildDto implements IChildDto {
    property?: string | undefined;

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

    init(data?: any) {
        if (data) {
            this.property = data["property"];
        }
    }

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

    toJSON(data?: any) {
        data = typeof data === 'object' ? data : {};
        data["property"] = this.property;
        return data; 
    }
}

export interface IChildDto {
    property?: string | undefined;
}

export class SwaggerException extends Error {
    message: string;
    status: number; 
    response: string; 
    headers: { [key: string]: any; };
    result: any; 

    constructor(message: string, status: number, response: string, headers: { [key: string]: any; }, result: any) {
        super();

        this.message = message;
        this.status = status;
        this.response = response;
        this.headers = headers;
        this.result = result;
    }

    protected isSwaggerException = true;

    static isSwaggerException(obj: any): obj is SwaggerException {
        return obj.isSwaggerException === true;
    }
}

function throwException(message: string, status: number, response: string, headers: { [key: string]: any; }, result?: any): any {
    if(result !== null && result !== undefined)
        throw result;
    else
        throw new SwaggerException(message, status, response, headers, null);
}

TypeScript client generated by OpenAPI spec:

/* tslint:disable */
//----------------------
// <auto-generated>
//     Generated using the NSwag toolchain v12.0.12.0 (NJsonSchema v9.13.15.0 (Newtonsoft.Json v11.0.0.0)) (http://NSwag.org)
// </auto-generated>
//----------------------
// ReSharper disable InconsistentNaming

export class Client {
    private http: { fetch(url: RequestInfo, init?: RequestInit): Promise<Response> };
    private baseUrl: string;
    protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined;

    constructor(baseUrl?: string, http?: { fetch(url: RequestInfo, init?: RequestInit): Promise<Response> }) {
        this.http = http ? http : <any>window;
        this.baseUrl = baseUrl ? baseUrl : "";
    }

    /**
     * @return Success
     */
    test(): Promise<ParentDto> {
        let url_ = this.baseUrl + "/test";
        url_ = url_.replace(/[?&]$/, "");

        let options_ = <RequestInit>{
            method: "GET",
            headers: {
                "Accept": "application/json"
            }
        };

        return this.http.fetch(url_, options_).then((_response: Response) => {
            return this.processTest(_response);
        });
    }

    protected processTest(response: Response): Promise<ParentDto> {
        const status = response.status;
        let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
        if (status === 200) {
            return response.text().then((_responseText) => {
            let result200: any = null;
            let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
            result200 = resultData200 ? ParentDto.fromJS(resultData200) : new ParentDto();
            return result200;
            });
        } else if (status !== 200 && status !== 204) {
            return response.text().then((_responseText) => {
            return throwException("An unexpected server error occurred.", status, _responseText, _headers);
            });
        }
        return Promise.resolve<ParentDto>(<any>null);
    }
}

export class ParentDto implements IParentDto {
    child?: ChildDto;

    constructor(data?: IParentDto) {
        if (data) {
            for (var property in data) {
                if (data.hasOwnProperty(property))
                    (<any>this)[property] = (<any>data)[property];
            }
        }
        if (!data) {
            this.child = new ChildDto();
        }
    }

    init(data?: any) {
        if (data) {
            this.child = data["child"] ? ChildDto.fromJS(data["child"]) : new ChildDto();
        }
    }

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

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

export interface IParentDto {
    child?: ChildDto;
}

export class ChildDto implements IChildDto {
    property?: string;

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

    init(data?: any) {
        if (data) {
            this.property = data["property"];
        }
    }

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

    toJSON(data?: any) {
        data = typeof data === 'object' ? data : {};
        data["property"] = this.property;
        return data; 
    }
}

export interface IChildDto {
    property?: string;
}

export class SwaggerException extends Error {
    message: string;
    status: number; 
    response: string; 
    headers: { [key: string]: any; };
    result: any; 

    constructor(message: string, status: number, response: string, headers: { [key: string]: any; }, result: any) {
        super();

        this.message = message;
        this.status = status;
        this.response = response;
        this.headers = headers;
        this.result = result;
    }

    protected isSwaggerException = true;

    static isSwaggerException(obj: any): obj is SwaggerException {
        return obj.isSwaggerException === true;
    }
}

function throwException(message: string, status: number, response: string, headers: { [key: string]: any; }, result?: any): any {
    if(result !== null && result !== undefined)
        throw result;
    else
        throw new SwaggerException(message, status, response, headers, null);
}

The TypeScript client generated with OpenAPI has an extra block in the constructor of ParentDto to create a new ChildDto:

        if (!data) {
            this.child = new ChildDto();
        }

and also creates a new ChildDto in the init method:

            this.child = data["child"] ? ChildDto.fromJS(data["child"]) : new ChildDto();

whereas it would not before:

            this.child = data["child"] ? ChildDto.fromJS(data["child"]) : <any>undefined;
@RicoSuter
Copy link
Owner

Created a PR to fix this: RicoSuter/NJsonSchema#882

But I need to check with more projects as this might be/is a breaking change.

RicoSuter added a commit that referenced this issue Jan 23, 2019
RicoSuter added a commit that referenced this issue Jan 23, 2019
* No FileResponse for 204 status code , closes #1890

* Improve

* Use operation

* Updated NJS, #1910
dougbu pushed a commit to dougbu/NSwag that referenced this issue Apr 20, 2019
* No FileResponse for 204 status code , closes RicoSuter#1890

* Improve

* Use operation

* Updated NJS, RicoSuter#1910
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants