openapi-generator icon indicating copy to clipboard operation
openapi-generator copied to clipboard

[typescript-axios] doesn't serialize deepObject query params

Open wamujlb opened this issue 5 years ago • 13 comments

Bug Report Checklist

  • [x] Have you provided a full/minimal spec to reproduce the issue?
  • [x] Have you validated the input using an OpenAPI validator (example)?
  • [x] Have you tested with the latest master to confirm the issue still exists?
  • [x] Have you searched for related issues/PRs?
  • [x] What's the actual output vs expected output? input: {day: 20, year: {min: 1990, max: 2000}} actual output: ?day=20&year expected output: ?day=20&year[min]=1990&year[max]=2000
Description

I'm using [typescript-axios] api client generator. Some of the properties I pass to it specified as deepObject. It looks that it doesn't support this type of args at all. Is there any possible ways to fix it?

openapi-generator version

v4.3.1 v5.0.0-beta

OpenAPI declaration file content or url
YearRange:
      in: query
      name: year
      required: false
      style: deepObject
      schema:
        type: object
        properties:
          min:
            type: string
            example: 2010
          max:
            type: string
            example: 2019
Generation Details

[typescript-axios] generator

Steps to reproduce
Related issues/PRs

https://github.com/OpenAPITools/openapi-generator/pull/6075

Suggest a fix

Replace old deprecated url API with URLSearchParams.

wamujlb avatar Oct 01 '20 12:10 wamujlb

👍 Thanks for opening this issue! 🏷 I have applied any labels matching special text in your issue.

The team will review the labels and make any necessary changes.

auto-labeler[bot] avatar Oct 01 '20 12:10 auto-labeler[bot]

@wamujlb do you have a snippet to work around this?

tsraza avatar Oct 02 '20 19:10 tsraza

i did this on the generated file

sed -i "s/localVarQueryParameter\['filter'\] \= filter/localVarQueryParameter\['filter'\] \= JSON.stringify(filter)/g" api.ts

tsraza avatar Dec 09 '20 21:12 tsraza

@tsraza this will not work in my case. API expects something like this:

{date: 'desc'} => ?sort[date]=desc.

wamujlb avatar Feb 16 '21 13:02 wamujlb

I have made a workaround but I think that it will be good to add it to the generator.

export const setSearchParams = function (url: URL, ...objects: any[]) {
    const searchParams = new URLSearchParams(url.search);
    for (const object of objects) {
        for (const key in object) {
            /**
             * @workaround
             * Convert object value to the string
             * {sort: {date: 'desc', order: 'asc'}} => 'sort[date]=desc&sort[order]=asc'
             */
            const value = object[key];

            if(typeof value === 'object'){
                Object.entries(value).forEach(([entryKey, entryValue]) => {
                    searchParams.set(`${key}[${entryKey}]`, String(entryValue));
                })
            } else {
                searchParams.set(key, value);
            }
        }
    }
    url.search = searchParams.toString();
}

wamujlb avatar Feb 16 '21 14:02 wamujlb

Any update on this?

ruiaraujo012 avatar Jan 18 '22 22:01 ruiaraujo012

Any update on this?

++, nothing changed

kamilkn avatar Mar 12 '22 23:03 kamilkn

I've just had a similar problem. Loopback 4 expects filter parameters as serialised JSON.

It can be solved in a 'nice' way using template overrides.

  • Create a template override directory eg. openapi-templates
  • Copy apiInner.mustacheinto openapi-templates from https://github.com/OpenAPITools/openapi-generator/blob/7ffd0711c3c3e7e08110a3e8d63e324daa1773d8/modules/openapi-generator/src/main/resources/typescript-axios/apiInner.mustache
  • Change the serialisation logic to your taste around lines 94 - 136. Search for localVarQueryParameter. My example is below.
  • Pass -t /path/to/openapi-templates to openapi-generator-cli
  • Rebuild

I modified the template to serialise any object passed as a parameter to JSON:

if(typeof {{paramName}} === 'object' && {{paramName}} !== null)
    localVarQueryParameter['{{baseName}}'] = JSON.stringify({{paramName}});
else
    localVarQueryParameter['{{baseName}}'] = {{paramName}};

@wamujlb - In your case, you could probably add your code to the template.

talss89 avatar May 29 '22 16:05 talss89

In my case I updated the common.mustache template with @wamujlb's answer. Its working as expected. But it really would be great to add this to the default templates.

RapGeneral avatar Jul 11 '22 07:07 RapGeneral

FYI since array is not being handled well, i updated based on @wamujlb answer

export const setSearchParams = function (url: URL, ...objects: any[]) {
    const searchParams = new URLSearchParams(url.search);
    for (const object of objects) {
        for (const key in object) {
            /**
             * @workaround for deep object
             * Convert object value to the string
             * {sort: {date: 'desc', order: 'asc', multiple: ['1','2']}} => 'sort[date]=desc&sort[order]=asc&multiple[]=1&multiple[]=2'
             */
            const value = object[key];

            if(typeof value === 'object'){
                Object.entries(value).forEach(([entryKey, entryValue]) => {
                    if(Array.isArray(entryValue)){
                        entryValue.forEach((item) => {
                            searchParams.append(`${key}[${entryKey}][]`, String(item));
                        })
                    }
                    else {
                        searchParams.set(`${key}[${entryKey}]`, String(entryValue));
                    } 
                })
            } else {
                searchParams.set(key, value);
            }
        }
    }
    url.search = searchParams.toString();
}

tokidoki11 avatar Jul 11 '23 04:07 tokidoki11

Any update on this?

abgora avatar Oct 30 '24 14:10 abgora

I'm not sure how my solution is correct, however, I needed to fix it, and here is what I did for that.

in the apiinner.mustache

I changed that

                {{^isPrimitiveType}}
                {{^isEnumRef}}
                {{^isEnum}}
                for (const [key, value] of Object.entries({{paramName}})) {
                    localVarQueryParameter[key] = value;
                }
                {{/isEnum}}
                {{/isEnumRef}}

for this

                  {{^isPrimitiveType}}
                    {{^isEnumRef}}
                    {{^isEnum}}
                    // deepObject serialization for nested params (e.g. filter[field]=…)
                    for (const [key, value] of Object.entries({{paramName}})) {
                        if (Array.isArray(value)) {
                            value.forEach(item => {
                                localVarQueryParameter[`{{baseName}}[${key}]`] = item;
                            });
                        } else {
                            localVarQueryParameter[`{{baseName}}[${key}]`] = value;
                        }
                    }
                    {{/isEnum}}
                    {{/isEnumRef}}

and then I give it up to the axios + qs to handle the parameters properly

import axios, { AxiosInstance, InternalAxiosRequestConfig, AxiosHeaders } from 'axios';
import qs from 'qs';

const sharedAxios: AxiosInstance = axios.create({
  paramsSerializer: (params) => qs.stringify(params, { encodeValuesOnly: true, arrayFormat: 'repeat' }),
});

which I also imported via modification of the baseApi.mustache

import sharedAxios from 'my-shared-axios-instance';
const globalAxios = sharedAxios;

It may not be a great solution because it may block me from the upgrade, but at least it works for me as expected.

nnn406 avatar May 01 '25 22:05 nnn406

Will this be fixed?

jameswillett avatar Dec 05 '25 20:12 jameswillett