openapi-class-transformer
v1.1.2
Published
Generate class-transformer compatible TypeScript classes from OpenAPI specs with full decorator support
Maintainers
Readme
openapi-class-transformer
Generate class-transformer compatible TypeScript classes from OpenAPI specifications with full decorator support.
Features
✨ Class-Transformer Compatible: Generates TypeScript classes with @Expose() and @Type() decorators
✅ Class-Validator Support (NEW in v1.1.0): Automatically generates validation decorators (@IsString(), @IsOptional(), etc.)
🎯 Type-Safe: Full TypeScript support with proper type definitions
📦 Vendor Extensions: Automatically includes all x-* properties in JSDoc comments
🔧 Customizable: Support for custom templates and additional properties
⚡ Easy to Use: Simple CLI and programmatic API
🧪 Well-Tested: Comprehensive test coverage
Installation
npm install openapi-class-transformer
# or
yarn add openapi-class-transformer
# or
pnpm add openapi-class-transformerUsage
CLI
# Generate from OpenAPI spec
npx openapi-class-transformer generate \
--input ./api-spec.json \
--output ./src/generated
# With custom template directory
npx openapi-class-transformer generate \
-i ./api-spec.yaml \
-o ./src/api \
-t ./custom-templates
# Generate only models (skip APIs)
npx openapi-class-transformer generate \
-i ./spec.json \
-o ./output \
--models-only
# With NestJS Swagger decorators
npx openapi-class-transformer generate \
-i ./spec.json \
-o ./output \
--nestjs-swagger
# With class-validator decorators (NEW in v1.1.0)
npx openapi-class-transformer generate \
-i ./spec.json \
-o ./output \
--class-validator
# Skip .js extensions (for bundlers like webpack/Next.js)
npx openapi-class-transformer generate \
-i ./spec.json \
-o ./output \
--skip-js-extensions
# With additional properties
npx openapi-class-transformer generate \
-i ./spec.json \
-o ./output \
--additional-properties modelPackage=dto,apiPackage=servicesCLI Options
| Option | Description |
|--------|-------------|
| -i, --input <path> | Required. Path to OpenAPI specification file (JSON or YAML) |
| -o, --output <path> | Required. Output directory for generated files |
| -t, --template <path> | Custom template directory |
| --models-only | Generate only model classes (skip APIs and configuration) |
| --nestjs-swagger | Include NestJS Swagger @ApiProperty decorators |
| --skip-js-extensions | Skip adding .js extensions to imports/exports |
| --class-validator | Include class-validator decorators (@IsString, @IsOptional, etc.) |
| --additional-properties <props> | Additional OpenAPI Generator properties (key=value,key2=value2) |
Programmatic API
import { Generator } from 'openapi-class-transformer';
const generator = new Generator({
inputSpec: './api-spec.json',
outputDir: './src/generated',
// Optional: custom template directory
templateDir: './custom-templates',
// Optional: generate only models (skip APIs)
modelsOnly: false,
// Optional: include NestJS Swagger decorators
nestJsSwagger: false,
// Optional: skip .js extensions
skipJsExtensions: false,
// Optional: include class-validator decorators (NEW in v1.1.0)
classValidator: false,
// Optional: additional OpenAPI Generator properties
additionalProperties: {
modelPackage: 'dto',
apiPackage: 'services'
}
});
await generator.generate();Generated Output
Given this OpenAPI schema:
components:
schemas:
User:
type: object
required:
- id
- name
properties:
id:
type: string
name:
type: string
profile:
$ref: '#/components/schemas/Profile'
Profile:
type: object
properties:
bio:
type: stringThe generator creates:
import { Expose, Type } from 'class-transformer';
import { Profile } from './profile.js';
export class User {
/**
* id
*/
@Expose()
'id': string;
/**
* name
*/
@Expose()
'name': string;
/**
* profile
*/
@Expose()
@Type(() => Profile)
'profile'?: Profile;
}Vendor Extensions
All OpenAPI vendor extensions (x-* properties) are automatically included in JSDoc comments:
properties:
count:
anyOf:
- type: integer
- type: string
x-kubernetes-int-or-string: true
x-custom-property: "custom-value"Generates:
/**
* count
* @x-kubernetes-int-or-string true
* @x-custom-property "custom-value"
*/
@Expose()
'count': string | number;Use with class-transformer
The generated classes work seamlessly with class-transformer:
import { plainToInstance } from 'class-transformer';
import { User } from './generated/models/user';
const plainData = {
id: '123',
name: 'John Doe',
profile: {
bio: 'Software developer'
}
};
const user = plainToInstance(User, plainData);
// ✅ user is properly typed User instance
// ✅ user.profile is properly typed Profile instanceUse with class-validator (NEW in v1.1.0)
When you generate models with the --class-validator flag, the generator automatically adds appropriate validation decorators based on your OpenAPI schema:
Installation
First, install class-validator as a peer dependency:
npm install class-validator
# or
yarn add class-validator
# or
pnpm add class-validatorGeneration
npx openapi-class-transformer generate \
-i ./api-spec.json \
-o ./src/generated \
--class-validatorOpenAPI Schema
components:
schemas:
User:
type: object
required:
- email
- age
properties:
email:
type: string
format: email
age:
type: integer
minimum: 0
maximum: 120
bio:
type: string
minLength: 10
maxLength: 500Generated Output
import { Expose, Type } from 'class-transformer';
import { IsEmail, IsInt, IsOptional, IsString, Length, Max, Min } from 'class-validator';
export class User {
@IsEmail()
@Expose()
'email': string;
@IsInt()
@Min(0)
@Max(120)
@Expose()
'age': number;
@IsOptional()
@IsString()
@Length(10, 500)
@Expose()
'bio'?: string;
}Usage with Validation
import { plainToInstance } from 'class-transformer';
import { validate } from 'class-validator';
import { User } from './generated/models/user';
const plainData = {
email: '[email protected]',
age: 25,
bio: 'Software developer with 5 years of experience'
};
// Transform to class instance
const user = plainToInstance(User, plainData);
// Validate
const errors = await validate(user);
if (errors.length > 0) {
console.error('Validation failed:', errors);
} else {
console.log('Validation passed!');
}Supported Decorators
The generator automatically adds decorators based on OpenAPI schema properties:
| OpenAPI Property | Generated Decorator |
|-----------------|---------------------|
| type: string | @IsString() |
| type: number | @IsNumber() |
| type: integer | @IsNumber() |
| type: boolean | @IsBoolean() |
| type: array | @IsArray() |
| type: object or $ref | @ValidateNested() |
| Not in required array | @IsOptional() |
| format: email | @IsEmail() |
| format: url or format: uri | @IsUrl() |
| format: uuid | @IsUUID() |
| pattern: <regex> | @Matches(/<regex>/) |
| minLength / maxLength | @Length(min, max) |
| minimum / maximum | @Min(min) / @Max(max) |
| enum | @IsIn([...values]) |
For array properties with object items, @ValidateNested({ each: true }) is added automatically.
Discriminated Unions (Polymorphic Types)
When your OpenAPI spec uses oneOf or anyOf with discriminators, the generator automatically creates discriminator decorators for proper runtime polymorphism:
OpenAPI Spec Example
components:
schemas:
HelmComponent:
type: object
required: [type, chartName]
properties:
type:
type: string
enum: [helm]
chartName:
type: string
KustomizeComponent:
type: object
required: [type, path]
properties:
type:
type: string
enum: [kustomize]
path:
type: string
Capability:
type: object
properties:
components:
type: array
items:
oneOf:
- $ref: '#/components/schemas/HelmComponent'
- $ref: '#/components/schemas/KustomizeComponent'
discriminator:
propertyName: type
mapping:
helm: '#/components/schemas/HelmComponent'
kustomize: '#/components/schemas/KustomizeComponent'Generated Code
import { Type, Expose } from 'class-transformer';
import { HelmComponent } from './helm-component.js';
import { KustomizeComponent } from './kustomize-component.js';
export class Capability {
@Expose()
@Type(() => Object, {
discriminator: {
property: 'type',
subTypes: [
{ value: HelmComponent, name: 'helm' },
{ value: KustomizeComponent, name: 'kustomize' }
]
},
keepDiscriminatorProperty: true
})
'components'?: Array<HelmComponent | KustomizeComponent>;
}Runtime Usage
import 'reflect-metadata';
import { plainToInstance } from 'class-transformer';
import { Capability } from './generated/models/capability';
import { HelmComponent } from './generated/models/helm-component';
import { KustomizeComponent } from './generated/models/kustomize-component';
const data = {
components: [
{ type: 'helm', chartName: 'nginx', version: '1.0.0' },
{ type: 'kustomize', path: './manifests' }
]
};
const capability = plainToInstance(Capability, data);
// ✅ Automatic polymorphic deserialization based on 'type' discriminator
console.log(capability.components[0] instanceof HelmComponent); // true
console.log(capability.components[1] instanceof KustomizeComponent); // true
// ✅ Type-safe property access
console.log(capability.components[0].chartName); // 'nginx'
console.log(capability.components[1].path); // './manifests'The discriminator decorator ensures that class-transformer automatically instantiates the correct concrete class based on the discriminator property value!
TypeScript Configuration
Ensure your tsconfig.json has the following settings to use the generated classes:
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"moduleResolution": "node",
"esModuleInterop": true
}
}Dependencies
Install class-transformer in your project:
npm install class-transformer reflect-metadataAnd import reflect-metadata at the top of your entry file:
import 'reflect-metadata';Runtime Type Metadata
Each generated class includes a static attributeTypeMap property that provides runtime access to property metadata:
import { User } from './generated/models/user';
// Access type metadata at runtime
console.log(User.attributeTypeMap);
/*
[
{
"name": "id",
"baseName": "id",
"type": "string",
"format": ""
},
{
"name": "name",
"baseName": "name",
"type": "string",
"format": ""
},
{
"name": "email",
"baseName": "email",
"type": "string",
"format": "email"
},
{
"name": "profile",
"baseName": "profile",
"type": "Profile",
"format": ""
}
]
*/This is useful for:
- Runtime validation - Check types dynamically
- Form generation - Build UI forms from metadata
- Documentation - Generate API docs from types
- Serialization - Custom serialization logic based on types
Array Types
Arrays are represented with the Array<Type> notation:
console.log(UserList.attributeTypeMap);
/*
[
{
"name": "users",
"baseName": "users",
"type": "Array<User>",
"format": ""
}
]
*/Configuration Options
GeneratorOptions
interface GeneratorOptions {
/**
* Path to the OpenAPI specification file (JSON or YAML)
*/
inputSpec: string;
/**
* Output directory for generated files
*/
outputDir: string;
/**
* Custom template directory (optional)
* If not provided, uses bundled templates
*/
templateDir?: string;
/**
* Generate only model classes (skip APIs and configuration)
* @default false
*/
modelsOnly?: boolean;
/**
* Include NestJS Swagger @ApiProperty decorators
* @default false
*/
nestJsSwagger?: boolean;
/**
* Skip adding .js extensions to imports/exports
* Useful for bundlers like webpack/Next.js that don't want extensions
* @default false
*/
skipJsExtensions?: boolean;
/**
* Include class-validator decorators (@IsString, @IsOptional, etc.)
* @default false
*/
classValidator?: boolean;
/**
* Additional OpenAPI Generator properties
*/
additionalProperties?: Record<string, string>;
}Default Additional Properties
The generator uses these defaults (can be overridden):
withSeparateModelsAndApi:truemodelPackage:modelsapiPackage:apigenerateAliasAsModel:truesupportsES6:trueuseSingleRequestParameter:truewithNodeImports:true
Custom Templates
You can provide custom Mustache templates to customize the generated output:
- Copy the default template from
templates/modelGeneric.mustache - Modify it according to your needs
- Pass the template directory to the generator:
const generator = new Generator({
inputSpec: './spec.json',
outputDir: './output',
templateDir: './my-templates'
});Development
# Install dependencies
npm install
# Run tests
npm test
# Run tests in watch mode
npm run test:watch
# Build
npm run build
# Run tests with coverage
npm run test:coverageTesting
The package includes comprehensive test coverage using Jest:
npm testTests follow TDD (Test-Driven Development) principles with fixtures in the test-fixtures directory.
Test Results
Each test generates its output into a separate directory under test-results/ (gitignored). This allows you to:
- Inspect generated code manually after running tests
- Debug issues by examining actual output
- See examples of what the generator produces
For example, after running tests:
# View generated User class with @Expose decorators
cat test-results/expose-decorators/models/user.ts
# View generated Replica class with vendor extensions
cat test-results/vendor-extensions/models/replica.tsTest result directories persist between runs and are organized by test name:
basic-generation/- Basic class generationexpose-decorators/- @Expose() decorator teststype-decorators/- @Type() decorator testsimport-type-fix/- Import type conversion testsvendor-extensions/- Vendor extension tests
See test-results/README.md for more details.
License
MIT
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Credits
Built on top of:
