Skip to main content
Version: dev

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 typeCross-language notes
boolDirect mapping
intSerialized as 64-bit by default. Use @ForyField(type: Int8Type/Int16Type/Int32Type/Uint8Type/Uint16Type/Uint32Type) when the peer expects a narrower integer
doubleMaps to 64-bit float. Use Float32 wrapper when the peer expects 32-bit
StringDirect mapping
Uint8ListBinary blob
List, Set, MapSupported; element types must also be supported
DateTimeUse 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 Java float, C# float, Go float32)
  • @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 zone
  • Duration — an elapsed time value using Dart's built-in Duration
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(...) and timestamp.toDateTime()
  • LocalDate.fromEpochDay(Int64(...)), date.toEpochDay() returns Int64
  • LocalDate.fromDateTime(...) and date.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:

  • BoolList plus @ArrayField(element: BoolType()) for array<bool>. Plain List<bool> maps to list<bool>.
  • Int8List, Int16List, Int32List, Int64List
  • Uint8List, Uint16List, Uint32List, Uint64List
  • Float16List, 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.