ts-json-schema-generator
Version:
Generate JSON schema from your Typescript sources
117 lines (93 loc) • 4.6 kB
text/typescript
import { Definition } from "../Schema/Definition";
import { SubTypeFormatter } from "../SubTypeFormatter";
import { AnyType } from "../Type/AnyType";
import { SymbolType } from "../Type/SymbolType";
import { BaseType } from "../Type/BaseType";
import { ObjectProperty, ObjectType } from "../Type/ObjectType";
import { UndefinedType } from "../Type/UndefinedType";
import { UnionType } from "../Type/UnionType";
import { TypeFormatter } from "../TypeFormatter";
import { getAllOfDefinitionReducer } from "../Utils/allOfDefinition";
import { derefType } from "../Utils/derefType";
import { preserveAnnotation } from "../Utils/preserveAnnotation";
import { removeUndefined } from "../Utils/removeUndefined";
import { StringMap } from "../Utils/StringMap";
import { uniqueArray } from "../Utils/uniqueArray";
export class ObjectTypeFormatter implements SubTypeFormatter {
public constructor(private childTypeFormatter: TypeFormatter) {}
public supportsType(type: ObjectType): 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 === undefined) {
return result;
}
return [...result, ...this.childTypeFormatter.getChildren(propertyType)];
}, []);
const children = [...childrenOfBase, ...childrenOfAdditionalProps, ...childrenOfProps];
return uniqueArray(children);
}
private getObjectDefinition(type: ObjectType): Definition {
const objectProperties = type.getProperties();
const additionalProperties: BaseType | boolean = type.getAdditionalProperties();
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) => {
const propertyType = property.getType();
if (propertyType !== undefined) {
result[property.getName()] = this.childTypeFormatter.getDefinition(propertyType);
}
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,
}),
};
}
private 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);
}
}