From 344dcd0ccaf2085f277153dc0eca97b3940e5531 Mon Sep 17 00:00:00 2001 From: Woohyun Park Date: Tue, 11 Aug 2026 10:53:53 +0900 Subject: [PATCH] Infer object type from additionalProperties --- src/schema-parser/schema-parser.ts | 3 ++ .../basic.test.ts | 35 +++++++++++++++++++ .../schema.json | 32 +++++++++++++++++ 3 files changed, 70 insertions(+) create mode 100644 tests/spec/additional-properties-without-type/basic.test.ts create mode 100644 tests/spec/additional-properties-without-type/schema.json diff --git a/src/schema-parser/schema-parser.ts b/src/schema-parser/schema-parser.ts index b6306b8a..045049d9 100644 --- a/src/schema-parser/schema-parser.ts +++ b/src/schema-parser/schema-parser.ts @@ -215,6 +215,9 @@ export class SchemaParser { ) { this.schema.type = SCHEMA_TYPES.ARRAY; } + if (this.schema.additionalProperties && !this.schema.type) { + this.schema.type = SCHEMA_TYPES.OBJECT; + } // schema is enum with one null value if ( Array.isArray(this.schema.enum) && diff --git a/tests/spec/additional-properties-without-type/basic.test.ts b/tests/spec/additional-properties-without-type/basic.test.ts new file mode 100644 index 00000000..1739d97c --- /dev/null +++ b/tests/spec/additional-properties-without-type/basic.test.ts @@ -0,0 +1,35 @@ +import * as fs from "node:fs/promises"; +import * as os from "node:os"; +import * as path from "node:path"; +import { afterAll, beforeAll, describe, expect, test } from "vitest"; +import { generateApi } from "../../../src/index.js"; + +describe("additionalProperties without type", async () => { + let tmpdir = ""; + + beforeAll(async () => { + tmpdir = await fs.mkdtemp(path.join(os.tmpdir(), "swagger-typescript-api")); + }); + + afterAll(async () => { + await fs.rm(tmpdir, { recursive: true }); + }); + + test("infers dictionary types", async () => { + await generateApi({ + fileName: "schema", + input: path.resolve(import.meta.dirname, "schema.json"), + output: tmpdir, + silent: true, + generateClient: false, + }); + + const content = await fs.readFile(path.join(tmpdir, "schema.ts"), { + encoding: "utf8", + }); + + expect(content).toContain("values: Record;"); + expect(content).toContain("open?: Record;"); + expect(content).toContain("closed?: any;"); + }); +}); diff --git a/tests/spec/additional-properties-without-type/schema.json b/tests/spec/additional-properties-without-type/schema.json new file mode 100644 index 00000000..42e54457 --- /dev/null +++ b/tests/spec/additional-properties-without-type/schema.json @@ -0,0 +1,32 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Additional properties without an explicit type", + "version": "1.0.0" + }, + "paths": {}, + "components": { + "schemas": { + "Widget": { + "type": "object", + "required": ["values"], + "properties": { + "values": { + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "open": { + "additionalProperties": true + }, + "closed": { + "additionalProperties": false + } + } + } + } + } +}