diff --git a/src/NodeParser/MappedTypeNodeParser.ts b/src/NodeParser/MappedTypeNodeParser.ts index 62561819b..a1603b7b1 100644 --- a/src/NodeParser/MappedTypeNodeParser.ts +++ b/src/NodeParser/MappedTypeNodeParser.ts @@ -103,7 +103,7 @@ export class MappedTypeNodeParser implements SubNodeParser { protected getProperties(node: ts.MappedTypeNode, keyListType: UnionType, context: Context): ObjectProperty[] { return keyListType - .getTypes() + .getFlattenedTypes(derefType) .filter((type): type is LiteralType => type instanceof LiteralType) .map((type) => [type, this.mapKey(node, type, context)]) .filter((value): value is [LiteralType, LiteralType] => value[1] instanceof LiteralType) diff --git a/src/Type/UnionType.ts b/src/Type/UnionType.ts index 2d36da255..19eb15067 100644 --- a/src/Type/UnionType.ts +++ b/src/Type/UnionType.ts @@ -1,7 +1,7 @@ import { BaseType } from "./BaseType.js"; import { uniqueTypeArray } from "../Utils/uniqueTypeArray.js"; import { NeverType } from "./NeverType.js"; -import { derefType } from "../Utils/derefType.js"; +import { derefAliasedType, isHiddenType } from "../Utils/derefType.js"; export class UnionType extends BaseType { private readonly types: BaseType[]; @@ -41,19 +41,18 @@ export class UnionType extends BaseType { return this.types; } - public normalize(): BaseType { - if (this.types.length === 0) { - return new NeverType(); - } else if (this.types.length === 1) { - return this.types[0]; - } else { - const union = new UnionType(this.types.filter((type) => !(derefType(type) instanceof NeverType))); - - if (union.getTypes().length > 1) { - return union; - } else { - return union.normalize(); - } - } + /** + * Get the types in this union as a flat list. + */ + public getFlattenedTypes(deref: (type: BaseType) => BaseType = derefAliasedType): BaseType[] { + return this.getTypes() + .filter((t) => !isHiddenType(t)) + .map(deref) + .flatMap((t) => { + if (t instanceof UnionType) { + return t.getFlattenedTypes(deref); + } + return t; + }); } } diff --git a/src/TypeFormatter/LiteralUnionTypeFormatter.ts b/src/TypeFormatter/LiteralUnionTypeFormatter.ts index 68572bfce..937d2a0ed 100644 --- a/src/TypeFormatter/LiteralUnionTypeFormatter.ts +++ b/src/TypeFormatter/LiteralUnionTypeFormatter.ts @@ -6,7 +6,6 @@ import { LiteralType, LiteralValue } from "../Type/LiteralType.js"; import { NullType } from "../Type/NullType.js"; import { StringType } from "../Type/StringType.js"; import { UnionType } from "../Type/UnionType.js"; -import { derefAliasedType, isHiddenType } from "../Utils/derefType.js"; import { typeName } from "../Utils/typeName.js"; import { uniqueArray } from "../Utils/uniqueArray.js"; @@ -20,10 +19,10 @@ export class LiteralUnionTypeFormatter implements SubTypeFormatter { let allStrings = true; let hasNull = false; - const flattenedTypes = flattenTypes(type); + const literals = type.getFlattenedTypes(); // filter out String types since we need to be more careful about them - const types = flattenedTypes.filter((t) => { + const types = literals.filter((t) => { if (t instanceof StringType) { hasString = true; preserveLiterals = preserveLiterals || t.getPreserveLiterals(); @@ -70,23 +69,10 @@ export class LiteralUnionTypeFormatter implements SubTypeFormatter { } } -function flattenTypes(type: UnionType): (StringType | LiteralType | NullType)[] { - return type - .getTypes() - .filter((t) => !isHiddenType(t)) - .map(derefAliasedType) - .flatMap((t) => { - if (t instanceof UnionType) { - return flattenTypes(t); - } - return t as StringType | LiteralType | NullType; - }); -} - export function isLiteralUnion(type: UnionType): boolean { - return flattenTypes(type).every( - (item) => item instanceof LiteralType || item instanceof NullType || item instanceof StringType, - ); + return type + .getFlattenedTypes() + .every((item) => item instanceof LiteralType || item instanceof NullType || item instanceof StringType); } function getLiteralValue(value: LiteralType | NullType): LiteralValue | null { diff --git a/test/valid-data/type-mapped-union/main.ts b/test/valid-data/type-mapped-union/main.ts new file mode 100644 index 000000000..6ecd7bfa3 --- /dev/null +++ b/test/valid-data/type-mapped-union/main.ts @@ -0,0 +1,6 @@ +type MyType1 = "s1"; +type MyType2 = MyType1 | "s2" | "s3"; +type MyType3 = MyType2 | "s4" | "s5"; +type MyType10 = MyType3 | MyType2 | "s6"; + +export type MyType = Record; diff --git a/test/valid-data/type-mapped-union/schema.json b/test/valid-data/type-mapped-union/schema.json new file mode 100644 index 000000000..720a444a4 --- /dev/null +++ b/test/valid-data/type-mapped-union/schema.json @@ -0,0 +1,40 @@ +{ + "$ref": "#/definitions/MyType", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "MyType": { + "additionalProperties": { + "type": "string" + }, + "properties": { + "s1": { + "type": "string" + }, + "s2": { + "type": "string" + }, + "s3": { + "type": "string" + }, + "s4": { + "type": "string" + }, + "s5": { + "type": "string" + }, + "s6": { + "type": "string" + } + }, + "required": [ + "s4", + "s5", + "s2", + "s3", + "s1", + "s6" + ], + "type": "object" + } + } +}