Cross-Language Serialization
Apache Fory™ Dart serializes to the same binary format as the Java, Go, C#, Python, Rust, and Swift Fory runtimes. You can write a message in Dart and read it in Java — or any other direction — without any conversion layer.
Setup
Create a Fory instance as normal. There is no separate "cross-language mode" to enable in Dart:
final fory = Fory(); // or Fory(compatible: true) for schema evolution
The key requirement is that both sides register the same type using the same identity.
Registration Identity
The most important rule: use the same type identity on every side. You have two options:
Numeric ID
Simpler for small, tightly-coordinated teams:
// Dart
ModelsFory.register(fory, Person, id: 100);
Namespace + Type Name
Better when multiple teams define types independently:
// Dart
ModelsFory.register(
fory,
Person,
namespace: 'example',
typeName: 'Person',
);
Do not mix the two strategies for the same type across runtimes.
Dart to Java Example
Dart
import 'package:fory/fory.dart';
part 'person.fory.dart';
@ForyStruct()
class Person {
Person();
String name = '';
@ForyField(type: Int32Type())
int age = 0;
}
final fory = Fory();
PersonFory.register(fory, Person, id: 100);
final bytes = fory.serialize(Person()
..name = 'Alice'
..age = 30);
Java
Fory fory = Fory.builder()
.withXlang(true).withCompatible(true)
.build();
fory.register(Person.class, 100);
Person value = (Person) fory.deserialize(bytesFromDart);
Dart to C# Example
Dart
final fory = Fory(compatible: true);
PersonFory.register(fory, Person, id: 100);
final bytes = fory.serialize(Person()
..name = 'Alice'
..age = 30);
CSharp
[ForyObject]
public sealed class Person
{
public string Name { get; set; } = string.Empty;
public int Age { get; set; }
}
Fory fory = Fory.Builder()
.Compatible(true)
.Build();
fory.Register<Person>(100);
Person person = fory.Deserialize<Person>(payloadFromDart);
Dart to Go Example
Dart
final fory = Fory();
PersonFory.register(fory, Person, id: 100);
final bytes = fory.serialize(Person()
..name = 'Alice'
..age = 30);
Go
type Person struct {
Name string
Age int32
}
f := fory.New(fory.WithXlang(true), fory.WithCompatible(true))
_ = f.RegisterStruct(Person{}, 100)
var person Person
_ = f.Deserialize(bytesFromDart, &person)
Field Matching Rules
Fory matches fields by name or by stable field ID. For robust cross-language interop:
- Use the same type identity on every side (same numeric ID or same
namespace + typeName). - Assign stable
@ForyField(id: ...)values to all fields before shipping the first payload. - Keep field names consistent or rely on IDs, since Dart typically uses
lowerCamelCasewhile Go usesPascalCasefor exported fields and C# often usesPascalCaseproperties. - Use explicit numeric field metadata:
@ForyField(type: Int32Type())in Dart for Javaint, Goint32, and C#int;doublein Dart for 64-bit floats;doubleplusFloat16TypeorBfloat16Typefor 16-bit floats;Float32for 32-bit;Int64/Uint64for full-range 64-bit values. - Use
Timestamp,LocalDate, andDurationfor temporal fields rather than rawDateTime. - Validate real round trips across all languages before shipping.
Type Mapping Notes for Dart
Because Dart int is not itself a promise about the exact xlang wire width, prefer explicit field metadata when exact cross-language interpretation matters:
@ForyField(type: Int32Type())for xlangint32@ForyField(type: Uint32Type())for xlanguint32@ForyField(type: Int8Type())/@ForyField(type: Int16Type())/@ForyField(type: Uint8Type())/@ForyField(type: Uint16Type())for narrower integer widthsInt64andUint64for full-range 64-bit values on webdoublefields annotated withFloat16TypeorBfloat16Typefor 16-bit floating-point scalars, andFloat32for single-precision valuesFloat16ListandBfloat16Listfor 16-bit floating-point array payloadsTimestamp,LocalDate, andDurationfor explicit temporal semantics
Lists and Dense Arrays
List<T> always represents Fory list<T> unless a field has explicit array
metadata. Use array<T> only for dense one-dimensional bool or numeric data.
| Fory schema | Dart field carrier and annotation |
|---|---|
list<bool> | List<bool> |
array<bool> | @ArrayField(element: BoolType()) BoolList |
array<int8> | @ArrayField(element: Int8Type()) Int8List |
array<int16> | @ArrayField(element: Int16Type()) Int16List |
array<int32> | @ArrayField(element: Int32Type()) Int32List |
array<int64> | @ArrayField(element: Int64Type()) Int64List |
array<uint8> | @ArrayField(element: Uint8Type()) Uint8List |
array<uint16> | @ArrayField(element: Uint16Type()) Uint16List |
array<uint32> | @ArrayField(element: Uint32Type()) Uint32List |
array<uint64> | @ArrayField(element: Uint64Type()) Uint64List |
array<float16> | @ArrayField(element: Float16Type()) Float16List |
array<bfloat16> | @ArrayField(element: Bfloat16Type()) Bfloat16List |
array<float32> | @ArrayField(element: Float32Type()) Float32List |
array<float64> | @ArrayField(element: Float64Type()) Float64List |
See Supported Types and xlang type mapping.
Validation
Before relying on a cross-language contract in production, test a payload end-to-end through every runtime you support.
Run the Dart side:
dart run build_runner build --delete-conflicting-outputs
dart analyze
dart test