Field Type Meta
Field type meta configuration controls whether type information is written during serialization for struct fields. This is essential for supporting polymorphism where the actual runtime type may differ from the declared field type.
Overview
When serializing a struct field, Fory needs to determine whether to write type metadata:
- Static typing: Use the declared field type's serializer directly (no type info written)
- Dynamic typing: Write type information to support runtime subtypes
When Type Meta Is Needed
Type metadata is required when:
- Interface/abstract fields: The declared type is abstract, so concrete type must be recorded
- Polymorphic fields: The runtime type may be a subclass of the declared type
- Cross-language compatibility: When the receiver needs type information to deserialize correctly
Type metadata is NOT needed when:
- Final/concrete types: The declared type is final/sealed and cannot be subclassed
- Primitive types: Type is known at compile time
- Performance optimization: When you know the runtime type always matches the declared type
Language-Specific Configuration
Java
Java requires explicit configuration because concrete classes can be subclassed unless marked final.
Use the @ForyField annotation with the dynamic parameter:
import org.apache.fory.annotation.ForyField;
import org.apache.fory.annotation.ForyField.Dynamic;
public class Container {
// AUTO (default): Interface types write type info, concrete types don't
@ForyField(id = 0)
private Shape shape; // Interface - type info written
// FALSE: Never write type info (use declared type's serializer)
@ForyField(id = 1, dynamic = Dynamic.FALSE)
private Circle circle; // Always treated as Circle
// TRUE: Always write type info (support runtime subtypes)
@ForyField(id = 2, dynamic = Dynamic.TRUE)
private Shape concreteShape; // Type info written even if concrete
}
Dynamic Options:
| Value | Behavior |
|---|---|
AUTO | Interface/abstract types are dynamic, concrete are not |
FALSE | Never write type info, use declared type's serializer |
TRUE | Always write type info to support runtime subtypes |
Use Cases:
AUTO: Default behavior, suitable for most casesFALSE: Performance optimization when you know the exact typeTRUE: When a concrete field may hold subclass instances
C++
C++ uses the fory::dynamic<V> template tag or .dynamic(bool) builder method:
Using fory::field<> template:
#include "fory/serialization/fory.h"
// Abstract base class with pure virtual methods
struct Animal {
virtual ~Animal() = default;
virtual std::string speak() const = 0;
};
struct Zoo {
// Auto: type info written because Animal is polymorphic (std::is_polymorphic)
fory::field<std::shared_ptr<Animal>, 0, fory::nullable> animal;
// Force non-dynamic: skip type info even though Animal is polymorphic
fory::field<std::shared_ptr<Animal>, 1, fory::nullable, fory::dynamic<false>> fixed_animal;
// Force dynamic: write type info even for non-polymorphic types
fory::field<std::shared_ptr<Data>, 2, fory::dynamic<true>> polymorphic_data;
};
FORY_STRUCT(Zoo, animal, fixed_animal, polymorphic_data);
Using FORY_FIELD_CONFIG macro:
struct Zoo {
std::shared_ptr<Animal> animal;
std::shared_ptr<Animal> fixed_animal;
std::shared_ptr<Data> polymorphic_data;
};
FORY_STRUCT(Zoo, animal, fixed_animal, polymorphic_data);
FORY_FIELD_CONFIG(Zoo,
(animal, fory::F(0).nullable()), // Auto-detect polymorphism
(fixed_animal, fory::F(1).nullable().dynamic(false)), // Skip type info
(polymorphic_data, fory::F(2).dynamic(true)) // Force type info
);
Default Behavior: Fory auto-detects polymorphism via std::is_polymorphic<T>. Types with pure virtual methods are treated as dynamic by default.
Go and Rust
Go and Rust do not require explicit dynamic configuration because:
- Go: Interface types are inherently dynamic - Fory can determine from the type whether it's an interface
- Rust: Trait objects (
dyn Trait) are explicitly marked in the type system
The type system in these languages already indicates whether a field is polymorphic:
// Go: interface types are automatically dynamic
type Container struct {
Shape Shape // Interface - type info written automatically
Circle Circle // Concrete struct - no type info needed
}