Skip to content
Open
2 changes: 1 addition & 1 deletion packages/quicktype-core/src/input/JSONSchemaInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,7 @@ class Canonizer {

this.addIDs(
schema,
new Location(Ref.root(address), Ref.root(undefined)),
new Location(Ref.parse(address), Ref.root(undefined)),
);
this._schemaAddressesAdded.add(address);
return true;
Expand Down
33 changes: 32 additions & 1 deletion test/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -809,7 +809,38 @@ class JSONSchemaFixture extends LanguageFixture {
}

getSamples(sources: string[]): { priority: Sample[]; others: Sample[] } {
const prioritySamples = testsInDir("test/inputs/schema/", "schema");
const schemaDirectory = "test/inputs/schema/";
const multiFileSamples = fs
.readdirSync(schemaDirectory)
.map((entry) => path.join(schemaDirectory, entry))
.filter(
(entry) =>
fs.statSync(entry).isDirectory() &&
fs
.readdirSync(entry)
.some((filename) => filename.endsWith(".schema")),
);
const prioritySamples = testsInDir(schemaDirectory, "schema").concat(
multiFileSamples,
);

if (
sources.length === 1 &&
fs.lstatSync(sources[0]).isDirectory() &&
path.resolve(sources[0]) !== path.resolve(schemaDirectory)
) {
return {
priority: [
{
path: sources[0],
additionalRendererOptions: {},
saveOutput: true,
},
],
others: [],
};
}

const samples = samplesFromSources(
sources,
prioritySamples,
Expand Down
8 changes: 8 additions & 0 deletions test/inputs/schema/issue-1833.1.fail.no-defaults.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"a": {
"name": "root-a",
"in": [
{ "name": "first" }
]
}
}
15 changes: 15 additions & 0 deletions test/inputs/schema/issue-1833.1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"a": {
"name": "root-a",
"in": [
{ "name": "first" },
{ "name": "second" }
]
},
"b": {
"name": "root-b",
"out": [
{ "name": "third" }
]
}
}
14 changes: 14 additions & 0 deletions test/inputs/schema/issue-1833/a.schema
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "/schemas/a",
"type": "object",
"title": "A",
"properties": {
"name": { "type": "string" },
"in": {
"type": "array",
"items": { "$ref": "/schemas/c" }
}
},
"required": ["name", "in"]
}
14 changes: 14 additions & 0 deletions test/inputs/schema/issue-1833/b.schema
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "/schemas/b",
"type": "object",
"title": "B",
"properties": {
"name": { "type": "string" },
"out": {
"type": "array",
"items": { "$ref": "/schemas/c" }
}
},
"required": ["name", "out"]
}
10 changes: 10 additions & 0 deletions test/inputs/schema/issue-1833/c.schema
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "/schemas/c",
"type": "object",
"title": "C",
"properties": {
"name": { "type": "string" }
},
"required": ["name"]
}
11 changes: 11 additions & 0 deletions test/inputs/schema/issue-1833/top-level.schema
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "/schemas/top-level",
"type": "object",
"title": "TopLevel",
"properties": {
"a": { "$ref": "/schemas/a" },
"b": { "$ref": "/schemas/b" }
},
"required": ["a", "b"]
}
33 changes: 32 additions & 1 deletion test/languages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,10 @@ export const JavaLanguage: Language = {
skipSchema: [
"integer-before-number.schema", // Python-specific union-order regression.
"keyword-unions.schema", // generates classes with names that are case-insensitively equal
// Multi-top-level directory input; the Java driver deserializes via the
// generic `Converter.fromJsonString`, which is only emitted for a single
// top-level (multi-top-level emits per-type `<Name>FromJsonString`).
"issue-1833",
// The generated converter deserializes a top-level array with a raw
// `List`, so a mistyped element round-trips instead of failing.
...skipsArrayElementValidation,
Expand Down Expand Up @@ -678,6 +682,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",
/* Multi-top-level directory input whose only negative case is a missing
* required property, which this renderer does not enforce (see above). */
"issue-1833",
// The default-value fail sample also relies on required-property
// enforcement, which cJSON does not do.
"default-value.schema",
Expand Down Expand Up @@ -938,6 +945,9 @@ export const ElmLanguage: Language = {
// The generated decoder accepts invalid union members because all
// class properties decode via `Jpipe.optional`.
"nested-intersection-union.schema",
// Multi-top-level directory input; the Elm driver decodes a single
// top-level named `QuickType`, which this fixture does not produce.
"issue-1833",
],
rendererOptions: {},
// `list` is the default now; keep the `Array` code path covered.
Expand Down Expand Up @@ -1012,6 +1022,9 @@ export const SwiftLanguage: Language = {
"class-with-additional.schema",
"vega-lite.schema",
"top-level-primitive.schema",
// Multi-top-level directory input whose only negative case is a missing
// required property, which the Swift driver does not treat as a failure.
"issue-1833",
],
rendererOptions: { "support-linux": "true" },
quickTestRendererOptions: [
Expand Down Expand Up @@ -1082,7 +1095,13 @@ export const ObjectiveCLanguage: Language = {
"combinations4.json",
],
skipMiscJSON: false,
skipSchema: ["integer-before-number.schema"], // Python-specific union-order regression.
skipSchema: [
"integer-before-number.schema", // Python-specific union-order regression.
// Multi-top-level directory input; the Objective-C driver decodes a
// single top-level named `QTTopLevel`, which this fixture does not
// produce.
"issue-1833",
],
rendererOptions: { functions: "true" },
quickTestRendererOptions: [],
sourceFiles: ["src/language/Objective-C/index.ts"],
Expand Down Expand Up @@ -1921,6 +1940,9 @@ export const HaskellLanguage: Language = {
// The default-value fail sample also relies on required-property enforcement.
"default-value.schema",
"required-non-properties.schema",
// Multi-top-level directory input; the Haskell driver decodes a single
// top-level named `QuickType`, which this fixture does not produce.
"issue-1833",
],
rendererOptions: {},
// The default is array-type=list; this keeps the Vector code path
Expand Down Expand Up @@ -2090,6 +2112,9 @@ export const TypeScriptZodLanguage: Language = {
// The default-value fail sample also relies on required-property enforcement.
"default-value.schema",
"required-non-properties.schema",
// Multi-top-level directory input whose only negative case is a missing
// required property, which this fixture does not treat as a failure.
"issue-1833",
],
rendererOptions: {},
quickTestRendererOptions: [],
Expand Down Expand Up @@ -2209,6 +2234,9 @@ export const TypeScriptEffectSchemaLanguage: Language = {
// The default-value fail sample also relies on required-property enforcement.
"default-value.schema",
"required-non-properties.schema",
// Multi-top-level directory input whose only negative case is a missing
// required property, which this fixture does not treat as a failure.
"issue-1833",
"issue2680-top-level-array.schema",
],
rendererOptions: {},
Expand Down Expand Up @@ -2285,6 +2313,9 @@ export const ElixirLanguage: Language = {

// The generated top-level type is not emitted as a TopLevel module the fixture can call.
"recursive-union-flattening.schema",
// Multi-top-level directory input whose only negative case is a missing
// required property, which Elixir cannot enforce at runtime (see above).
"issue-1833",

// A top-level array is deserialized without enforcing its element
// type, so a mistyped element round-trips instead of failing.
Expand Down
42 changes: 42 additions & 0 deletions test/unit/json-schema-multiple-sources.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// The fixture runner exercises this multi-file input end to end, but duplicate
// structural interfaces still compile. Assert here that the dead interface is
// not generated.
import * as fs from "node:fs";
import * as path from "node:path";

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

const fixtureDirectory = "test/inputs/schema/issue-1833";

async function generateTypeScript(): Promise<string> {
const schemaInput = new JSONSchemaInput(undefined);
for (const filename of ["a.schema", "b.schema", "c.schema"]) {
const fixturePath = path.resolve(fixtureDirectory, filename);
await schemaInput.addSource({
name: path.basename(filename, ".schema"),
schema: fs.readFileSync(fixturePath, "utf8"),
uris: [fixturePath],
});
}

const inputData = new InputData();
inputData.addInput(schemaInput);
const result = await quicktype({
inputData,
lang: "typescript",
rendererOptions: { "just-types": true },
});
return result.lines.join("\n");
}

describe("JSON Schema multiple sources (issue #1833)", () => {
test("a top-level schema and references to its $id share one type", async () => {
const output = await generateTypeScript();

expect(output).toContain("in: C[];");
expect(output).toContain("out: C[];");
expect(output.match(/export interface C/g)).toHaveLength(1);
expect(output).not.toContain("export interface InElement");
});
});
Loading