ack_generator 1.4.0
ack_generator: ^1.4.0 copied to clipboard
Generates immutable models from Ack schemas and validated schemas from Dart classes
Ack Generator #
ack_generator supports two modern directions: @AckInfer() turns a
top-level Ack schema into an immutable model, while @AckModel() derives an
Ack codec schema from a hand-written class. It also retains the deprecated Ack
1.1 @AckType() generator unchanged.
Schema-first usage #
import 'package:ack/ack.dart';
import 'package:ack_annotations/ack_annotations.dart';
part 'user_schema.ack.dart';
part 'user_schema.ack.g.dart';
@AckInfer()
final userSchema = Ack.object({
'name': Ack.string(),
'email': Ack.string().email(),
});
Run dart run build_runner build. A declaration ending in Schema loses that
suffix, so userSchema generates User:
void main() {
final user = User.parse({
'name': 'Ada',
'email': 'ada@example.com',
});
print(user.name); // String
print(user.toJson()); // {'name': 'Ada', 'email': 'ada@example.com'}
}
Constructors don't validate immediately. Use parse for untrusted input;
toJson and safeToJson validate a directly constructed model while encoding
it. Generated models don't implement Map, and there are no fromMap or
toMap aliases.
Omit name when the inferred class name is right. Use
@AckInfer(name: 'Member') only when you need an exact custom name. Custom
names must be unchanged UpperCamelCase identifiers.
Class-first usage #
In a class-first library, keep the model in source and apply the generated mixin:
import 'package:ack/ack.dart';
import 'package:ack_annotations/ack_annotations.dart';
part 'account.ack.dart';
part 'account.ack.g.dart';
@AckModel()
final class Account with _$AccountAck {
const Account({required this.name});
@MinLength(2)
final String name;
static final fromJson = AccountSchema.fromJson;
}
After generation, the public facade and model JSON methods use the same Ack codec boundary:
void main() {
final account = Account.fromJson({'name': 'Ada'});
print(account.toJson());
print(AccountSchema.toJsonSchema());
}
Schema support #
The generator supports objects, empty objects, scalar and collection roots,
literals, enums, defaults, additional properties, built-in and custom
bidirectional codecs, named nested models, aliases, named Ack.lazy recursion,
and same-library discriminated unions. Lists, sets, and maps stored by a model
generated with @AckInfer() are copied recursively into unmodifiable
collections. @AckModel() parsing provides the same guarantee, including for
captured extras. Hand-written constructors and collection replacements passed
to copyWith remain responsible for their own defensive copies; use
deepUnmodifiableJsonMap for dynamic JSON maps. Raw
Ack.object(..., additionalProperties: true) schemas preserve extras, while a
class-first model applies its later unknownProperties projection. Use
discard only for tolerant, read-only consumers and capture for round trips.
Generation rejects shapes without a useful static, encodable model contract:
- one-way
.transform()calls, including.trim(),.toLowerCase(), and.toUpperCase(); use.codec()with an encoder; - nullable roots;
- nullable
Ack.listitem schemas and automatically inferredList<T?>orSet<T?>fields; make the collection nullable instead, or use an explicit@AckField(schema: ...)codec for a different collection contract; Ack.any(),Ack.anyOf(), and bareAck.instance<T>();- anonymous inline object fields and unresolved dynamic schema factories;
- invalid names, generated-member collisions, and cross-library union branches.
Named model references work through direct imports, prefixes, and re-exports.
Nested conversion uses each model's public $ack adapter so codec runtime
values aren't parsed twice.
JSON serialization #
Every annotated library declares both parts:
part 'account.ack.dart';
part 'account.ack.g.dart';
Ack owns schema validation, defaults, codecs, union dispatch, and the public
parse / fromJson / toJson methods. json_serializable generates the
structural _$ClassFromJson / _$ClassToJson helpers into the Ack JSON
part. Ack-only apps do not add json_annotation or json_serializable;
ack_generator activates that second phase itself.
When a modern-only target also uses an ordinary source-gen builder that owns
.g.dart, disable the unused legacy builder in that target so it remains the
sole .g.dart owner:
targets:
$default:
builders:
ack_generator:ack_generator:
enabled: false
Supported declarations #
@AckInfer() can annotate top-level schema variables and top-level schema
getters. Classes, instance members, and local variables are rejected.
@AckModel() annotates public, constructable final class declarations whose
stored fields are final. Annotated sealed union bases remain supported, and
their concrete branches must also be final. Use @Optional() or @Required()
to override inferred key presence, @NotNull() to reject JSON null without
requiring the key, and @AckField(schema: ...) for custom codecs. See the
Model Code Generation guide
for both directions, field inference, sealed unions, passthrough properties,
and build configuration.
For a hand-written Account, class-first generation exposes an
AccountSchema facade backed by a private _accountSchema codec. The facade
provides parsing, safe parsing, encoding, JSON Schema/schema-model export,
typed schema, and raw wireSchema. Instantiable models apply the generated
_$AccountAck mixin, which supplies toJson, copyWith (omitted means keep;
explicit null clears a nullable field), and deep collection-aware equality. Add
static final fromJson = AccountSchema.fromJson; when the class should expose
the conventional one-argument entry point. Imported nested models compose as
prefix.AddressSchema.schema. Across all imports and barrel exports,
show/hide combinators must expose both the authored declaration and its
generated companion (Address plus AddressSchema for class-first, or
addressSchema plus Address for schema-first). Visibility may be split
across multiple imports that use the same prefix.
Class-first wire-name overrides support @JsonKey(name: 'wire_name') on the
field. Other JsonKey options and constructor-parameter placement fail
generation so schema validation and JSON mapping remain identical.
For design details and migration notes, see the model and schema generation architecture.
Deprecated AckType compatibility #
An unchanged Ack 1.1 declaration still uses part 'file.g.dart'; and generates
the same *Type, .args, Map, parse, and safeParse APIs. AckType is frozen
until its removal in Ack 2.0. New connected model graphs must use AckInfer or
AckModel; nested references between legacy and modern graphs are rejected
with a migration diagnostic.
| Legacy | Modern opt-in |
|---|---|
@AckType() |
@AckInfer() |
file.g.dart |
file.ack.dart + file.ack.g.dart |
UserType.parse(...) |
User.parse(...) or User.fromJson(...) |
Map access and .args |
Typed fields, .additionalProperties, and toJson() |