diff --git a/packages/quicktype-core/src/language/Swift/SwiftRenderer.ts b/packages/quicktype-core/src/language/Swift/SwiftRenderer.ts index 7f0c0b2e4..674ec395c 100644 --- a/packages/quicktype-core/src/language/Swift/SwiftRenderer.ts +++ b/packages/quicktype-core/src/language/Swift/SwiftRenderer.ts @@ -289,11 +289,18 @@ export class SwiftRenderer extends ConvenienceRenderer { "(json)", ); } else { + const decoder = + this._options.convenienceInitializers || + this._options.alamofire + ? "newJSONDecoder()" + : "JSONDecoder()"; this.emitLine( "// let ", modifySource(camelCase, name), " = ", - "try? newJSONDecoder().decode(", + "try? ", + decoder, + ".decode(", name, ".self, from: jsonData)", ); @@ -719,7 +726,9 @@ export class SwiftRenderer extends ConvenienceRenderer { } private emitNewEncoderDecoder(): void { - this.emitBlock("func newJSONDecoder() -> JSONDecoder", () => { + const access = this._options.multiFileOutput ? "" : "fileprivate "; + + this.emitBlock([access, "func newJSONDecoder() -> JSONDecoder"], () => { this.emitLine("let decoder = JSONDecoder()"); if (!this._options.linux) { this.emitBlock( @@ -754,7 +763,7 @@ export class SwiftRenderer extends ConvenienceRenderer { this.emitLine("return decoder"); }); this.ensureBlankLine(); - this.emitBlock("func newJSONEncoder() -> JSONEncoder", () => { + this.emitBlock([access, "func newJSONEncoder() -> JSONEncoder"], () => { this.emitLine("let encoder = JSONEncoder()"); if (!this._options.linux) { this.emitBlock( diff --git a/test/unit/swift-helper-visibility.test.ts b/test/unit/swift-helper-visibility.test.ts new file mode 100644 index 000000000..870ba660a --- /dev/null +++ b/test/unit/swift-helper-visibility.test.ts @@ -0,0 +1,83 @@ +import { describe, expect, test } from "vitest"; + +import { + InputData, + JSONSchemaInput, + quicktype, + quicktypeMultiFile, +} from "../../packages/quicktype-core/src/index.js"; + +async function inputData(): Promise { + const schemaInput = new JSONSchemaInput(undefined); + await schemaInput.addSource({ + name: "TopLevel", + schema: JSON.stringify({ + type: "object", + properties: { value: { type: "string" } }, + required: ["value"], + }), + }); + + const result = new InputData(); + result.addInput(schemaInput); + return result; +} + +describe("Swift JSON helper visibility", () => { + test("keeps helpers file-private in standalone output", async () => { + const result = await quicktype({ + inputData: await inputData(), + lang: "swift", + }); + const output = result.lines.join("\n"); + + expect(output).toContain("fileprivate func newJSONDecoder()"); + expect(output).toContain("fileprivate func newJSONEncoder()"); + }); + + test("keeps helpers visible to multi-file model output", async () => { + const result = await quicktypeMultiFile({ + inputData: await inputData(), + lang: "swift", + rendererOptions: { "multi-file-output": true }, + }); + const support = Array.from(result).find( + ([filename]) => filename === "JSONSchemaSupport.swift", + ); + + expect(support).toBeDefined(); + expect(support?.[1].lines.join("\n")).toContain( + "func newJSONDecoder()", + ); + expect(support?.[1].lines.join("\n")).not.toContain( + "fileprivate func newJSONDecoder()", + ); + }); + + test("uses JSONDecoder when multi-file output has no helpers", async () => { + const result = await quicktypeMultiFile({ + inputData: await inputData(), + lang: "swift", + rendererOptions: { + "multi-file-output": true, + initializers: false, + }, + }); + const model = Array.from(result).find( + ([filename]) => filename === "TopLevel.swift", + ); + const support = Array.from(result).find( + ([filename]) => filename === "JSONSchemaSupport.swift", + ); + + expect(model?.[1].lines.join("\n")).toContain( + "try? JSONDecoder().decode(TopLevel.self, from: jsonData)", + ); + expect(model?.[1].lines.join("\n")).not.toContain( + "newJSONDecoder().decode", + ); + expect(support?.[1].lines.join("\n")).not.toContain( + "func newJSONDecoder()", + ); + }); +});