openapi-dotnet-generator
0.9.0
See the version list below for details.
dotnet tool install --global openapi-dotnet-generator --version 0.9.0
dotnet new tool-manifest
dotnet tool install --local openapi-dotnet-generator --version 0.9.0
#tool dotnet:?package=openapi-dotnet-generator&version=0.9.0
nuke :add-package openapi-dotnet-generator --version 0.9.0
OpenAPI .NET Client Generator
A modern OpenAPI/Swagger client code generator for .NET that produces high-quality, strongly-typed HTTP clients with full NodaTime support for date and time handling.
Features
- ๐ Modern .NET: Built for .NET 10 with C# 14.0
- ๐ NodaTime Integration: Automatic mapping of date/time formats to NodaTime types (
Instant,LocalDate,LocalTime,LocalDateTime,Duration) - โก System.Text.Json: Native JSON serialization with optimal performance
- ๐ก๏ธ Type-Safe: Generates strongly-typed models and client methods
- ๐งฑ Fluent Builder API: Navigate resources naturally โ
client.Pets[123].Photos[photoId].Get() - ๐งช Mockable by Design: All builders use
virtualmethods, navigation properties, andprotectedconstructors for seamless Moq integration - โป๏ธ Async First: All HTTP operations are async with proper cancellation support
- ๐ Well Documented: Preserves OpenAPI descriptions as XML documentation comments
- ๐ Format Registry: Comprehensive OpenAPI Format Registry support โ integers, URIs, binary, decimals, and more
- โ Nullable Aware: Respects required/optional properties โ required fields use the C#
requiredmodifier, optional fields use nullable reference types - ๐ท๏ธ Enum Support: Generates C# enums from OpenAPI string enums with
JsonStringEnumConverter - ๐ฆ Inline Object Schemas: Inline
type: objectschemas in responses and request bodies are generated as public nested classes inside builder classes - ๐ป Modern CLI: Uses
System.CommandLinewith built-in help, validation, and shell tab-completion - ๐พ Configuration Persistence: Saves generation parameters to a JSON config file for easy re-generation via
updatecommand - ๐ง Configurable Type Mappings: Override default OpenAPI-to-.NET type mappings via the configuration file
- ๐ Spec Conversion: Convert OpenAPI specifications between versions (2.0, 3.0, 3.1, 3.2) and formats (JSON, YAML)
- ๐งฉ OpenAPI Overlays: Apply OpenAPI Overlay documents to patch specifications before generation โ powered by BinkyLabs.OpenApi.Overlays
Type Mapping
The generator maps OpenAPI types and formats to idiomatic C# types following the OpenAPI Format Registry. All mappings can be overridden via the configuration file.
String Formats
| OpenAPI Format | C# Type | Notes |
|---|---|---|
date-time |
NodaTime.Instant |
NodaTime โ RFC 3339 date-time |
date |
NodaTime.LocalDate |
NodaTime โ RFC 3339 full-date |
time |
NodaTime.LocalTime |
NodaTime โ RFC 3339 full-time |
time-local |
NodaTime.LocalTime |
NodaTime โ time without timezone |
date-time-local |
NodaTime.LocalDateTime |
NodaTime โ date-time without timezone |
duration |
NodaTime.Duration |
NodaTime โ RFC 3339 duration |
uuid |
Guid |
RFC 4122 UUID |
uri |
Uri |
RFC 3986 URI |
uri-reference |
Uri |
RFC 3986 URI reference |
iri |
Uri |
RFC 3987 Internationalized URI |
iri-reference |
Uri |
RFC 3987 IRI reference |
byte |
byte[] |
Base64-encoded binary (RFC 4648 ยง4) |
binary |
byte[] |
Raw binary octets |
base64url |
byte[] |
URL-safe base64 (RFC 4648 ยง5) |
char |
char |
Single character |
| (other / none) | string |
Default for unrecognised string formats |
Integer Formats
| OpenAPI Format | C# Type |
|---|---|
int8 |
sbyte |
int16 |
short |
int32 |
int |
int64 |
long |
uint8 |
byte |
uint16 |
ushort |
uint32 |
uint |
uint64 |
ulong |
| (none) | int |
Number Formats
| OpenAPI Format | C# Type |
|---|---|
float |
float |
double |
double |
decimal |
decimal |
decimal128 |
decimal |
double-int |
long |
| (none) | double |
Enum Types
| OpenAPI Schema | C# Type | Notes |
|---|---|---|
type: string + enum: [...] |
enum |
Generated with [JsonStringEnumConverter] |
$ref to enum schema |
Enum type name | Strongly-typed enum reference |
Enum values are converted to PascalCase members (e.g., extra-large โ ExtraLarge) with [JsonStringEnumMemberName] attributes preserving the original value.
Other Types
| OpenAPI Type | C# Type |
|---|---|
boolean |
bool |
array |
List<T> |
object (inline) |
Nested class in builder (e.g., GetResponse) |
$ref |
Referenced class / enum |
Installation
As a .NET Tool (recommended)
# Install globally
dotnet tool install -g openapi-dotnet-generator
# Or install as a local tool
dotnet new tool-manifest # if you don't have one yet
dotnet tool install openapi-dotnet-generator
Build from Source
git clone https://github.com/Ilchert/OpenApiDotNet.git
cd OpenApiDotNet
dotnet build
Prerequisites
- .NET 10.0 SDK or later
Usage
Command Line
openapi-dotnet-generator <openapi-file> [options]
Arguments & Options
| Argument / Option | Description | Default |
|---|---|---|
<openapi-file> |
Path to the OpenAPI specification file (JSON or YAML) | required |
-o, --output <dir> |
Directory where generated code will be placed | ./Generated |
-n, --namespace <ns> |
Namespace for generated code | GeneratedClient |
-p, --namespace-prefix <prefix> |
Strip this dotted prefix from schema names when generating namespaces | none |
-c, --client-name <name> |
Custom name for the generated client class | Derived from API title |
--overlay <file> |
Path to overlay file(s) to apply before generation (repeatable) | none |
Built-in flags provided by System.CommandLine:
| Flag | Description |
|---|---|
--help, -h, -? |
Show help and usage information |
--version |
Show version information |
Update Command
After the initial generation, a .openapidotnet.json configuration file is saved in the output directory. Use the update command to re-generate the client using the saved parameters:
# Re-generate from config in the current directory
openapi-dotnet-generator update
# Re-generate from a specific config file
openapi-dotnet-generator update ./Generated/.openapidotnet.json
| Argument | Description | Default |
|---|---|---|
[config-file] |
Path to the .openapidotnet.json configuration file |
.openapidotnet.json |
The update command automatically tracks generated files. When the OpenAPI specification changes (e.g., schemas or endpoints are removed), files that are no longer needed are automatically deleted and empty directories are cleaned up.
Convert Command
Convert an OpenAPI specification to a different version and/or format:
# Convert to OpenAPI 3.1 JSON (default)
openapi-dotnet-generator convert petstore.yaml output.json
# Convert to OpenAPI 2.0 (Swagger) JSON
openapi-dotnet-generator convert petstore.yaml swagger.json -v 2.0
# Convert to OpenAPI 3.0 YAML
openapi-dotnet-generator convert api.json api-v3.yaml -v 3.0 -f yaml
# Convert to OpenAPI 3.2 YAML
openapi-dotnet-generator convert api.yaml api-v32.yaml -v 3.2 -f yaml
| Argument / Option | Description | Default |
|---|---|---|
<openapi-file> |
Path to the OpenAPI specification file to convert | required |
<output-file> |
Path for the converted output file | required |
-v, --version |
Target OpenAPI version (2.0, 3.0, 3.1, 3.2) |
3.1 |
-f, --format |
Output format (json, yaml) |
json |
Shell Tab-Completion
The CLI supports shell tab-completion via the dotnet-suggest global tool.
Once configured, pressing <kbd>Tab</kbd> will auto-complete the <openapi-file> argument with .json, .yaml, and .yml files from the current directory.
# Install the suggest tool (one-time)
dotnet tool install -g dotnet-suggest
# Follow the shell-specific setup instructions from dotnet-suggest
Examples
Basic Usage:
openapi-dotnet-generator petstore.yaml
With Custom Output Directory:
openapi-dotnet-generator api.yaml -o ./src/Client
With Custom Namespace:
openapi-dotnet-generator swagger.json -o ./Generated -n MyCompany.ApiClient
Show Help:
openapi-dotnet-generator --help
With Overlays:
# Apply a single overlay before generation
openapi-dotnet-generator petstore.yaml --overlay remove-deprecated.yaml
# Apply multiple overlays (applied in order)
openapi-dotnet-generator petstore.yaml --overlay base-overlay.yaml --overlay team-overlay.yaml
With Namespace Prefix Stripping:
# Strip the 'Commerce' prefix from dotted schema names
# Commerce.Order โ Order (in root Models namespace)
# Identity.Customer โ Customer (in Identity sub-namespace, unchanged)
openapi-dotnet-generator api.yaml -n MyCompany.Client -p Commerce
With Custom Client Name:
# Override the default client class name derived from the API title
openapi-dotnet-generator petstore.yaml -c PetStoreClient
Re-generate from Saved Configuration:
# After initial generation, update from the saved config (overlay paths are preserved)
openapi-dotnet-generator update ./Generated/.openapidotnet.json
Convert to a Different Version/Format:
openapi-dotnet-generator convert petstore.yaml petstore-v2.json --version 2.0
openapi-dotnet-generator convert api.json api.yaml -f yaml
Generated Code Structure
The generator creates the following structure:
Generated/
โโโ Models/
โ โโโ Pet.cs
โ โโโ NewPet.cs
โ โโโ PetStatus.cs
โโโ Builders/
โ โโโ PetsBuilder.cs
โ โโโ PetsIdBuilder.cs
โ โโโ PhotosBuilder.cs
โ โโโ PhotosIdBuilder.cs
โโโ IOpenApiBuilder.cs
โโโ IOpenApiClient.cs
โโโ IPetStoreClient.cs
โโโ .openapidotnet.json
Each API path segment gets its own builder class. Static segments (e.g., /pets) produce a PetsBuilder, while parameterized segments (e.g., /{petId}) produce a PetsIdBuilder. When the same segment name appears at different tree positions (e.g., /pets and /owners/{ownerId}/pets), the generator resolves collisions by prefixing with ancestor context (e.g., OwnersIdPetsBuilder).
The .openapidotnet.json file stores the generation parameters so the client can be re-generated with the update command:
{
"openApiFile": "../petstore.yaml",
"outputDirectory": ".",
"namespace": "GeneratedClient",
"overlayFiles": [
"../remove-deprecated.yaml"
],
"namespacePrefix": "Commerce",
"clientName": "PetStoreClient",
"typeMappings": {
"string:date-time": "DateTimeOffset",
"integer": "long"
},
"generatedFiles": [
"Models/Pet.cs",
"Models/NewPet.cs",
"IOpenApiBuilder.cs",
"IOpenApiClient.cs",
"IPetStoreClient.cs",
"Builders/PetsBuilder.cs"
]
}
Custom Type Mappings
You can override default OpenAPI-to-.NET type mappings by adding a typeMappings section to the .openapidotnet.json configuration file. Mappings use keys in the format "type:format" (e.g. "string:date-time") or just "type" for the default mapping of a type (e.g. "integer").
Only specified keys are overridden; all other defaults remain intact.
{
"openApiFile": "../api.yaml",
"outputDirectory": ".",
"namespace": "MyApp",
"typeMappings": {
"string:date-time": "DateTimeOffset",
"string:date": "DateTime",
"string:email": "EmailAddress",
"integer": "long"
}
}
In the example above:
stringwith formatdate-timemaps toDateTimeOffsetinstead of the defaultNodaTime.Instantstringwith formatdatemaps toDateTimeinstead of the defaultNodaTime.LocalDatestringwith formatemailis a new custom mapping (no built-in default)integerwithout a format maps tolonginstead of the defaultint
Example Generated Model
namespace PetStoreClient.Models;
/// <summary>
/// A pet in the store
/// </summary>
public class Pet
{
/// <summary>
/// Unique identifier for the pet
/// </summary>
[System.Text.Json.Serialization.JsonPropertyName("id")]
public required long Id { get; set; }
/// <summary>
/// Name of the pet
/// </summary>
[System.Text.Json.Serialization.JsonPropertyName("name")]
public required string Name { get; set; }
/// <summary>
/// Birth date of the pet
/// </summary>
[System.Text.Json.Serialization.JsonPropertyName("birthDate")]
public NodaTime.LocalDate? BirthDate { get; set; }
/// <summary>
/// When the pet was created
/// </summary>
[System.Text.Json.Serialization.JsonPropertyName("createdAt")]
public NodaTime.Instant? CreatedAt { get; set; }
}
Example Generated Enum
namespace PetStoreClient.Models;
/// <summary>
/// The status of a pet in the store
/// </summary>
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.JsonStringEnumConverter))]
public enum PetStatus
{
[System.Text.Json.Serialization.JsonStringEnumMemberName("available")]
Available,
[System.Text.Json.Serialization.JsonStringEnumMemberName("pending")]
Pending,
[System.Text.Json.Serialization.JsonStringEnumMemberName("sold")]
Sold,
}
Example Generated IOpenApiClient Interface
IOpenApiClient is a base interface containing the HTTP infrastructure. A separate named interface (derived from the --client-name option or the API title) inherits from it and exposes the top-level navigation properties:
namespace PetStoreClient;
/// <summary>
/// Base interface for all OpenAPI clients
/// </summary>
public interface IOpenApiClient : IOpenApiBuilder
{
System.Net.Http.HttpClient HttpClient { get; }
System.Text.Json.JsonSerializerOptions JsonOptions { get; }
IOpenApiClient IOpenApiBuilder.Client => this;
string IOpenApiBuilder.GetPath() => "";
}
Example Generated Named Client Interface
namespace PetStoreClient;
/// <summary>
/// A simple pet store API
/// </summary>
public interface IPetStoreClient : IOpenApiClient
{
public virtual PetStoreClient.Builders.PetsBuilder Pets => new(this);
}
Example Generated Builder
namespace PetStoreClient.Builders;
public class PetsBuilder : IOpenApiBuilder
{
private readonly IOpenApiBuilder _parentBuilder;
#pragma warning disable CS8618
protected PetsBuilder() { }
#pragma warning restore CS8618
public PetsBuilder(IOpenApiBuilder parentBuilder)
{
_parentBuilder = parentBuilder;
}
public IOpenApiClient Client => _parentBuilder.Client;
public string GetPath() => $"{_parentBuilder.GetPath()}/pets";
public virtual PetStoreClient.Builders.PetsIdBuilder this[long petId]
{
get => new(this, petId);
}
/// <summary>
/// List all pets
/// </summary>
public virtual async System.Threading.Tasks.Task<System.Collections.Generic.List<PetStoreClient.Models.Pet>> Get(
int? limit = default, System.Threading.CancellationToken cancellationToken = default)
{
var url = GetPath();
var queryString = new System.Collections.Generic.List<string>();
if (limit is {} limitValue)
queryString.Add($"limit={System.Uri.EscapeDataString(
System.Text.Json.JsonSerializer.Serialize(limitValue, Client.JsonOptions).Trim('"'))}");
if (queryString.Count > 0)
url += "?" + string.Join("&", queryString);
var response = await Client.HttpClient.GetAsync(url, cancellationToken);
response.EnsureSuccessStatusCode();
var deserializedResponse = await System.Net.Http.Json.HttpContentJsonExtensions
.ReadFromJsonAsync<System.Collections.Generic.List<PetStoreClient.Models.Pet>>(
response.Content, Client.JsonOptions, cancellationToken);
if (deserializedResponse is { } deserializedResponseValue)
return deserializedResponseValue;
throw new System.InvalidOperationException($"Response from {url} is null");
}
}
namespace PetStoreClient.Builders;
public class PetsIdBuilder : IOpenApiBuilder
{
private readonly IOpenApiBuilder _parentBuilder;
private readonly long _petId;
#pragma warning disable CS8618
protected PetsIdBuilder() { }
#pragma warning restore CS8618
public PetsIdBuilder(IOpenApiBuilder parentBuilder, long petId)
{
_parentBuilder = parentBuilder;
_petId = petId;
}
public IOpenApiClient Client => _parentBuilder.Client;
public string GetPath() => $"{_parentBuilder.GetPath()}/{_petId}";
public virtual PetStoreClient.Builders.PhotosBuilder Photos => new(this);
/// <summary>
/// Get a pet by ID
/// </summary>
public virtual async System.Threading.Tasks.Task<PetStoreClient.Models.Pet> Get(
System.Threading.CancellationToken cancellationToken = default)
{
var url = GetPath();
var response = await Client.HttpClient.GetAsync(url, cancellationToken);
response.EnsureSuccessStatusCode();
var deserializedResponse = await System.Net.Http.Json.HttpContentJsonExtensions
.ReadFromJsonAsync<PetStoreClient.Models.Pet>(
response.Content, Client.JsonOptions, cancellationToken);
if (deserializedResponse is { } deserializedResponseValue)
return deserializedResponseValue;
throw new System.InvalidOperationException($"Response from {url} is null");
}
}
Example Generated Inline Response
When a response schema is defined inline (not via $ref), the generator creates a public nested class inside the builder:
# OpenAPI spec
/stats:
get:
responses:
200:
content:
application/json:
schema:
type: object
properties:
totalCount:
type: integer
format: int32
activeCount:
type: integer
format: int32
lastUpdated:
type: string
format: date-time
// Generated StatsBuilder.cs
public class StatsBuilder : IOpenApiBuilder
{
// ... builder infrastructure ...
/// <summary>
/// Get statistics
/// </summary>
public virtual async System.Threading.Tasks.Task<GetResponse> Get(
System.Threading.CancellationToken cancellationToken = default)
{
var url = GetPath();
var response = await Client.HttpClient.GetAsync(url, cancellationToken);
response.EnsureSuccessStatusCode();
var deserializedResponse = await System.Net.Http.Json.HttpContentJsonExtensions
.ReadFromJsonAsync<GetResponse>(
response.Content, Client.JsonOptions, cancellationToken);
if (deserializedResponse is { } deserializedResponseValue)
return deserializedResponseValue;
throw new System.InvalidOperationException($"Response from {url} is null");
}
public class GetResponse
{
[System.Text.Json.Serialization.JsonPropertyName("totalCount")]
public int? TotalCount { get; set; }
[System.Text.Json.Serialization.JsonPropertyName("activeCount")]
public int? ActiveCount { get; set; }
[System.Text.Json.Serialization.JsonPropertyName("lastUpdated")]
public NodaTime.Instant? LastUpdated { get; set; }
}
}
The same applies to inline request body schemas, which generate {HttpMethod}Request nested classes (e.g., PostRequest).
Fluent Builder Pattern
The generator produces a fluent builder API where each URL segment maps to its own builder class. Path parameters are captured by the builder chain via indexers, and operations are invoked on the terminal builder:
// GET /pets?limit=10
var pets = await client.Pets.Get(limit: 10);
// GET /pets/123
var pet = await client.Pets[123].Get();
// GET /pets/123/photos/{photoId}
var photo = await client.Pets[123].Photos[photoId].Get();
// DELETE /pets/123
await client.Pets[123].Delete();
Path building is handled automatically by chaining GetPath() through the builder hierarchy โ no manual URL construction needed.
Mocking Support
All builder classes are designed for easy mocking with frameworks like Moq:
virtualmethods on all operations, indexers, and navigation propertiesprotectedparameterless constructors so Moq can create subclass proxies- Named client interface (e.g.,
IPetStoreClient) as the entry point for mock setup
var mock = new Mock<IPetStoreClient>();
mock.Setup(c => c.Pets[123].Get(It.IsAny<CancellationToken>()))
.ReturnsAsync(new List<Pet> { new Pet() });
var result = await mock.Object.Pets[123].Get(default);
URL Encoding & Query Parameters
Query string values are automatically URL-encoded for safe transmission:
// Query parameter encoding
var pets = await client.Pets.Get(limit: 10, status: "available & active");
// Generates: /pets?limit=10&status=available%20%26%20active
Using Generated Code
1. Add Required NuGet Packages
Add these packages to your project:
<PackageReference Include="NodaTime" Version="3.3.0" />
<PackageReference Include="NodaTime.Serialization.SystemTextJson" Version="1.3.0" />
2. Implement the Named Client Interface
Create a concrete class that implements the generated named client interface (e.g., IPetStoreClient):
using System.Text.Json;
using System.Text.Json.Serialization;
public class PetStoreClient : IPetStoreClient
{
public HttpClient HttpClient { get; }
public JsonSerializerOptions JsonOptions { get; }
public PetStoreClient(HttpClient httpClient)
{
HttpClient = httpClient;
JsonOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
PropertyNameCaseInsensitive = true,
Converters = { new JsonStringEnumConverter() }
};
}
}
Register it with dependency injection:
builder.Services.AddHttpClient<PetStoreClient>(client =>
{
client.BaseAddress = new Uri("https://api.petstore.example.com");
});
3. Use the Client
// List pets with query parameters
var pets = await client.Pets.Get(limit: 10);
// Create a pet
var newPet = new NewPet
{
Name = "Fluffy",
BirthDate = LocalDate.FromDateTime(DateTime.Now.AddYears(-2))
};
var createdPet = await client.Pets.Post(newPet);
// Get specific pet by ID
var pet = await client.Pets[123].Get();
// Navigate nested resources
var photo = await client.Pets[123].Photos[photoId].Get();
// Delete a pet
await client.Pets[123].Delete();
// Check timestamps with NodaTime
if (pet.CreatedAt.HasValue)
{
var zonedTime = pet.CreatedAt.Value.InUtc();
Console.WriteLine($"Pet created at: {zonedTime}");
}
Development
Running Tests
# Run all tests
dotnet test
# Run with verbosity
dotnet test --logger "console;verbosity=detailed"
# Run specific test
dotnet test --filter "FullyQualifiedName~TypeMappingTests"
The project includes comprehensive test coverage:
- Type Mapping Tests: Verify OpenAPI to C# type conversions
- Naming Convention Tests: Ensure proper PascalCase/camelCase conversions
- Integration Tests: End-to-end client generation tests
- Validation Tests: Argument validation and error handling
Test Coverage
- 112 tests covering all major functionality
- 100% pass rate
- Unit tests for type mapping and naming conventions
- Integration tests with real OpenAPI specifications
- Builder generation and path tree construction tests
Architecture
Core Components
- ClientGenerator: Main orchestrator that generates all code โ models, builder classes, and interfaces
- PathTreeBuilder: Parses OpenAPI paths into a tree of
PathSegmentNodeobjects, where each node represents a URL segment (static or parameterized), and resolves unique builder class names with collision detection - TypeMappingConfig: Configurable OpenAPI-to-.NET type mappings with defaults and user overrides
- Model Generator: Creates C# classes and enums from OpenAPI component schemas
- Builder Generator: For each path tree node, emits a builder class (
IBuilderimplementation) with navigation properties, indexers, and HTTP operation methods
Builder Generation Pipeline
OpenAPI Paths โ PathTreeBuilder.Build() โ Path Tree โ Builder Classes
โ IOpenApiBuilder.cs
โ IOpenApiClient.cs
โ I{ClientName}.cs
OpenAPI Schemas โ Model Generator โ Models/*.cs
The path tree maps URL structure to builder hierarchy:
/pets โ PetsBuilder (operations: Get, Post)
/pets/{petId} โ PetsIdBuilder (operations: Get, Delete)
/pets/{petId}/photos/{photoId} โ PhotosBuilder + PhotosIdBuilder (operation: Get)
/owners/{ownerId}/pets/{petId} โ OwnersBuilder + OwnersIdBuilder
+ OwnersIdPetsBuilder + OwnersIdPetsIdBuilder
When the same segment name appears at multiple tree positions, context-prefixed names are assigned automatically to avoid collisions.
Type Mapping Logic
The type mapping logic is driven by TypeMappingConfig, which holds a dictionary of mappings keyed by "type:format" (e.g. "string:date-time" โ "Instant") or just "type" for defaults (e.g. "string" โ "string"). Custom mappings from the configuration file are merged on top of the built-in defaults.
The generator maps OpenAPI types and format registry values to C# types:
String formats
"string" โ string
"string" (format: "date-time") โ NodaTime.Instant
"string" (format: "date") โ NodaTime.LocalDate
"string" (format: "time") โ NodaTime.LocalTime
"string" (format: "time-local") โ NodaTime.LocalTime
"string" (format: "date-time-local") โ NodaTime.LocalDateTime
"string" (format: "duration") โ NodaTime.Duration
"string" (format: "uuid") โ Guid
"string" (format: "uri/iri") โ Uri
"string" (format: "byte/binary") โ byte[]
"string" (format: "char") โ char
Integer formats
"integer" โ int
"integer" (format: "int8") โ sbyte
"integer" (format: "int16") โ short
"integer" (format: "int32") โ int
"integer" (format: "int64") โ long
"integer" (format: "uint8") โ byte
"integer" (format: "uint16") โ ushort
"integer" (format: "uint32") โ uint
"integer" (format: "uint64") โ ulong
Number formats
"number" โ double
"number" (format: "float") โ float
"number" (format: "double") โ double
"number" (format: "decimal") โ decimal
"number" (format: "decimal128") โ decimal
"number" (format: "double-int") โ long
Enum types
"string" + enum: [...] โ C# enum (with JsonStringEnumConverter)
$ref to enum schema โ Enum type
Other types
"boolean" โ bool
"array" โ List<T>
Inline "object" (in response/body) โ Nested class in builder
Reference ($ref) โ Custom Type / Enum
Supported OpenAPI Features
- โ OpenAPI 3.0 specifications
- โ JSON and YAML input formats
- โ Fluent builder-style API with path segment navigation
- โ Path parameters captured via builder indexers
- โ Query parameters with URL encoding
- โ
Multiple path parameters (e.g.,
/owners/{ownerId}/pets/{petId}) - โ Automatic builder name collision resolution for shared segment names
- โ
Named client interface (e.g.,
IPetStoreClient) derived fromIOpenApiClient - โ
Mock-friendly design (
virtualmethods, navigation properties, andprotectedconstructors, and named client interface) - โ Request bodies
- โ Response models
- โ
Schema references (
$ref) - โ Required/optional properties
- โ Arrays and nested objects
- โ Inline object schemas as nested classes in builders
- โ
Enum types with
JsonStringEnumConverter - โ HTTP methods: GET, POST, PUT, PATCH, DELETE
- โ HTTP method-based operation naming (Get, Post, Put, Patch, Delete)
- โ Descriptions and summaries
- โ OpenAPI Format Registry type mappings
- โ
Configurable type mappings via
.openapidotnet.json - โ Specification conversion between OpenAPI versions and formats
- โ OpenAPI Overlay Specification support (single or multiple overlays)
- โ Namespace prefix stripping for dotted schema names
Naming Conventions
The generator follows standard .NET naming conventions:
- Model Classes: PascalCase (e.g.,
Pet,NewPet) - Builder Classes:
{Segment}Builder/{Segment}IdBuilder(e.g.,PetsBuilder,PetsIdBuilder) - Collision Resolution: Context-prefixed names when the same segment appears at multiple tree positions (e.g.,
OwnersIdPetsBuilderfor/owners/{ownerId}/pets) - Properties: PascalCase (e.g.,
BirthDate,CreatedAt) - Method Parameters: camelCase (e.g.,
petId,limit) - JSON Properties: Preserved from OpenAPI spec (typically camelCase)
The generator automatically converts:
birth_dateโBirthDate(property) /birthDate(parameter)created-atโCreatedAt(property) /createdAt(parameter)user-nameโUserName(property) /userName(parameter)
Dependencies
Runtime Dependencies
- BinkyLabs.OpenApi.Overlays (2.4.0)
- Microsoft.OpenApi (3.3.1)
- Microsoft.OpenApi.YamlReader (3.3.1)
- NodaTime (3.3.0)
- NodaTime.Serialization.SystemTextJson (1.3.1)
- System.CommandLine (2.0.3)
Generated Code Dependencies
Projects using the generated code need:
- NodaTime (3.3.0)
- NodaTime.Serialization.SystemTextJson (1.3.0)
Contributing
Contributions are welcome! Please feel free to submit issues or pull requests.
Development Setup
- Clone the repository
- Ensure .NET 10 SDK is installed
- Run
dotnet restore - Run
dotnet build - Run
dotnet testto verify everything works
License
This project is open source. Please check the license file for details.
Roadmap
Future enhancements being considered:
- Enum types with
JsonStringEnumConvertersupport - Configurable type mappings via configuration file
- Fluent builder-style API with path segment navigation
- Mock-friendly design for unit testing
- Support for authentication schemes (Bearer, API Key, OAuth2)
- Polymorphic types with discriminators
-
allOf,oneOf,anyOfschema composition - Webhook support
- Multipart form data handling
- Custom templates for code generation
- MSBuild task for build-time generation
- Source generator for zero-overhead generation
Acknowledgments
Built with:
- Microsoft.OpenApi - OpenAPI parsing
- NodaTime - Modern date/time library
- xUnit - Testing framework
- FluentAssertions - Assertion library
Made with โค๏ธ for the .NET community
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
This package has no dependencies.