| title | Basic Serialization |
|---|---|
| sidebar_position | 1 |
| id | basic-serialization |
| license | Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. |
This page shows how to serialize and deserialize values in the default xlang mode for Apache Fory™ Dart.
Create one instance and reuse it — creating a new Fory for every call wastes resources.
import 'package:fory/fory.dart';
final fory = Fory();import 'package:fory/fory.dart';
part 'person.fory.dart';
@ForyStruct()
class Person {
Person();
String name = '';
@ForyField(type: Int32Type())
int age = 0;
}
void main() {
final fory = Fory();
PersonForyModule.register(
fory,
Person,
name: 'example.Person',
);
final person = Person()
..name = 'Ada'
..age = 36;
final bytes = fory.serialize(person);
final roundTrip = fory.deserialize<Person>(bytes);
print(roundTrip.name);
}deserialize<T> returns the decoded value cast to T. If the payload describes a different type than T, it throws.
Serializing null is supported directly:
final fory = Fory();
final bytes = fory.serialize(null);
final value = fory.deserialize<Object?>(bytes);You can serialize collection values directly:
final fory = Fory();
final bytes = fory.serialize(<Object?>[
'hello',
42,
true,
]);
final value = fory.deserialize<List<Object?>>(bytes);For heterogeneous collections, deserialize to Object?, List<Object?>, or Map<Object?, Object?>.
By default, Fory does not track object identity — if the same object appears twice in a list, it is serialized twice. Enable reference tracking when your data contains shared references or circular structures.
For a top-level collection:
final fory = Fory();
final shared = String.fromCharCodes('shared'.codeUnits);
final bytes = fory.serialize(<Object?>[shared, shared], trackRef: true);
final roundTrip = fory.deserialize<List<Object?>>(bytes);
print(identical(roundTrip[0], roundTrip[1])); // trueFor fields inside a generated struct, use @ForyField(ref: true) on that field instead.
If you want to avoid allocating a new Uint8List on every call, use serializeTo and deserializeFrom with an explicit Buffer:
final fory = Fory();
final buffer = Buffer();
fory.serializeTo('Ada', buffer);
final value = fory.deserializeFrom<String>(buffer);This is an optimization. For most applications the default serialize/deserialize pair is fine.
Before you can serialize a custom class or enum, register it with Fory. The generated code makes this easy:
PersonForyModule.register(
fory,
Person,
id: 100,
);If you skip registration, deserialization fails with Type ... is not registered. See Type Registration and Code Generation.
The default xlang format is shared by all Fory implementations. The following sections cover its cross-language type mapping, type identity, and interoperability requirements.
Apache Fory™ Dart serializes to the same binary format as the Java, Go, C#, Python, Rust, and Swift Fory implementations. You can write a message in Dart and read it in Java — or any other direction — without any conversion layer.
Create a Fory instance as normal. There is no separate xlang option to enable in Dart:
final fory = Fory(); // xlang payloads with compatible schema evolutionThe key requirement is that both sides register the same type using the same identity.
The most important rule: use the same type identity on every side. You have two options:
Simpler for small, tightly-coordinated teams:
// Dart
ModelsForyModule.register(fory, Person, id: 100);Better when multiple teams define types independently:
// Dart
ModelsForyModule.register(
fory,
Person,
name: 'example.Person',
);Do not mix the two strategies for the same type across implementations.
For a struct class owned by another Dart package, define an external structural serializer and register the target with the same ID or name used by every peer:
@ForyStruct(target: third_party.User)
abstract final class UserSerializer {
@ForyField(id: 1)
late final String name;
@ForyField(id: 2, type: Int32Type())
late final int age;
}
ExternalSerializersForyModule.register(
fory,
third_party.User,
id: 100,
);The declaration's field IDs, names, nullability, and wire-width annotations define the Dart-side xlang schema. An external declaration may explicitly list an accessible inherited target property, but Fory does not automatically scan the external target hierarchy.
import 'package:fory/fory.dart';
part 'person.fory.dart';
@ForyStruct()
class Person {
Person();
String name = '';
@ForyField(type: Int32Type())
int age = 0;
}
final fory = Fory();
PersonForyModule.register(fory, Person, id: 100);
final bytes = fory.serialize(Person()
..name = 'Alice'
..age = 30);Fory fory = Fory.builder()
.withXlang(true)
.build();
fory.register(Person.class, 100);
Person value = (Person) fory.deserialize(bytesFromDart);final fory = Fory();
PersonForyModule.register(fory, Person, id: 100);
final bytes = fory.serialize(Person()
..name = 'Alice'
..age = 30);[ForyStruct]
public sealed class Person
{
public string Name { get; set; } = string.Empty;
public int Age { get; set; }
}
Fory fory = Fory.Builder()
.Build();
fory.Register<Person>(100);
Person person = fory.Deserialize<Person>(payloadFromDart);final fory = Fory();
PersonForyModule.register(fory, Person, id: 100);
final bytes = fory.serialize(Person()
..name = 'Alice'
..age = 30);type Person struct {
Name string
Age int32
}
f := fory.New(fory.WithXlang(true))
_ = f.RegisterStruct(Person{}, 100)
var person Person
_ = f.Deserialize(bytesFromDart, &person)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
name). - 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.
For an ordinary Dart class, Fory flattens concrete superclass and applied-mixin
storage into the annotated child's one struct schema. Parent and child fields
share one field-ID namespace and one canonical ordering, so the peer language
should define the equivalent included flat field set. Fields omitted by
@ForyField(ignore: true) or the concrete child's
ignoreInheritedPrivateFields option are absent from that peer schema. A
parent is not encoded as a nested object.
Included inherited @ForyField(ref: true) and nested container reference
metadata use the same reference behavior as fields declared directly on the
child. Inheritance does not change xlang reference framing or add parent-level
reference state.
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
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.
Before relying on a cross-language contract in production, test a payload end-to-end through every implementation you support.
Run the Dart side:
dart run build_runner build
dart analyze
dart test