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
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:
Option<T>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.6.1", 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.
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 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.