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

[BUG] [JAVA] Using an object to pass query params ends up using the toString of that class #4808

Open
mtohmaz opened this issue Dec 16, 2019 · 24 comments · Fixed by #11682
Open

Comments

@mtohmaz
Copy link

mtohmaz commented Dec 16, 2019

Description

When specifying the query parameters for an endpoint as an object and generate a Java SDK using openapi-generator-cli, it will actually use the class's toString as the query parameter values instead. For example, here is a sample config I have for openapi version 3.

paths:
  /my_endpoint:
    get:
      tags:
        - "MyEndpoint"
      operationId: "getMyEndpoint"
      parameters:
        - in: query
          name: queryParams
          explode: true
          schema:
            type: object
            properties:
              to:
                type: string
              from:
                type: string

After generating the java SDK and make a request like so:

QueryParams queryParams = new QueryParams();
queryParams.setTo("Person");
MyEndpointSuccessResponse result = apiInstance.getMyEndpoint(queryParams);

The request is received by my endpoint, but this is what the query params look like:
{"query_params"=>"class QueryParams {\n to: Person\n from: null\n}"}

openapi-generator version

4.2.1-SNAPSHOT

Command line used for generation
docker run --rm -v ${PWD}:/local openapitools/openapi-generator-cli generate
-i ./local/swagger.yaml
-g java
-o ./local/java
-c ./local/config.json
@auto-labeler
Copy link

auto-labeler bot commented Dec 16, 2019

👍 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.

@mtohmaz
Copy link
Author

mtohmaz commented Dec 17, 2019

@lwlee2608 @Zomzog @karismann is there any update regarding this? Wanted to know if this is a known issue that will be fixed soon

@ShahBinoy
Copy link

I agree, this is an issue @wing328 same thing can be experienced in the issue #5545

@MatijaMitic
Copy link

MatijaMitic commented Mar 30, 2020

Maybe this can be fixed by modifying ApiClient#parameterToString((java.lang.Object) to use available Json.mapper#writeValueAsString(java.lang.Object) method instead of java.lang.String#valueOf(java.lang.Object).

@petitout
Copy link

petitout commented May 22, 2020

I'm interested in the fix. Any update/workaround for this issue ?

It has been fixed for typescript client here : #4407
#4404

@Dretch
Copy link
Contributor

Dretch commented Aug 13, 2020

There is a similar issue when sending POST data parameters that should be JSON-encoded - e.g.

  /some-path:
    post:
      requestBody:
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              properties:
                data:
                  type: object
                  properties:
                    someProp:
                      type: string
            encoding:
              data:
                contentType: application/json

This is valid in OpenAPI 3 according to https://swagger.io/docs/specification/describing-request-body/ (see "Complex Serialization in Form Data").

However, this does not work because the data POST body parameter gets sent as its Java toString representation rather than its JSON-encoded form.

@ermannob
Copy link

ermannob commented Dec 8, 2020

+1

@sdahlberg
Copy link

I'm experiencing the same issue, though I'm not sure what @mtohmaz would expect regarding the key. I would expect the client to translate it to /my_endpoint?from=zero&to=ten.

This lines up with how the parameters are serialized in a spring backend, since #2655 is fixed.

A recent issue is #8352, which addresses the same issue, but then without specifying explicit properties on the object. In that case a Map is generated. The PR over there won't fix this issue.

@EmhyrVarEmreis
Copy link
Contributor

I am also experiencing this issue, is anybody trying to fix it with a PR?

@dtha1002
Copy link

I also have this problem, I have to edit the source code after generated.
Object -> Map<String, String>

@sorin-florea
Copy link
Contributor

I opened a PR that should fix this issue.

@Hollerweger
Copy link

Hollerweger commented May 5, 2022

I still see this issue in latest release. When will the fix be released?

@tomcruise81
Copy link

tomcruise81 commented Oct 6, 2022

Seems to still be an issue, and is presumably related to #907

The allSearchCriteria query parameter in the searchFolderGet operation is still getting serialized via ApiClient.parameterToPairs(...)ApiClient.parameterToString(...)String.valueOf(...)

Here's my stripped down API definition in which I end up seeing it as well:

{
    "openapi": "3.0.1",
    "info": {
        "title": "OpenAPI definition",
        "version": "v0"
    },
    "servers": [
        {
            "url": "http://localhost:8080",
            "description": "Generated server url"
        }
    ],
    "paths": {
        "/v2/folders/{folderName}/search": {
            "get": {
                "operationId": "searchFolderGet",
                "parameters": [
                    {
                        "description": "Case-sensitive folder name",
                        "in": "path",
                        "name": "folderName",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "description": "All search criteria to use for searching",
                        "in": "query",
                        "name": "allSearchCriteria",
                        "required": true,
                        "schema": {
                            "type": "array",
                            "items": {
                                "$ref": "#/components/schemas/SearchCriteria"
                            }
                        },
                        "style": "form"
                    }
                ],
                "responses": {
                    "200": {
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/SearchResults"
                                }
                            }
                        },
                        "description": "OK"
                    }
                },
                "summary": "Search the 'folderName' folder for documents based on the provided search criteria"
            }
        }
    },
    "components": {
        "schemas": {
            "Hit": {
                "type": "object",
                "properties": {
                    "content": {
                        "type": "array",
                        "items": {
                            "type": "string",
                            "format": "byte"
                        }
                    },
                    "displayValues": {
                        "type": "object",
                        "additionalProperties": {
                            "type": "string"
                        }
                    },
                    "docId": {
                        "type": "string"
                    },
                    "mimeType": {
                        "type": "string",
                        "enum": [
                            "PDF",
                            "TIFF",
                            "ZIP"
                        ]
                    }
                }
            },
            "SearchCriteria": {
                "title": "SearchCriteria",
                "required": [
                    "name",
                    "operator",
                    "searchValue1"
                ],
                "type": "object",
                "properties": {
                    "name": {
                        "title": "name",
                        "type": "string",
                        "description": "Search criteria name"
                    },
                    "operator": {
                        "title": "operator",
                        "type": "string",
                        "description": "Search criteria operator",
                        "enum": [
                            "EQUAL",
                            "NOT_EQUAL",
                            "LESS_THAN",
                            "LESS_THAN_EQUAL",
                            "GREATER_THAN",
                            "GREATER_THAN_EQUAL",
                            "IN",
                            "NOT_IN",
                            "LIKE",
                            "NOT_LIKE",
                            "BETWEEN",
                            "NOT_BETWEEN"
                        ]
                    },
                    "searchValue1": {
                        "title": "searchValue1",
                        "type": "string",
                        "description": "Search criteria search value 1"
                    },
                    "searchValue2": {
                        "title": "searchValue2",
                        "type": "string",
                        "description": "Search criteria search value 2 (optional)",
                        "nullable": true
                    }
                },
                "description": "A single search criteria entity"
            },
            "SearchResults": {
                "type": "object",
                "properties": {
                    "hits": {
                        "type": "array",
                        "items": {
                            "$ref": "#/components/schemas/Hit"
                        }
                    },
                    "searchMessage": {
                        "type": "string"
                    }
                }
            }
        }
    }
}

I've tried different style values, as well as explode to no avail.

@sorin-florea
Copy link
Contributor

@tomcruise81 Which generator are you using?

@tomcruise81
Copy link

@tomcruise81
Copy link

@sorin-florea - any ideas?

@BenderRodrigez
Copy link

I fell into the same issue with latest generator docker image and retrofit2/default java generators. I first tried on 6.2.1 with no success. Here is an example of generated request:

https://example.io/api/v1/customers?pageable=class%20Pageable%20%7B%0A%20%20%20%20page%3A%200%0A%20%20%20%20size%3A%20null%0A%20%20%20%20sort%3A%20%5Bid%5D%0A%7D&platform=...

@sorin-florea
Copy link
Contributor

@tomcruise81 sorry if I was not clear, do you use the native or apachehttpclient generators? Or something else?

@sorin-florea
Copy link
Contributor

sorin-florea commented Jan 16, 2023

@BenderRodrigez, this is not fixed for retrofit. The native generator should work though it still has some edge cases in which it doesn't properly work (using allOf, for example). If the native one doesn't work, could you try the master snapshot, as some fixes have already been merged. You can find the snapshots here.

Ping me on the openapi-generator slack if you need any help with it.

@tomcruise81
Copy link

@tomcruise81 sorry if I was not clear, do you use the native or apachehttpclient generators? Or something else?

I was providing:

    "library": "resttemplate",
    "openApiNullable": false

@sorin-florea
Copy link
Contributor

@tomcruise81 only native and apachehttp are fixed.

@tomcruise81
Copy link

@wing328 - as stated by @sorin-florea, this issue isn't fully resolved. Please re-open it as it has only been partially fixed.

Thanks!

@stephenmontgomery
Copy link

Doesn't work with java weblient generation in openapi-generator-maven-plugin:6.40. Generates same error as #4808 (comment) ie ?pageable=class%20Pageable%20%7B%0A%20%20%20%20page%3A%200%0A%20%20%20%20size%3A%20100%0A%20%20%20%20sort%3A%20%5Bname%5D%0A%7D

In Swagger UI it creates the expected ?page=0&size=20&sort=name

Caused by: org.springframework.core.codec.DecodingException: JSON decoding error: Cannot deserialize value of type `com.contrastsecurity.authz.client.model.SortObject` from Array value (token `JsonToken.START_ARRAY`); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type `com.contrastsecurity.authz.client.model.SortObject` from Array value (token `JsonToken.START_ARRAY`)
 at [Source: (io.netty.buffer.ByteBufInputStream); line: 1, column: 426] (through reference chain: com.contrastsecurity.authz.client.model.PageUserAccessGroupResponse["pageable"]->com.contrastsecurity.authz.client.model.PageableObject["sort"])
	at org.springframework.http.codec.json.AbstractJackson2Decoder.processException(AbstractJackson2Decoder.java:242)
	Suppressed: The stacktrace has been enhanced by Reactor, refer to additional information below: 
Error has been observed at the following site(s):
	*__checkpoint ⇢ Body from GET http://localhost:10005/organizations/f4546458-72ae-4784-a7e2-a440761fb92e/user-access-groups/user/388f1310-7dfd-44f6-82ad-be7e8997c08c?pageable=class%20Pageable%20%7B%0A%20%20%20%20page%3A%200%0A%20%20%20%20size%3A%20100%0A%20%20%20%20sort%3A%20%5Bname%5D%0A%7D [DefaultClientResponse]
Original Stack Trace:
		at org.springframework.http.codec.json.AbstractJackson2Decoder.processException(AbstractJackson2Decoder.java:242)
		at org.springframework.http.codec.json.AbstractJackson2Decoder.decode(AbstractJackson2Decoder.java:198)
		at org.springframework.http.codec.json.AbstractJackson2Decoder.lambda$decodeToMono$1(AbstractJackson2Decoder.java:179)
		at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:125)
		at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onNext(FluxContextWrite.java:107)
		at reactor.core.publisher.FluxMapFuseable$MapFuseableConditionalSubscriber.onNext(FluxMapFuseable.java:299)
		at reactor.core.publisher.FluxFilterFuseable$FilterFuseableConditionalSubscriber.onNext(FluxFilterFuseable.java:337)
		at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1839)
		at reactor.core.publisher.MonoCollect$CollectSubscriber.onComplete(MonoCollect.java:160)
		at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:144)
		at reactor.core.publisher.FluxPeek$PeekSubscriber.onComplete(FluxPeek.java:260)
		at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:144)
		at reactor.netty.channel.FluxReceive.onInboundComplete(FluxReceive.java:415)
		at reactor.netty.channel.ChannelOperations.onInboundComplete(ChannelOperations.java:424)
		at reactor.netty.channel.ChannelOperations.terminate(ChannelOperations.java:478)
		at reactor.netty.http.client.HttpClientOperations.onInboundNext(HttpClientOperations.java:712)
		at reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:113)
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444)
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
		at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
		at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:103)
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444)
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
		at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
		at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:436)
		at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:346)
		at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:318)
		at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:251)
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442)
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
		at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
		at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440)
		at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
		at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
		at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166)
		at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:788)
		at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:724)
		at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:650)
		at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:562)
		at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997)
		at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
		at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
		at java.base/java.lang.Thread.run(Thread.java:833)
	Suppressed: java.lang.Exception: #block terminated with an error
		at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:139)
		at reactor.core.publisher.Mono.block(Mono.java:1766)

@GregDThomas
Copy link
Contributor

If you're using WebClient, you want the bug over at #14691

@wing328 wing328 reopened this Mar 31, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.