Dart
Output Layout
Dart output is two files per schema: a main .dart file with annotated types and the IDL module owner, and a .fory.dart part file with generated serializers and metadata.
<dart_out>/package/package.dart<dart_out>/package/package.fory.dart
Type Generation
Messages generate @ForyStruct annotated final class declarations with @ForyField on each field:
()
final class Person {
Person();
(id: 1)
String name = '';
(id: 2, type: Int32Type())
int id = 0;
(id: 7)
List<Person_PhoneNumber> phones = <Person_PhoneNumber>[];
(id: 8)
Animal pet = Animal._empty();
}
Enums generate Dart enum declarations with a rawValue getter and fromRawValue factory:
enum Person_PhoneType {
mobile,
home,
work;
int get rawValue => switch (this) {
Person_PhoneType.mobile => 0,
Person_PhoneType.home => 1,
Person_PhoneType.work => 2,
};
static Person_PhoneType fromRawValue(int value) => switch (value) {
0 => Person_PhoneType.mobile,
1 => Person_PhoneType.home,
2 => Person_PhoneType.work,
_ => throw StateError('Unknown Person_PhoneType raw value $value.'),
};
}
Unions generate @ForyUnion annotated classes with factory constructors, a case enum, and a custom serializer:
enum AnimalCase {
dog,
cat;
int get id => switch (this) {
AnimalCase.dog => 1,
AnimalCase.cat => 2,
};
}
()
final class Animal {
final AnimalCase _case;
final Object? _value;
const Animal._(this._case, this._value);
factory Animal.dog(Dog value) => Animal._(AnimalCase.dog, value);
factory Animal.cat(Cat value) => Animal._(AnimalCase.cat, value);
bool get isDog => _case == AnimalCase.dog;
Dog get dogValue => _value as Dog;
// ...
}
Nested types use flat underscore naming (e.g., Person_PhoneNumber, Person_PhoneType).
list<T> fields generate ordered collection carriers and use the Fory list
protocol. array<T> fields generate dense one-dimensional bool or numeric
carriers and use the specialized dense-array protocol. Generated code must not
choose array<T> only because a language has an optimized list-like carrier;
the schema kind comes from the IDL.
| IDL schema | Dart generated carrier | Notes |
|---|---|---|
list<int32> | List<int> | List protocol, varint element encoding |
list<fixed int32> | List<int> | List protocol, fixed-width element segment |
array<bool> | BoolList | One byte per bool |
array<int8> | Int8List | Dense signed bytes |
array<int16> | Int16List | Dense little-endian int16 |
array<int32> | Int32List | Dense little-endian int32 |
array<int64> | Int64List | Dense little-endian int64 |
array<uint8> | Uint8List | Dense unsigned bytes |
array<uint16> | Uint16List | Dense little-endian uint16 |
array<uint32> | Uint32List | Dense little-endian uint32 |
array<uint64> | Uint64List | Dense little-endian uint64 |
array<float16> | Float16List | Dense binary16 storage |
array<bfloat16> | Bfloat16List | Dense bfloat16 storage |
array<float32> | Float32List | Dense little-endian float32 |
array<float64> | Float64List | Dense little-endian float64 |
Generated Dart fields that use ArrayType(element: BoolType()) must use
BoolList; plain List<bool> remains the generated and handwritten carrier
for list<bool>.
Reference tracking on list elements or map values uses the container sugar annotations:
(element: DeclaredType(ref: true))
(id: 3)
List<Node> children = <Node>[];
(value: DeclaredType(ref: true))
(id: 2)
Map<String, Node> byName = <String, Node>{};
Module Installation
Each generated Dart IDL library includes a module owner named after the input
file, such as AddressbookForyModule for addressbook.dart. The module
installs imported modules first and then registers every local schema type with
its default IDL identity:
abstract final class AddressbookForyModule {
static void install(Fory fory) {
complex_pb.ComplexPbForyModule.install(fory);
_registerType(fory, Person);
_registerType(fory, Dog);
}
static Fory getFory() { ... }
static void _registerType(Fory fory, Type type) {
if (type == Person) {
registerGeneratedStruct(fory, _personForySchema, id: 100, namespace: null, typeName: null);
return;
}
// ... other types
}
}
Usage
import 'package:fory/fory.dart';
import 'generated/addressbook/addressbook.dart';
void main() {
final fory = Fory();
AddressbookForyModule.install(fory);
final person = Person()
..name = 'Alice'
..id = 1;
final bytes = fory.serialize(person);
final roundTrip = fory.deserialize<Person>(bytes);
}
gRPC Service Companions
With --grpc, Dart emits one <stem>_grpc.dart per schema containing <Service>Client and <Service>ServiceBase. The service base registers its own method descriptors, so no separate registrar is generated. See Dart gRPC for generated module naming, Fory installation, and usage.