title: Custom Serializers sidebar_position: 4 id: rust_custom_serializers 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.

For types that don't support #[derive(ForyObject)], implement the Serializer trait manually.

When to Use Custom Serializers

  • External types from other crates
  • Types with special serialization requirements
  • Legacy data format compatibility
  • Performance-critical custom encoding

Implementing the Serializer Trait

use fory::{Fory, ReadContext, WriteContext, Serializer, ForyDefault, Error};
use std::any::Any;

#[derive(Debug, PartialEq, Default)]
struct CustomType {
    value: i32,
    name: String,
}

impl Serializer for CustomType {
    fn fory_write_data(&self, context: &mut WriteContext, is_field: bool) {
        context.writer.write_i32(self.value);
        context.writer.write_varuint32(self.name.len() as u32);
        context.writer.write_utf8_string(&self.name);
    }

    fn fory_read_data(context: &mut ReadContext, is_field: bool) -> Result<Self, Error> {
        let value = context.reader.read_i32();
        let len = context.reader.read_varuint32() as usize;
        let name = context.reader.read_utf8_string(len);
        Ok(Self { value, name })
    }

    fn fory_type_id_dyn(&self, type_resolver: &TypeResolver) -> u32 {
        Self::fory_get_type_id(type_resolver)
    }

    fn as_any(&self) -> &dyn Any {
        self
    }
}

// ForyDefault delegates to Default
impl ForyDefault for CustomType {
    fn fory_default() -> Self {
        Self::default()
    }
}

Note: When implementing ForyDefault manually, ensure your type also implements Default if you use Self::default(). Alternatively, you can construct a default instance directly in fory_default().

Tip: If your type supports #[derive(ForyObject)], you can use #[fory(generate_default)] to automatically generate both ForyDefault and Default implementations.

Registering Custom Serializers

let mut fory = Fory::default();
fory.register_serializer::<CustomType>(100);

let custom = CustomType {
    value: 42,
    name: "test".to_string(),
};
let bytes = fory.serialize(&custom);
let decoded: CustomType = fory.deserialize(&bytes)?;
assert_eq!(custom, decoded);

WriteContext and ReadContext

The WriteContext and ReadContext provide access to:

  • writer/reader: Binary buffer operations
  • type_resolver: Type registration information
  • ref_resolver: Reference tracking (for shared/circular references)

Common Writer Methods

// Primitive types
context.writer.write_i8(value);
context.writer.write_i16(value);
context.writer.write_i32(value);
context.writer.write_i64(value);
context.writer.write_f32(value);
context.writer.write_f64(value);
context.writer.write_bool(value);

// Variable-length integers
context.writer.write_varint32(value);
context.writer.write_varuint32(value);

// Strings
context.writer.write_utf8_string(&string);

Common Reader Methods

// Primitive types
let value = context.reader.read_i8();
let value = context.reader.read_i16();
let value = context.reader.read_i32();
let value = context.reader.read_i64();
let value = context.reader.read_f32();
let value = context.reader.read_f64();
let value = context.reader.read_bool();

// Variable-length integers
let value = context.reader.read_varint32();
let value = context.reader.read_varuint32();

// Strings
let string = context.reader.read_utf8_string(len);

Best Practices

  1. Use variable-length encoding for integers that may be small
  2. Write length first for variable-length data
  3. Handle errors properly in read methods
  4. Implement ForyDefault for schema evolution support

Related Topics