Skip to main content
Version: 1.7

Basic Serialization

This page covers basic object graph serialization and supported types in the default xlang mode for Fory Rust.

Object Graph Serialization​

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);

Supported Types​

Primitive Types​

Rust TypeDescription
boolBoolean
i8, i16, i32, i64Signed integers
f32, f64Floating point
BFloat1616-bit brain floating point
StringUTF-8 string

Collections​

Rust TypeDescription
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>.

Smart Pointers​

Rust TypeDescription
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

Date and Time​

Rust TypeDescription
DateDate without timezone, stored as epoch days
TimestampPoint in time, stored as epoch seconds and nanos
DurationSigned 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.7.7", features = ["chrono"] }

Custom Types​

Use #[derive(ForyStruct)] for object graph serialization. The separate Rust Row Format guide documents #[derive(ForyRow)] and its supported type set.

Serialization APIs​

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.

Performance Tips​

  • 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

Cross-Language Interoperability​

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.

Xlang Configuration​

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")?;

Type Registration for Xlang​

Register by ID​

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.

Register by Name​

For more flexible type naming:

fory.register_by_name::<User>("com.example.User")?;

Xlang Example​

Rust (Serializer)​

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.

Third-Party Rust Types​

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.

Dynamic Rust Carriers​

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.

Java (Deserializer)​

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);

Python (Deserializer)​

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)

Type Mapping​

See xlang_type_mapping.md for complete type mapping across languages.

Common Type Mappings​

RustJavaPython
i32intint32
i64longint64
f32floatfloat32
f64doublefloat64
Float16Float16float16
BFloat16BFloat16bfloat16
StringStringstr
Vec<T>List<T>List[T]
Vec<Float16>Float16ListFloat16Array
Vec<BFloat16>BFloat16ListBFloat16Array
[Float16; N]Float16ListFloat16Array
[BFloat16; N]BFloat16ListBFloat16Array
HashMap<K,V>Map<K,V>Dict[K,V]
Option<T>nullable TOptional[T]

Lists and Dense Arrays​

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 schemaRust 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>

Interoperability Best Practices​

  1. Use consistent type IDs across all languages
  2. Keep compatible mode for schema evolution
  3. Register all types before serialization
  4. Test cross-language compatibility during development

Specifications and References​

Built-in values​

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);
}

Custom values​

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);
}

Shared and circular references​

Circular references cannot be implemented in Rust due to ownership restrictions.