-
Notifications
You must be signed in to change notification settings - Fork 408
Incorrectly generated schema when "type" is an array #228
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
The schema's type is declared as |
Did my own limited implementation that has missing features. However it is able to bootstrap the schema itself. One somewhat unrelated TypeScript limitation is that TypeScript json imports don't work nicely with string literal types (microsoft/TypeScript#31920). function isSimple(type) {
if (typeof type === "object" && "type" in type && type.type === "object") {
return false;
}
return true;
}
export function generateType(type: SimpleTypes | SimpleTypes[], context: any, indent: number) {
debug("generateType", type, context, indent);
if (typeof type === "string") {
if (type === "integer") {
return "number";
}
return type;
}
if (type instanceof Array) {
return type.map((typ) => {
if (["object", "array"].includes(typ)) {
return generateSchemaWithEnums({ ...context, type: typ }, indent);
}
return generateType(typ, context, indent);
}).join(" | ");
}
debug(`warning: ignoring type for ${JSON.stringify(type)}`);
}
export function generateSchema(object: Schema, indent = 1) {
debug("generateSchema", object, indent);
if (object === true) {
return `any`;
}
if (object === false) {
return `never`;
}
if ("$ref" in object) {
if (object.$ref === "#") {
return rootName;
}
const name = object.$ref.substring(object.$ref.lastIndexOf("/") + 1);
return camelcase(name);
}
if ("anyOf" in object) {
return object.anyOf
.map((type) => generateSchemaWithEnums(type, indent))
.filter((i) => i !== undefined)
.join(" | ");
}
if ("oneOf" in object) {
return object.oneOf
.map((type) => generateSchemaWithEnums(type, indent))
.filter((i) => i !== undefined)
.join(" | ");
}
if ("allOf" in object) {
return object.allOf
.map((type) => generateSchemaWithEnums(type, indent))
.filter((i) => i !== undefined)
.join(" & ");
}
if ("type" in object) {
if (object.type === "array" && "items" in object) {
if (object.items instanceof Array) {
let additional = ["...any[]"];
if ("additionalItems" in object) {
if (object.additionalItems === false) {
additional = [];
} else {
additional = [`...${generateSchemaWithEnums(object.additionalItems)}[]`];
}
}
const tuple = object.items
.map((type) => generateSchemaWithEnums(type) + "?")
.concat(additional)
.join(", ");
return `[${tuple}]`;
}
if (isSimple(object.items)) {
return `${generateSchemaWithEnums(object.items, indent)}[]`;
} else {
return `Array<${generateSchemaWithEnums(object.items, indent)}>`;
}
}
if (object.type === "object") {
const space = `\n${Array(indent).join(" ")}`;
const morespace = space + " ";
let properties = [];
if ("properties" in object) {
const required = "required" in object ? object.required : [];
properties = Object.entries(object.properties).map(([name, property]: [string, any]) => {
const opt = required.includes(name) ? "" : "?";
const type = generateSchemaWithEnums(property, indent + 1);
return `${morespace}${name}${opt}: ${type};`;
});
}
const additionalProperties
= object.additionalProperties === undefined
? true
: object.additionalProperties;
if (additionalProperties) {
let type = generateSchemaWithEnums(additionalProperties, indent);
if (type !== "any") {
if (properties.length > 0) {
type += " | any";
}
}
if (typeof additionalProperties === "object" || properties.length > 0) {
properties.push(`${morespace}[key: string]: ${type};`);
}
}
if ("definitions" in object) {
Object.entries(object.definitions).forEach(([name, schema]) => {
generate(name, schema);
});
}
if (properties.length === 0) {
return "any";
}
return `{${properties.join("")}${space}}`;
}
return generateType(object.type, object, indent);
}
debug(`warning: ignoring schema for ${JSON.stringify(object)}`);
}
export function generateSchemaWithEnums(schema: Schema, indent = 1) {
debug("generateSchemaWithEnums", schema, indent);
const source = generateSchema(schema, indent);
if (typeof schema === "object" && "enum" in schema) {
const enums = schema.enum.map((e) => JSON.stringify(e)).join(" | ");
return [source, enums]
.filter((i) => i !== undefined)
.join(" & ");
}
return source;
}
export function camelcase(name: string) {
const parts = name.split(/[-_]/g);
return parts.map((part) => {
return part[0].toUpperCase() + part.substring(1);
}).join("");
}
let code: string[];
let rootName: string;
export function generate(name: string, schema: Schema, root = false) {
debug("generate", name, schema);
name = camelcase(name);
if (root) {
rootName = name;
code = [];
}
const type = generateSchemaWithEnums(schema);
if (isSimple(schema)) {
code.push(`export type ${name} = ${type};`);
} else {
code.push(`export interface I${name} ${type}`);
}
code.push("");
if (root) {
return {
code: code.join("\n"),
name,
};
}
}
function debug(...args: any[]) {
if (false) {
console.log(...args);
}
}
export type SchemaArray = Schema[];
export type NonNegativeInteger = number;
export type NonNegativeIntegerDefault0 = NonNegativeInteger;
export type SimpleTypes = "array" | "boolean" | "integer" | "null" | "number" | "object" | "string";
export type StringArray = string[];
export type Schema = {
$id?: string;
$schema?: string;
$ref?: string;
$comment?: string;
title?: string;
description?: string;
default?: any;
readOnly?: boolean;
examples?: any[];
multipleOf?: number;
maximum?: number;
exclusiveMaximum?: number;
minimum?: number;
exclusiveMinimum?: number;
maxLength?: NonNegativeInteger;
minLength?: NonNegativeIntegerDefault0;
pattern?: string;
additionalItems?: Schema;
items?: Schema | SchemaArray;
maxItems?: NonNegativeInteger;
minItems?: NonNegativeIntegerDefault0;
uniqueItems?: boolean;
contains?: Schema;
maxProperties?: NonNegativeInteger;
minProperties?: NonNegativeIntegerDefault0;
required?: StringArray;
additionalProperties?: Schema;
definitions?: {
[key: string]: Schema;
};
properties?: {
[key: string]: Schema;
};
patternProperties?: {
[key: string]: Schema;
};
dependencies?: {
[key: string]: Schema | StringArray;
};
propertyNames?: Schema;
const?: any;
enum?: any[];
type?: SimpleTypes | SimpleTypes[];
format?: string;
contentMediaType?: string;
contentEncoding?: string;
if?: Schema;
then?: Schema;
else?: Schema;
allOf?: SchemaArray;
anyOf?: SchemaArray;
oneOf?: SchemaArray;
not?: Schema;
[key: string]: any;
} | boolean; |
Let me know if this PR fixes the issue: #261 |
Have tried to run your PR on the draft-07 schema at: https://raw.githubusercontent.com/json-schema-org/json-schema-spec/draft-07/schema.json This results in an error, maybe i am doing something wrong:
|
@sanderhahn thanks for checking. Looks like the issue is in master as well. The traverse doesn't appear to support json-schema-to-typescript/src/utils.ts Lines 94 to 95 in 85c3ab0
Looks like your issue is still valid and #261 won't help until the traverse function is updated to support Draft-07. |
Trying to generate a schema definition from http://json-schema.org/draft-07/schema# itself. The type becomes:
However when the type is changed into
"type": "object"
and boolean default option is removed, the type becomes:The last type seems more useful... Is there a way to improve the code generation of the schema itself without adjusting the schema definition?
The text was updated successfully, but these errors were encountered: