Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ import {
singleWord,
} from "../../Source.js";
import { camelCase, utf16StringEscape } from "../../support/Strings.js";
import type { ArrayType, ClassType, EnumType, Type } from "../../Type/index.js";
import {
ArrayType,
type ClassType,
type EnumType,
type Type,
} from "../../Type/index.js";
import { isNamedType } from "../../Type/TypeUtils.js";
import type { JavaScriptTypeAnnotations } from "../JavaScript/index.js";

Expand All @@ -33,6 +38,14 @@ export class TypeScriptRenderer extends TypeScriptFlowBaseRenderer {
return ["Array", "Date"];
}

protected namedTypeToNameForTopLevel(type: Type): Type | undefined {
if (type instanceof ArrayType) {
return undefined;
}

return super.namedTypeToNameForTopLevel(type);
}

// An array with `minItems` >= 1 becomes a tuple that spells out the
// guaranteed elements, followed by a rest element: `minItems: 2`
// renders as `[T, T, ...T[]]`. Only `minItems` shapes the type;
Expand Down Expand Up @@ -81,7 +94,7 @@ export class TypeScriptRenderer extends TypeScriptFlowBaseRenderer {
"(json: ",
jsonType,
"): ",
this.sourceFor(t).source,
t instanceof ArrayType ? name : this.sourceFor(t).source,
];
}

Expand All @@ -93,7 +106,7 @@ export class TypeScriptRenderer extends TypeScriptFlowBaseRenderer {
"public static ",
camelCaseName,
"ToJson(value: ",
this.sourceFor(t).source,
t instanceof ArrayType ? name : this.sourceFor(t).source,
"): ",
returnType,
];
Expand All @@ -116,7 +129,7 @@ export class TypeScriptRenderer extends TypeScriptFlowBaseRenderer {
(_t, name) => {
topLevelNames.push(", ", name);
},
isNamedType,
(t) => isNamedType(t) || t instanceof ArrayType,
);
this.emitLine(
"// import { Convert",
Expand All @@ -127,6 +140,26 @@ export class TypeScriptRenderer extends TypeScriptFlowBaseRenderer {
);
}

protected emitTypes(): void {
super.emitTypes();

this.forEachTopLevel("none", (t, name) => {
if (!(t instanceof ArrayType)) {
return;
}

this.ensureBlankLine();
this.emitDescription(this.descriptionForType(t));
this.emitLine(
"export type ",
name,
" = ",
this.sourceFor(t).source,
";",
);
});
}

protected emitEnum(e: EnumType, enumName: Name): void {
this.emitDescription(this.descriptionForType(e));

Expand Down
3 changes: 3 additions & 0 deletions test/inputs/schema/root-array-ref.1.fail.no-defaults.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[
{}
]
5 changes: 5 additions & 0 deletions test/inputs/schema/root-array-ref.1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[
{
"name": "quicktype"
}
]
26 changes: 26 additions & 0 deletions test/inputs/schema/root-array-ref.schema
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$ref": "#/definitions/GetResp",
"definitions": {
"GetResp": {
"title": "GetResp",
"type": "array",
"items": {
"$ref": "#/definitions/SomeObject"
}
},
"SomeObject": {
"title": "SomeObject",
"type": "object",
"additionalProperties": false,
"properties": {
"name": {
"type": "string"
}
},
"required": [
"name"
]
}
}
}
19 changes: 19 additions & 0 deletions test/languages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,9 @@ export const CJSONLanguage: Language = {
/* Required properties absent are not checked (for the current implementation, can be added later, should abord parsing and return NULL) */
"intersection.schema",
"required.schema",
/* Root-level array of a required-property object: the missing
* required property in the fail sample is likewise not rejected. */
"root-array-ref.schema",
// The default-value fail sample also relies on required-property
// enforcement, which cJSON does not do.
"default-value.schema",
Expand Down Expand Up @@ -1002,6 +1005,9 @@ export const SwiftLanguage: Language = {
// This works on macOS, but on Linux one of the failure test cases doesn't fail
...skipsUntypedUnions,
"required.schema",
// Same Linux-only issue: the missing-required-property fail sample
// for the root-level array is not rejected.
"root-array-ref.schema",
// The default-value fail sample also relies on required-property enforcement.
"default-value.schema",
"multi-type-enum.schema",
Expand Down Expand Up @@ -1706,6 +1712,7 @@ export const KotlinXLanguage: Language = {
// Top-level array: `typealias TopLevel = JsonArray<T>` doesn't
// compile (documented TODO in KotlinXRenderer.ts).
"union.schema",
"root-array-ref.schema",
"issue2680-top-level-array.schema",
],
skipMiscJSON: false,
Expand Down Expand Up @@ -1918,6 +1925,9 @@ export const HaskellLanguage: Language = {
"keyword-unions.schema",
"optional-any.schema",
"required.schema",
// The driver encodes the Maybe result, so the missing-required-property
// fail sample for the root-level array decodes to null and exits 0.
"root-array-ref.schema",
// The default-value fail sample also relies on required-property enforcement.
"default-value.schema",
"required-non-properties.schema",
Expand Down Expand Up @@ -1976,6 +1986,7 @@ export const PHPLanguage: Language = {
"top-level-enum.schema",
// The driver does not support top-level arrays.
"union.schema",
"root-array-ref.schema",
"issue2680-top-level-array.schema",
],
rendererOptions: {},
Expand Down Expand Up @@ -2090,6 +2101,11 @@ export const TypeScriptZodLanguage: Language = {
// The default-value fail sample also relies on required-property enforcement.
"default-value.schema",
"required-non-properties.schema",
// Top-level array whose item type has its own title: the zod
// renderer emits only the item schema (SomeObjectSchema) and no
// TopLevelElementSchema, so the driver can't locate a top-level
// schema to parse the array against.
"root-array-ref.schema",
],
rendererOptions: {},
quickTestRendererOptions: [],
Expand Down Expand Up @@ -2273,6 +2289,9 @@ export const ElixirLanguage: Language = {
// Struct keys cannot be enforced at runtime in Elixir and their values will just be set to null.
"strict-optional.schema",
"required.schema",
// Same reason: the missing-required-property fail sample for the
// root-level array is not rejected.
"root-array-ref.schema",
// The default-value fail sample also relies on required-property enforcement.
"default-value.schema",
"boolean-subschema.schema",
Expand Down
28 changes: 28 additions & 0 deletions test/unit/typescript-root-array-schema.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { readFileSync } from "node:fs";

import {
FetchingJSONSchemaStore,
InputData,
JSONSchemaInput,
quicktype,
} from "quicktype-core";
import { expect, test } from "vitest";

test("TypeScript preserves a named root array and its object item type", async () => {
const schema = readFileSync(
new URL("../inputs/schema/root-array-ref.schema", import.meta.url),
"utf8",
);
const schemaInput = new JSONSchemaInput(new FetchingJSONSchemaStore());
await schemaInput.addSource({ name: "PR", schema });

const inputData = new InputData();
inputData.addInput(schemaInput);

const result = await quicktype({ inputData, lang: "typescript" });
const output = result.lines.join("\n");

expect(output).toContain("export interface SomeObject");
expect(output).toContain("export type PR = SomeObject[];");
expect(output).not.toContain("export interface PR");
});
Loading