ts-json-schema-generator
Version:
Generate JSON schema from your Typescript sources
119 lines (96 loc) • 4.82 kB
text/typescript
import type { Definition } from "../Schema/Definition.js";
import type { SubTypeFormatter } from "../SubTypeFormatter.js";
import { AnyType } from "../Type/AnyType.js";
import { SymbolType } from "../Type/SymbolType.js";
import { BaseType } from "../Type/BaseType.js";
import { ObjectProperty, ObjectType } from "../Type/ObjectType.js";
import { UndefinedType } from "../Type/UndefinedType.js";
import { UnionType } from "../Type/UnionType.js";
import type { TypeFormatter } from "../TypeFormatter.js";
import { getAllOfDefinitionReducer } from "../Utils/allOfDefinition.js";
import { derefType } from "../Utils/derefType.js";
import { preserveAnnotation } from "../Utils/preserveAnnotation.js";
import { removeUndefined } from "../Utils/removeUndefined.js";
import type { StringMap } from "../Utils/StringMap.js";
import { uniqueArray } from "../Utils/uniqueArray.js";
import { NeverType } from "../Type/NeverType.js";
export class ObjectTypeFormatter implements SubTypeFormatter {
public constructor(protected childTypeFormatter: TypeFormatter) {}
public supportsType(type: BaseType): boolean {
return type instanceof ObjectType;
}
public getDefinition(type: ObjectType): Definition {
const types = type.getBaseTypes();
if (types.length === 0) {
return this.getObjectDefinition(type);
}
return types.reduce(getAllOfDefinitionReducer(this.childTypeFormatter), this.getObjectDefinition(type));
}
public getChildren(type: ObjectType): BaseType[] {
const properties = type.getProperties();
const additionalProperties: BaseType | boolean = type.getAdditionalProperties();
const childrenOfBase = type
.getBaseTypes()
.reduce(
(result: BaseType[], baseType) => [...result, ...this.childTypeFormatter.getChildren(baseType)],
[],
);
const childrenOfAdditionalProps =
additionalProperties instanceof BaseType ? this.childTypeFormatter.getChildren(additionalProperties) : [];
const childrenOfProps = properties.reduce((result: BaseType[], property) => {
const propertyType = property.getType();
if (propertyType instanceof NeverType) {
return result;
}
return [...result, ...this.childTypeFormatter.getChildren(propertyType)];
}, []);
const children = [...childrenOfBase, ...childrenOfAdditionalProps, ...childrenOfProps];
return uniqueArray(children);
}
protected getObjectDefinition(type: ObjectType): Definition {
let objectProperties = type.getProperties();
const additionalProperties: BaseType | boolean = type.getAdditionalProperties();
if (additionalProperties === false) {
objectProperties = objectProperties.filter(
(property) => !(derefType(property.getType()) instanceof NeverType),
);
}
const preparedProperties = objectProperties.map((property) => this.prepareObjectProperty(property));
const required = preparedProperties
.filter((property) => property.isRequired())
.map((property) => property.getName());
const properties = preparedProperties.reduce((result: StringMap<Definition>, property) => {
result[property.getName()] = this.childTypeFormatter.getDefinition(property.getType());
return result;
}, {});
return {
type: "object",
...(Object.keys(properties).length > 0 ? { properties } : {}),
...(required.length > 0 ? { required } : {}),
...(additionalProperties === true ||
additionalProperties instanceof AnyType ||
additionalProperties instanceof SymbolType
? {}
: {
additionalProperties:
additionalProperties instanceof BaseType
? this.childTypeFormatter.getDefinition(additionalProperties)
: additionalProperties,
}),
};
}
protected prepareObjectProperty(property: ObjectProperty): ObjectProperty {
const propertyType = property.getType();
const propType = derefType(propertyType);
if (propType instanceof UndefinedType) {
return new ObjectProperty(property.getName(), propertyType, false);
} else if (!(propType instanceof UnionType)) {
return property;
}
const { newType: newPropType, numRemoved } = removeUndefined(propType);
if (numRemoved == 0) {
return property;
}
return new ObjectProperty(property.getName(), preserveAnnotation(propertyType, newPropType), false);
}
}