Generated Code
This document explains generated code for each target language.
Fory IDL generated types are idiomatic in host languages and can be used directly as domain objects. Generated types also include to/from bytes helpers and registration helpers.
Reference Schemas
The examples below use two real schemas:
addressbook.fdl(explicit type IDs)auto_id.fdl(no explicit type IDs)
addressbook.fdl (excerpt)
package addressbook;
option go_package = "github.com/myorg/myrepo/gen/addressbook;addressbook";
message Person [id=100] {
string name = 1;
int32 id = 2;
enum PhoneType [id=101] {
PHONE_TYPE_MOBILE = 0;
PHONE_TYPE_HOME = 1;
PHONE_TYPE_WORK = 2;
}
message PhoneNumber [id=102] {
string number = 1;
PhoneType phone_type = 2;
}
list<PhoneNumber> phones = 7;
Animal pet = 8;
}
message Dog [id=104] {
string name = 1;
int32 bark_volume = 2;
}
message Cat [id=105] {
string name = 1;
int32 lives = 2;
}
union Animal [id=106] {
Dog dog = 1;
Cat cat = 2;
}
message AddressBook [id=103] {
list<Person> people = 1;
map<string, Person> people_by_name = 2;
}
auto_id.fdl (excerpt)
package auto_id;
enum Status {
UNKNOWN = 0;
OK = 1;
}
message Envelope {
string id = 1;
message Payload {
int32 value = 1;
}
union Detail {
Payload payload = 1;
string note = 2;
}
Payload payload = 2;
Detail detail = 3;
Status status = 4;
}
union Wrapper {
Envelope envelope = 1;
string raw = 2;
}
Java
Output Layout
For package addressbook, Java output is generated under:
<java_out>/addressbook/- Type files:
AddressBook.java,Person.java,Dog.java,Cat.java,Animal.java - Registration helper:
AddressbookForyRegistration.java
Type Generation
Messages generate Java classes with @ForyField, default constructors, getters/setters, and byte helpers:
public class Person {
public static enum PhoneType {
MOBILE,
HOME,
WORK;
}
public static class PhoneNumber {
@ForyField(id = 1)
private String number;
@ForyField(id = 2)
private PhoneType phoneType;
public byte[] toBytes() { ... }
public static PhoneNumber fromBytes(byte[] bytes) { ... }
}
@ForyField(id = 1)
private String name;
@ForyField(id = 8)
private Animal pet;
public byte[] toBytes() { ... }
public static Person fromBytes(byte[] bytes) { ... }
}
Unions generate classes extending org.apache.fory.type.union.Union:
public final class Animal extends Union {
public enum AnimalCase {
DOG(1),
CAT(2);
public final int id;
AnimalCase(int id) { this.id = id; }
}
public static Animal ofDog(Dog v) { ... }
public AnimalCase getAnimalCase() { ... }
public int getAnimalCaseId() { ... }
public boolean hasDog() { ... }
public Dog getDog() { ... }
public void setDog(Dog v) { ... }
}
Registration
Generated registration helper:
public static void register(Fory fory) {
org.apache.fory.resolver.TypeResolver resolver = fory.getTypeResolver();
resolver.registerUnion(Animal.class, 106L, new org.apache.fory.serializer.UnionSerializer(fory, Animal.class));
resolver.register(Person.class, 100L);
resolver.register(Person.PhoneType.class, 101L);
resolver.register(Person.PhoneNumber.class, 102L);
resolver.register(Dog.class, 104L);
resolver.register(Cat.class, 105L);
resolver.register(AddressBook.class, 103L);
}
For schemas without explicit [id=...], generated registration uses computed numeric IDs (for example from auto_id.fdl):
resolver.register(Status.class, 1124725126L);
resolver.registerUnion(Wrapper.class, 1471345060L, new org.apache.fory.serializer.UnionSerializer(fory, Wrapper.class));
resolver.register(Envelope.class, 3022445236L);
resolver.registerUnion(Envelope.Detail.class, 1609214087L, new org.apache.fory.serializer.UnionSerializer(fory, Envelope.Detail.class));
resolver.register(Envelope.Payload.class, 2862577837L);
If option enable_auto_type_id = false; is set, registration uses namespace and type name:
resolver.register(Config.class, "myapp.models", "Config");
resolver.registerUnion(
Holder.class,
"myapp.models",
"Holder",
new org.apache.fory.serializer.UnionSerializer(fory, Holder.class));
Usage
Person person = new Person();
person.setName("Alice");
person.setPet(Animal.ofDog(new Dog()));
byte[] data = person.toBytes();
Person restored = Person.fromBytes(data);
Python
Output Layout
Python output is one module per schema file, for example:
<python_out>/addressbook.py
Type Generation
Unions generate a case enum plus a Union subclass with typed helpers:
class AnimalCase(Enum):
DOG = 1
CAT = 2
class Animal(Union):
@classmethod
def dog(cls, v: Dog) -> "Animal": ...
def case(self) -> AnimalCase: ...
def case_id(self) -> int: ...
def is_dog(self) -> bool: ...
def dog_value(self) -> Dog: ...
def set_dog(self, v: Dog) -> None: ...
Messages generate @pyfory.dataclass types, and nested types stay nested:
@pyfory.dataclass
class Person:
class PhoneType(IntEnum):
MOBILE = 0
HOME = 1
WORK = 2
@pyfory.dataclass
class PhoneNumber:
number: str = pyfory.field(id=1, default="")
phone_type: Person.PhoneType = pyfory.field(id=2, default=None)
name: str = pyfory.field(id=1, default="")
phones: List[Person.PhoneNumber] = pyfory.field(id=7, default_factory=list)
pet: Animal = pyfory.field(id=8, default=None)
def to_bytes(self) -> bytes: ...
@classmethod
def from_bytes(cls, data: bytes) -> "Person": ...
Registration
Generated registration function:
def register_addressbook_types(fory: pyfory.Fory):
fory.register_union(Animal, type_id=106, serializer=AnimalSerializer(fory))
fory.register_type(Person, type_id=100)
fory.register_type(Person.PhoneType, type_id=101)
fory.register_type(Person.PhoneNumber, type_id=102)
fory.register_type(Dog, type_id=104)
fory.register_type(Cat, type_id=105)
fory.register_type(AddressBook, type_id=103)
For schemas without explicit [id=...], generated registration uses computed numeric IDs:
fory.register_type(Status, type_id=1124725126)
fory.register_union(Wrapper, type_id=1471345060, serializer=WrapperSerializer(fory))
fory.register_type(Envelope, type_id=3022445236)
fory.register_union(Envelope.Detail, type_id=1609214087, serializer=Envelope.DetailSerializer(fory))
fory.register_type(Envelope.Payload, type_id=2862577837)
If option enable_auto_type_id = false; is set:
fory.register_type(Config, namespace="myapp.models", typename="Config")
fory.register_union(
Holder,
namespace="myapp.models",
typename="Holder",
serializer=HolderSerializer(fory),
)
Usage
person = Person(name="Alice", pet=Animal.dog(Dog(name="Rex", bark_volume=10)))
data = person.to_bytes()
restored = Person.from_bytes(data)
Rust
Output Layout
Rust output is one module file per schema, for example:
<rust_out>/addressbook.rs
Type Generation
Unions map to Rust enums with #[fory(id = ...)] case attributes:
#[derive(ForyObject, Debug, Clone, PartialEq)]
pub enum Animal {
#[fory(id = 1)]
Dog(Dog),
#[fory(id = 2)]
Cat(Cat),
}
Nested types generate nested modules:
pub mod person {
#[derive(ForyObject, Debug, Clone, PartialEq, Default)]
#[repr(i32)]
pub enum PhoneType {
#[default]
Mobile = 0,
Home = 1,
Work = 2,
}
#[derive(ForyObject, Debug, Clone, PartialEq, Default)]
pub struct PhoneNumber {
#[fory(id = 1)]
pub number: String,
#[fory(id = 2)]
pub phone_type: PhoneType,
}
}
Messages derive ForyObject and include to_bytes/from_bytes helpers:
#[derive(ForyObject, Debug, Clone, PartialEq, Default)]
pub struct Person {
#[fory(id = 1)]
pub name: String,
#[fory(id = 7)]
pub phones: Vec<person::PhoneNumber>,
#[fory(id = 8, type_id = "union")]
pub pet: Animal,
}
Registration
Generated registration function:
pub fn register_types(fory: &mut Fory) -> Result<(), fory::Error> {
fory.register_union::<Animal>(106)?;
fory.register::<person::PhoneType>(101)?;
fory.register::<person::PhoneNumber>(102)?;
fory.register::<Person>(100)?;
fory.register::<Dog>(104)?;
fory.register::<Cat>(105)?;
fory.register::<AddressBook>(103)?;
Ok(())
}
For schemas without explicit [id=...], generated registration uses computed numeric IDs:
fory.register::<Status>(1124725126)?;
fory.register_union::<Wrapper>(1471345060)?;
fory.register::<Envelope>(3022445236)?;
fory.register_union::<envelope::Detail>(1609214087)?;
fory.register::<envelope::Payload>(2862577837)?;
If option enable_auto_type_id = false; is set:
fory.register_by_namespace::<Config>("myapp.models", "Config")?;
fory.register_union_by_namespace::<Holder>("myapp.models", "Holder")?;
Usage
let person = Person {
name: "Alice".into(),
pet: Animal::Dog(Dog::default()),
..Default::default()
};
let bytes = person.to_bytes()?;
let restored = Person::from_bytes(&bytes)?;
C++
Output Layout
C++ output is one header per schema file, for example:
<cpp_out>/addressbook.h
Type Generation
Messages generate final classes with typed accessors and byte helpers:
class Person final {
public:
class PhoneNumber final {
public:
const std::string& number() const;
std::string* mutable_number();
template <class Arg, class... Args>
void set_number(Arg&& arg, Args&&... args);
fory::Result<std::vector<uint8_t>, fory::Error> to_bytes() const;
static fory::Result<PhoneNumber, fory::Error> from_bytes(const std::vector<uint8_t>& data);
};
const std::string& name() const;
std::string* mutable_name();
template <class Arg, class... Args>
void set_name(Arg&& arg, Args&&... args);
const Animal& pet() const;
Animal* mutable_pet();
};
Optional message fields generate has_xxx, mutable_xxx, and clear_xxx APIs:
class Envelope final {
public:
bool has_payload() const { return payload_ != nullptr; }
const Envelope::Payload& payload() const { return *payload_; }
Envelope::Payload* mutable_payload() {
if (!payload_) {
payload_ = std::make_unique<Envelope::Payload>();
}
return payload_.get();
}
void clear_payload() { payload_.reset(); }
private:
std::unique_ptr<Envelope::Payload> payload_;
};
Unions generate std::variant wrappers:
class Animal final {
public:
enum class AnimalCase : uint32_t {
DOG = 1,
CAT = 2,
};
static Animal dog(Dog v);
static Animal cat(Cat v);
AnimalCase animal_case() const noexcept;
uint32_t animal_case_id() const noexcept;
bool is_dog() const noexcept;
const Dog* as_dog() const noexcept;
Dog* as_dog() noexcept;
const Dog& dog() const;
Dog& dog();
template <class Visitor>
decltype(auto) visit(Visitor&& vis) const;
private:
std::variant<Dog, Cat> value_;
};
Generated headers also include FORY_UNION, FORY_FIELD_CONFIG, FORY_ENUM, and FORY_STRUCT macros for serialization metadata.
Registration
Generated registration function:
inline void register_types(fory::serialization::BaseFory& fory) {
fory.register_union<Animal>(106);
fory.register_enum<Person::PhoneType>(101);
fory.register_struct<Person::PhoneNumber>(102);
fory.register_struct<Person>(100);
fory.register_struct<Dog>(104);
fory.register_struct<Cat>(105);
fory.register_struct<AddressBook>(103);
}
For schemas without explicit [id=...], generated registration uses computed numeric IDs:
fory.register_enum<Status>(1124725126);
fory.register_union<Wrapper>(1471345060);
fory.register_struct<Envelope>(3022445236);
fory.register_union<Envelope::Detail>(1609214087);
fory.register_struct<Envelope::Payload>(2862577837);
If option enable_auto_type_id = false; is set:
fory.register_struct<Config>("myapp.models", "Config");
fory.register_union<Holder>("myapp.models", "Holder");
Usage
addressbook::Person person;
person.set_name("Alice");
*person.mutable_pet() = addressbook::Animal::dog(addressbook::Dog{});
auto bytes = person.to_bytes();
auto restored = addressbook::Person::from_bytes(bytes.value());
Go
Output Layout
Go output path depends on schema options and --go_out.
For addressbook.fdl, go_package is configured and generated output follows the configured import path/package (for example under your --go_out root).
Without go_package, output uses the requested --go_out directory and package-derived file naming.
Type Generation
Nested types use underscore naming by default (Person_PhoneType, Person_PhoneNumber):
type Person_PhoneType int32
const (
Person_PhoneTypeMobile Person_PhoneType = 0
Person_PhoneTypeHome Person_PhoneType = 1
Person_PhoneTypeWork Person_PhoneType = 2
)
type Person_PhoneNumber struct {
Number string `fory:"id=1"`
PhoneType Person_PhoneType `fory:"id=2"`
}
Messages generate structs with fory tags and byte helpers:
type Person struct {
Name string `fory:"id=1"`
Id int32 `fory:"id=2,compress=true"`
Phones []Person_PhoneNumber `fory:"id=7"`
Pet Animal `fory:"id=8"`
}
func (m *Person) ToBytes() ([]byte, error) { ... }
func (m *Person) FromBytes(data []byte) error { ... }
Unions generate typed case structs with constructors/accessors/visitor APIs:
type AnimalCase uint32
type Animal struct {
case_ AnimalCase
value any
}
func DogAnimal(v *Dog) Animal { ... }
func CatAnimal(v *Cat) Animal { ... }
func (u Animal) Case() AnimalCase { ... }
func (u Animal) AsDog() (*Dog, bool) { ... }
func (u Animal) Visit(visitor AnimalVisitor) error { ... }
Registration
Generated registration function:
func RegisterTypes(f *fory.Fory) error {
if err := f.RegisterUnion(Animal{}, 106, fory.NewUnionSerializer(...)); err != nil {
return err
}
if err := f.RegisterEnum(Person_PhoneType(0), 101); err != nil {
return err
}
if err := f.RegisterStruct(Person_PhoneNumber{}, 102); err != nil {
return err
}
if err := f.RegisterStruct(Person{}, 100); err != nil {
return err
}
return nil
}
For schemas without explicit [id=...], generated registration uses computed numeric IDs:
if err := f.RegisterEnum(Status(0), 1124725126); err != nil { ... }
if err := f.RegisterUnion(Wrapper{}, 1471345060, fory.NewUnionSerializer(...)); err != nil { ... }
if err := f.RegisterStruct(Envelope{}, 3022445236); err != nil { ... }
if err := f.RegisterUnion(Envelope_Detail{}, 1609214087, fory.NewUnionSerializer(...)); err != nil { ... }
if err := f.RegisterStruct(Envelope_Payload{}, 2862577837); err != nil { ... }
If option enable_auto_type_id = false; is set:
if err := f.RegisterNamedStruct(Config{}, "myapp.models.Config"); err != nil { ... }
if err := f.RegisterNamedUnion(Holder{}, "myapp.models.Holder", fory.NewUnionSerializer(...)); err != nil { ... }
go_nested_type_style controls nested type naming:
option go_nested_type_style = "camelcase";
Usage
person := &Person{
Name: "Alice",
Pet: DogAnimal(&Dog{Name: "Rex"}),
}
data, err := person.ToBytes()
if err != nil {
panic(err)
}
var restored Person
if err := restored.FromBytes(data); err != nil {
panic(err)
}
Cross-Language Notes
Type ID Behavior
- Explicit
[id=...]values are used directly in generated registration. - When type IDs are omitted, generated code uses computed numeric IDs (see
auto_id.*outputs). - If
option enable_auto_type_id = false;is set, generated registration uses namespace/type-name APIs instead of numeric IDs.
Nested Type Shape
| Language | Nested type form |
|---|---|
| Java | Person.PhoneNumber |
| Python | Person.PhoneNumber |
| Rust | person::PhoneNumber |
| C++ | Person::PhoneNumber |
| Go | Person_PhoneNumber (default) |
Byte Helper Naming
| Language | Helpers |
|---|---|
| Java | toBytes / fromBytes |
| Python | to_bytes / from_bytes |
| Rust | to_bytes / from_bytes |
| C++ | to_bytes / from_bytes |
| Go | ToBytes / FromBytes |