diff --git a/src/NodeParser/MappedTypeNodeParser.ts b/src/NodeParser/MappedTypeNodeParser.ts index 1a9bbcd04..bae958e05 100644 --- a/src/NodeParser/MappedTypeNodeParser.ts +++ b/src/NodeParser/MappedTypeNodeParser.ts @@ -10,13 +10,14 @@ import { LiteralType } from "../Type/LiteralType"; import { NumberType } from "../Type/NumberType"; import { ObjectProperty, ObjectType } from "../Type/ObjectType"; import { StringType } from "../Type/StringType"; +import { SymbolType } from "../Type/SymbolType"; import { UnionType } from "../Type/UnionType"; +import assert from "../Utils/assert"; import { derefAnnotatedType, derefType } from "../Utils/derefType"; import { getKey } from "../Utils/nodeKey"; +import { notUndefined } from "../Utils/notUndefined"; import { preserveAnnotation } from "../Utils/preserveAnnotation"; import { removeUndefined } from "../Utils/removeUndefined"; -import { notUndefined } from "../Utils/notUndefined"; -import { SymbolType } from "../Type/SymbolType"; export class MappedTypeNodeParser implements SubNodeParser { public constructor(protected childNodeParser: NodeParser, protected readonly additionalProperties: boolean) {} @@ -67,11 +68,23 @@ export class MappedTypeNodeParser implements SubNodeParser { } } + protected mapKey(node: ts.MappedTypeNode, rawKey: LiteralType, context: Context): LiteralType { + if (!node.nameType) { + return rawKey; + } + const key = derefType( + this.childNodeParser.createType(node.nameType, this.createSubContext(node, rawKey, context)) + ); + assert(key instanceof LiteralType, "Must resolve to Literal"); + return key; + } + protected getProperties(node: ts.MappedTypeNode, keyListType: UnionType, context: Context): ObjectProperty[] { return keyListType .getTypes() .filter((type) => type instanceof LiteralType) .reduce((result: ObjectProperty[], key: LiteralType) => { + const namedKey = this.mapKey(node, key, context); const propertyType = this.childNodeParser.createType( node.type!, this.createSubContext(node, key, context) @@ -90,7 +103,7 @@ export class MappedTypeNodeParser implements SubNodeParser { } const objectProperty = new ObjectProperty( - key.getValue().toString(), + namedKey.getValue().toString(), preserveAnnotation(propertyType, newType), !node.questionToken && !hasUndefined ); diff --git a/test/valid-data-type.test.ts b/test/valid-data-type.test.ts index a1abf173c..a05bc9c3d 100644 --- a/test/valid-data-type.test.ts +++ b/test/valid-data-type.test.ts @@ -76,6 +76,8 @@ describe("valid-data-type", () => { it("type-keyof-object-function", assertValidSchema("type-keyof-object-function", "MyType")); it("type-mapped-simple", assertValidSchema("type-mapped-simple", "MyObject")); it("type-mapped-index", assertValidSchema("type-mapped-index", "MyObject")); + it("type-mapped-index-as", assertValidSchema("type-mapped-index-as", "MyObject")); + it("type-mapped-index-as-template", assertValidSchema("type-mapped-index-as-template", "MyObject")); it("type-mapped-literal", assertValidSchema("type-mapped-literal", "MyObject")); it("type-mapped-generic", assertValidSchema("type-mapped-generic", "MyObject")); it("type-mapped-native", assertValidSchema("type-mapped-native", "MyObject")); diff --git a/test/valid-data/type-mapped-index-as-template/main.ts b/test/valid-data/type-mapped-index-as-template/main.ts new file mode 100644 index 000000000..00a268183 --- /dev/null +++ b/test/valid-data/type-mapped-index-as-template/main.ts @@ -0,0 +1,9 @@ +interface Message { + id: number; + name: string; + title: string; +} + +export type MyObject = { + [K in keyof Message as `message${Capitalize}`]: Message[K]; +}; diff --git a/test/valid-data/type-mapped-index-as-template/schema.json b/test/valid-data/type-mapped-index-as-template/schema.json new file mode 100644 index 000000000..3d2b5d218 --- /dev/null +++ b/test/valid-data/type-mapped-index-as-template/schema.json @@ -0,0 +1,26 @@ +{ + "$ref": "#/definitions/MyObject", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "MyObject": { + "additionalProperties": false, + "properties": { + "messageId": { + "type": "number" + }, + "messageName": { + "type": "string" + }, + "messageTitle": { + "type": "string" + } + }, + "required": [ + "messageId", + "messageName", + "messageTitle" + ], + "type": "object" + } + } +} diff --git a/test/valid-data/type-mapped-index-as/main.ts b/test/valid-data/type-mapped-index-as/main.ts new file mode 100644 index 000000000..6c8601d6a --- /dev/null +++ b/test/valid-data/type-mapped-index-as/main.ts @@ -0,0 +1,8 @@ +interface SomeInterface { + foo: 12; + bar: "baz"; +} + +export type MyObject = { + [K in keyof SomeInterface as Capitalize]: `${K}.${SomeInterface[K]}`; +}; diff --git a/test/valid-data/type-mapped-index-as/schema.json b/test/valid-data/type-mapped-index-as/schema.json new file mode 100644 index 000000000..ae5209fb1 --- /dev/null +++ b/test/valid-data/type-mapped-index-as/schema.json @@ -0,0 +1,24 @@ +{ + "$ref": "#/definitions/MyObject", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "MyObject": { + "additionalProperties": false, + "properties": { + "Bar": { + "const": "bar.baz", + "type": "string" + }, + "Foo": { + "const": "foo.12", + "type": "string" + } + }, + "required": [ + "Foo", + "Bar" + ], + "type": "object" + } + } +}