question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[REQ] [typescript-angular]: support for object as query parameters

See original GitHub issue

Is your feature request related to a problem? Please describe.

At present, typescript-angular only supports arrays and simple types (string, number, Date) as query parameters. However, when you use openapi-generator in conjuction with springdoc and (java) spring, you wish to have support for objects as query parameters as well.

In spring-data, there is REST support for an idea called Pageable. This would be the openapi schema for it:

      "Pageable": {
        "nullable": false,
        "type": "object",
        "required": [
          "size",
          "page"
        ],
        "properties": {
          "size": {
            "format": "int32",
            "type": "integer"
          },
          "sort": {"$ref": "#/components/schemas/Sort"},
          "page": {
            "format": "int32",
            "type": "integer"
          }
        }
      },
      "Sort": {
        "nullable": false,
        "type": "array",
        "items": {"type": "string"}
      },

The items of Sort are the names of the properties to sort the data with. Optionally, there could be a sort direction (ASC, DESC) appended like in ansatz,DESC. The comma (,) should not be urlencoded.

What I want is sane support for Pageable and other objects.

Describe the solution you’d like

Lets think about a (GET) REST endpoint that takes an Pageable and a map with parameters as query parameters. openapi-generator will emits something like:

    public getAllAnsatz(pageable: Pageable, map: { [key: string]: Array<string>; }, observe?: 'body', reportProgress?: boolean): Observable<Array<AnsatzDTO>>;
    public getAllAnsatz(pageable: Pageable, map: { [key: string]: Array<string>; }, observe?: 'response', reportProgress?: boolean): Observable<HttpResponse<Array<AnsatzDTO>>>;
    public getAllAnsatz(pageable: Pageable, map: { [key: string]: Array<string>; }, observe?: 'events', reportProgress?: boolean): Observable<HttpEvent<Array<AnsatzDTO>>>;
    public getAllAnsatz(pageable: Pageable, map: { [key: string]: Array<string>; }, observe: any = 'body', reportProgress: boolean = false ): Observable<any> {
        if (pageable === null || pageable === undefined) {
            throw new Error('Required parameter pageable was null or undefined when calling getAllAnsatzfreigabes.');
        }
        if (map === null || map === undefined) {
            throw new Error('Required parameter map was null or undefined when calling getAllAnsatzfreigabes.');
        }

        let queryParameters = new HttpParams({encoder: this.encoder});
        if (pageable !== undefined && pageable !== null) {
            queryParameters = queryParameters.set('pageable', <any>pageable);
        }
        if (map !== undefined && map !== null) {
            queryParameters = queryParameters.set('map', <any>map);
        }

Certainly, this will not lead to the desired result, as implicit toString is called on pageable and map.

The idea is to have a method that will act according to the dynamic type of the variable that should be added to queryParameters:

        let queryParameters = new HttpParams({encoder: this.encoder});
        if (pageable !== undefined && pageable !== null) {
            queryParameters = this.addToHttpParams(queryParameters, <any>pageable, 'pageable')
        }
        if (map !== undefined && map !== null) {
            queryParameters = this.addToHttpParams(queryParameters, <any>map, 'map')
        }

Proposed implementation of addToHttpParams

    private addToHttpParams(httpParams: HttpParams, value: any, key?: string): HttpParams {
        if (typeof value === "object") {
            httpParams = this.addToHttpParams1(httpParams, value);
        } else {
            httpParams = this.addToHttpParams1(httpParams, value, key);
        }
        return httpParams;
    }

    private addToHttpParams1(httpParams: HttpParams, value: any, key?: string): HttpParams {
        if (typeof value === "object") {
            if (this.isArrayLike(value)) {
                (value as []).forEach( elem => httpParams = this.addToHttpParams1(httpParams, elem, key));
            } else if (value instanceof Date) {
                if (key != null) {
                    httpParams = httpParams.append(key,
                        (value as Date).toISOString().substr(0, 10));
                } else {
                   throw Error("key may not be null if value is Date");
                }
            } else {
                Object.keys(value).forEach( k => httpParams = this.addToHttpParams1(
                    httpParams, value[k], key != null ? `${key}.${k}` : k));
            }
        } else if (key != null) {
            httpParams = httpParams.append(key, value);
        } else {
            throw Error("key may not be null if value is not object or array");
        }
        return httpParams;
    }

    private isArrayLike(obj: any): boolean {
        return (Array.isArray(obj) ||
            typeof obj === "object" &&
            typeof (obj.length) === "number" &&
            (obj.length === 0 ||
                (obj.length > 0 && (obj.length - 1) in obj))
        );
  }

The implementation will flatten the top level object to properties, but nested are added with . Syntax to the key (like in toplevelProperty.propertyOfNestedObject). Arrays and simple types are treated like before (no change). The advantage of the new solution is that REST endpoints from spring-data are supported out of the box.

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Comments:7 (3 by maintainers)

github_iconTop GitHub Comments

2reactions
aanno2commented, Feb 4, 2021

The implementation will flatten the top level object to properties

@kostadin-mandulov You are right, and I proposed it this way on purpose: This is the way (java) spring-data is supporting Pageable out of the box, and I was mainly interested in Pageable support.

I never thought about several objects as query parameters. Flattening is one solution, but wrapping your objects in one root object is also possible!

1reaction
kostadin-mandulovcommented, Jan 27, 2021

@macjohnny thanks for confirming. I wouldn’t say it’s a bug, but a rather unfortuantely behaving feature 😅 No point in changing it, especially considering the amount of people affected. May be something to note going forward.

We have already worked around the issue by flattening the API parameters themselves (e.g. object1Key, object2Key).

Read more comments on GitHub >

github_iconTop Results From Across the Web

Angular 4 HttpClient Query Parameters - Stack Overflow
The best way I have found thus far is to define a Params object with all of the parameters I want to define...
Read more >
How To Use Query Parameters with Angular Router
Query parameters in Angular allow for passing optional parameters across any route in the application. Query parameters are different from ...
Read more >
Communicating with backend services using HTTP - Angular
The HTTP client service offers the following major features. ... Use the params property to configure a request with HTTP URL parameters, and...
Read more >
$http - AngularJS: API
The $http service is a function which takes a single argument — a configuration object — that is used to generate an HTTP...
Read more >
Angular HTTP GET request with parameters example
Import HttpParams from @angular/common/http · Create a HttpParams() object. · Append the parameters to the query parameters object using HttpParams(). · Re-assign ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found