Skip to main content
Version: dev

Field Configuration

Add @ForyField(...) to a field inside a @ForyStruct() class to change how that field is serialized.

Quick Reference

@ForyField(
skip: false, // exclude the field from serialization
id: 10, // stable field ID for schema evolution
nullable: true, // override nullability detection
ref: true, // enable reference tracking for this field
dynamic: false, // control whether the runtime type is written
)

skip

Exclude a field from serialization entirely. Useful for cached, computed, or UI-only values that should not land in a persisted or transmitted message.

@ForyField(skip: true)
String cachedDisplayName = '';

id

Assigns a stable identity to the field so that Fory can match it by ID after a schema change (a field rename or reorder). If you plan to add, remove, or rename fields in the future, assign IDs to all fields now — before you ship the first payload.

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

Once a payload is shared across services, never reuse an id for a different field.

nullable

Explicitly marks a field as nullable or non-nullable, overriding what Fory infers from the Dart type. Use this when the Dart type is non-nullable but you want Fory to accept null on the wire (e.g., reading messages from an older producer that can omit the field).

@ForyField(nullable: true)
String nickname = '';

In cross-language scenarios, make sure the nullability contract also matches what peer runtimes expect.

ref

Enables reference tracking for a specific field. Use this when multiple objects in the graph can point to the same instance, or when the field type can be circular. Without ref: true, Fory serializes the same object value twice if it appears in two fields.

@ForyField(ref: true)
List<Object?> sharedNodes = <Object?>[];

Note: scalar types like int, double, and bool never benefit from reference tracking even if ref: true is set.

dynamic

Controls whether Fory writes the concrete runtime type of the field value into the payload.

  • null (default) — Fory decides automatically based on the declared type.
  • false — always use the declared field type; more compact but the deserializer must know the exact type.
  • true — always write the actual runtime type; needed when the field is declared as Object? or a base class but can hold different concrete types at runtime (polymorphism).
@ForyField(dynamic: true)
Object? payload; // can hold any registered type at runtime

Numeric Field Annotations

Dart int is a 64-bit value at runtime. When exchanging messages with Java, Go, or C#, the receiving side may expect a narrower integer. Use a numeric annotation to pin the exact wire format:

@ForyStruct()
class Sample {
Sample();

@Int32Type(compress: false) // always writes 4 bytes
int fixedWidthInt = 0;

@Int64Type(encoding: LongEncoding.tagged) // variable-length encoding
int compactLong = 0;

@Uint32Type(compress: true) // variable-length unsigned
int smallUnsigned = 0;
}

Available annotations: @Int32Type, @Int64Type, @Uint8Type, @Uint16Type, @Uint32Type, @Uint64Type.

Alternatively, use the explicit wrapper types (Int32, UInt32, etc.) described in Supported Types.

Aligning Fields Across Languages

When the same model is defined in multiple languages:

  • Assign stable id values to every field that might change over time.
  • Use dynamic: true for fields that are genuinely polymorphic.
  • Keep the logical meaning of each field consistent across languages — Fory matches fields by name or ID, but cannot reconcile semantic differences.