dart.md

August 3, 2026 ยท View on GitHub

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:

@ForyStruct()
final class Person {
  Person();

  @ForyField(id: 1)
  String name = '';

  @ForyField(id: 2, type: Int32Type())
  int id = 0;

  @ForyField(id: 7)
  List<Person_PhoneNumber> phones = <Person_PhoneNumber>[];

  @ForyField(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,
  };
}

@ForyUnion()
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 schemaDart generated carrierNotes
list<int32>List<int>List protocol, varint element encoding
list<fixed int32>List<int>List protocol, fixed-width element segment
array<bool>BoolListOne byte per bool
array<int8>Int8ListDense signed bytes
array<int16>Int16ListDense little-endian int16
array<int32>Int32ListDense little-endian int32
array<int64>Int64ListDense little-endian int64
array<uint8>Uint8ListDense unsigned bytes
array<uint16>Uint16ListDense little-endian uint16
array<uint32>Uint32ListDense little-endian uint32
array<uint64>Uint64ListDense little-endian uint64
array<float16>Float16ListDense binary16 storage
array<bfloat16>Bfloat16ListDense bfloat16 storage
array<float32>Float32ListDense little-endian float32
array<float64>Float64ListDense 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:

@ListField(element: DeclaredType(ref: true))
@ForyField(id: 3)
List<Node> children = <Node>[];

@MapField(value: DeclaredType(ref: true))
@ForyField(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.