Skip to content

Commit d269a2a

Browse files
authored
Add deepObject query string support in Java native client (#14378)
* add deepObject query string support in java native client * fix array of query parameters * minor fix * update samples * fix test
1 parent b22bf0a commit d269a2a

File tree

131 files changed

+4587
-36
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

131 files changed

+4587
-36
lines changed

modules/openapi-generator/src/main/resources/Java/libraries/native/api.mustache

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -338,21 +338,24 @@ public class {{classname}} {
338338

339339
{{#hasQueryParams}}
340340
{{javaUtilPrefix}}List<Pair> localVarQueryParams = new {{javaUtilPrefix}}ArrayList<>();
341+
StringJoiner localVarQueryDeepObjectStringJoiner = new StringJoiner("&");
342+
String localVarQueryParameterBaseName;
341343
{{#queryParams}}
344+
localVarQueryParameterBaseName = "{{{baseName}}}";
342345
{{#collectionFormat}}
343346
localVarQueryParams.addAll(ApiClient.parameterToPairs("{{{collectionFormat}}}", "{{baseName}}", {{paramName}}));
344347
{{/collectionFormat}}
345348
{{^collectionFormat}}
346349
{{#isDeepObject}}
347350
if ({{paramName}} != null) {
348-
{{#items.vars}}
349-
{{#isArray}}
350-
localVarQueryParams.addAll(ApiClient.parameterToPairs("csv", "{{paramName}}[{{name}}]", {{paramName}}.{{getter}}()));
351-
{{/isArray}}
352-
{{^isArray}}
353-
localVarQueryParams.addAll(ApiClient.parameterToPairs("{{paramName}}[{{name}}]", {{paramName}}.{{getter}}()));
354-
{{/isArray}}
355-
{{/items.vars}}
351+
{{#isArray}}
352+
for (int i=0; i < {{paramName}}.size(); i++) {
353+
localVarQueryDeepObjectStringJoiner.add({{paramName}}.get(i).toUrlQueryString(String.format("{{baseName}}[%d]", i)));
354+
}
355+
{{/isArray}}
356+
{{^isArray}}
357+
localVarQueryDeepObjectStringJoiner.add({{paramName}}.toUrlQueryString("{{baseName}}"));
358+
{{/isArray}}
356359
}
357360
{{/isDeepObject}}
358361
{{^isDeepObject}}
@@ -378,9 +381,12 @@ public class {{classname}} {
378381
{{/collectionFormat}}
379382
{{/queryParams}}
380383

381-
if (!localVarQueryParams.isEmpty()) {
384+
if (!localVarQueryParams.isEmpty() || localVarQueryDeepObjectStringJoiner.length() != 0) {
382385
{{javaUtilPrefix}}StringJoiner queryJoiner = new {{javaUtilPrefix}}StringJoiner("&");
383386
localVarQueryParams.forEach(p -> queryJoiner.add(p.getName() + '=' + p.getValue()));
387+
if (localVarQueryDeepObjectStringJoiner.length() != 0) {
388+
queryJoiner.add(localVarQueryDeepObjectStringJoiner.toString());
389+
}
384390
localVarRequestBuilder.uri(URI.create(memberVarBaseUri + localVarPath + '?' + queryJoiner.toString()));
385391
} else {
386392
localVarRequestBuilder.uri(URI.create(memberVarBaseUri + localVarPath));

modules/openapi-generator/src/main/resources/Java/libraries/native/model.mustache

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,13 @@ import com.fasterxml.jackson.annotation.JsonAnySetter;
1616
{{/additionalPropertiesType}}
1717
{{/model}}
1818
{{/models}}
19+
import java.net.URLEncoder;
20+
import java.nio.charset.StandardCharsets;
1921
import java.util.Objects;
2022
import java.util.Arrays;
2123
import java.util.Map;
2224
import java.util.HashMap;
25+
import java.util.StringJoiner;
2326
{{#imports}}
2427
import {{import}};
2528
{{/imports}}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
{{#jackson}}
2+
import com.fasterxml.jackson.annotation.JsonCreator;
3+
import com.fasterxml.jackson.annotation.JsonValue;
4+
{{/jackson}}
5+
{{#gson}}
6+
import java.io.IOException;
7+
import com.google.gson.TypeAdapter;
8+
import com.google.gson.annotations.JsonAdapter;
9+
import com.google.gson.stream.JsonReader;
10+
import com.google.gson.stream.JsonWriter;
11+
{{/gson}}
12+
13+
/**
14+
* {{description}}{{^description}}Gets or Sets {{{name}}}{{/description}}
15+
*/
16+
{{#gson}}
17+
@JsonAdapter({{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}.Adapter.class)
18+
{{/gson}}
19+
{{#jsonb}}
20+
@JsonbTypeSerializer({{datatypeWithEnum}}.Serializer.class)
21+
@JsonbTypeDeserializer({{datatypeWithEnum}}.Deserializer.class)
22+
{{/jsonb}}
23+
{{>additionalEnumTypeAnnotations}}public enum {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} {
24+
{{#allowableValues}}{{#enumVars}}
25+
{{#enumDescription}}
26+
/**
27+
* {{.}}
28+
*/
29+
{{/enumDescription}}
30+
{{#withXml}}
31+
@XmlEnumValue({{#isInteger}}"{{/isInteger}}{{#isDouble}}"{{/isDouble}}{{#isLong}}"{{/isLong}}{{#isFloat}}"{{/isFloat}}{{{value}}}{{#isInteger}}"{{/isInteger}}{{#isDouble}}"{{/isDouble}}{{#isLong}}"{{/isLong}}{{#isFloat}}"{{/isFloat}})
32+
{{/withXml}}
33+
{{{name}}}({{{value}}}){{^-last}},
34+
{{/-last}}{{#-last}};{{/-last}}{{/enumVars}}{{/allowableValues}}
35+
36+
private {{{dataType}}} value;
37+
38+
{{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}({{{dataType}}} value) {
39+
this.value = value;
40+
}
41+
42+
{{#jackson}}
43+
@JsonValue
44+
{{/jackson}}
45+
public {{{dataType}}} getValue() {
46+
return value;
47+
}
48+
49+
@Override
50+
public String toString() {
51+
return String.valueOf(value);
52+
}
53+
54+
{{#jackson}}
55+
@JsonCreator
56+
{{/jackson}}
57+
public static {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} fromValue({{{dataType}}} value) {
58+
for ({{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} b : {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}.values()) {
59+
if (b.value.equals(value)) {
60+
return b;
61+
}
62+
}
63+
{{#isNullable}}return null;{{/isNullable}}{{^isNullable}}{{#enumUnknownDefaultCase}}{{#allowableValues}}{{#enumVars}}{{#-last}}return {{{name}}};{{/-last}}{{/enumVars}}{{/allowableValues}}{{/enumUnknownDefaultCase}}{{^enumUnknownDefaultCase}}throw new IllegalArgumentException("Unexpected value '" + value + "'");{{/enumUnknownDefaultCase}}{{/isNullable}}
64+
}
65+
66+
/**
67+
* Convert the instance into URL query string.
68+
*
69+
* @param prefix prefix of the query string
70+
* @return URL query string
71+
*/
72+
public String toUrlQueryString(String prefix) {
73+
if (prefix == null) {
74+
prefix = "";
75+
}
76+
77+
return String.format("%s=%s", prefix, this.toString());
78+
}
79+
80+
{{#gson}}
81+
82+
public static class Adapter extends TypeAdapter<{{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}> {
83+
@Override
84+
public void write(final JsonWriter jsonWriter, final {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} enumeration) throws IOException {
85+
jsonWriter.value(enumeration.getValue());
86+
}
87+
88+
@Override
89+
public {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} read(final JsonReader jsonReader) throws IOException {
90+
{{^isNumber}}{{{dataType}}}{{/isNumber}}{{#isNumber}}String{{/isNumber}} value = jsonReader.{{#isNumber}}nextString(){{/isNumber}}{{#isInteger}}nextInt(){{/isInteger}}{{^isNumber}}{{^isInteger}}next{{{dataType}}}(){{/isInteger}}{{/isNumber}};
91+
return {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}.fromValue({{#isNumber}}new BigDecimal({{/isNumber}}value{{#isNumber}}){{/isNumber}});
92+
}
93+
}
94+
{{/gson}}
95+
{{#jsonb}}
96+
public static final class Deserializer implements JsonbDeserializer<{{datatypeWithEnum}}> {
97+
@Override
98+
public {{datatypeWithEnum}} deserialize(JsonParser parser, DeserializationContext ctx, Type rtType) {
99+
for ({{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}} b : {{{datatypeWithEnum}}}{{^datatypeWithEnum}}{{{classname}}}{{/datatypeWithEnum}}.values()) {
100+
if (String.valueOf(b.value).equals(parser.getString())) {
101+
return b;
102+
}
103+
}
104+
{{#useNullForUnknownEnumValue}}return null;{{/useNullForUnknownEnumValue}}{{^useNullForUnknownEnumValue}}throw new IllegalArgumentException("Unexpected value '" + parser.getString() + "'");{{/useNullForUnknownEnumValue}}
105+
}
106+
}
107+
108+
public static final class Serializer implements JsonbSerializer<{{datatypeWithEnum}}> {
109+
@Override
110+
public void serialize({{datatypeWithEnum}} obj, JsonGenerator generator, SerializationContext ctx) {
111+
generator.write(obj.value);
112+
}
113+
}
114+
{{/jsonb}}
115+
}

modules/openapi-generator/src/main/resources/Java/libraries/native/pojo.mustache

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,131 @@ public class {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{{#vendorExtens
349349
return o.toString().replace("\n", "\n ");
350350
}
351351
352+
353+
/**
354+
* Convert the instance into URL query string.
355+
*
356+
* @param prefix prefix of the query string
357+
* @return URL query string
358+
*/
359+
public String toUrlQueryString(String prefix) {
360+
if (prefix == null) {
361+
prefix = "";
362+
}
363+
364+
StringJoiner joiner = new StringJoiner("&");
365+
366+
{{#allVars}}
367+
// add `{{baseName}}` to the URL query string
368+
{{#isArray}}
369+
{{#items.isPrimitiveType}}
370+
{{#uniqueItems}}
371+
if ({{getter}}() != null) {
372+
int i = 0;
373+
for ({{items.dataType}} _item : {{getter}}()) {
374+
joiner.add(String.format("%s[{{baseName}}][%d]=%s", prefix, i, URLEncoder.encode(String.valueOf(_item), StandardCharsets.UTF_8).replaceAll("\\+", "%20")));
375+
}
376+
i++;
377+
}
378+
{{/uniqueItems}}
379+
{{^uniqueItems}}
380+
if ({{getter}}() != null) {
381+
for (int i = 0; i < {{getter}}().size(); i++) {
382+
joiner.add(String.format("%s[{{baseName}}][%d]=%s", prefix, i, URLEncoder.encode(String.valueOf({{getter}}().get(i)), StandardCharsets.UTF_8).replaceAll("\\+", "%20")));
383+
}
384+
}
385+
{{/uniqueItems}}
386+
{{/items.isPrimitiveType}}
387+
{{^items.isPrimitiveType}}
388+
{{#items.isModel}}
389+
{{#uniqueItems}}
390+
if ({{getter}}() != null) {
391+
int i = 0;
392+
for ({{items.dataType}} _item : {{getter}}()) {
393+
if ({{getter}}().get(i) != null) {
394+
joiner.add(_item.toUrlQueryString(String.format("%s[{{baseName}}][%d]", prefix, i)));
395+
}
396+
}
397+
i++;
398+
}
399+
{{/uniqueItems}}
400+
{{^uniqueItems}}
401+
if ({{getter}}() != null) {
402+
for (int i = 0; i < {{getter}}().size(); i++) {
403+
if ({{getter}}().get(i) != null) {
404+
joiner.add({{getter}}().get(i).toUrlQueryString(String.format("%s[{{baseName}}][%d]", prefix, i)));
405+
}
406+
}
407+
}
408+
{{/uniqueItems}}
409+
{{/items.isModel}}
410+
{{^items.isModel}}
411+
{{#uniqueItems}}
412+
if ({{getter}}() != null) {
413+
int i = 0;
414+
for ({{items.dataType}} _item : {{getter}}()) {
415+
if (_item != null) {
416+
joiner.add(String.format("%s[{{baseName}}][%d]=%s", prefix, i, URLEncoder.encode(String.valueOf(_item), StandardCharsets.UTF_8).replaceAll("\\+", "%20")));
417+
}
418+
i++;
419+
}
420+
}
421+
{{/uniqueItems}}
422+
{{^uniqueItems}}
423+
if ({{getter}}() != null) {
424+
for (int i = 0; i < {{getter}}().size(); i++) {
425+
if ({{getter}}().get(i) != null) {
426+
joiner.add(String.format("%s[{{baseName}}][%d]=%s", prefix, i, URLEncoder.encode(String.valueOf({{getter}}().get(i)), StandardCharsets.UTF_8).replaceAll("\\+", "%20")));
427+
}
428+
}
429+
}
430+
{{/uniqueItems}}
431+
{{/items.isModel}}
432+
{{/items.isPrimitiveType}}
433+
{{/isArray}}
434+
{{^isArray}}
435+
{{#isMap}}
436+
{{#items.isPrimitiveType}}
437+
if ({{getter}}() != null) {
438+
for (String _key : {{getter}}().keySet()) {
439+
joiner.add(String.format("%s[{{baseName}}][%s]=%s", prefix, {{getter}}().get(_key), URLEncoder.encode(String.valueOf({{getter}}().get(_key)), StandardCharsets.UTF_8).replaceAll("\\+", "%20")));
440+
}
441+
}
442+
{{/items.isPrimitiveType}}
443+
{{^items.isPrimitiveType}}
444+
if ({{getter}}() != null) {
445+
for (String _key : {{getter}}().keySet()) {
446+
if ({{getter}}().get(_key) != null) {
447+
joiner.add({{getter}}().get(_key).toUrlQueryString(String.format("%s[{{baseName}}][%s]", prefix, _key)));
448+
}
449+
}
450+
}
451+
{{/items.isPrimitiveType}}
452+
{{/isMap}}
453+
{{^isMap}}
454+
{{#isPrimitiveType}}
455+
if ({{getter}}() != null) {
456+
joiner.add(String.format("%s[{{{baseName}}}]=%s", prefix, URLEncoder.encode(String.valueOf({{{getter}}}()), StandardCharsets.UTF_8).replaceAll("\\+", "%20")));
457+
}
458+
{{/isPrimitiveType}}
459+
{{^isPrimitiveType}}
460+
{{#isModel}}
461+
if ({{getter}}() != null) {
462+
joiner.add({{getter}}().toUrlQueryString(prefix + "[{{{baseName}}}]"));
463+
}
464+
{{/isModel}}
465+
{{^isModel}}
466+
if ({{getter}}() != null) {
467+
joiner.add(String.format("%s[{{{baseName}}}]=%s", prefix, URLEncoder.encode(String.valueOf({{{getter}}}()), StandardCharsets.UTF_8).replaceAll("\\+", "%20")));
468+
}
469+
{{/isModel}}
470+
{{/isPrimitiveType}}
471+
{{/isMap}}
472+
{{/isArray}}
473+
474+
{{/allVars}}
475+
return joiner.toString();
476+
}
352477
{{#parcelableModel}}
353478
354479
public void writeToParcel(Parcel out, int flags) {

modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaClientDeepObjectTest.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,7 @@ public void deepObject() throws IOException {
5858
generator.opts(input).generate();
5959

6060
assertFileContains(Paths.get(outputPath + "/src/main/java/org/openapitools/client/api/DefaultApi.java"),
61-
"options[id]", "options[name]", "options[status]",
62-
"\"csv\", \"options[tags]\"",
63-
"\"csv\", \"options[photoUrls]\"",
64-
"inputOptions[a]", "inputOptions[b]", "\"csv\", \"inputOptions[c]\"");
61+
"options.toUrlQueryString(\"options\")",
62+
"inputOptions.toUrlQueryString(\"inputOptions\"))");
6563
}
6664
}

modules/openapi-generator/src/test/resources/3_0/echo_api.yaml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,27 @@ paths:
137137
text/plain:
138138
schema:
139139
type: string
140+
/query/style_deepObject/explode_true/object:
141+
get:
142+
tags:
143+
- query
144+
summary: Test query parameter(s)
145+
description: Test query parameter(s)
146+
operationId: test/query/style_deepObject/explode_true/object
147+
parameters:
148+
- in: query
149+
name: query_object
150+
style: deepObject
151+
explode: true #default
152+
schema:
153+
$ref: '#/components/schemas/Pet'
154+
responses:
155+
'200':
156+
description: Successful operation
157+
content:
158+
text/plain:
159+
schema:
160+
type: string
140161
/echo/body/Pet:
141162
post:
142163
tags:

samples/client/echo_api/java/apache-httpclient/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ Class | Method | HTTP request | Description
109109
*BodyApi* | [**testEchoBodyPet**](docs/BodyApi.md#testEchoBodyPet) | **POST** /echo/body/Pet | Test body parameter(s)
110110
*PathApi* | [**testsPathStringPathStringIntegerPathInteger**](docs/PathApi.md#testsPathStringPathStringIntegerPathInteger) | **GET** /path/string/{path_string}/integer/{path_integer} | Test path parameter(s)
111111
*QueryApi* | [**testQueryIntegerBooleanString**](docs/QueryApi.md#testQueryIntegerBooleanString) | **GET** /query/integer/boolean/string | Test query parameter(s)
112+
*QueryApi* | [**testQueryStyleDeepObjectExplodeTrueObject**](docs/QueryApi.md#testQueryStyleDeepObjectExplodeTrueObject) | **GET** /query/style_deepObject/explode_true/object | Test query parameter(s)
112113
*QueryApi* | [**testQueryStyleFormExplodeTrueArrayString**](docs/QueryApi.md#testQueryStyleFormExplodeTrueArrayString) | **GET** /query/style_form/explode_true/array_string | Test query parameter(s)
113114
*QueryApi* | [**testQueryStyleFormExplodeTrueObject**](docs/QueryApi.md#testQueryStyleFormExplodeTrueObject) | **GET** /query/style_form/explode_true/object | Test query parameter(s)
114115

samples/client/echo_api/java/apache-httpclient/api/openapi.yaml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,29 @@ paths:
124124
tags:
125125
- query
126126
x-accepts: text/plain
127+
/query/style_deepObject/explode_true/object:
128+
get:
129+
description: Test query parameter(s)
130+
operationId: test/query/style_deepObject/explode_true/object
131+
parameters:
132+
- explode: true
133+
in: query
134+
name: query_object
135+
required: false
136+
schema:
137+
$ref: '#/components/schemas/Pet'
138+
style: deepObject
139+
responses:
140+
"200":
141+
content:
142+
text/plain:
143+
schema:
144+
type: string
145+
description: Successful operation
146+
summary: Test query parameter(s)
147+
tags:
148+
- query
149+
x-accepts: text/plain
127150
/echo/body/Pet:
128151
post:
129152
description: Test body parameter(s)

0 commit comments

Comments
 (0)