| 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 covers basic object graph serialization and supported types in the default xlang mode for Fory Rust.
Apache Fory™ provides automatic serialization of complex object graphs, preserving the structure and relationships between objects. The #[derive(ForyStruct)] macro generates efficient serialization code at compile time, eliminating reflection overhead.
Key capabilities:
- Nested struct serialization with arbitrary depth
- Collection types (Vec, HashMap, HashSet, BTreeMap)
- Optional fields with
Option<T> - Automatic handling of primitive types and strings
- Efficient binary encoding with variable-length integers
use fory::{Fory, Error};
use fory::ForyStruct;
use std::collections::HashMap;
#[derive(ForyStruct, Debug, PartialEq)]
struct Person {
name: String,
age: i32,
address: Address,
hobbies: Vec<String>,
metadata: HashMap<String, String>,
}
#[derive(ForyStruct, Debug, PartialEq)]
struct Address {
street: String,
city: String,
country: String,
}
let mut fory = Fory::builder().xlang(true).build();
fory.register_by_name::<Address>("example.Address").unwrap();
fory.register_by_name::<Person>("example.Person").unwrap();
let person = Person {
name: "John Doe".to_string(),
age: 30,
address: Address {
street: "123 Main St".to_string(),
city: "New York".to_string(),
country: "USA".to_string(),
},
hobbies: vec!["reading".to_string(), "coding".to_string()],
metadata: HashMap::from([
("role".to_string(), "developer".to_string()),
]),
};
let bytes = fory.serialize(&person).unwrap();
let decoded: Person = fory.deserialize(&bytes)?;
assert_eq!(person, decoded);| Rust Type | Description |
|---|---|
bool |
Boolean |
i8, i16, i32, i64 |
Signed integers |
f32, f64 |
Floating point |
BFloat16 |
16-bit brain floating point |
String |
UTF-8 string |
| Rust Type | Description |
|---|---|
Vec<T> |
Dynamic array |
VecDeque<T> |
Double-ended queue |
LinkedList<T> |
Doubly-linked list |
HashMap<K, V> |
Hash map |
BTreeMap<K, V> |
Ordered map |
HashSet<T> |
Hash set |
BTreeSet<T> |
Ordered set |
BinaryHeap<T> |
Binary heap |
Option<T> |
Optional value |
Vec<BFloat16> is the dense carrier when the schema is array<bfloat16>.
| Rust Type | Description |
|---|---|
Box<T> |
Heap allocation |
Rc<T> |
Reference counting (shared refs tracked) |
Arc<T> |
Thread-safe reference counting (shared refs tracked) |
RcWeak<T> |
Weak reference to Rc<T> (breaks circular refs) |
ArcWeak<T> |
Weak reference to Arc<T> (breaks circular refs) |
RefCell<T> |
Interior mutability (runtime borrow checking) |
Mutex<T> |
Thread-safe interior mutability |
| Rust Type | Description |
|---|---|
Date |
Date without timezone, stored as epoch days |
Timestamp |
Point in time, stored as epoch seconds and nanos |
Duration |
Signed duration, stored as seconds and normalized nanos |
The built-in carriers expose dependency-free constructors, accessors, conversions, and checked arithmetic:
use fory::{Date, Duration, Timestamp};
let date = Date::from_epoch_days(19_782);
assert_eq!(date.checked_add_days(1)?.epoch_days(), 19_783);
let timestamp = Timestamp::from_epoch_millis(-1);
assert_eq!(timestamp.to_epoch_millis()?, -1);
let duration = Duration::from_parts(1, 1_500_000_000)?;
assert_eq!(duration.to_millis()?, 2_500);
let later = timestamp.checked_add_duration(duration)?;chrono::NaiveDate, chrono::NaiveDateTime, and chrono::Duration are supported when the Rust
chrono feature is enabled:
[dependencies]
fory = { version = "1.5.0", features = ["chrono"] }Use #[derive(ForyStruct)] for object graph serialization. The separate
Rust Row Format guide documents #[derive(ForyRow)] and its supported
type set.
use fory::{Fory, Reader};
let mut fory = Fory::builder().xlang(true).build();
fory.register::<MyStruct>(1)?;
let obj = MyStruct { /* ... */ };
// Basic serialize/deserialize
let bytes = fory.serialize(&obj)?;
let decoded: MyStruct = fory.deserialize(&bytes)?;
// Serialize to existing buffer
let mut buf: Vec<u8> = vec![];
fory.serialize_to(&mut buf, &obj)?;
// Deserialize from reader
let mut reader = Reader::new(&buf);
let decoded: MyStruct = fory.deserialize_from(&mut reader)?;When the Rust value type uses an external structural serializer or custom serializer, select it explicitly at the root:
let bytes = fory.serialize_with::<UserSerializer>(&user)?;
let decoded: third_party::User =
fory.deserialize_with::<UserSerializer>(&bytes)?;Carrier serializers compose the same selection for a root container:
use fory::VecSerializer;
let bytes =
fory.serialize_with::<VecSerializer<UserSerializer>>(&users)?;
let decoded: Vec<third_party::User> =
fory.deserialize_with::<VecSerializer<UserSerializer>>(&bytes)?;See External-Type Serialization for field annotations, all supported carriers, and registration.
- Buffer Pre-allocation: Minimizes memory allocations during serialization
- Compact Encoding: Variable-length encoding for space efficiency
- Little-Endian: Optimized for modern CPU architectures
- Reference Deduplication: Shared objects serialized only once
The default xlang format is shared by all supported Fory implementations. The following sections cover its cross-language type mapping, type identity, and interoperability requirements.
Apache Fory™ supports seamless data exchange across Java, Python, C++, Go, Rust, JavaScript/TypeScript, C#, Swift, Dart, Scala, and Kotlin.
Rust defaults to xlang mode with compatible schema evolution. Set the mode explicitly in xlang examples:
use fory::Fory;
// Use xlang mode
let mut fory = Fory::builder().xlang(true).build();
// Register types with consistent IDs across languages
fory.register::<MyStruct>(100)?;
// Or, on a different Fory instance, use name-based registration
// fory.register_by_name::<MyStruct>("com.example.MyStruct")?;For fast, compact serialization with consistent IDs across languages:
let mut fory = Fory::builder().xlang(true).build();
fory.register::<User>(100)?; // Same ID in Java, Python, etc.For more flexible type naming:
fory.register_by_name::<User>("com.example.User")?;use fory::Fory;
use fory::ForyStruct;
#[derive(ForyStruct)]
struct Person {
name: String,
age: i32,
}
let mut fory = Fory::builder().xlang(true).build();
fory.register::<Person>(100)?;
let person = Person {
name: "Alice".to_string(),
age: 30,
};
let bytes = fory.serialize(&person)?;
// bytes can be deserialized by Java, Python, etc.An external structural serializer gives a third-party Rust type the same xlang schema as an equivalent local derive:
#[derive(ForyStruct)]
#[fory(target = third_party::User)]
struct UserSerializer {
name: String,
age: u32,
}
let mut fory = Fory::builder().xlang(true).build();
fory.register::<UserSerializer>(100)?;
let bytes = fory.serialize_with::<UserSerializer>(&user)?;Container roots compose with carrier serializers and keep the ordinary xlang LIST, MAP, tuple, or array representation:
use fory::VecSerializer;
let bytes =
fory.serialize_with::<VecSerializer<UserSerializer>>(&users)?;Only xlang-representable schemas are accepted. A native Rust enum variant with
multiple tuple or named fields is supported with xlang(false), but its
serializer registration is rejected in xlang mode. See
External-Type Serialization.
Box<dyn Any>, Rc<dyn Any>, Arc<dyn Any + Send + Sync>, and application
dyn Trait carriers can be used in xlang mode when every selected concrete
target has an xlang-compatible structural or EXT identity. Fory writes the
concrete registered target identity; the Rust trait or erased-carrier identity
does not appear on the wire.
import org.apache.fory.*;
import org.apache.fory.config.*;
public class Person {
public String name;
public int age;
}
Fory fory = Fory.builder()
.withXlang(true)
.withRefTracking(true)
.build();
fory.register(Person.class, 100); // Same ID as Rust
Person person = (Person) fory.deserialize(bytesFromRust);import pyfory
from dataclasses import dataclass
@dataclass
class Person:
name: str
age: pyfory.Int32
fory = pyfory.Fory(xlang=True, ref=True)
fory.register_type(Person, type_id=100) # Same ID as Rust
person = fory.deserialize(bytes_from_rust)See xlang_type_mapping.md for complete type mapping across languages.
| Rust | Java | Python |
|---|---|---|
i32 |
int |
int32 |
i64 |
long |
int64 |
f32 |
float |
float32 |
f64 |
double |
float64 |
Float16 |
Float16 |
float16 |
BFloat16 |
BFloat16 |
bfloat16 |
String |
String |
str |
Vec<T> |
List<T> |
List[T] |
Vec<Float16> |
Float16List |
Float16Array |
Vec<BFloat16> |
BFloat16List |
BFloat16Array |
[Float16; N] |
Float16List |
Float16Array |
[BFloat16; N] |
BFloat16List |
BFloat16Array |
HashMap<K,V> |
Map<K,V> |
Dict[K,V] |
Option<T> |
nullable T |
Optional[T] |
Rust Vec<T> maps to Fory list<T> by default for manual structs. Use an
explicit array field attribute when the schema is dense array<T>.
| Fory schema | Rust carrier and metadata |
|---|---|
list<int32> |
Vec<i32> |
array<bool> |
#[fory(array)] Vec<bool> |
array<int8> |
#[fory(array)] Vec<i8> |
array<int16> |
#[fory(array)] Vec<i16> |
array<int32> |
#[fory(array)] Vec<i32> |
array<int64> |
#[fory(array)] Vec<i64> |
array<uint8> |
#[fory(array)] Vec<u8> |
array<uint16> |
#[fory(array)] Vec<u16> |
array<uint32> |
#[fory(array)] Vec<u32> |
array<uint64> |
#[fory(array)] Vec<u64> |
array<float16> |
#[fory(array)] Vec<Float16> |
array<bfloat16> |
#[fory(array)] Vec<BFloat16> |
array<float32> |
#[fory(array)] Vec<f32> |
array<float64> |
#[fory(array)] Vec<f64> |
- Use consistent type IDs across all languages
- Keep compatible mode for schema evolution
- Register all types before serialization
- Test cross-language compatibility during development
- Xlang Serialization Specification
- Type Mapping Reference
- Java Interoperability Guide
- Python Interoperability Guide
- Configuration - xlang mode configuration
- Schema Evolution - Compatible mode
- Type Registration - Registration methods
- External-Type Serialization - Third-party values in xlang mode
use fory::Fory;
fn run() {
let fory = Fory::builder().xlang(true).build();
let bin = fory.serialize(&"hello".to_string()).expect("serialize success");
let obj: String = fory.deserialize(&bin).expect("deserialize success");
assert_eq!("hello".to_string(), obj);
}use chrono::{NaiveDate, NaiveDateTime};
use fory::{Fory, ForyStruct};
use std::collections::HashMap;
#[test]
fn complex_struct() {
#[derive(ForyStruct, Debug, PartialEq)]
struct Animal {
category: String,
}
#[derive(ForyStruct, Debug, PartialEq)]
struct Person {
c1: Vec<u8>, // binary
c2: Vec<i16>, // primitive array
animal: Vec<Animal>,
c3: Vec<Vec<u8>>,
name: String,
c4: HashMap<String, String>,
age: u16,
op: Option<String>,
op2: Option<String>,
date: NaiveDate,
time: NaiveDateTime,
c5: f32,
c6: f64,
}
let person: Person = Person {
c1: vec![1, 2, 3],
c2: vec![5, 6, 7],
c3: vec![vec![1, 2], vec![1, 3]],
animal: vec![Animal {
category: "Dog".to_string(),
}],
c4: HashMap::from([
("hello1".to_string(), "hello2".to_string()),
("hello2".to_string(), "hello3".to_string()),
]),
age: 12,
name: "helo".to_string(),
op: Some("option".to_string()),
op2: None,
date: NaiveDate::from_ymd_opt(2025, 12, 12).unwrap(),
time: NaiveDateTime::from_timestamp_opt(1689912359, 0).unwrap(),
c5: 2.0,
c6: 4.0,
};
let mut fory = Fory::builder().xlang(true).build();
fory
.register_by_name::<Animal>("example.foo2")
.expect("register Animal");
fory
.register_by_name::<Person>("example.foo")
.expect("register Person");
let bin = fory.serialize(&person).expect("serialize success");
let obj: Person = fory.deserialize(&bin).expect("deserialize success");
assert_eq!(person, obj);
}Circular references cannot be implemented in Rust due to ownership restrictions.
- Type Registration - Registering types
- References - Shared and circular references
- Custom Serializers - Custom serialization
- External-Type Serialization - Third-party values and carrier roots
- Row Format - Standard Row Format and zero-copy borrowed views