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; list<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,csharp,javascript,scala --output ./generated # Language-specific output directories (protoc-style) foryc schema.fdl --java_out=./src/main/java --python_out=./python/src --csharp_out=./csharp/src/Generated --javascript_out=./javascript --scala_out=./scala/src/main/scala # Combine with other options foryc schema.fdl --java_out=./gen --go_out=./gen/go --csharp_out=./gen/csharp --javascript_out=./gen/js --scala_out=./gen/scala -I ./proto # Also generate gRPC service stubs foryc schema.fdl --lang java,python,go --grpc --output ./generated
Java:
import demo.*; import org.apache.fory.Fory; Fory fory = Fory.builder() .withXlang(true) .withRefTracking(true) .withModule(DemoForyModule.INSTANCE) .build(); 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(xlang=True) 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 name-based registration:
message Config { ... } // Registered as "package.Config"
| FDL Type | Java | Python | Go | Rust | C++ | C# | JavaScript |
|---|---|---|---|---|---|---|---|
bool | boolean | bool | bool | bool | bool | bool | boolean |
int8 | byte | pyfory.Int8 | int8 | i8 | int8_t | sbyte | number |
int16 | short | pyfory.Int16 | int16 | i16 | int16_t | short | number |
int32 | int | pyfory.Int32 | int32 | i32 | int32_t | int | number |
int64 | long | pyfory.Int64 | int64 | i64 | int64_t | long | bigint | number |
float16 | Float16 | pyfory.Float16 | float16 | Float16 | fory::float16_t | Half | number |
bfloat16 | BFloat16 | pyfory.BFloat16 | bfloat16 | BFloat16 | fory::bfloat16_t | BFloat16 | number |
float32 | float | pyfory.Float32 | float32 | f32 | float | float | number |
float64 | double | pyfory.Float64 | float64 | f64 | double | double | number |
string | String | str | string | String | std::string | string | string |
bytes | byte[] | bytes | []byte | Vec<u8> | std::vector<uint8_t> | byte[] | Uint8Array |
date | LocalDate | datetime.date | time.Time | fory::Date | fory::Date | DateOnly | Date |
timestamp | Instant | datetime.datetime | time.Time | fory::Timestamp | fory::Timestamp | DateTimeOffset | Date |
list<string> tags = 1; // List<String> array<int32> dense_numbers = 2; // Packed dense int32 array map<string, fixed int32> scores = 3; // Map<String, fixed-width Integer>
optional: Field can be null/Noneref: Enable reference tracking for shared/circular referenceslist<T>: Ordered collection schema (alias: repeated T)array<T>: Dense numeric/vector schemamessage Example { optional string nullable_field = 1; ref OtherMessage shared_ref = 2; list<int32> numbers = 3; list<fixed int32> offsets = 4; array<float32> embedding = 5; }
Define gRPC services alongside message types in the same FDL file:
package demo.greeter; message HelloRequest { string name = 1; } message HelloReply { string reply = 1; } service Greeter { rpc SayHello (HelloRequest) returns (HelloReply); rpc StreamReplies (HelloRequest) returns (stream HelloReply); rpc CollectRequests (stream HelloRequest) returns (HelloReply); rpc Chat (stream HelloRequest) returns (stream HelloReply); }
Each rpc declaration supports four streaming modes:
| Mode | Syntax |
|---|---|
| Unary | rpc Method (Req) returns (Res) |
| Server streaming | rpc Method (Req) returns (stream Res) |
| Client streaming | rpc Method (stream Req) returns (Res) |
| Bidirectional | rpc Method (stream Req) returns (stream Res) |
FDL uses plain option keys without a (fory) prefix:
File-level options:
option use_record_for_java_message = true; option polymorphism = true; option enable_auto_type_id = true;
enable_auto_type_id defaults to true. Set it to false to keep name-based registration for types that omit explicit IDs.
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 (Schema, Message, Enum, Service, RpcMethod)
│ ├── validator.py # Schema validation
│ └── emitter.py # Optional FDL emitter
└── generators/
├── base.py # Base generator class and GeneratorOptions
├── 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
├── csharp.py # C# class generator
├── javascript.py # JavaScript interface generator
└── services/
├── base.py # StreamingMode enum and shared helpers
├── java.py # Java gRPC stub generator (grpc-java style)
├── python.py # Python gRPC companion module (grpcio style)
├── go.py # Go gRPC stub generator (google.golang.org/grpc)
├── rust.py # Rust gRPC service module (tonic style)
├── csharp.py # C# gRPC service companion (Grpc.Core style)
├── javascript.py # JavaScript Node.js and gRPC-Web client generators
├── dart.py # Dart gRPC service companion
├── kotlin.py # Kotlin coroutine gRPC service companion
└── scala.py # Scala gRPC service companion
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 objects for type definitionsgenerate_type(): Converts FDL types to target language typesgenerate_services(): Returns gRPC service companion files when --grpc is setService generators live in generators/services/ as mixins and are combined with the corresponding type generator via multiple inheritance in each language generator class.
Generates POJOs with:
@Nullable annotations for nullable fields and @Ref annotations for ref fieldspublic class Cat { @Ref private Dog friend; @Nullable 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(ForyStruct)], #[derive(ForyEnum)], and #[derive(ForyUnion)] macros#[fory(...)] field attributes#[derive(ForyStruct, 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); };
Generates classes with:
[ForyStruct], [ForyEnum], and [ForyUnion] model attributesToBytes/FromBytes helpers[ForyStruct] public sealed partial class Cat { public Dog? Friend { get; set; } public string Name { get; set; } = string.Empty; public List<string> Tags { get; set; } = new(); }
For full C# IDL verification (including root cross-package imports and file-based roundtrip paths), run:
cd integration_tests/idl_tests ./run_csharp_tests.sh
Generates interfaces with:
export interface declarations for messagesexport enum declarations for enumsexport interface Cat { friend?: Dog | null; name?: string | null; tags: string[]; scores: Map<string, number>; lives: number; }
Pass --grpc to generate gRPC service stubs alongside type definitions for all selected languages that support service generation (Java, Python, Go, Rust, C#, JavaScript, Dart, Kotlin, and Scala). Stubs use Fory serialization as the on-wire codec.
# Generate type definitions and gRPC stubs foryc examples/service.fdl --lang java,python,go --grpc --output ./generated # JavaScript gRPC-Web client (requires --grpc-web, implies JavaScript output) foryc examples/service.fdl --javascript_out=./gen/js --grpc-web # Python async mode (default) or sync mode foryc examples/service.fdl --python_out=./gen/python --grpc --grpc-python-mode sync
For each language the compiler emits one gRPC companion file per schema file. The following examples use the schema from examples/service.fdl:
package demo.greeter; message HelloRequest { string name = 1; } message HelloReply { string reply = 1; } service Greeter { rpc SayHello (HelloRequest) returns (HelloReply); }
Generates <ServiceName>Grpc.java with a grpc-java-style companion class:
<ServiceName>ImplBase abstract server base<ServiceName>Stub (async), <ServiceName>BlockingStub, and <ServiceName>FutureStub client stubsnewStub, newBlockingStub, and newFutureStub// GreeterGrpc.java (demo/greeter/GreeterGrpc.java) public final class GreeterGrpc { public static final String SERVICE_NAME = "demo.greeter.Greeter"; public abstract static class GreeterImplBase implements io.grpc.BindableService { public void sayHello(HelloRequest request, io.grpc.stub.StreamObserver<HelloReply> responseObserver) { io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall( getSayHelloMethod(), responseObserver); } @Override public final io.grpc.ServerServiceDefinition bindService() { return GreeterGrpc.bindService(this); } } public static final class GreeterStub extends io.grpc.stub.AbstractAsyncStub<GreeterStub> { ... } public static final class GreeterBlockingStub extends io.grpc.stub.AbstractBlockingStub<GreeterBlockingStub> { ... } public static final class GreeterFutureStub extends io.grpc.stub.AbstractFutureStub<GreeterFutureStub> { ... } }
Generates <module>_grpc.py with a grpcio-style companion module. The default API mode is async (grpc.aio); pass --grpc-python-mode sync for the classic sync API:
<ServiceName>Stub client class wired to the Fory serializer/deserializer pair<ServiceName>Servicer server base with UNIMPLEMENTED stubsadd_servicer(servicer, server) dispatcher# demo_greeter_grpc.py class GreeterStub(object): """Client stub for Greeter.""" def __init__(self, channel): self.say_hello = channel.unary_unary( "/demo.greeter.Greeter/SayHello", request_serializer=_serialize, response_deserializer=_deserialize, ) class GreeterServicer(object): """AsyncIO base servicer for Greeter.""" async def say_hello(self, request, context): await context.abort(grpc.StatusCode.UNIMPLEMENTED, "Method not implemented!") def add_servicer(servicer, server): ...
Generates <file>_grpc.go with a google.golang.org/grpc-compatible stub file:
CodecV2 implementing grpc/encoding.CodecV2 using the Fory thread-safe runtime<ServiceName>Client interface and New<ServiceName>Client constructor<ServiceName>Server interface and Unimplemented<ServiceName>Server structRegister<ServiceName>Server and a ServiceDesc variable// greeter_grpc.go type GreeterClient interface { SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) } func NewGreeterClient(cc grpc.ClientConnInterface) GreeterClient { ... } type GreeterServer interface { SayHello(context.Context, *HelloRequest) (*HelloReply, error) mustEmbedUnimplementedGreeterServer() } func RegisterGreeterServer(s grpc.ServiceRegistrar, srv GreeterServer) { ... }
Generates two files: <module>_api.rs (service trait definitions) and <module>_grpc.rs (tonic-compatible client/server modules):
<service_name>_client and <service_name>_server submodules compatible with tonic<SERVICE>_SERVICE_NAME constant// greeter_grpc.rs pub mod greeter_client { pub struct GreeterClient<T> { inner: tonic::client::Grpc<T> } impl<T> GreeterClient<T> { pub async fn say_hello(&mut self, request: impl tonic::IntoRequest<HelloRequest>) -> std::result::Result<tonic::Response<HelloReply>, tonic::Status> { ... } } } pub mod greeter_server { pub trait Greeter: std::marker::Send + std::marker::Sync + 'static { async fn say_hello(&self, request: tonic::Request<HelloRequest>) -> std::result::Result<tonic::Response<HelloReply>, tonic::Status>; } }
Generates <ServiceName>Grpc.cs with a Grpc.Core-style partial class:
Method<TReq, TRes> descriptors for each RPC<ServiceName>Base abstract server base class<ServiceName>Client client classBindService helper for server-side registration// GreeterGrpc.cs public static partial class Greeter { static readonly string __ServiceName = "demo.greeter.Greeter"; public abstract class GreeterBase { public virtual Task<HelloReply> SayHello( HelloRequest request, grpc::ServerCallContext context) => throw new grpc::RpcException(new grpc::Status( grpc::StatusCode.Unimplemented, "")); } public class GreeterClient : grpc::ClientBase<GreeterClient> { public virtual HelloReply SayHello( HelloRequest request, grpc::CallOptions options = default) { ... } } }
Generates <module>_grpc.js (Node.js, --grpc) and/or <module>_grpc_web.js (browser, --grpc-web) TypeScript/JavaScript companion modules:
<ServiceName>Client class extending grpc.Client (Node) or a gRPC-Web base (browser)<ServiceName>Service descriptor object for server-side registration (Node)// greeter_grpc.js (Node) export class GreeterClient extends grpc.Client { sayHello(argument, metadata, options, callback) { ... } } export const GreeterService = { sayHello: { path: "/demo.greeter.Greeter/SayHello", ... }, };
Generates <file>_grpc.dart with a dart-grpc-compatible companion:
<ServiceName>Client class extending grpc.Client<ServiceName>ServiceBase abstract server base classserialize/deserialize pair on each ClientMethodGenerates <ServiceName>GrpcKt.kt with grpc-kotlin coroutine companions:
<ServiceName>CoroutineImplBase abstract server base using suspend functions<ServiceName>CoroutineStub coroutine client stubGenerates <ServiceName>GrpcScala.scala with ZIO/Monix-friendly stubs:
<ServiceName>Grpc object with a bindService method for server registration<ServiceName>Stub client classforyc [OPTIONS] FILES...
Arguments:
FILES FDL files to compile
Options:
--lang TEXT Target languages (java,python,cpp,rust,go,csharp,
javascript,swift,dart,scala,kotlin or "all")
Default: all
--output, -o PATH Output directory
Default: ./generated
--java_out DST_DIR Generate Java code in DST_DIR
--python_out DST_DIR Generate Python code in DST_DIR
--go_out DST_DIR Generate Go code in DST_DIR
--rust_out DST_DIR Generate Rust code in DST_DIR
--cpp_out DST_DIR Generate C++ code in DST_DIR
--csharp_out DST_DIR Generate C# code in DST_DIR
--javascript_out DST_DIR Generate JavaScript code in DST_DIR
--swift_out DST_DIR Generate Swift code in DST_DIR
--dart_out DST_DIR Generate Dart code in DST_DIR
--scala_out DST_DIR Generate Scala 3 code in DST_DIR
--kotlin_out DST_DIR Generate Kotlin code in DST_DIR
-I PATH Add a directory to the import search path
--grpc Generate gRPC service stubs alongside type definitions
--grpc-web Generate JavaScript gRPC-Web client code
--grpc-python-mode MODE Python gRPC API style: async (default) or sync
--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