Swagger to Dart
A powerful Dart package that auto-generates type-safe API clients and models from OpenAPI specifications (Swagger). Supports OpenAPI 3.0 and 3.1 specifications.
Requirements
- Dart >= 3.9 (bundled with Flutter >= 3.35) to run
swagger_to_dartitself. - The code it generates uses
json_serializable>= 6.10, which emits Dart 3.8 syntax (null-aware elements) in.g.dartfiles. Your consuming project's own SDK lower bound must be at least3.8.0(e.g.environment: sdk: '>=3.8.0 <4.0.0') or that generated code won't compile.
Support
- FastAPI
- abpIO
- NestJS
- Spring Boot
- AspNet Core
- Flask
- Any other framework that generates OpenAPI 3.1.0 specs with JSON format
Features
- π Generates Dart models with freezed for immutability and serialization
- π Creates retrofit API clients for type-safe HTTP requests
- π§© Supports nested objects, enums, and complex data structures
- π Handles query parameters, path parameters, and request bodies
- π Generates documentation comments from OpenAPI descriptions
- π Fetch OpenAPI specifications directly from URLs (JSON format)
- πΎ Automatically saves fetched specs locally
- π οΈ Customizable output with configuration options
- β‘ Supports FastAPI, NestJS, Spring Boot, and any other framework that generates OpenAPI 3.1.0 specs
Getting Started
Add dependencies
First, add the required dependencies to your pubspec.yaml file:
dependencies:
dio: ^x.x.x
retrofit: ^x.x.x
freezed_annotation: ^x.x.x
json_annotation: ^x.x.x
dev_dependencies:
swagger_to_dart: ^x.x.x
build_runner: ^x.x.x
freezed: ^x.x.x
json_serializable: ^x.x.x
retrofit_generator: ^x.x.x
Or use Dart CLI to add the dependencies:
dart pub add dev:swagger_to_dart
dart pub add dev:build_runner
dart pub add freezed_annotation
dart pub add dev:freezed
dart pub add json_annotation
dart pub add dev:json_serializable
dart pub add dio
dart pub add retrofit
dart pub add dev:retrofit_generator
Configure build order
Create a build.yaml file in your project root to ensure the correct build order:
global_options:
freezed:
runs_before:
- json_serializable
json_serializable:
runs_before:
- retrofit_generator
Update the analysis_options.yaml file:
linter:
rules:
prefer_single_quotes: true
analyzer:
exclude:
# for retrofit and json_serializable
- "**/*.g.dart"
# for freezed
- "**/*.freezed.dart"
errors:
# for json_serializable
invalid_annotation_target: ignore
Generate code from your OpenAPI specification
Create a swagger_to_dart.yaml file in your project root:
swagger_to_dart:
input_directory: schema/swagger.json
output_directory: lib/src/gen
model:
support_generic_arguments: true
union_class_fallback_name: fallback
enum_fallback_type: first
api_client:
base_api_client_class_name: BaseApiClient
use_class_for_query_parameters: true
skipped_parameters:
- Language
- X-API-Key
See Configuration Options below for every key. Then run:
dart run swagger_to_dart
The CLI takes one optional flag: --config (short -c) points to a config
file at a different path. It defaults to swagger_to_dart.yaml in the
current directory.
dart run swagger_to_dart [--config path/to/swagger_to_dart.yaml]
Run code generation
After generating the API clients and models, run build_runner to generate the necessary code:
dart run build_runner build
Example Usage
1. Define the OpenAPI specification location
Create a swagger_to_dart.yaml configuration file
2. Generate code
Once you've set up your configuration file, run the following commands:
# First, generate the Dart code from your OpenAPI specification
dart run swagger_to_dart
# Then, generate the implementation with freezed, json_serializable, and retrofit
dart run build_runner build
3. Use the generated code
import 'package:dio/dio.dart';
import 'package:your_project/api/api.dart';
void main() async {
final dio = Dio();
// Add interceptors for auth, logging, etc.
dio.interceptors.add(LogInterceptor(responseBody: true));
// Create API client
final userClient = UserApi(dio, baseUrl: 'https://api.example.com');
try {
// Use the generated API client
final users = await userClient.getUsers();
print('Users: ${users.map((u) => u.name).join(', ')}');
// Create a model instance
final newUser = User(id: '123', name: 'John Doe', email: 'john@example.com');
// Use the model in an API call
final createdUser = await userClient.createUser(newUser);
print('Created user: ${createdUser.name}');
} catch (e) {
print('Error: $e');
}
}
Configuration Options
The package configuration is defined in a swagger_to_dart.yaml file. Every
key, with its default:
swagger_to_dart:
# Fetch the spec from a URL instead of only reading `input_directory`.
# On success, `input_directory` is overwritten with the fetched JSON
# (pretty-printed) so it stays a fresh local copy. On failure, generation
# falls back to the existing local copy with a loud console warning
# naming its age; with no local copy either, generation fails.
# Default: unset (read `input_directory` only).
url: https://api.example.com/openapi.json
# Local OpenAPI JSON file: read directly when `url` is unset, and used as
# the fetch/refresh target when it is set.
input_directory: schema/swagger.json # default
# Where generated models and API clients are written.
output_directory: lib/src/gen # default
# Backend that produced the spec: FastAPI | dotnet | abp.io. Selects the
# generic-type-name parser (e.g. `PagedResultDto<UserDto>`-style naming
# differs per framework) and, in Flutter projects, which string `format`s
# map to native types instead of `String` (color / color-hex -> Color,
# FastAPI only; time / duration -> TimeOfDay, FastAPI only).
# Default: unset.
generation_source: FastAPI
imports:
# Raw import statements prepended verbatim to every generated file.
# Default: [].
global:
- "import 'package:my_app/interceptors.dart';"
model:
# Generate a real generic Dart class (`class Foo<T>`, with a generic
# `fromJson`) for a component schema whose title matches
# `generation_source`'s generic-instantiation naming convention,
# instead of one flat class per instantiation. Default: false.
support_generic_arguments: false
# Name of the fallback variant for a oneOf/anyOf union whose payload
# matches no variant (bad/missing discriminator value, or nothing
# decodes without error). The fallback wraps the raw
# `Map<String, dynamic>`. Default: unset β an unmatched payload throws
# `ArgumentError` instead.
union_class_fallback_name: fallback
# What an unrecognized value decodes to. Default: throwException.
# throwException - throws (also what happens at runtime when unset).
# unknown - adds a real `unknown` member (reusing one if the
# enum already declares it; int enums get a JSON
# value below the real ones, e.g. -1) and decodes
# unrecognized values to it.
# first / last - decodes to the first / last declared member.
enum_fallback_type: throwException
# Prefixes stripped from a schema's name/title before it becomes a Dart
# class name (longest match wins), e.g. ["Api"] turns `ApiUserDto` into
# `UserDto`. Applied consistently to regular, enum, union and generic
# models and their file names. Default: [].
remove_model_prefixes: []
# Opt-in per-enum member renaming, keyed by the enum's schema name or
# generated Dart class name; the inner map is the raw enum value (as a
# string β works for string and integer enums) to the desired Dart
# member name (recased to camelCase). Enums absent here keep their
# default member names (e.g. `value0`). Default: {}.
enums:
MyStatusEnum:
0: created
10: pgRegistered
api_client:
# Class name of the shared base client every generated client uses.
base_api_client_class_name: BaseApiClient # default
# Bundle an operation's query parameters into one generated
# `<Method>QueryParameters` class, passed as a single `@Queries()`
# argument, instead of one named method parameter per query field.
# Default: false.
use_class_for_query_parameters: false
# Operation parameters dropped entirely from the generated method
# signature (and so never sent) β e.g. ones your own Dio interceptors
# already inject, like `Language` or `X-API-Key`. Default: [].
skipped_parameters:
- Language
- X-API-Key
# Default each generated method's `@Extras()` to that operation's
# OpenAPI metadata (tags, operationId, parameters, responses), readable
# via `options.extra` in a Dio interceptor. Set to `false` to stop
# embedding it. Default: true.
include_openapi_extras: true
Unions
A component schema that is a oneOf/anyOf of $refs generates a sealed
class with one final subclass per variant, plus a fallback variant when
model.union_class_fallback_name is set. fromJson/toJson work on the
variant's own flat JSON β there is no {"value": ...} envelope β so a union
decodes the same way whether it's a field, a list item, a request body or a
response:
- With a discriminator: decoding switches on the discriminator property (variants the mapping leaves out, or all of them when it is omitted, use their schema name as the value); encoding writes the discriminator back.
- Without one: among the variants whose required keys are all present, the one declaring the most of the payload's keys wins (the earlier one on a tie).
- No match: the fallback variant wraps the raw
Map<String, dynamic>; with nounion_class_fallback_nameconfigured, decoding throwsArgumentErrorinstead.
final Animal animal = Animal.fromJson(json); // AnimalDog, AnimalCat, or the fallback
final label = switch (animal) {
AnimalDog(:final value) => 'Dog: ${value.name}',
AnimalCat(:final value) => 'Cat: ${value.name}',
AnimalFallback(:final value) => 'Unknown animal: $value',
};
print(animal.toJson()); // flat JSON, discriminator included
Handling Breaking Changes
When your API changes, you can use the following workflow to update your generated code:
- Update your OpenAPI specification
- Run
dart run swagger_to_dart - Run
dart run build_runner build - Check for breaking changes in your codebase and update as needed
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This repository is test-driven: every behaviour change starts as a failing test, and CI blocks anything that isn't green. See CONTRIBUTING.md for setup, the package layout and the red-green loop.
CI/CD
Publish new release https://github.com/masreplay/api_spec_to_dart/actions
make publish
License
This package is available under the MIT License.
Acknowledgements
This package was inspired by and builds upon other great Dart packages including freezed, retrofit, and json_serializable and FastAPI.