Skip to content

Latest commit

 

History

History
176 lines (139 loc) · 5.34 KB

File metadata and controls

176 lines (139 loc) · 5.34 KB
title Custom Serializers
sidebar_position 10
id 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(ForyStruct)], implement the Serializer trait manually.

When to Use Custom Serializers

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

Implementing the Serializer Trait

use fory::{Error, Fory, ForyDefault, ReadContext, Serializer, TypeResolver, WriteContext};
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) -> Result<(), Error> {
        context.writer.write_i32(self.value);
        context.writer.write_var_u32(self.name.len() as u32);
        context.writer.write_utf8_string(&self.name);
        Ok(())
    }

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

    fn fory_type_id_dyn(&self, type_resolver: &TypeResolver) -> Result<fory::TypeId, Error> {
        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(ForyStruct)], you can use #[fory(generate_default)] to automatically generate both ForyDefault and Default implementations.

Manual Serializers and Arc Any

If a manually registered serializer needs its type to round-trip behind Arc<dyn Any + Send + Sync> or preserve UnknownCase payloads, implement the send-sync Any reader and return the concrete value as a boxed Any value:

impl Serializer for CustomType {
    fn fory_read_data_as_send_sync_any(
        context: &mut ReadContext,
    ) -> Result<Box<dyn Any + Send + Sync>, Error> {
        Ok(Box::new(Self::fory_read_data(context)?))
    }

    // Implement the ordinary Serializer methods as shown above.
    // ...
}

Do not override this method for values that contain fields whose types are not Send + Sync, such as Rc<T>, RcWeak<T>, RefCell<T>, or Cell<T>.

Registering Custom Serializers

let mut fory = Fory::builder().xlang(false).build();
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_var_i32(value);
context.writer.write_var_u32(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_var_i32();
let value = context.reader.read_var_u32();

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