Dataforge Generator
The build_runner adapter for Dataforge v1 deeply immutable value models.
1.0.0-dev.0is a preview, not the 1.0 GA release. APIs, diagnostics and generated output may still change in later preview versions.
Installation
dependencies:
dataforge_annotation: ^1.0.0-dev.0
dev_dependencies:
build_runner: ^2.4.13
dataforge: ^1.0.0-dev.0
All Dataforge packages in an application must resolve to the same preview version. Dart 3.9 or later is required.
Model declaration
import 'package:dataforge_annotation/dataforge_annotation.dart';
part 'user.data.dart';
@Dataforge()
abstract final class User with _$User {
const User._();
factory User({
required String name,
@DataforgeDefault(<String>[]) List<String> tags,
@JsonKey(name: 'display_name') String? displayName,
}) = _User;
factory User.fromJson(Map<String, Object?> json) = _User.fromJson;
}
The declaration contract is strict:
- the public model is
abstract final; - value properties come from one unnamed redirecting factory;
- the generated target is
_Userand the generated mixin is_$User; const User._();provides the base constructor for the final implementation;- the
partURI must end in.data.dartand match the source file; - JSON input is
Map<String, Object?>; - a
fromJsonfactory, when enabled, redirects to_User.fromJson.
Invalid declarations fail generation with a source-located DF diagnostic.
The generator never rewrites the model source.
Generate
dart run build_runner build
For continuous generation:
dart run build_runner watch
The published builder always emits .data.dart part files. The output suffix is
not configurable in this preview.
Value semantics
Generated implementations are final. Constructor, copyWith, default and
decode boundaries recursively freeze the complete declared type tree:
List,SetandMapinputs are copied and exposed through unmodifiable collections;- nested collections and records are traversed recursively;
- equality and hash use the same leaf witnesses as freeze;
copyWithmay safely share values already frozen under compatible witness semantics.
Mutating caller-owned collections after construction cannot change a model. Set elements and Map keys that collapse under witness equality are rejected instead of being silently discarded.
Annotations
@Dataforge
@Dataforge(
name: '',
includeFromJson: true,
includeToJson: true,
)
name changes the private implementation base name. JSON directions can be
disabled independently.
@DataforgeDefault
Dart does not allow a default directly on a redirecting factory parameter. Use compile-time metadata instead:
factory Page({
@DataforgeDefault(1) int number,
@DataforgeDefault(<String>[]) List<String> labels,
}) = _Page;
The resolved frontend validates that the constant is assignable and can be rendered without losing meaning.
@JsonKey
factory User({
@JsonKey(
name: 'user_name',
alternateNames: ['username'],
includeIfNull: false,
)
String? name,
}) = _User;
ignore: true excludes a field from both JSON directions. An ignored required
field must still have a construction default.
Strict JSON
Generated codecs do not coerce unrelated runtime types. They validate required
fields, nullability, unknown keys, aliases, duplicate decoded keys and custom
witness output. Failures use DataforgeDecodeException or
DataforgeEncodeException with a stable DFJ code and JSONPath.
DateTime is encoded as normalized UTC ISO-8601 text. Record values have
recursive non-JSON value semantics, but a Record subtree used by JSON requires
an exact DataforgeType<RecordShape> witness; otherwise generation reports
DF1006.
Generic and custom values
Any behavior that depends on a type parameter or unsupported custom leaf needs
an explicit DataforgeType<T> witness:
@Dataforge()
abstract final class Box<T> with _$Box<T> {
const Box._();
factory Box({
required DataforgeType<T> type,
required T value,
}) = _Box<T>;
factory Box.fromJson(
Map<String, Object?> json, {
required DataforgeType<T> type,
}) = _Box<T>.fromJson;
}
A custom witness is part of the value contract. Its identity tree and its
freeze/equality/hash/codec behavior must remain immutable for its entire
lifetime. freeze must isolate mutable input; equality and hash must agree.
Standalone CLI
The dataforge_cli package provides a standalone resolved adapter:
dataforge generate .
dataforge check .
Both adapters use the same resolved facade and renderer. Representative fixtures assert byte parity, but the full GA type/platform parity matrix is not complete yet.
Troubleshooting
- Run
dart pub getin the nearest package root. - Check the exact
part,_$Model,_Modeland private base constructor names. - Use
@DataforgeDefaultrather than a redirecting-factory default. - Provide one compatible witness for every type-dependent semantic subtree.
- Follow the first source-located
DFdiagnostic; later errors are often consequences of the same declaration mismatch.
See the 1.0 RFC and support matrix for the preview boundary.