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 asObject?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
idvalues to every field that might change over time. - Use
dynamic: truefor 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.