Supported Types
This page lists the Dart types you can use in Fory messages, and flags where you need to be careful for cross-language compatibility.
Built-in Primitive Types
The following Dart types serialize directly without any special handling:
| Dart type | Cross-language notes |
|---|---|
bool | Direct mapping |
int | Serialized as 64-bit by default. Use @ForyField(type: Int8Type/Int16Type/Int32Type/Uint8Type/Uint16Type/Uint32Type) when the peer expects a narrower integer |
double | Maps to 64-bit float. Use Float32 wrapper when the peer expects 32-bit |
String | Direct mapping |
Uint8List | Binary blob |
List, Set, Map | Supported; element types must also be supported |
DateTime | Use Timestamp or LocalDate wrappers for explicit semantics |
Integer Fields
Dart VM/native int can represent signed 64-bit values, while Dart web int
is limited to JavaScript-safe integer precision. If the peer language expects a
32-bit integer (Java int, Go int32, C# int) and you send a Dart int,
the deserialization may fail or silently truncate. For browser and Flutter web
precision rules, see Web Platform Support.
Use field metadata to select the wire type explicitly for 8/16/32-bit fields:
()
class Metrics {
Metrics();
(type: Int8Type())
int tiny = 0;
(type: Int32Type(encoding: Encoding.fixed))
int age = 0;
(type: Uint32Type())
int count = 0;
Int64 sequence = Int64(0);
Uint64 offset = Uint64(0);
}
Generated serializers range-check annotated int values before writing them.
Use Int64 and Uint64 when you need full-range 64-bit values, especially on
web. A plain root int value serializes as xlang int64; exact 8/16/32-bit
wire widths are selected through field metadata or low-level Buffer APIs.
On Dart VM, Int64 and Uint64 are extension types over int. Once a value is
passed through an Object-typed dynamic/root boundary, the VM cannot recover
whether it was originally a plain int, Int64, or Uint64. Use generated
field metadata or explicit Buffer APIs when native VM payloads must preserve
unsigned 64-bit identity across dynamic boundaries. Dart web uses wrapper
classes, so web root Uint64 values keep varuint64 metadata.
Floating-Point Types
Dart double maps to 64-bit float by default. If the peer uses
reduced-precision floating-point values, keep the Dart field as double and
mark the exact wire type with field metadata:
Float32— 32-bit float (matches Javafloat, C#float, Gofloat32)@ForyField(type: Float16Type()) double value— half-precision scalar@ForyField(type: Bfloat16Type()) double value— bfloat16 scalar
For contiguous 16-bit floating-point arrays, use Float16List and
Bfloat16List rather than Uint16List when the schema is array<float16>
or array<bfloat16>.
Time and Date Types
Avoid sending raw DateTime across languages — time zone handling and epoch differences vary. Use the explicit wrappers instead:
Timestamp— a UTC instant with nanosecond precision (seconds + nanoseconds)LocalDate— a calendar date without time or time zoneDuration— an elapsed time value using Dart's built-inDuration
final now = Timestamp.fromDateTime(DateTime.now().toUtc());
final birthday = LocalDate(1990, 12, 1);
final timeout = const Duration(seconds: 30);
The temporal wrappers expose conversion helpers:
Timestamp.fromDateTime(...)andtimestamp.toDateTime()LocalDate.fromEpochDay(Int64(...)),date.toEpochDay()returnsInt64LocalDate.fromDateTime(...)anddate.toDateTime()
Duration support in Dart is exact to microseconds. Incoming xlang duration
payloads that use sub-microsecond nanoseconds are rejected instead of being
silently truncated.
Structs and Enums
Annotate classes with @ForyStruct() and run build_runner to make them serializable. Enums in the same file are included automatically.
()
class User {
User();
String name = '';
(type: Int32Type())
int age = 0; // use explicit field metadata when peers expect a 32-bit integer
}
Ordinary annotated classes may extend classes and apply mixins. Fory discovers every concrete instance storage field from that hierarchy, including inherited generic fields after specialization, then builds one child schema. See Struct Inheritance for private-field access and omission.
Abstract getters, interfaces, static members, and other declarations without
instance storage are not fields. Every field that remains after explicit
omission must have a supported type, an unambiguous access path, and a valid
reconstruction path; otherwise generation fails. In particular, each included
final or late final field must receive the decoded value unchanged through
the concrete child's constructor chain.
See Code Generation for generator setup.
Classes owned by another library can use an external structural serializer when their public fields and construction match a local Fory schema declaration. External declarations list their schema fields explicitly and do not scan the target hierarchy.
Collections
Fory supports List<T>, Set<T>, and Map<K, V>. Element and key types must
also be serializable types. Avoid using mutable objects as map keys.
Generic List<int> with primitive element metadata still uses list<T> schema.
Dedicated dense array schema comes from dedicated carriers:
BoolListplus@ArrayField(element: BoolType())forarray<bool>. PlainList<bool>maps tolist<bool>.Int8List,Int16List,Int32List,Int64ListUint8List,Uint16List,Uint32List,Uint64ListFloat16List,Bfloat16List,Float32List,Float64List
Compatibility Tip
When in doubt about whether a Dart type will match what the peer expects, make
the width explicit with @ForyField(type: ...). Guessing the wrong numeric
width is one of the most common cross-language bugs.