The FDL compiler generates cross-language serialization code from schema definitions. It enables type-safe cross-language data exchange by generating native data structures with Fory serialization support for multiple programming languages.
For comprehensive documentation, see the FDL Schema Guide:
cd compiler pip install -e .
Create a .fdl file:
package demo; enum Color [id=101] { GREEN = 0; RED = 1; BLUE = 2; } message Dog [id=102] { optional string name = 1; int32 age = 2; } message Cat [id=103] { ref Dog friend = 1; optional string name = 2; repeated string tags = 3; map<string, int32> scores = 4; int32 lives = 5; }
# Generate for all languages foryc schema.fdl --output ./generated # Generate for specific languages foryc schema.fdl --lang java,python --output ./generated # Override package name foryc schema.fdl --package myapp.models --output ./generated # Language-specific output directories (protoc-style) foryc schema.fdl --java_out=./src/main/java --python_out=./python/src # Combine with other options foryc schema.fdl --java_out=./gen --go_out=./gen/go -I ./proto
Java:
import demo.*; import org.apache.fory.Fory; Fory fory = Fory.builder().build(); DemoForyRegistration.register(fory); Cat cat = new Cat(); cat.setName("Whiskers"); cat.setLives(9); byte[] bytes = fory.serialize(cat);
Python:
import pyfory from demo import Cat, register_demo_types fory = pyfory.Fory() register_demo_types(fory) cat = Cat(name="Whiskers", lives=9) data = fory.serialize(cat)
package com.example.models;
Import types from other FDL files:
import "common/types.fdl"; import "models/address.fdl";
Imports are resolved relative to the importing file. All types from imported files become available for use in the current file.
Example:
// common.fdl package common; message Address [id=100] { string street = 1; string city = 2; }
// user.fdl package user; import "common.fdl"; message User [id=101] { string name = 1; Address address = 2; // Uses imported type }
enum Status [id=100] { PENDING = 0; ACTIVE = 1; INACTIVE = 2; }
message User [id=101] { string name = 1; int32 age = 2; optional string email = 3; }
Types can have options specified in brackets after the name:
message User [id=101] { ... } // Registered with type ID 101 message User [id=101, deprecated=true] { ... } // Multiple options
Types without [id=...] use namespace-based registration:
message Config { ... } // Registered as "package.Config"
| FDL Type | Java | Python | Go | Rust | C++ |
|---|---|---|---|---|---|
bool | boolean | bool | bool | bool | bool |
int8 | byte | pyfory.int8 | int8 | i8 | int8_t |
int16 | short | pyfory.int16 | int16 | i16 | int16_t |
int32 | int | pyfory.int32 | int32 | i32 | int32_t |
int64 | long | pyfory.int64 | int64 | i64 | int64_t |
float32 | float | pyfory.float32 | float32 | f32 | float |
float64 | double | pyfory.float64 | float64 | f64 | double |
string | String | str | string | String | std::string |
bytes | byte[] | bytes | []byte | Vec<u8> | std::vector<uint8_t> |
date | LocalDate | datetime.date | time.Time | chrono::NaiveDate | fory::Date |
timestamp | Instant | datetime.datetime | time.Time | chrono::NaiveDateTime | fory::Timestamp |
repeated string tags = 1; // List<String> map<string, int32> scores = 2; // Map<String, Integer>
optional: Field can be null/Noneref: Enable reference tracking for shared/circular referencesrepeated: Field is a list/arraymessage Example { optional string nullable_field = 1; ref OtherMessage shared_ref = 2; repeated int32 numbers = 3; }
FDL uses plain option keys without a (fory) prefix:
File-level options:
option use_record_for_java_message = true; option polymorphism = true;
Message/Enum options:
message MyMessage [id=100] { option evolving = false; option use_record_for_java = true; string name = 1; } enum Status [id=101] { UNKNOWN = 0; ACTIVE = 1; }
Field options:
message Example { ref MyType friend = 1; string nickname = 2 [nullable=true]; ref MyType data = 3 [nullable=true]; ref(weak=true) MyType parent = 4; }
fory_compiler/
├── __init__.py # Package exports
├── __main__.py # Module entry point
├── cli.py # Command-line interface
├── frontend/
│ └── fdl/
│ ├── __init__.py
│ ├── lexer.py # Hand-written tokenizer
│ └── parser.py # Recursive descent parser
├── ir/
│ ├── __init__.py
│ ├── ast.py # Canonical Fory IDL AST
│ ├── validator.py # Schema validation
│ └── emitter.py # Optional FDL emitter
└── generators/
├── base.py # Base generator class
├── java.py # Java POJO generator
├── python.py # Python dataclass generator
├── go.py # Go struct generator
├── rust.py # Rust struct generator
└── cpp.py # C++ struct generator
The FDL frontend is a hand-written lexer/parser that produces the Fory IDL AST:
frontend/fdl/lexer.py): Tokenizes FDL source into tokensfrontend/fdl/parser.py): Builds the AST from the token streamir/ast.py): Canonical node types - Schema, Message, Enum, Field, FieldTypeEach generator extends BaseGenerator and implements:
generate(): Returns list of GeneratedFile objectsgenerate_type(): Converts FDL types to target language typesGenerates POJOs with:
@ForyField annotations for nullable/ref fieldspublic class Cat { @ForyField(ref = true) private Dog friend; @ForyField(nullable = true) private String name; private List<String> tags; // ... }
Generates dataclasses with:
@dataclass class Cat: friend: Optional[Dog] = None name: Optional[str] = None tags: List[str] = None
Generates structs with:
type Cat struct {
Friend *Dog `fory:"ref"`
Name *string `fory:"nullable"`
Tags []string
}
Generates structs with:
#[derive(ForyObject)] macro#[fory(...)] field attributes#[derive(ForyObject, Debug, Clone, PartialEq, Default)] pub struct Cat { pub friend: Arc<Dog>, #[fory(nullable = true)] pub name: Option<String>, pub tags: Vec<String>, }
Generates structs with:
FORY_STRUCT macro for serializationstd::optional for nullable fieldsstd::shared_ptr for ref fieldsstruct Cat { std::shared_ptr<Dog> friend; std::optional<std::string> name; std::vector<std::string> tags; int32_t scores; int32_t lives; FORY_STRUCT(Cat, friend, name, tags, scores, lives); };
foryc [OPTIONS] FILES...
Arguments:
FILES FDL files to compile
Options:
--lang TEXT Target languages (java,python,cpp,rust,go or "all")
Default: all
--output, -o PATH Output directory
Default: ./generated
--package TEXT Override package name from FDL file
--help Show help message
See the examples/ directory for sample FDL files and generated output.
# Compile the demo schema foryc examples/demo.fdl --output examples/generated
# Install in development mode pip install -e . # Run the compiler python -m fory_compiler compile examples/demo.fdl # Or use the installed command foryc examples/demo.fdl
Apache License 2.0