title: Generated Code sidebar_position: 5 id: generated_code 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
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.
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; }
For package addressbook, Java output is generated under:
<java_out>/addressbook/AddressBook.java, Person.java, Dog.java, Cat.java, Animal.javaAddressbookForyRegistration.javaMessages 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) { ... } }
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));
Person person = new Person(); person.setName("Alice"); person.setPet(Animal.ofDog(new Dog())); byte[] data = person.toBytes(); Person restored = Person.fromBytes(data);
Python output is one module per schema file, for example:
<python_out>/addressbook.pyUnions 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": ...
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), )
person = Person(name="Alice", pet=Animal.dog(Dog(name="Rex", bark_volume=10))) data = person.to_bytes() restored = Person.from_bytes(data)
Rust output is one module file per schema, for example:
<rust_out>/addressbook.rsUnions 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, }
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")?;
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 is one header per schema file, for example:
<cpp_out>/addressbook.hMessages 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.
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");
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 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.
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 { ... }
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";
The CLI flag --go_nested_type_style overrides this schema option when both are set.
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)
}
C# output is one .cs file per schema, for example:
<csharp_out>/addressbook/addressbook.csMessages generate [ForyObject] classes with C# properties and byte helpers:
[ForyObject] public sealed partial class Person { public string Name { get; set; } = string.Empty; public int Id { get; set; } public List<Person.PhoneNumber> Phones { get; set; } = new(); public Animal Pet { get; set; } = null!; public byte[] ToBytes() { ... } public static Person FromBytes(byte[] data) { ... } }
Unions generate Union subclasses with typed case helpers:
public sealed class Animal : Union { public static Animal Dog(Dog value) { ... } public static Animal Cat(Cat value) { ... } public bool IsDog => ...; public Dog DogValue() { ... } }
Each schema generates a registration helper:
public static class AddressbookForyRegistration { public static void Register(Fory fory) { fory.Register<addressbook.Animal>((uint)106); fory.Register<addressbook.Person>((uint)100); // ... } }
When explicit type IDs are not provided, generated registration uses computed numeric IDs (same behavior as other targets).
JavaScript output is one .ts file per schema, for example:
<javascript_out>/addressbook.tsMessages generate export interface declarations with camelCase field names:
export interface Person { name: string; id: number; phones: PhoneNumber[]; pet?: Animal | null; }
Enums generate export enum declarations:
export enum PhoneType { MOBILE = 0, HOME = 1, WORK = 2, }
Unions generate a discriminated union with a case enum:
export enum AnimalCase { DOG = 1, CAT = 2, } export type Animal = | { case: AnimalCase.DOG; value: Dog } | { case: AnimalCase.CAT; value: Cat };
Swift output is one .swift file per schema, for example:
<swift_out>/addressbook/addressbook.swiftThe generator creates Swift models with @ForyObject and field IDs.
When package/namespace is non-empty, namespace shaping is controlled by swift_namespace_style:
enum (default): nested enum namespace wrappers.flatten: package-derived prefix on top-level type names (for example Demo_Foo_User).When package/namespace is empty, no enum wrapper or flatten prefix is applied.
For non-empty package with default enum style:
public enum Addressbook { @ForyObject public enum Animal: Equatable { @ForyField(id: 1) case dog(Addressbook.Dog) @ForyField(id: 2) case cat(Addressbook.Cat) } @ForyObject public struct Person: Equatable { @ForyField(id: 1) public var name: String = "" @ForyField(id: 8) public var pet: Addressbook.Animal = .foryDefault() } }
For non-empty package with flatten style:
@ForyObject public struct Addressbook_Person: Equatable { ... }
The CLI flag --swift_namespace_style overrides schema option swift_namespace_style when both are set.
Unions are generated as tagged Swift enums with associated payload values. Messages with ref/weak_ref fields are generated as final class models to preserve reference semantics.
Each schema includes a registration helper with transitive import registration:
public enum ForyRegistration { public static func register(_ fory: Fory) throws { try ComplexPb.ForyRegistration.register(fory) fory.register(Addressbook.Person.self, id: 100) fory.register(Addressbook.Animal.self, id: 106) } }
With non-empty package and flatten style, the helper is prefixed too (for example Addressbook_ForyRegistration).
For schemas without explicit [id=...], registration uses computed numeric IDs. If option enable_auto_type_id = false; is set, generated code uses name-based registration APIs.
Dart output is two files per schema: a main .dart file with annotated types, and a .fory.dart part file with generated serializers and registration helpers.
<dart_out>/package/package.dart<dart_out>/package/package.fory.dartMessages generate @ForyStruct annotated final class declarations with @ForyField on each field:
@ForyStruct() final class Person { Person(); @ForyField(id: 1) String name = ''; @ForyField(id: 2) Int32 id = Int32(0); @ForyField(id: 7) List<Person_PhoneNumber> phones = <Person_PhoneNumber>[]; @ForyField(id: 8) Animal pet = Animal._empty(); }
Enums generate Dart enum declarations with a rawValue getter and fromRawValue factory:
enum Person_PhoneType { mobile, home, work; int get rawValue => switch (this) { Person_PhoneType.mobile => 0, Person_PhoneType.home => 1, Person_PhoneType.work => 2, }; static Person_PhoneType fromRawValue(int value) => switch (value) { 0 => Person_PhoneType.mobile, 1 => Person_PhoneType.home, 2 => Person_PhoneType.work, _ => throw StateError('Unknown Person_PhoneType raw value $value.'), }; }
Unions generate @ForyUnion annotated classes with factory constructors, a case enum, and a custom serializer:
enum AnimalCase { dog, cat; int get id => switch (this) { AnimalCase.dog => 1, AnimalCase.cat => 2, }; } @ForyUnion() final class Animal { final AnimalCase _case; final Object? _value; const Animal._(this._case, this._value); factory Animal.dog(Dog value) => Animal._(AnimalCase.dog, value); factory Animal.cat(Cat value) => Animal._(AnimalCase.cat, value); bool get isDog => _case == AnimalCase.dog; Dog get dogValue => _value as Dog; // ... }
Nested types use flat underscore naming (e.g., Person_PhoneNumber, Person_PhoneType).
Non-optional, non-ref lists of primitive types use typed arrays for zero-copy performance (e.g., list<int32> → Int32List).
Reference tracking on list elements or map values uses @ListType / @MapType annotations:
@ListType(element: ValueType.ref()) @ForyField(id: 3) List<Node> children = <Node>[]; @MapType(value: ValueType.ref()) @ForyField(id: 2) Map<String, Node> byName = <String, Node>{};
Each schema includes a registration helper that handles all types in the file and transitively registers imported types:
abstract final class ForyRegistration { static void register( Fory fory, Type type, { int? id, String? namespace, String? typeName, }) { if (type == Person) { registerGeneratedStruct(fory, _personForyRegistration, id: id, namespace: namespace, typeName: typeName); return; } // ... other types } }
import 'package:fory/fory.dart'; import 'generated/addressbook/addressbook.dart'; void main() { final fory = Fory(); ForyRegistration.register(fory, Person, id: 100); ForyRegistration.register(fory, Dog, id: 104); // ... final person = Person() ..name = 'Alice' ..id = Int32(1); final bytes = fory.serialize(person); final roundTrip = fory.deserialize<Person>(bytes); }
[id=...] values are used directly in generated registration.auto_id.* outputs).option enable_auto_type_id = false; is set, generated registration uses namespace/type-name APIs instead of numeric IDs.| Language | Nested type form |
|---|---|
| Java | Person.PhoneNumber |
| Python | Person.PhoneNumber |
| Rust | person::PhoneNumber |
| C++ | Person::PhoneNumber |
| Go | Person_PhoneNumber (default) |
| C# | Person.PhoneNumber |
| JavaScript | Person.PhoneNumber |
| Swift | Person.PhoneNumber |
| Dart | Person_PhoneNumber |
| Language | Helpers |
|---|---|
| Java | toBytes / fromBytes |
| Python | to_bytes / from_bytes |
| Rust | to_bytes / from_bytes |
| C++ | to_bytes / from_bytes |
| Go | ToBytes / FromBytes |
| C# | ToBytes / FromBytes |
| JavaScript | (via fory.serialize()) |
| Swift | toBytes / fromBytes |
| Dart | (via fory.serialize()) |