blob: 0b28adb02b3cda7fb32584411b80af102f3c37c7 [file] [view]
---
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 a `Fory` Instance
Create one instance and reuse it — creating a new `Fory` for every call wastes resources.
```dart
import 'package:fory/fory.dart';
final fory = Fory();
```
## Serialize and Deserialize Annotated Types
```dart
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.
## Null Values
Serializing `null` is supported directly:
```dart
final fory = Fory();
final bytes = fory.serialize(null);
final value = fory.deserialize<Object?>(bytes);
```
## Serialize Collections and Dynamic Payloads
You can serialize collection values directly:
```dart
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?>`.
## Reference Tracking
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:
```dart
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])); // true
```
For fields inside a generated struct, use `@ForyField(ref: true)` on that field instead.
## Reusing a Buffer
If you want to avoid allocating a new `Uint8List` on every call, use `serializeTo` and `deserializeFrom` with an explicit `Buffer`:
```dart
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.
## Register Your Types Before Serializing
Before you can serialize a custom class or enum, register it with `Fory`. The generated code makes this easy:
```dart
PersonForyModule.register(
fory,
Person,
id: 100,
);
```
If you skip registration, deserialization fails with `Type ... is not registered`. See [Type Registration](type-registration.md) and [Code Generation](code-generation.md).
## Cross-Language Interoperability
The default xlang format is shared by all Fory runtimes. 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.
### Xlang Configuration
Create a `Fory` instance as normal. There is no separate xlang option to enable in Dart:
```dart
final fory = Fory(); // xlang payloads with compatible 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
// Dart
ModelsForyModule.register(fory, Person, id: 100);
```
#### Namespace + Type Name
Better when multiple teams define types independently:
```dart
// Dart
ModelsForyModule.register(
fory,
Person,
name: 'example.Person',
);
```
Do not mix the two strategies for the same type across implementations.
### External Types
For a struct class owned by another Dart package, define an
[external structural serializer](external-types.md) and register the target
with the same ID or name used by every peer:
```dart
@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.
### Dart to Java Example
#### Dart
```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();
PersonForyModule.register(fory, Person, id: 100);
final bytes = fory.serialize(Person()
..name = 'Alice'
..age = 30);
```
#### Java
```java
Fory fory = Fory.builder()
.withXlang(true)
.build();
fory.register(Person.class, 100);
Person value = (Person) fory.deserialize(bytesFromDart);
```
### Dart to C# Example
#### Dart
```dart
final fory = Fory();
PersonForyModule.register(fory, Person, id: 100);
final bytes = fory.serialize(Person()
..name = 'Alice'
..age = 30);
```
#### CSharp
```csharp
[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);
```
### Dart to Go Example
#### Dart
```dart
final fory = Fory();
PersonForyModule.register(fory, Person, id: 100);
final bytes = fory.serialize(Person()
..name = 'Alice'
..age = 30);
```
#### Go
```go
type Person struct {
Name string
Age int32
}
f := fory.New(fory.WithXlang(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:
1. Use the same type identity on every side (same numeric ID or same `name`).
2. Assign stable `@ForyField(id: ...)` values to all fields before shipping the first payload.
3. Keep field names consistent or rely on IDs, since Dart typically uses `lowerCamelCase` while Go uses `PascalCase` for exported fields and C# often uses `PascalCase` properties.
4. Use explicit numeric field metadata: `@ForyField(type: Int32Type())` in Dart for Java `int`, Go `int32`, and C# `int`; `double` in Dart for 64-bit floats; `double` plus `Float16Type` or `Bfloat16Type` for 16-bit floats; `Float32` for 32-bit; `Int64` / `Uint64` for full-range 64-bit values.
5. Use `Timestamp`, `LocalDate`, and `Duration` for temporal fields rather than raw `DateTime`.
6. 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.
### 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 xlang `int32`
- `@ForyField(type: Uint32Type())` for xlang `uint32`
- `@ForyField(type: Int8Type())` / `@ForyField(type: Int16Type())` / `@ForyField(type: Uint8Type())` / `@ForyField(type: Uint16Type())` for narrower integer widths
- `Int64` and `Uint64` for full-range 64-bit values on web
- `double` fields annotated with `Float16Type` or `Bfloat16Type` for 16-bit
floating-point scalars, and `Float32` for single-precision values
- `Float16List` and `Bfloat16List` for 16-bit floating-point array payloads
- `Timestamp`, `LocalDate`, and `Duration` for 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](supported-types.md) and [xlang type mapping](../../specification/xlang_type_mapping.md).
### Validation
Before relying on a cross-language contract in production, test a payload end-to-end through every implementation you support.
Run the Dart side:
```bash
dart run build_runner build
dart analyze
dart test
```
### Related Guides
- [Struct Inheritance](inheritance.md)
- [Type Registration](type-registration.md)
- [External-Type Serialization](external-types.md)
- [Schema Evolution](schema-evolution.md)
- [Cross-language guide](../xlang.md)
## Related Topics
- [Configuration](configuration.md)
- [Type Registration](type-registration.md)
- [Schema Metadata](schema-metadata.md)