Skip to main content
Version: 0.15

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:

  1. addressbook.fdl (explicit type IDs)
  2. 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

LanguageNested type form
JavaPerson.PhoneNumber
PythonPerson.PhoneNumber
Rustperson::PhoneNumber
C++Person::PhoneNumber
GoPerson_PhoneNumber (default)

Byte Helper Naming

LanguageHelpers
JavatoBytes / fromBytes
Pythonto_bytes / from_bytes
Rustto_bytes / from_bytes
C++to_bytes / from_bytes
GoToBytes / FromBytes