0
# Custom Extensions
1
2
Create custom parsers and formatters to handle special TypeScript constructs, generate custom JSON schema formats, or integrate with specific validation systems.
3
4
## Capabilities
5
6
### Custom Type Formatters
7
8
Create custom formatters to control how internal types are converted to JSON schema definitions.
9
10
```typescript { .api }
11
interface SubTypeFormatter extends TypeFormatter {
12
/**
13
* Check if this formatter can handle the given type
14
* @param type - Internal type representation to check
15
* @returns true if this formatter supports the type
16
*/
17
supportsType(type: BaseType): boolean;
18
19
/**
20
* Generate JSON schema definition for a type
21
* @param type - Internal type representation
22
* @returns JSON schema definition object
23
*/
24
getDefinition(type: BaseType): Definition;
25
26
/**
27
* Get child types that need definitions
28
* @param type - Internal type representation
29
* @returns Array of child types
30
*/
31
getChildren(type: BaseType): BaseType[];
32
}
33
```
34
35
**Custom Formatter Example:**
36
37
```typescript
38
import {
39
SubTypeFormatter,
40
BaseType,
41
Definition,
42
FunctionType,
43
TypeFormatter
44
} from "ts-json-schema-generator";
45
46
export class MyFunctionTypeFormatter implements SubTypeFormatter {
47
constructor(private childTypeFormatter: TypeFormatter) {}
48
49
public supportsType(type: BaseType): boolean {
50
return type instanceof FunctionType;
51
}
52
53
public getDefinition(type: FunctionType): Definition {
54
// Custom schema for function properties
55
return {
56
type: "object",
57
properties: {
58
isFunction: {
59
type: "boolean",
60
const: true,
61
},
62
signature: {
63
type: "string",
64
description: "Function signature"
65
}
66
},
67
};
68
}
69
70
public getChildren(type: FunctionType): BaseType[] {
71
// Return empty if no child types, or delegate to child formatter
72
return [];
73
}
74
}
75
```
76
77
### Custom Node Parsers
78
79
Create custom parsers to handle new TypeScript syntax or convert special constructs to internal types.
80
81
```typescript { .api }
82
interface SubNodeParser extends NodeParser {
83
/**
84
* Check if this parser can handle the given AST node
85
* @param node - TypeScript AST node to check
86
* @returns true if this parser supports the node type
87
*/
88
supportsNode(node: ts.Node): boolean;
89
90
/**
91
* Convert a TypeScript AST node to internal type representation
92
* @param node - TypeScript AST node to parse
93
* @param context - Parsing context
94
* @param reference - Optional reference type
95
* @returns Internal type representation
96
*/
97
createType(node: ts.Node, context: Context, reference?: ReferenceType): BaseType;
98
}
99
```
100
101
**Custom Parser Example:**
102
103
```typescript
104
import {
105
SubNodeParser,
106
BaseType,
107
Context,
108
StringType,
109
ReferenceType
110
} from "ts-json-schema-generator";
111
import ts from "ts-json-schema-generator";
112
113
export class MyConstructorParser implements SubNodeParser {
114
supportsNode(node: ts.Node): boolean {
115
return node.kind === ts.SyntaxKind.ConstructorType;
116
}
117
118
createType(node: ts.Node, context: Context, reference?: ReferenceType): BaseType {
119
// Treat constructor types as strings for this example
120
return new StringType();
121
}
122
}
123
```
124
125
### Formatter Integration
126
127
Add custom formatters to the main formatter using the augmentation callback.
128
129
```typescript { .api }
130
type FormatterAugmentor = (
131
formatter: MutableTypeFormatter,
132
circularReferenceTypeFormatter: CircularReferenceTypeFormatter
133
) => void;
134
135
function createFormatter(
136
config: Config,
137
augmentor?: FormatterAugmentor
138
): TypeFormatter;
139
```
140
141
**Integration Example:**
142
143
```typescript
144
import {
145
createProgram,
146
createParser,
147
SchemaGenerator,
148
createFormatter
149
} from "ts-json-schema-generator";
150
import { MyFunctionTypeFormatter } from "./my-function-formatter.js";
151
152
const config = {
153
path: "path/to/source/file.ts",
154
tsconfig: "path/to/tsconfig.json",
155
type: "*",
156
};
157
158
// Configure formatter with custom extension
159
const formatter = createFormatter(config, (fmt, circularReferenceTypeFormatter) => {
160
// Add custom formatter to the chain
161
fmt.addTypeFormatter(new MyFunctionTypeFormatter(circularReferenceTypeFormatter));
162
});
163
164
const program = createProgram(config);
165
const parser = createParser(program, config);
166
const generator = new SchemaGenerator(program, parser, formatter, config);
167
const schema = generator.createSchema(config.type);
168
```
169
170
### Parser Integration
171
172
Add custom parsers to the main parser using the augmentation callback.
173
174
```typescript { .api }
175
type ParserAugmentor = (parser: MutableParser) => void;
176
177
function createParser(
178
program: ts.Program,
179
config: Config,
180
augmentor?: ParserAugmentor
181
): NodeParser;
182
```
183
184
**Integration Example:**
185
186
```typescript
187
import {
188
createProgram,
189
createParser,
190
SchemaGenerator,
191
createFormatter
192
} from "ts-json-schema-generator";
193
import { MyConstructorParser } from "./my-constructor-parser.js";
194
195
const config = {
196
path: "path/to/source/file.ts",
197
tsconfig: "path/to/tsconfig.json",
198
type: "*",
199
};
200
201
const program = createProgram(config);
202
203
// Configure parser with custom extension
204
const parser = createParser(program, config, (prs) => {
205
prs.addNodeParser(new MyConstructorParser());
206
});
207
208
const formatter = createFormatter(config);
209
const generator = new SchemaGenerator(program, parser, formatter, config);
210
const schema = generator.createSchema(config.type);
211
```
212
213
### Complete Custom Extension Example
214
215
Full example showing both custom parser and formatter working together.
216
217
```typescript
218
// custom-date-parser.ts
219
import {
220
SubNodeParser,
221
BaseType,
222
Context,
223
ReferenceType
224
} from "ts-json-schema-generator";
225
import ts from "ts-json-schema-generator";
226
227
// Custom type for Date objects
228
export class DateType extends BaseType {
229
public getId(): string {
230
return "date";
231
}
232
}
233
234
export class CustomDateParser implements SubNodeParser {
235
supportsNode(node: ts.Node): boolean {
236
// Handle references to Date type
237
return ts.isTypeReferenceNode(node) &&
238
ts.isIdentifier(node.typeName) &&
239
node.typeName.text === "Date";
240
}
241
242
createType(node: ts.Node, context: Context, reference?: ReferenceType): BaseType {
243
return new DateType();
244
}
245
}
246
247
// custom-date-formatter.ts
248
import {
249
SubTypeFormatter,
250
BaseType,
251
Definition
252
} from "ts-json-schema-generator";
253
254
export class CustomDateFormatter implements SubTypeFormatter {
255
supportsType(type: BaseType): boolean {
256
return type instanceof DateType;
257
}
258
259
getDefinition(type: DateType): Definition {
260
return {
261
type: "string",
262
format: "date-time",
263
description: "ISO 8601 date-time string"
264
};
265
}
266
267
getChildren(type: DateType): BaseType[] {
268
return [];
269
}
270
}
271
272
// usage.ts
273
import {
274
createProgram,
275
createParser,
276
createFormatter,
277
SchemaGenerator
278
} from "ts-json-schema-generator";
279
import { CustomDateParser } from "./custom-date-parser.js";
280
import { CustomDateFormatter } from "./custom-date-formatter.js";
281
282
const config = {
283
path: "src/api.ts",
284
type: "UserProfile"
285
};
286
287
const program = createProgram(config);
288
289
const parser = createParser(program, config, (prs) => {
290
prs.addNodeParser(new CustomDateParser());
291
});
292
293
const formatter = createFormatter(config, (fmt, circularRef) => {
294
fmt.addTypeFormatter(new CustomDateFormatter());
295
});
296
297
const generator = new SchemaGenerator(program, parser, formatter, config);
298
const schema = generator.createSchema(config.type);
299
300
console.log(JSON.stringify(schema, null, 2));
301
```
302
303
### Advanced Formatter Patterns
304
305
Complex formatter scenarios for specialized JSON schema generation.
306
307
**Formatter with Child Types:**
308
309
```typescript
310
export class ComplexTypeFormatter implements SubTypeFormatter {
311
constructor(private childTypeFormatter: TypeFormatter) {}
312
313
supportsType(type: BaseType): boolean {
314
return type instanceof MyComplexType;
315
}
316
317
getDefinition(type: MyComplexType): Definition {
318
// Use child formatter for nested types
319
const childDef = this.childTypeFormatter.getDefinition(type.getChildType());
320
321
return {
322
type: "object",
323
properties: {
324
value: childDef,
325
metadata: { type: "object" }
326
}
327
};
328
}
329
330
getChildren(type: MyComplexType): BaseType[] {
331
// Return child types that need their own definitions
332
return this.childTypeFormatter.getChildren(type.getChildType());
333
}
334
}
335
```
336
337
**Validation-Specific Formatter:**
338
339
```typescript
340
export class ValidationFormatter implements SubTypeFormatter {
341
supportsType(type: BaseType): boolean {
342
return type instanceof StringType;
343
}
344
345
getDefinition(type: StringType): Definition {
346
const definition: Definition = { type: "string" };
347
348
// Add custom validation rules
349
if (type.getId().includes("email")) {
350
definition.format = "email";
351
}
352
353
if (type.getId().includes("password")) {
354
definition.minLength = 8;
355
definition.pattern = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)";
356
}
357
358
return definition;
359
}
360
361
getChildren(type: StringType): BaseType[] {
362
return [];
363
}
364
}
365
```
366
367
### Error Handling in Extensions
368
369
Proper error handling for custom parsers and formatters.
370
371
```typescript
372
import { UnhandledError } from "ts-json-schema-generator";
373
374
export class SafeCustomParser implements SubNodeParser {
375
supportsNode(node: ts.Node): boolean {
376
return node.kind === ts.SyntaxKind.CustomSyntax;
377
}
378
379
createType(node: ts.Node, context: Context, reference?: ReferenceType): BaseType {
380
try {
381
// Custom parsing logic
382
return this.parseCustomNode(node, context);
383
} catch (error) {
384
throw new UnhandledError(
385
"Failed to parse custom node",
386
node,
387
error
388
);
389
}
390
}
391
392
private parseCustomNode(node: ts.Node, context: Context): BaseType {
393
// Implementation details
394
throw new Error("Not implemented");
395
}
396
}
397
```
398
399
### Testing Custom Extensions
400
401
Unit testing patterns for custom parsers and formatters.
402
403
```typescript
404
// test-custom-extensions.ts
405
import { expect } from "chai";
406
import ts from "typescript";
407
import { Context } from "ts-json-schema-generator";
408
import { CustomDateParser, DateType } from "./custom-date-parser.js";
409
410
describe("CustomDateParser", () => {
411
let parser: CustomDateParser;
412
413
beforeEach(() => {
414
parser = new CustomDateParser();
415
});
416
417
it("should support Date type references", () => {
418
const source = "let date: Date;";
419
const sourceFile = ts.createSourceFile("test.ts", source, ts.ScriptTarget.Latest);
420
const dateNode = /* extract Date reference node */;
421
422
expect(parser.supportsNode(dateNode)).to.be.true;
423
});
424
425
it("should create DateType for Date references", () => {
426
const dateNode = /* create Date reference node */;
427
const context = new Context();
428
const result = parser.createType(dateNode, context);
429
430
expect(result).to.be.instanceOf(DateType);
431
});
432
});
433
```
434
435
### Extension Distribution
436
437
Package and distribute custom extensions for reuse.
438
439
```json
440
// package.json for extension package
441
{
442
"name": "ts-json-schema-generator-date-extension",
443
"version": "1.0.0",
444
"main": "dist/index.js",
445
"types": "dist/index.d.ts",
446
"peerDependencies": {
447
"ts-json-schema-generator": "^2.0.0"
448
}
449
}
450
```
451
452
```typescript
453
// index.ts - extension package entry point
454
export { CustomDateParser, DateType } from "./custom-date-parser.js";
455
export { CustomDateFormatter } from "./custom-date-formatter.js";
456
457
// Convenience function for common usage
458
export function createDateExtension() {
459
return {
460
parser: new CustomDateParser(),
461
formatter: new CustomDateFormatter()
462
};
463
}
464
```