CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-ts-json-schema-generator

Generate JSON schema from your TypeScript sources with extensive customization options

Pending
Overview
Eval results
Files

custom-extensions.mddocs/

Custom Extensions

Create custom parsers and formatters to handle special TypeScript constructs, generate custom JSON schema formats, or integrate with specific validation systems.

Capabilities

Custom Type Formatters

Create custom formatters to control how internal types are converted to JSON schema definitions.

interface SubTypeFormatter extends TypeFormatter {
  /**
   * Check if this formatter can handle the given type
   * @param type - Internal type representation to check
   * @returns true if this formatter supports the type
   */
  supportsType(type: BaseType): boolean;
  
  /**
   * Generate JSON schema definition for a type
   * @param type - Internal type representation
   * @returns JSON schema definition object
   */
  getDefinition(type: BaseType): Definition;
  
  /**
   * Get child types that need definitions
   * @param type - Internal type representation
   * @returns Array of child types
   */
  getChildren(type: BaseType): BaseType[];
}

Custom Formatter Example:

import { 
  SubTypeFormatter, 
  BaseType, 
  Definition, 
  FunctionType, 
  TypeFormatter 
} from "ts-json-schema-generator";

export class MyFunctionTypeFormatter implements SubTypeFormatter {
  constructor(private childTypeFormatter: TypeFormatter) {}
  
  public supportsType(type: BaseType): boolean {
    return type instanceof FunctionType;
  }
  
  public getDefinition(type: FunctionType): Definition {
    // Custom schema for function properties
    return {
      type: "object",
      properties: {
        isFunction: {
          type: "boolean",
          const: true,
        },
        signature: {
          type: "string",
          description: "Function signature"
        }
      },
    };
  }
  
  public getChildren(type: FunctionType): BaseType[] {
    // Return empty if no child types, or delegate to child formatter
    return [];
  }
}

Custom Node Parsers

Create custom parsers to handle new TypeScript syntax or convert special constructs to internal types.

interface SubNodeParser extends NodeParser {
  /**
   * Check if this parser can handle the given AST node
   * @param node - TypeScript AST node to check
   * @returns true if this parser supports the node type
   */
  supportsNode(node: ts.Node): boolean;
  
  /**
   * Convert a TypeScript AST node to internal type representation
   * @param node - TypeScript AST node to parse
   * @param context - Parsing context
   * @param reference - Optional reference type
   * @returns Internal type representation
   */
  createType(node: ts.Node, context: Context, reference?: ReferenceType): BaseType;
}

Custom Parser Example:

import { 
  SubNodeParser, 
  BaseType, 
  Context, 
  StringType, 
  ReferenceType 
} from "ts-json-schema-generator";
import ts from "ts-json-schema-generator";

export class MyConstructorParser implements SubNodeParser {
  supportsNode(node: ts.Node): boolean {
    return node.kind === ts.SyntaxKind.ConstructorType;
  }
  
  createType(node: ts.Node, context: Context, reference?: ReferenceType): BaseType {
    // Treat constructor types as strings for this example
    return new StringType();
  }
}

Formatter Integration

Add custom formatters to the main formatter using the augmentation callback.

type FormatterAugmentor = (
  formatter: MutableTypeFormatter,
  circularReferenceTypeFormatter: CircularReferenceTypeFormatter
) => void;

function createFormatter(
  config: Config, 
  augmentor?: FormatterAugmentor
): TypeFormatter;

Integration Example:

import { 
  createProgram, 
  createParser, 
  SchemaGenerator, 
  createFormatter 
} from "ts-json-schema-generator";
import { MyFunctionTypeFormatter } from "./my-function-formatter.js";

const config = {
  path: "path/to/source/file.ts",
  tsconfig: "path/to/tsconfig.json",
  type: "*",
};

// Configure formatter with custom extension
const formatter = createFormatter(config, (fmt, circularReferenceTypeFormatter) => {
  // Add custom formatter to the chain
  fmt.addTypeFormatter(new MyFunctionTypeFormatter(circularReferenceTypeFormatter));
});

const program = createProgram(config);
const parser = createParser(program, config);
const generator = new SchemaGenerator(program, parser, formatter, config);
const schema = generator.createSchema(config.type);

Parser Integration

Add custom parsers to the main parser using the augmentation callback.

type ParserAugmentor = (parser: MutableParser) => void;

function createParser(
  program: ts.Program, 
  config: Config, 
  augmentor?: ParserAugmentor
): NodeParser;

Integration Example:

import { 
  createProgram, 
  createParser, 
  SchemaGenerator, 
  createFormatter 
} from "ts-json-schema-generator";
import { MyConstructorParser } from "./my-constructor-parser.js";

const config = {
  path: "path/to/source/file.ts",
  tsconfig: "path/to/tsconfig.json",
  type: "*",
};

const program = createProgram(config);

// Configure parser with custom extension
const parser = createParser(program, config, (prs) => {
  prs.addNodeParser(new MyConstructorParser());
});

const formatter = createFormatter(config);
const generator = new SchemaGenerator(program, parser, formatter, config);
const schema = generator.createSchema(config.type);

Complete Custom Extension Example

Full example showing both custom parser and formatter working together.

// custom-date-parser.ts
import { 
  SubNodeParser, 
  BaseType, 
  Context, 
  ReferenceType 
} from "ts-json-schema-generator";
import ts from "ts-json-schema-generator";

// Custom type for Date objects
export class DateType extends BaseType {
  public getId(): string {
    return "date";
  }
}

export class CustomDateParser implements SubNodeParser {
  supportsNode(node: ts.Node): boolean {
    // Handle references to Date type
    return ts.isTypeReferenceNode(node) && 
           ts.isIdentifier(node.typeName) && 
           node.typeName.text === "Date";
  }
  
  createType(node: ts.Node, context: Context, reference?: ReferenceType): BaseType {
    return new DateType();
  }
}

// custom-date-formatter.ts
import { 
  SubTypeFormatter, 
  BaseType, 
  Definition 
} from "ts-json-schema-generator";

export class CustomDateFormatter implements SubTypeFormatter {
  supportsType(type: BaseType): boolean {
    return type instanceof DateType;
  }
  
  getDefinition(type: DateType): Definition {
    return {
      type: "string",
      format: "date-time",
      description: "ISO 8601 date-time string"
    };
  }
  
  getChildren(type: DateType): BaseType[] {
    return [];
  }
}

// usage.ts
import { 
  createProgram, 
  createParser, 
  createFormatter, 
  SchemaGenerator 
} from "ts-json-schema-generator";
import { CustomDateParser } from "./custom-date-parser.js";
import { CustomDateFormatter } from "./custom-date-formatter.js";

const config = {
  path: "src/api.ts",
  type: "UserProfile"
};

const program = createProgram(config);

const parser = createParser(program, config, (prs) => {
  prs.addNodeParser(new CustomDateParser());
});

const formatter = createFormatter(config, (fmt, circularRef) => {
  fmt.addTypeFormatter(new CustomDateFormatter());
});

const generator = new SchemaGenerator(program, parser, formatter, config);
const schema = generator.createSchema(config.type);

console.log(JSON.stringify(schema, null, 2));

Advanced Formatter Patterns

Complex formatter scenarios for specialized JSON schema generation.

Formatter with Child Types:

export class ComplexTypeFormatter implements SubTypeFormatter {
  constructor(private childTypeFormatter: TypeFormatter) {}
  
  supportsType(type: BaseType): boolean {
    return type instanceof MyComplexType;
  }
  
  getDefinition(type: MyComplexType): Definition {
    // Use child formatter for nested types
    const childDef = this.childTypeFormatter.getDefinition(type.getChildType());
    
    return {
      type: "object",
      properties: {
        value: childDef,
        metadata: { type: "object" }
      }
    };
  }
  
  getChildren(type: MyComplexType): BaseType[] {
    // Return child types that need their own definitions
    return this.childTypeFormatter.getChildren(type.getChildType());
  }
}

Validation-Specific Formatter:

export class ValidationFormatter implements SubTypeFormatter {
  supportsType(type: BaseType): boolean {
    return type instanceof StringType;
  }
  
  getDefinition(type: StringType): Definition {
    const definition: Definition = { type: "string" };
    
    // Add custom validation rules
    if (type.getId().includes("email")) {
      definition.format = "email";
    }
    
    if (type.getId().includes("password")) {
      definition.minLength = 8;
      definition.pattern = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)";
    }
    
    return definition;
  }
  
  getChildren(type: StringType): BaseType[] {
    return [];
  }
}

Error Handling in Extensions

Proper error handling for custom parsers and formatters.

import { UnhandledError } from "ts-json-schema-generator";

export class SafeCustomParser implements SubNodeParser {
  supportsNode(node: ts.Node): boolean {
    return node.kind === ts.SyntaxKind.CustomSyntax;
  }
  
  createType(node: ts.Node, context: Context, reference?: ReferenceType): BaseType {
    try {
      // Custom parsing logic
      return this.parseCustomNode(node, context);
    } catch (error) {
      throw new UnhandledError(
        "Failed to parse custom node",
        node,
        error
      );
    }
  }
  
  private parseCustomNode(node: ts.Node, context: Context): BaseType {
    // Implementation details
    throw new Error("Not implemented");
  }
}

Testing Custom Extensions

Unit testing patterns for custom parsers and formatters.

// test-custom-extensions.ts
import { expect } from "chai";
import ts from "typescript";
import { Context } from "ts-json-schema-generator";
import { CustomDateParser, DateType } from "./custom-date-parser.js";

describe("CustomDateParser", () => {
  let parser: CustomDateParser;
  
  beforeEach(() => {
    parser = new CustomDateParser();
  });
  
  it("should support Date type references", () => {
    const source = "let date: Date;";
    const sourceFile = ts.createSourceFile("test.ts", source, ts.ScriptTarget.Latest);
    const dateNode = /* extract Date reference node */;
    
    expect(parser.supportsNode(dateNode)).to.be.true;
  });
  
  it("should create DateType for Date references", () => {
    const dateNode = /* create Date reference node */;
    const context = new Context();
    const result = parser.createType(dateNode, context);
    
    expect(result).to.be.instanceOf(DateType);
  });
});

Extension Distribution

Package and distribute custom extensions for reuse.

// package.json for extension package
{
  "name": "ts-json-schema-generator-date-extension",
  "version": "1.0.0",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "peerDependencies": {
    "ts-json-schema-generator": "^2.0.0"
  }
}
// index.ts - extension package entry point
export { CustomDateParser, DateType } from "./custom-date-parser.js";
export { CustomDateFormatter } from "./custom-date-formatter.js";

// Convenience function for common usage
export function createDateExtension() {
  return {
    parser: new CustomDateParser(),
    formatter: new CustomDateFormatter()
  };
}

Install with Tessl CLI

npx tessl i tessl/npm-ts-json-schema-generator

docs

ast-parsing.md

cli-usage.md

custom-extensions.md

index.md

program-management.md

schema-generation.md

type-formatting.md

tile.json