Generated Code
Every file type generated by dart_openapi_generator — structure, naming, and real examples.
Running dart run build_runner build writes the following file tree into your outputDir
(e.g. lib/generated/). Every file starts with a // GENERATED CODE - DO NOT MODIFY BY HAND
header and a // @dart=3.0 directive.
lib/generated/
├── api_client.dart ← aggregator class (named via clientName)
├── generated.dart ← barrel: exports everything alphabetically
├── models/
│ └── *.dart ← one file per schema (alphabetical)
└── services/
└── *_api.dart ← one file per OpenAPI tag (alphabetical)
Ordering
All generated output is deterministically alphabetical:
- Models in
models/ordered by schema name - Services in
services/ordered by tag name - Properties within each class ordered by field name
- Imports within each file ordered alphabetically
- The
generated.dartbarrel exports in alphabetical order
Regenerating the same spec twice always produces byte-identical output — no diff noise in version control.
Barrel: generated.dart
One import gives access to every generated type:
import 'generated/generated.dart';
The barrel content from the example project:
// GENERATED CODE - DO NOT MODIFY BY HAND
// @dart=3.0
// Generated by dart_openapi_generator
export 'api_client.dart';
export 'models/category.dart';
export 'models/metadata.dart';
export 'models/notification.dart';
export 'models/token_request.dart';
export 'models/token_response.dart';
export 'models/user.dart';
export 'models/user_profile.dart';
export 'models/user_role.dart';
export 'models/user_search_request.dart';
export 'services/auth_api.dart';
export 'services/categories_api.dart';
export 'services/debug_api.dart';
export 'services/users_api.dart';
Model: Plain Object
For an OpenAPI object schema the generator emits a final class with:
- Alphabetically ordered
finalfields - Named constructor (
required this.fieldfor required fields,this.fieldfor optional) factory fromJson(Map<String, dynamic> json)— throwsArgumentError.notNullon missing required fieldsMap<String, dynamic> toJson()— omitsnulloptional fields withifguardscopyWith— uses an_Undefinedsentinel for nullable fields to distinguish "not provided" fromnulloperator ==andhashCode— deep equality, usesObject.hash
Verbatim from example/lib/generated/models/user.dart:
// GENERATED CODE - DO NOT MODIFY BY HAND
// @dart=3.0
// Generated by dart_openapi_generator
import 'user_role.dart';
class _Undefined {
const _Undefined();
}
const _Undefined _undefined = _Undefined();
final class User {
final String? class_;
final DateTime? createdAt;
final String? default_;
final String email;
final String id;
final String name;
final UserRole? role;
User({
this.class_,
this.createdAt,
this.default_,
required this.email,
required this.id,
required this.name,
this.role,
});
factory User.fromJson(Map<String, dynamic> json) => User(
class_: json['class'] == null ? null : json['class'] as String,
createdAt: json['createdAt'] == null
? null
: DateTime.parse(json['createdAt'] as String),
default_: json['default'] == null ? null : json['default'] as String,
email: json['email'] == null
? (throw ArgumentError.notNull('User.email'))
: json['email'] as String,
id: json['id'] == null
? (throw ArgumentError.notNull('User.id'))
: json['id'] as String,
name: json['name'] == null
? (throw ArgumentError.notNull('User.name'))
: json['name'] as String,
role: json['role'] == null
? null
: UserRole.fromJson(json['role'] as String),
);
Map<String, dynamic> toJson() => {
if (class_ != null) 'class': class_,
if (createdAt != null) 'createdAt': createdAt!.toIso8601String(),
if (default_ != null) 'default': default_,
'email': email,
'id': id,
'name': name,
if (role != null) 'role': role!.toJson(),
};
User copyWith({
Object? class_ = _undefined,
Object? createdAt = _undefined,
Object? default_ = _undefined,
String? email,
String? id,
String? name,
Object? role = _undefined,
}) =>
User(
class_: identical(class_, _undefined) ? this.class_ : class_ as String?,
createdAt: identical(createdAt, _undefined)
? this.createdAt
: createdAt as DateTime?,
default_: identical(default_, _undefined)
? this.default_
: default_ as String?,
email: email ?? this.email,
id: id ?? this.id,
name: name ?? this.name,
role: identical(role, _undefined) ? this.role : role as UserRole?,
);
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is User &&
class_ == other.class_ &&
createdAt == other.createdAt &&
default_ == other.default_ &&
email == other.email &&
id == other.id &&
name == other.name &&
role == other.role;
@override
int get hashCode => Object.hash(
class_,
createdAt,
default_,
email,
id,
name,
role,
);
}
Notes:
- Fields
class_anddefault_show reserved-word escaping. Dart reserved words get a_suffix. The JSON key'class'maps to the Dart fieldclass_. createdAtusesDateTime.parsebecausedateTimeConverterdefaults toiso8601. UseDateTimeConverter.timestampfor millisecond epoch integers.- The
_Undefinedsentinel incopyWithallows passingnullexplicitly to clear a nullable field — without it,nullwould be indistinguishable from "leave unchanged".
Model: Enum
For an OpenAPI enum schema the generator emits a Dart enum with fromJson/toJson
switch expressions. String, integer, and number enum types are all handled.
// GENERATED CODE - DO NOT MODIFY BY HAND
// @dart=3.0
// Generated by dart_openapi_generator
enum UserRole {
admin,
member,
viewer;
static UserRole fromJson(String v) => switch (v) {
'admin' => UserRole.admin,
'member' => UserRole.member,
'viewer' => UserRole.viewer,
final t => throw ArgumentError('Unknown UserRole value: $t'),
};
String toJson() => switch (this) {
UserRole.admin => 'admin',
UserRole.member => 'member',
UserRole.viewer => 'viewer',
};
}
Enum values are sorted alphabetically by their sanitized Dart identifier. Wire values with special characters (spaces, hyphens, leading digits) are sanitized to valid camelCase Dart identifiers. If sanitization produces a duplicate identifier, generation fails with a clear error asking you to rename the conflicting values in the spec.
Model: allOf
For an OpenAPI allOf schema, the generator merges all object members into a single flat
final class. Properties from all members are collected, duplicates are resolved by taking
the first occurrence (a warning is emitted when types conflict), and required lists are
unioned. The resulting class has the same structure as a plain object class.
Model: oneOf with Discriminator
For an OpenAPI oneOf schema with a discriminator, the generator emits a sealed class
parent and one final class per variant. All variants are inlined into the single parent
file — no separate files per variant.
The parent's factory fromJson checks that the discriminator key is present, then dispatches
to the correct variant using a switch expression:
// GENERATED CODE - DO NOT MODIFY BY HAND
// @dart=3.0
// Generated by dart_openapi_generator
sealed class Notification {
const Notification();
factory Notification.fromJson(Map<String, dynamic> json) {
if (!json.containsKey('type')) {
throw ArgumentError('Missing discriminator key "type" in JSON');
}
return switch (json['type']!.toString()) {
'email' => EmailNotification.fromJson(json),
'push' => PushNotification.fromJson(json),
final t => throw ArgumentError(
'Unknown Notification discriminator value: $t (key: type)'),
};
}
Map<String, dynamic> toJson();
}
final class EmailNotification extends Notification {
final String email;
final String type;
const EmailNotification({required this.email, required this.type});
factory EmailNotification.fromJson(Map<String, dynamic> json) =>
EmailNotification(
email: json['email'] == null
? (throw ArgumentError.notNull('EmailNotification.email'))
: json['email'] as String,
type: json['type'] == null
? (throw ArgumentError.notNull('EmailNotification.type'))
: json['type'] as String,
);
@override
Map<String, dynamic> toJson() => {'email': email, 'type': type};
EmailNotification copyWith({String? email, String? type}) =>
EmailNotification(
email: email ?? this.email,
type: type ?? this.type,
);
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is EmailNotification &&
email == other.email &&
type == other.type;
@override
int get hashCode => Object.hash(email, type);
}
// PushNotification follows the same pattern ...
Sealed classes enable exhaustive switch in your app code. Add a new variant to the spec and regenerate — the Dart compiler will catch any unhandled case in existing switch expressions.
A oneOf schema without a discriminator produces a sealed class whose fromJson always
throws UnimplementedError. Add a discriminator to the spec to get a working fromJson.
Service
For each OpenAPI tag, the generator emits one file services/{tag}_api.dart containing
class {Tag}Api. Every method:
- Is named from
operationId(lowerCamelCase), falling back to{method}{PathSegments}when nooperationIdis set - Accepts Dio override parameters:
cancelToken,headers,extra,validateStatus, and where applicableonSendProgress/onReceiveProgress - Returns
Future<void>when there is no 2xx response schema,Future<List<T>>for array responses,Future<T>for object responses - Throws
DioExceptionon non-2xx responses (standard Dio behavior) - Documents
Throws [DioException] on non-2xx response.in its dartdoc - URL-encodes path parameters with
Uri.encodeComponent
Verbatim listUsers method from example/lib/generated/services/users_api.dart:
class UsersApi {
final Dio _dio;
const UsersApi(this._dio);
/// List all users
///
/// Throws [DioException] on non-2xx response.
Future<List<User>> listUsers({
int? page,
CancelToken? cancelToken,
Map<String, dynamic>? headers,
Map<String, dynamic>? extra,
ValidateStatus? validateStatus,
ProgressCallback? onReceiveProgress,
}) async {
final response = await _dio.get<List<dynamic>>(
'/users',
queryParameters: {
if (page != null) 'page': page,
},
options: Options(
headers: headers, extra: extra, validateStatus: validateStatus),
cancelToken: cancelToken,
onReceiveProgress: onReceiveProgress,
);
final data = response.data;
if (data == null) {
throw StateError('Expected JSON list response body but received null.');
}
return data.map((e) => User.fromJson(e as Map<String, dynamic>)).toList();
}
}
Parameter handling:
- Path parameters become positional required parameters, ordered as they appear in the URL template
- Required query/header parameters become named required parameters
- Optional query/header parameters become named optional (
T?) parameters cookieparameters are not emitted — a warning is logged, and the dartdoc recommends Dio interceptors instead- Header parameters are merged with the caller-supplied
headersmap at runtime
Aggregator: api_client.dart
The aggregator is named via clientName (default: 'ApiClient'). It holds one late final
service field per tag and static auth factories generated from securitySchemes.
Constructor parameters:
| Parameter | Type | Description |
|---|---|---|
dio | Dio? | Inject a pre-configured Dio instance. If null, a new instance is created. |
baseUrl | String? | Override the base URL. Defaults to servers[0].url from the spec. |
interceptors | List<Interceptor>? | Dio interceptors added to the internal Dio instance. |
connectTimeout | Duration | Default: Duration(seconds: 30). |
receiveTimeout | Duration | Default: Duration(seconds: 30). |
Auth factories are generated based on securitySchemes in the spec:
| Scheme type | Factory method |
|---|---|
type: http, scheme: bearer | static Interceptor bearerAuth(String token) |
type: http, scheme: basic | static Interceptor basicAuth(String username, String password) |
type: apiKey, in: header | static Interceptor apiKeyAuth(String apiKey, {String headerName}) |
type: apiKey, in: query | static Interceptor apiKeyQueryAuth(String apiKey, {String paramName}) |
See Advanced — Authentication for usage examples of each factory.
See also: Annotation Reference · Advanced