Skip to content

Commit

Permalink
feat: support key remapping via as (#1174)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jason3S committed Mar 21, 2022
1 parent 8f2dc15 commit 71fb087
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 3 deletions.
19 changes: 16 additions & 3 deletions src/NodeParser/MappedTypeNodeParser.ts
Expand Up @@ -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) {}
Expand Down Expand Up @@ -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)
Expand All @@ -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
);
Expand Down
2 changes: 2 additions & 0 deletions test/valid-data-type.test.ts
Expand Up @@ -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"));
Expand Down
9 changes: 9 additions & 0 deletions 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<K>}`]: Message[K];
};
26 changes: 26 additions & 0 deletions 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"
}
}
}
8 changes: 8 additions & 0 deletions 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>]: `${K}.${SomeInterface[K]}`;
};
24 changes: 24 additions & 0 deletions 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"
}
}
}

0 comments on commit 71fb087

Please sign in to comment.