Skip to content

feat(typescript-angular): add opt-in form object dot notation - #24984

Open
angelaki wants to merge 1 commit into
OpenAPITools:masterfrom
angelaki:feat/typescript-angular-form-object-dot-notation
Open

angelaki wants to merge 1 commit into
OpenAPITools:masterfrom
angelaki:feat/typescript-angular-form-object-dot-notation

Conversation

@angelaki

@angelaki angelaki commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #23895

Add useDotNotationForFormObjects with a false default so standard form serialization remains unchanged. Preserve parameter-prefixed property paths when enabled, document the server-specific convention, and add generator and runtime regression coverage with a generated sample.

This PR was AI generated! But looks quite good to me. Intention is to support .Net Query Param serialization style.


Summary by cubic

Adds an opt-in useDotNotationForFormObjects option to the TypeScript Angular generator so exploded form query objects can be serialized with parameter-prefixed dot notation (e.g., filter.name=Alice) for servers like .NET that require this convention.

  • Defaults to false, leaving standard OpenAPI form serialization unchanged.
  • Nested property paths are preserved (e.g., filter.first.id=1), and primitive arrays and sets keep repeated keys.
  • Adds generator and runtime regression tests plus a generated sample, and documents the option and the server-specific convention in the README.

Written for commit 713c536. Summary will update on new commits.

Review in cubic

Add useDotNotationForFormObjects with a false default so standard form serialization remains unchanged. Preserve parameter-prefixed property paths when enabled, document the server-specific convention, and add generator and runtime regression coverage with a generated sample.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

5 issues found across 32 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="modules/openapi-generator/src/main/resources/typescript-angular/README.mustache">

<violation number="1" location="modules/openapi-generator/src/main/resources/typescript-angular/README.mustache:186">
P3: The new dot-notation documentation is only added to README.mustache, which is used only when ngVersion >= 17. Clients generated with pre-v17 templates (README_beforeV17.mustache) still get the `useDotNotationForFormObjects` behavior from the shared api.base.service.mustache, but their README.md never documents it. Add the equivalent section to README_beforeV17.mustache so both template variants document the option.</violation>
</file>

<file name="samples/client/others/typescript-angular-v20/builds/query-param-form-dot/query.params.ts">

<violation number="1" location="samples/client/others/typescript-angular-v20/builds/query-param-form-dot/query.params.ts:92">
P2: When an exploded parameter has multiple values, `toString()` joins the array with commas instead of emitting repeated key/value pairs. Iterate array values and add one `key=value` entry per value.</violation>

<violation number="2" location="samples/client/others/typescript-angular-v20/builds/query-param-form-dot/query.params.ts:104">
P2: A query parameter named `__proto__` is silently lost because `toRecord()` writes encoded keys into a normal object. Use a null-prototype record so all valid parameter names serialize safely.</violation>
</file>

<file name="modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/typescriptangular/TypeScriptAngularClientCodegenTest.java">

<violation number="1" location="modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/typescriptangular/TypeScriptAngularClientCodegenTest.java:495">
P3: In the enabled (`true`) branch, the test asserts only `contains` for the dot-notation template, but the disabled branch also asserts `doesNotContain` for the opposite variant. Add the symmetric negative assertion so a regression that emits both the plain `k` and the `` `${key}.${k}` `` form fails the test.</violation>
</file>

<file name="samples/client/others/typescript-angular-v20/builds/query-param-form-dot/README.md">

<violation number="1" location="samples/client/others/typescript-angular-v20/builds/query-param-form-dot/README.md:192">
P3: The README advertises nested dot-notation (`filter.name.contains=Alice`), but the sample spec (`query-param-form.yaml`) defines `Filter` with only flat properties, so the generated sample never exercises the recursive nested-object branch of `addToHttpParams`. Add a nested object property (e.g. `name: { contains: string }`) to the `filter` param so the documented behavior and the recursion in the generated code are actually covered by the sample build.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

* - If a parameter has multiple values, returns a readonly array of values.
*/
toRecord(): Record<string, string | number | boolean | ReadonlyArray<string | number | boolean>> {
const parts: Record<string, string | number | boolean | ReadonlyArray<string | number | boolean>> = {};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: A query parameter named __proto__ is silently lost because toRecord() writes encoded keys into a normal object. Use a null-prototype record so all valid parameter names serialize safely.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/client/others/typescript-angular-v20/builds/query-param-form-dot/query.params.ts, line 104:

<comment>A query parameter named `__proto__` is silently lost because `toRecord()` writes encoded keys into a normal object. Use a null-prototype record so all valid parameter names serialize safely.</comment>

<file context>
@@ -0,0 +1,160 @@
+     * - If a parameter has multiple values, returns a readonly array of values.
+     */
+    toRecord(): Record<string, string | number | boolean | ReadonlyArray<string | number | boolean>> {
+        const parts: Record<string, string | number | boolean | ReadonlyArray<string | number | boolean>> = {};
+
+        for (const [key, entry] of this.params.entries()) {
</file context>
Suggested change
const parts: Record<string, string | number | boolean | ReadonlyArray<string | number | boolean>> = {};
const parts: Record<string, string | number | boolean | ReadonlyArray<string | number | boolean>> = Object.create(null);

const parts: string[] = [];

for (const key in records) {
parts.push(`${key}=${records[key]}`);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When an exploded parameter has multiple values, toString() joins the array with commas instead of emitting repeated key/value pairs. Iterate array values and add one key=value entry per value.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/client/others/typescript-angular-v20/builds/query-param-form-dot/query.params.ts, line 92:

<comment>When an exploded parameter has multiple values, `toString()` joins the array with commas instead of emitting repeated key/value pairs. Iterate array values and add one `key=value` entry per value.</comment>

<file context>
@@ -0,0 +1,160 @@
+        const parts: string[] = [];
+
+        for (const key in records) {
+            parts.push(`${key}=${records[key]}`);
+        }
+
</file context>
Suggested change
parts.push(`${key}=${records[key]}`);
const value = records[key];
if (Array.isArray(value)) {
value.forEach((item) => parts.push(`${key}=${item}`));
} else {
parts.push(`${key}=${value}`);
}

[parameter-locations-url]: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#parameter-locations
[style-values-url]: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#style-values
[@honoluluhenk/http-param-expander]: https://www.npmjs.com/package/@honoluluhenk/http-param-expander
{{#useDotNotationForFormObjects}}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The new dot-notation documentation is only added to README.mustache, which is used only when ngVersion >= 17. Clients generated with pre-v17 templates (README_beforeV17.mustache) still get the useDotNotationForFormObjects behavior from the shared api.base.service.mustache, but their README.md never documents it. Add the equivalent section to README_beforeV17.mustache so both template variants document the option.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/resources/typescript-angular/README.mustache, line 186:

<comment>The new dot-notation documentation is only added to README.mustache, which is used only when ngVersion >= 17. Clients generated with pre-v17 templates (README_beforeV17.mustache) still get the `useDotNotationForFormObjects` behavior from the shared api.base.service.mustache, but their README.md never documents it. Add the equivalent section to README_beforeV17.mustache so both template variants document the option.</comment>

<file context>
@@ -183,3 +183,17 @@ new Configuration({
 [parameter-locations-url]: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#parameter-locations
 [style-values-url]: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#style-values
 [@honoluluhenk/http-param-expander]: https://www.npmjs.com/package/@honoluluhenk/http-param-expander
+{{#useDotNotationForFormObjects}}
+
+## Form query object dot notation
</file context>

new DefaultGenerator().opts(configurator.toClientOptInput()).generate();
String service = Files.readString(output.toPath().resolve("api.base.service.ts"));
if (Boolean.parseBoolean(String.valueOf(option))) {
assertThat(service).contains("this.addToHttpParams(httpParams, `${key}.${k}`, value[k], paramStyle, explode)");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: In the enabled (true) branch, the test asserts only contains for the dot-notation template, but the disabled branch also asserts doesNotContain for the opposite variant. Add the symmetric negative assertion so a regression that emits both the plain k and the `${key}.${k}` form fails the test.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/typescriptangular/TypeScriptAngularClientCodegenTest.java, line 495:

<comment>In the enabled (`true`) branch, the test asserts only `contains` for the dot-notation template, but the disabled branch also asserts `doesNotContain` for the opposite variant. Add the symmetric negative assertion so a regression that emits both the plain `k` and the `` `${key}.${k}` `` form fails the test.</comment>

<file context>
@@ -477,6 +477,29 @@ public void testEnumAsConst() throws IOException {
+            new DefaultGenerator().opts(configurator.toClientOptInput()).generate();
+            String service = Files.readString(output.toPath().resolve("api.base.service.ts"));
+            if (Boolean.parseBoolean(String.valueOf(option))) {
+                assertThat(service).contains("this.addToHttpParams(httpParams, `${key}.${k}`, value[k], paramStyle, explode)");
+            } else {
+                assertThat(service).contains("this.addToHttpParams(httpParams, k, value[k], paramStyle, explode)");
</file context>
Suggested change
assertThat(service).contains("this.addToHttpParams(httpParams, `${key}.${k}`, value[k], paramStyle, explode)");
assertThat(service).contains("this.addToHttpParams(httpParams, `${key}.${k}`, value[k], paramStyle, explode)");
assertThat(service).doesNotContain("this.addToHttpParams(httpParams, k, value[k], paramStyle, explode)");

This client was generated with `useDotNotationForFormObjects=true`. For exploded
form query objects, property names include the parameter name and all parent
properties: `filter = { name: { contains: "Alice" } }` is serialized as
`filter.name.contains=Alice`. Primitive arrays and sets retain repeated keys,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The README advertises nested dot-notation (filter.name.contains=Alice), but the sample spec (query-param-form.yaml) defines Filter with only flat properties, so the generated sample never exercises the recursive nested-object branch of addToHttpParams. Add a nested object property (e.g. name: { contains: string }) to the filter param so the documented behavior and the recursion in the generated code are actually covered by the sample build.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/client/others/typescript-angular-v20/builds/query-param-form-dot/README.md, line 192:

<comment>The README advertises nested dot-notation (`filter.name.contains=Alice`), but the sample spec (`query-param-form.yaml`) defines `Filter` with only flat properties, so the generated sample never exercises the recursive nested-object branch of `addToHttpParams`. Add a nested object property (e.g. `name: { contains: string }`) to the `filter` param so the documented behavior and the recursion in the generated code are actually covered by the sample build.</comment>

<file context>
@@ -0,0 +1,197 @@
+This client was generated with `useDotNotationForFormObjects=true`. For exploded
+form query objects, property names include the parameter name and all parent
+properties: `filter = { name: { contains: "Alice" } }` is serialized as
+`filter.name.contains=Alice`. Primitive arrays and sets retain repeated keys,
+for example `filter.ids=1&filter.ids=2`.
+
</file context>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] [Angular] Form-Query-Params for recursive objects don't include property path

1 participant