Skip to content
Merged
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
2 changes: 2 additions & 0 deletions .github/workflows/test-pr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ jobs:
# Kotlin is also slow
- fixture: kotlin,schema-kotlin,kotlin-jackson,schema-kotlin-jackson
runs-on: ubuntu-latest-16-cores
- fixture: kotlinx,schema-kotlinx
runs-on: ubuntu-latest-16-cores

# - fixture: objective-c # segfault on compiled test cmd
# runs-on: macos-latest
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
![](https://raw.githubusercontent.com/quicktype/quicktype/master/media/quicktype-logo.svg?sanitize=true)

[![npm version](https://badge.fury.io/js/quicktype.svg)](https://badge.fury.io/js/quicktype)
![Build status](https://github.com/quicktype/quicktype/actions/workflows/master.yaml/badge.svg)
[![Build status](https://github.com/glideapps/quicktype/actions/workflows/test-pr.yaml/badge.svg?branch=master)](https://github.com/glideapps/quicktype/actions/workflows/test-pr.yaml)

`quicktype` generates strongly-typed models and serializers from JSON, JSON Schema, TypeScript, and [GraphQL queries](https://blog.quicktype.io/graphql-with-quicktype/), making it a breeze to work with JSON type-safely in many programming languages.

Expand Down
50 changes: 41 additions & 9 deletions packages/quicktype-core/src/input/JSONSchemaInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1034,17 +1034,48 @@ async function addTypesInSchema(
async function makeArrayType(): Promise<TypeRef> {
const singularAttributes = singularizeTypeNames(typeAttributes);
const items = schema.items;
// JSON Schema 2020-12 renamed the array (tuple) form of `items` to
// `prefixItems`; treat it the same as a draft-07 array-valued
// `items` so tuple schemas produced by e.g. Pydantic v2 and
// schemars are not silently dropped.
const prefixItems = schema.prefixItems;
const tupleItems = Array.isArray(prefixItems) ? prefixItems : items;
const tupleKey = Array.isArray(prefixItems)
? "prefixItems"
: "items";
let itemType: TypeRef;
if (Array.isArray(items)) {
const itemsLoc = loc.push("items");
const itemTypes = await arrayMapSync(items, async (item, i) => {
const itemLoc = itemsLoc.push(i.toString());
return await toType(
checkJSONSchema(item, itemLoc.canonicalRef),
itemLoc,
singularAttributes,
if (Array.isArray(tupleItems)) {
const itemsLoc = loc.push(tupleKey);
const itemTypes = await arrayMapSync(
tupleItems,
async (item, i) => {
const itemLoc = itemsLoc.push(i.toString());
return await toType(
checkJSONSchema(item, itemLoc.canonicalRef),
itemLoc,
singularAttributes,
);
},
);
// In 2020-12 an object-form `items` next to `prefixItems`
// describes the rest elements of an open tuple. quicktype
// models tuples as arrays of a union of the member types, so
// the rest type joins that union. A boolean `items` (`false`
// closes the tuple, `true` allows anything) is ignored.
if (
tupleKey === "prefixItems" &&
typeof items === "object" &&
!Array.isArray(items)
) {
const restItemsLoc = loc.push("items");
itemTypes.push(
await toType(
checkJSONSchema(items, restItemsLoc.canonicalRef),
restItemsLoc,
singularAttributes,
),
);
});
}
itemType = typeBuilder.getUnionType(
emptyTypeAttributes,
new Set(itemTypes),
Expand Down Expand Up @@ -1229,6 +1260,7 @@ async function addTypesInSchema(
schema.properties !== undefined ||
schema.additionalProperties !== undefined ||
schema.items !== undefined ||
schema.prefixItems !== undefined ||
schema.required !== undefined ||
enumArray !== undefined ||
isConst;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -493,15 +493,27 @@ function r(name${stringAnnotation}) {

protected emitTypes(): void {}

protected emitUsageImportComment(): void {
this.emitLine('// const Convert = require("./file");');
protected usageModuleName(givenOutputFilename: string): string {
return givenOutputFilename === "stdout"
? "file"
: givenOutputFilename
.replace(/^.*[/\\]/, "")
.replace(/\.[^.]+$/, "");
}

protected emitUsageImportComment(givenOutputFilename: string): void {
this.emitLine(
'// const Convert = require("./',
this.usageModuleName(givenOutputFilename),
'");',
);
}

protected emitUsageComments(): void {
protected emitUsageComments(givenOutputFilename: string): void {
this.emitMultiline(`// To parse this data:
//`);

this.emitUsageImportComment();
this.emitUsageImportComment(givenOutputFilename);
this.emitLine("//");
this.forEachTopLevel("none", (_t, name) => {
const camelCaseName = modifySource(camelCase, name);
Expand Down Expand Up @@ -537,11 +549,11 @@ function r(name${stringAnnotation}) {
});
}

protected emitSourceStructure(): void {
protected emitSourceStructure(givenOutputFilename: string): void {
if (this.leadingComments !== undefined) {
this.emitComments(this.leadingComments);
} else {
this.emitUsageComments();
this.emitUsageComments(givenOutputFilename);
}

this.emitTypes();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ export class FlowRenderer extends TypeScriptFlowBaseRenderer {
});
}

protected emitSourceStructure(): void {
protected emitSourceStructure(givenOutputFilename: string): void {
this.emitLine("// @flow");
this.ensureBlankLine();
super.emitSourceStructure();
super.emitSourceStructure(givenOutputFilename);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -191,9 +191,9 @@ export abstract class TypeScriptFlowBaseRenderer extends JavaScriptRenderer {
);
}

protected emitUsageComments(): void {
protected emitUsageComments(givenOutputFilename: string): void {
if (this._tsFlowOptions.justTypes) return;
super.emitUsageComments();
super.emitUsageComments(givenOutputFilename);
}

protected deserializerFunctionLine(t: Type, name: Name): Sourcelike {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export class TypeScriptRenderer extends TypeScriptFlowBaseRenderer {

protected emitModuleExports(): void {}

protected emitUsageImportComment(): void {
protected emitUsageImportComment(givenOutputFilename: string): void {
const topLevelNames: Sourcelike[] = [];
this.forEachTopLevel(
"none",
Expand All @@ -62,7 +62,9 @@ export class TypeScriptRenderer extends TypeScriptFlowBaseRenderer {
this.emitLine(
"// import { Convert",
topLevelNames,
' } from "./file";',
' } from "./',
this.usageModuleName(givenOutputFilename),
'";',
);
}

Expand Down
2 changes: 2 additions & 0 deletions test/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1562,6 +1562,7 @@ export const allFixtures: Fixture[] = [
new JSONFixture(languages.KotlinLanguage),
new JSONFixture(languages.Scala3Language),
new JSONFixture(languages.KotlinJacksonLanguage, "kotlin-jackson"),
new JSONFixture(languages.KotlinXLanguage, "kotlinx"),
new JSONFixture(languages.DartLanguage),
new JSONFixture(languages.PikeLanguage),
new JSONFixture(languages.HaskellLanguage),
Expand Down Expand Up @@ -1601,6 +1602,7 @@ export const allFixtures: Fixture[] = [
languages.KotlinJacksonLanguage,
"schema-kotlin-jackson",
),
new JSONSchemaFixture(languages.KotlinXLanguage, "schema-kotlinx"),
new JSONSchemaFixture(languages.Scala3Language),
new JSONSchemaFixture(languages.DartLanguage),
new JSONSchemaFixture(languages.PikeLanguage),
Expand Down
8 changes: 8 additions & 0 deletions test/fixtures/kotlinx/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/bin/env bash

# The kotlinx-serialization compiler plugin ships with the Kotlin compiler
# distribution, in lib/ next to bin/kotlinc.
KOTLINC_PATH="$(readlink -f "$(command -v kotlinc)")"
KOTLIN_LIB="$(cd "$(dirname "$KOTLINC_PATH")/../lib" && pwd)"

kotlinc main.kt TopLevel.kt -include-runtime -Xplugin="$KOTLIN_LIB/kotlinx-serialization-compiler-plugin.jar" -cp kotlinx-serialization-core-jvm-1.7.3.jar:kotlinx-serialization-json-jvm-1.7.3.jar -d main.jar
Binary file not shown.
Binary file not shown.
21 changes: 21 additions & 0 deletions test/fixtures/kotlinx/main.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package quicktype

import java.io.File
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json

// explicitNulls = false makes kotlinx omit nulls when serializing, like the
// other Kotlin frameworks we test; the test harness runs this fixture with
// allowMissingNull.
val json = Json { allowStructuredMapKeys = true; explicitNulls = false }

fun output(text: String) {
val bytes = text.toByteArray()
System.out.write(bytes, 0, bytes.size)
}

fun main(args: Array<String>) {
val text = File(args[0]).readText()
val top = json.decodeFromString<TopLevel>(text)
output(json.encodeToString(top))
}
3 changes: 3 additions & 0 deletions test/fixtures/kotlinx/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/usr/bin/env bash

kotlin -cp kotlinx-serialization-core-jvm-1.7.3.jar:kotlinx-serialization-json-jvm-1.7.3.jar:main.jar quicktype.MainKt "$1"
4 changes: 4 additions & 0 deletions test/inputs/schema/prefix-items.1.fail.union.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"tuple": [123, "not-a-tuple-member"],
"open": [false, 11, 13]
}
4 changes: 4 additions & 0 deletions test/inputs/schema/prefix-items.1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"tuple": [123, true],
"open": [false, 11, 13]
}
16 changes: 16 additions & 0 deletions test/inputs/schema/prefix-items.schema
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"tuple": {
"type": "array",
"prefixItems": [{ "type": "integer" }, { "type": "boolean" }]
},
"open": {
"type": "array",
"prefixItems": [{ "type": "boolean" }],
"items": { "type": "integer" }
}
},
"required": ["tuple", "open"]
}
Loading
Loading