| --- |
| 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 |
| |
| 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. |
| --- |
| |
| This document explains the code generated by the FDL compiler for each target language. |
| |
| ## Example Schema |
| |
| The examples in this document use this FDL schema: |
| |
| ```protobuf |
| package demo; |
| |
| enum Status [id=100] { |
| PENDING = 0; |
| ACTIVE = 1; |
| COMPLETED = 2; |
| } |
| |
| message User [id=101] { |
| string id = 1; |
| string name = 2; |
| optional string email = 3; |
| int32 age = 4; |
| } |
| |
| message Order [id=102] { |
| string id = 1; |
| ref User customer = 2; |
| repeated string items = 3; |
| map<string, int32> quantities = 4; |
| Status status = 5; |
| } |
| ``` |
| |
| ## Enum Prefix Stripping |
| |
| When enum values use a protobuf-style prefix (enum name in UPPER_SNAKE_CASE), the compiler automatically strips the prefix for languages with scoped enums. This produces cleaner, more idiomatic code. |
| |
| **Input FDL:** |
| |
| ```protobuf |
| enum DeviceTier { |
| DEVICE_TIER_UNKNOWN = 0; |
| DEVICE_TIER_TIER1 = 1; |
| DEVICE_TIER_TIER2 = 2; |
| } |
| ``` |
| |
| **Generated output by language:** |
| |
| | Language | Generated Values | Notes | |
| | -------- | ----------------------------------------- | ------------------------- | |
| | Java | `UNKNOWN, TIER1, TIER2` | Scoped enum | |
| | Rust | `Unknown, Tier1, Tier2` | PascalCase variants | |
| | C++ | `UNKNOWN, TIER1, TIER2` | Scoped `enum class` | |
| | Python | `UNKNOWN, TIER1, TIER2` | Scoped `IntEnum` | |
| | Go | `DeviceTierUnknown, DeviceTierTier1, ...` | Unscoped, prefix re-added | |
| |
| **Note:** Go uses unscoped constants, so the enum name prefix is added back to avoid naming collisions. |
| |
| ## Nested Types |
| |
| When using nested message and enum definitions, the generated code varies by language. |
| |
| **Input FDL:** |
| |
| ```protobuf |
| message SearchResponse { |
| message Result { |
| string url = 1; |
| string title = 2; |
| } |
| repeated Result results = 1; |
| } |
| ``` |
| |
| ### Java - Inner Classes |
| |
| ```java |
| public class SearchResponse { |
| public static class Result { |
| private String url; |
| private String title; |
| // getters, setters... |
| } |
| |
| private List<Result> results; |
| // getters, setters... |
| } |
| ``` |
| |
| ### Python - Nested Classes |
| |
| ```python |
| @dataclass |
| class SearchResponse: |
| @dataclass |
| class Result: |
| url: str = "" |
| title: str = "" |
| |
| results: List[Result] = field(default_factory=list) |
| ``` |
| |
| ### Go - Underscore |
| |
| ```go |
| type SearchResponse_Result struct { |
| Url string |
| Title string |
| } |
| |
| type SearchResponse struct { |
| Results []SearchResponse_Result |
| } |
| ``` |
| |
| **Note:** Set `option (fory).go_nested_type_style = "camelcase";` to generate `SearchResponseResult` instead. |
| |
| ### Rust - Nested Module |
| |
| ```rust |
| pub mod search_response { |
| use super::*; |
| |
| #[derive(ForyObject)] |
| pub struct Result { |
| pub url: String, |
| pub title: String, |
| } |
| } |
| |
| #[derive(ForyObject)] |
| pub struct SearchResponse { |
| pub results: Vec<search_response::Result>, |
| } |
| ``` |
| |
| ### C++ - Nested Classes |
| |
| ```cpp |
| class SearchResponse final { |
| public: |
| class Result final { |
| public: |
| std::string url; |
| std::string title; |
| }; |
| |
| std::vector<Result> results; |
| }; |
| |
| FORY_STRUCT(SearchResponse::Result, url, title); |
| FORY_STRUCT(SearchResponse, results); |
| ``` |
| |
| **Summary:** |
| |
| | Language | Approach | Syntax Example | |
| | -------- | ------------------------- | ------------------------- | |
| | Java | Static inner classes | `SearchResponse.Result` | |
| | Python | Nested dataclasses | `SearchResponse.Result` | |
| | Go | Underscore (configurable) | `SearchResponse_Result` | |
| | Rust | Nested module | `search_response::Result` | |
| | C++ | Nested classes | `SearchResponse::Result` | |
| |
| ## Union Generation |
| |
| FDL unions generate type-safe APIs with an explicit active case. This example is |
| based on `integration_tests/idl_tests/idl/addressbook.fdl`: |
| |
| ```protobuf |
| package addressbook; |
| |
| 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 Person [id=100] { |
| Animal pet = 8; |
| } |
| ``` |
| |
| ### Java |
| |
| ```java |
| Animal pet = Animal.ofDog(new Dog()); |
| if (pet.hasDog()) { |
| Dog dog = pet.getDog(); |
| } |
| Animal.AnimalCase caseId = pet.getAnimalCase(); |
| ``` |
| |
| ### Python |
| |
| ```python |
| pet = Animal.dog(Dog(name="Rex", bark_volume=5)) |
| if pet.is_dog(): |
| dog = pet.dog_value() |
| case_id = pet.case_id() |
| ``` |
| |
| ### Go |
| |
| ```go |
| pet := DogAnimal(&Dog{Name: "Rex", BarkVolume: 5}) |
| if dog, ok := pet.AsDog(); ok { |
| _ = dog |
| } |
| _ = pet.Visit(AnimalVisitor{ |
| Dog: func(d *Dog) error { return nil }, |
| }) |
| ``` |
| |
| ### Rust |
| |
| ```rust |
| let pet = Animal::Dog(Dog { |
| name: "Rex".into(), |
| bark_volume: 5, |
| }); |
| ``` |
| |
| ### C++ |
| |
| ```cpp |
| addressbook::Animal pet = addressbook::Animal::dog( |
| addressbook::Dog{"Rex", 5}); |
| if (pet.is_dog()) { |
| const addressbook::Dog& dog = pet.dog(); |
| } |
| ``` |
| |
| Generated registration helpers also register union types, for example: |
| |
| - Java: `fory.registerUnion(Animal.class, 106, new UnionSerializer(...))` |
| - Python: `fory.register_union(Animal, type_id=106, serializer=AnimalSerializer(fory))` |
| - Go: `f.RegisterUnion(...)` |
| - Rust: `fory.register_union::<Animal>(106)?` |
| - C++: `FORY_UNION(addressbook::Animal, ...)` |
| |
| ## Java |
| |
| ### Enum Generation |
| |
| ```java |
| package demo; |
| |
| public enum Status { |
| PENDING, |
| ACTIVE, |
| COMPLETED; |
| } |
| ``` |
| |
| ### Message Generation |
| |
| ```java |
| package demo; |
| |
| import java.util.List; |
| import java.util.Map; |
| import org.apache.fory.annotation.ForyField; |
| |
| public class User { |
| private String id; |
| private String name; |
| |
| @ForyField(nullable = true) |
| private String email; |
| |
| private int age; |
| |
| public User() { |
| } |
| |
| public String getId() { |
| return id; |
| } |
| |
| public void setId(String id) { |
| this.id = id; |
| } |
| |
| public String getName() { |
| return name; |
| } |
| |
| public void setName(String name) { |
| this.name = name; |
| } |
| |
| public String getEmail() { |
| return email; |
| } |
| |
| public void setEmail(String email) { |
| this.email = email; |
| } |
| |
| public int getAge() { |
| return age; |
| } |
| |
| public void setAge(int age) { |
| this.age = age; |
| } |
| } |
| ``` |
| |
| ```java |
| package demo; |
| |
| import java.util.List; |
| import java.util.Map; |
| import org.apache.fory.annotation.ForyField; |
| |
| public class Order { |
| private String id; |
| |
| @ForyField(ref = true) |
| private User customer; |
| |
| private List<String> items; |
| private Map<String, Integer> quantities; |
| private Status status; |
| |
| public Order() { |
| } |
| |
| // Getters and setters... |
| } |
| ``` |
| |
| ### Registration Helper |
| |
| ```java |
| package demo; |
| |
| import org.apache.fory.Fory; |
| import org.apache.fory.ThreadSafeFory; |
| import org.apache.fory.pool.SimpleForyPool; |
| |
| public class DemoForyRegistration { |
| |
| private static ThreadSafeFory createFory() { |
| ThreadSafeFory fory = new SimpleForyPool(c -> Fory.builder() |
| .withXlang(true) |
| .withRefTracking(true) |
| .build()); |
| fory.registerCallback(f -> registerAllTypes(f)); |
| return fory; |
| } |
| |
| private static void registerAllTypes(Fory fory) { |
| register(fory); |
| } |
| |
| public static void register(Fory fory) { |
| fory.register(Status.class, 100); |
| fory.register(User.class, 101); |
| fory.register(Order.class, 102); |
| } |
| } |
| ``` |
| |
| `register` only contains types defined in the current file. The generated |
| `registerAllTypes` registers imported types first and then calls `register`. |
| |
| ### Usage |
| |
| ```java |
| import demo.*; |
| |
| public class Example { |
| public static void main(String[] args) { |
| User user = new User(); |
| user.setId("u123"); |
| user.setName("Alice"); |
| user.setAge(30); |
| |
| Order order = new Order(); |
| order.setId("o456"); |
| order.setCustomer(user); |
| order.setStatus(Status.ACTIVE); |
| |
| byte[] bytes = order.toBytes(); |
| Order restored = Order.fromBytes(bytes); |
| } |
| } |
| ``` |
| |
| ## Python |
| |
| ### Module Generation |
| |
| ```python |
| # Licensed to the Apache Software Foundation (ASF)... |
| |
| from dataclasses import dataclass |
| from enum import IntEnum |
| from typing import Dict, List, Optional |
| import pyfory |
| |
| |
| class Status(IntEnum): |
| PENDING = 0 |
| ACTIVE = 1 |
| COMPLETED = 2 |
| |
| |
| @dataclass |
| class User: |
| id: str = "" |
| name: str = "" |
| email: Optional[str] = None |
| age: pyfory.int32 = 0 |
| |
| |
| @dataclass |
| class Order: |
| id: str = "" |
| customer: Optional[User] = None |
| items: List[str] = None |
| quantities: Dict[str, pyfory.int32] = None |
| status: Status = None |
| |
| |
| def register_demo_types(fory: pyfory.Fory): |
| fory.register_type(Status, type_id=100) |
| fory.register_type(User, type_id=101) |
| fory.register_type(Order, type_id=102) |
| |
| |
| def _register_all_types(fory: pyfory.Fory): |
| register_demo_types(fory) |
| ``` |
| |
| `register_demo_types` only contains types defined in the current file. The |
| generated `_register_all_types` registers imported types first and then calls |
| `register_demo_types`. |
| |
| ### Usage |
| |
| ```python |
| from demo import User, Order, Status |
| |
| user = User(id="u123", name="Alice", age=30) |
| order = Order( |
| id="o456", |
| customer=user, |
| items=["item1", "item2"], |
| quantities={"item1": 2, "item2": 1}, |
| status=Status.ACTIVE |
| ) |
| |
| data = order.to_bytes() |
| restored = Order.from_bytes(data) |
| ``` |
| |
| ## Go |
| |
| ### File Generation |
| |
| ```go |
| // Licensed to the Apache Software Foundation (ASF)... |
| |
| package demo |
| |
| import ( |
| fory "github.com/apache/fory/go/fory" |
| ) |
| |
| type Status int32 |
| |
| const ( |
| StatusPending Status = 0 |
| StatusActive Status = 1 |
| StatusCompleted Status = 2 |
| ) |
| |
| type User struct { |
| Id string |
| Name string |
| Email *string `fory:"nullable"` |
| Age int32 |
| } |
| |
| type Order struct { |
| Id string |
| Customer *User `fory:"ref"` |
| Items []string |
| Quantities map[string]int32 |
| Status Status |
| } |
| |
| func RegisterTypes(f *fory.Fory) error { |
| if err := f.RegisterEnum(Status(0), 100); err != nil { |
| return err |
| } |
| if err := f.Register(User{}, 101); err != nil { |
| return err |
| } |
| if err := f.Register(Order{}, 102); err != nil { |
| return err |
| } |
| return nil |
| } |
| |
| func registerAllTypes(f *fory.Fory) error { |
| if err := RegisterTypes(f); err != nil { |
| return err |
| } |
| return nil |
| } |
| ``` |
| |
| `RegisterTypes` only contains types defined in the current file. The generated |
| `registerAllTypes` registers imported types first and then calls `RegisterTypes`. |
| |
| ### Usage |
| |
| ```go |
| package main |
| |
| import ( |
| "demo" |
| ) |
| |
| func main() { |
| email := "alice@example.com" |
| user := &demo.User{ |
| Id: "u123", |
| Name: "Alice", |
| Email: &email, |
| Age: 30, |
| } |
| |
| order := &demo.Order{ |
| Id: "o456", |
| Customer: user, |
| Items: []string{"item1", "item2"}, |
| Quantities: map[string]int32{ |
| "item1": 2, |
| "item2": 1, |
| }, |
| Status: demo.StatusActive, |
| } |
| bytes, err := order.ToBytes() |
| if err != nil { |
| panic(err) |
| } |
| var restored demo.Order |
| if err := restored.FromBytes(bytes); err != nil { |
| panic(err) |
| } |
| } |
| ``` |
| |
| ## Rust |
| |
| ### Module Generation |
| |
| ```rust |
| // Licensed to the Apache Software Foundation (ASF)... |
| |
| use fory::{Fory, ForyObject}; |
| use std::collections::HashMap; |
| use std::sync::Arc; |
| |
| #[derive(ForyObject, Debug, Clone, PartialEq, Default)] |
| #[repr(i32)] |
| pub enum Status { |
| #[default] |
| Pending = 0, |
| Active = 1, |
| Completed = 2, |
| } |
| |
| #[derive(ForyObject, Debug, Clone, PartialEq, Default)] |
| pub struct User { |
| pub id: String, |
| pub name: String, |
| #[fory(nullable = true)] |
| pub email: Option<String>, |
| pub age: i32, |
| } |
| |
| #[derive(ForyObject, Debug, Clone, PartialEq, Default)] |
| pub struct Order { |
| pub id: String, |
| pub customer: Arc<User>, |
| pub items: Vec<String>, |
| pub quantities: HashMap<String, i32>, |
| pub status: Status, |
| } |
| |
| pub fn register_types(fory: &mut Fory) -> Result<(), fory::Error> { |
| fory.register::<Status>(100)?; |
| fory.register::<User>(101)?; |
| fory.register::<Order>(102)?; |
| Ok(()) |
| } |
| |
| fn register_all_types(fory: &mut Fory) -> Result<(), fory::Error> { |
| register_types(fory)?; |
| Ok(()) |
| } |
| ``` |
| |
| `register_types` only contains types defined in the current file. The generated |
| `register_all_types` registers imported types first and then calls |
| `register_types`. |
| |
| **Note:** Rust uses `Arc` by default for `ref` fields. In FDL, use |
| `ref(thread_safe = false)` to generate `Rc`, and `ref(weak = true)` to generate |
| `ArcWeak`/`RcWeak`. For protobuf/IDL extensions, use |
| `[(fory).thread_safe_pointer = false]` and `[(fory).weak_ref = true]`. |
| |
| ### Usage |
| |
| ```rust |
| use demo::{User, Order, Status}; |
| use std::sync::Arc; |
| use std::collections::HashMap; |
| |
| fn main() -> Result<(), fory::Error> { |
| let user = Arc::new(User { |
| id: "u123".to_string(), |
| name: "Alice".to_string(), |
| email: Some("alice@example.com".to_string()), |
| age: 30, |
| }); |
| |
| let mut quantities = HashMap::new(); |
| quantities.insert("item1".to_string(), 2); |
| quantities.insert("item2".to_string(), 1); |
| |
| let order = Order { |
| id: "o456".to_string(), |
| customer: user, |
| items: vec!["item1".to_string(), "item2".to_string()], |
| quantities, |
| status: Status::Active, |
| }; |
| |
| let bytes = order.to_bytes()?; |
| let restored = Order::from_bytes(&bytes)?; |
| |
| Ok(()) |
| } |
| ``` |
| |
| ## C++ |
| |
| ### Header Generation |
| |
| ```cpp |
| /* |
| * Licensed to the Apache Software Foundation (ASF)... |
| */ |
| |
| #ifndef DEMO_H_ |
| #define DEMO_H_ |
| |
| #include <cstdint> |
| #include <map> |
| #include <memory> |
| #include <optional> |
| #include <string> |
| #include <vector> |
| #include "fory/serialization/fory.h" |
| |
| namespace demo { |
| |
| struct User; |
| struct Order; |
| |
| enum class Status : int32_t { |
| PENDING = 0, |
| ACTIVE = 1, |
| COMPLETED = 2, |
| }; |
| FORY_ENUM(Status, PENDING, ACTIVE, COMPLETED); |
| |
| struct User { |
| std::string id; |
| std::string name; |
| std::optional<std::string> email; |
| int32_t age; |
| |
| bool operator==(const User& other) const { |
| return id == other.id && name == other.name && |
| email == other.email && age == other.age; |
| } |
| }; |
| FORY_STRUCT(User, id, name, email, age); |
| |
| struct Order { |
| std::string id; |
| std::shared_ptr<User> customer; |
| std::vector<std::string> items; |
| std::map<std::string, int32_t> quantities; |
| Status status; |
| |
| bool operator==(const Order& other) const { |
| return id == other.id && customer == other.customer && |
| items == other.items && quantities == other.quantities && |
| status == other.status; |
| } |
| }; |
| FORY_STRUCT(Order, id, customer, items, quantities, status); |
| |
| inline void register_types(fory::serialization::BaseFory& fory) { |
| fory.register_enum<Status>(100); |
| fory.register_struct<User>(101); |
| fory.register_struct<Order>(102); |
| } |
| |
| inline void register_all_types(fory::serialization::BaseFory& fory) { |
| register_types(fory); |
| } |
| |
| } // namespace demo |
| |
| #endif // DEMO_H_ |
| ``` |
| |
| `register_types` only contains types defined in the current file. The generated |
| `register_all_types` registers imported types first and then calls |
| `register_types`. |
| |
| ### Usage |
| |
| ```cpp |
| #include "demo.h" |
| #include <iostream> |
| |
| int main() { |
| auto user = std::make_shared<demo::User>(); |
| user->id = "u123"; |
| user->name = "Alice"; |
| user->email = "alice@example.com"; |
| user->age = 30; |
| |
| demo::Order order; |
| order.id = "o456"; |
| order.customer = user; |
| order.items = {"item1", "item2"}; |
| order.quantities = {{"item1", 2}, {"item2", 1}}; |
| order.status = demo::Status::ACTIVE; |
| |
| auto bytes = order.to_bytes(); |
| auto restored = demo::Order::from_bytes(bytes.value()); |
| |
| return 0; |
| } |
| ``` |
| |
| **Note:** C++ uses `std::shared_ptr<T>` for `ref` fields. Set |
| `ref(weak = true)` in FDL (or `[(fory).weak_ref = true]` in protobuf) to generate |
| `fory::serialization::SharedWeak<T>` for weak references. |
| |
| ## Generated Annotations Summary |
| |
| ### Java Annotations |
| |
| | Annotation | Purpose | |
| | ----------------------------- | -------------------------- | |
| | `@ForyField(nullable = true)` | Marks field as nullable | |
| | `@ForyField(ref = true)` | Enables reference tracking | |
| |
| ### Python Type Hints |
| |
| | Hint | Purpose | |
| | -------------- | ------------------- | |
| | `Optional[T]` | Nullable field | |
| | `List[T]` | Repeated field | |
| | `Dict[K, V]` | Map field | |
| | `pyfory.int32` | Fixed-width integer | |
| |
| ### Go Struct Tags |
| |
| | Tag | Purpose | |
| | ----------------- | -------------------------- | |
| | `fory:"nullable"` | Marks field as nullable | |
| | `fory:"ref"` | Enables reference tracking | |
| |
| ### Rust Attributes |
| |
| | Attribute | Purpose | |
| | -------------------------- | -------------------------- | |
| | `#[derive(ForyObject)]` | Enables Fory serialization | |
| | `#[fory(nullable = true)]` | Marks field as nullable | |
| | `#[repr(i32)]` | Enum representation | |
| |
| ### C++ Macros |
| |
| | Macro | Purpose | |
| | ---------------------------- | ----------------------- | |
| | `FORY_STRUCT(T[, fields..])` | Registers struct fields | |
| | `FORY_ENUM(T, values..)` | Registers enum values | |
| |
| ## Name-Based Registration |
| |
| When types don't have explicit type IDs, they use namespace-based registration: |
| |
| ### FDL |
| |
| ```protobuf |
| package myapp.models; |
| |
| message Config { // No @id |
| string key = 1; |
| string value = 2; |
| } |
| ``` |
| |
| ### Generated Registration |
| |
| **Java:** |
| |
| ```java |
| fory.register(Config.class, "myapp.models", "Config"); |
| ``` |
| |
| **Python:** |
| |
| ```python |
| fory.register_type(Config, namespace="myapp.models", typename="Config") |
| ``` |
| |
| **Go:** |
| |
| ```go |
| f.RegisterTagType("myapp.models.Config", Config{}) |
| ``` |
| |
| **Rust:** |
| |
| ```rust |
| fory.register_by_namespace::<Config>("myapp.models", "Config")?; |
| ``` |
| |
| **C++:** |
| |
| ```cpp |
| fory.register_struct<Config>("myapp.models", "Config"); |
| ``` |
| |
| ## Customization |
| |
| ### Extending Generated Code |
| |
| Generated code can be extended through language-specific mechanisms: |
| |
| **Java:** Use inheritance or composition: |
| |
| ```java |
| public class ExtendedUser extends User { |
| public String getDisplayName() { |
| return getName() + " <" + getEmail() + ">"; |
| } |
| } |
| ``` |
| |
| **Python:** Add methods after import: |
| |
| ```python |
| from demo import User |
| |
| def get_display_name(self): |
| return f"{self.name} <{self.email}>" |
| |
| User.get_display_name = get_display_name |
| ``` |
| |
| **Go:** Use separate file in same package: |
| |
| ```go |
| package demo |
| |
| func (u *User) DisplayName() string { |
| return u.Name + " <" + *u.Email + ">" |
| } |
| ``` |
| |
| **Rust:** Use trait extensions: |
| |
| ```rust |
| trait UserExt { |
| fn display_name(&self) -> String; |
| } |
| |
| impl UserExt for User { |
| fn display_name(&self) -> String { |
| format!("{} <{}>", self.name, self.email.as_deref().unwrap_or("")) |
| } |
| } |
| ``` |
| |
| **C++:** Use inheritance or free functions: |
| |
| ```cpp |
| std::string display_name(const demo::User& user) { |
| return user.name + " <" + user.email.value_or("") + ">"; |
| } |
| ``` |