diff --git a/blog/2026-06-25-fory_1_3_0_release.md b/blog/2026-06-25-fory_1_3_0_release.md new file mode 100644 index 00000000000..a20e2c07ed0 --- /dev/null +++ b/blog/2026-06-25-fory_1_3_0_release.md @@ -0,0 +1,185 @@ +--- +slug: fory_1_3_0_release +title: Fory v1.3.0 Released +authors: [chaokunyang] +tags: [fory, java, kotlin, scala, android, python, rust, c++, go, c#, swift, dart, compiler] +--- + +The Apache Fory team is pleased to announce the 1.3.0 release. This release includes [8 PRs](https://github.com/apache/fory/compare/v1.2.0...v1.3.0) from 3 distinct contributors and continues to improve the cross-language runtime across supported languages. See the [Install](https://fory.apache.org/docs/start/install) page to get the libraries for your platform. + +## Highlights + +* Python gRPC code generation now defaults to the `grpc.aio` AsyncIO API, while synchronous `grpcio` output remains available through `--grpc-python-mode=sync`. +* Dart joins the generated gRPC service surface: `foryc --dart_out=... --grpc` now emits `package:grpc` clients, service bases, method descriptors, and Fory-backed payload serialization. +* Compiler gRPC documentation was refined across languages, including clearer guidance for generated service dependencies and transport behavior. +* Runtime hardening continued with remote schema metadata limits and Java aligned-varint/type-checker fixes. + +## Python Async gRPC Mode + +Python gRPC generation now targets AsyncIO by default. Generated companions use +`grpc.aio`: servicer bases expose `async def` methods, stubs are used with +`grpc.aio.Channel` instances, and streaming RPCs use async iterables. This keeps +the generated code aligned with modern Python async services while preserving +the same Fory-backed request and response encoding used by the existing gRPC +support. + +Generate the default async companion with: + +```bash +foryc service.fdl --python_out=./generated/python --grpc +``` + +For a simple unary service, the generated async server shape is: + +```python +import asyncio + +import grpc.aio + +import demo_greeter +import demo_greeter_grpc + + +class Greeter(demo_greeter_grpc.GreeterServicer): + async def say_hello(self, request, context): + return demo_greeter.HelloReply(reply=f"Hello, {request.name}") + + +async def serve(): + server = grpc.aio.server() + demo_greeter_grpc.add_servicer(Greeter(), server) + server.add_insecure_port("[::]:50051") + await server.start() + await server.wait_for_termination() + + +asyncio.run(serve()) +``` + +Clients use a `grpc.aio` channel and await generated stub methods: + +```python +import grpc +import grpc.aio + +import demo_greeter +import demo_greeter_grpc + + +credentials = grpc.ssl_channel_credentials() +async with grpc.aio.secure_channel("api.example.com:443", credentials) as channel: + stub = demo_greeter_grpc.GreeterStub(channel) + reply = await stub.say_hello(demo_greeter.HelloRequest(name="Fory")) +``` + +Existing synchronous applications can still request sync companions explicitly: + +```bash +foryc service.fdl --python_out=./generated/python --grpc --grpc-python-mode=sync +``` + +In sync mode the generated public names and `_grpc.py` filename stay the +same, but applications use `grpc.server(...)`, standard `grpc.Channel` +instances, and regular `def` servicer methods. + +## Dart gRPC Code Generation + +Fory 1.3.0 adds Dart gRPC service generation for schemas with service +definitions. Service definitions can come from Fory IDL, protobuf IDL, or +FlatBuffers `rpc_service` definitions. The generated code uses normal +grpc-dart APIs for clients, service bases, method descriptors, call options, +deadlines, cancellations, metadata, and status codes, while each request and +response object is serialized with Fory instead of protobuf message bytes. + +Add `grpc` and `build_runner` alongside the Fory package in the Dart +application: + +```yaml +dependencies: + fory: ^1.3.0 + grpc: ^4.0.0 + +dev_dependencies: + build_runner: ^2.4.0 +``` + +Generate Dart models and the gRPC companion with: + +```bash +foryc service.fdl --dart_out=./lib/generated --grpc +dart run build_runner build --delete-conflicting-outputs +``` + +For a `demo.greeter` package, the generator emits the model file, the +`build_runner` serializer part, and a `_grpc.dart` companion with +`GreeterServiceBase` and `GreeterClient`. The generated client and service base +install the schema's Fory module automatically on first use, so service +implementations do not need a separate manual registration step for the +generated message types. + +A unary Dart server uses grpc-dart's `Server` and the generated service base: + +```dart +import 'dart:io'; + +import 'package:grpc/grpc.dart'; +import 'demo/greeter/greeter.dart'; +import 'demo/greeter/greeter_grpc.dart'; + +class GreeterService extends GreeterServiceBase { + @override + Future sayHello(ServiceCall call, HelloRequest request) async { + return HelloReply()..reply = 'Hello, ${request.name}'; + } +} + +Future main() async { + final server = Server.create(services: [GreeterService()]); + await server.serve(address: InternetAddress.loopbackIPv4, port: 50051); +} +``` + +Generated Dart clients use standard `ClientChannel` values and return the +grpc-dart call types: + +```dart +import 'package:grpc/grpc.dart'; +import 'demo/greeter/greeter.dart'; +import 'demo/greeter/greeter_grpc.dart'; + +final channel = ClientChannel( + 'localhost', + port: 50051, + options: const ChannelOptions(credentials: ChannelCredentials.insecure()), +); +final client = GreeterClient(channel); + +final reply = await client.sayHello(HelloRequest()..name = 'Fory'); +await channel.shutdown(); +``` + +Dart generation covers unary, server-streaming, client-streaming, and +bidirectional streaming RPC shapes following grpc-dart conventions. + +## Features + +* feat(python): add async grpc mode for python by @chaokunyang in https://github.com/apache/fory/pull/3768 +* feat: limit remote schema metadata by @chaokunyang in https://github.com/apache/fory/pull/3770 +* feat(compiler): add dart gRPC codegen by @yash-agarwa-l in https://github.com/apache/fory/pull/3723 + +## Bug Fix + +* fix(java): guard aligned varint unsafe read by @chaokunyang in https://github.com/apache/fory/pull/3772 +* fix(java): cache accepted type checker classes by @chaokunyang in https://github.com/apache/fory/pull/3773 + +## Other Improvements + +* docs: refine gRPC support guides by @chaokunyang in https://github.com/apache/fory/pull/3767 +* docs: add threat model + SECURITY.md/AGENTS.md discoverability by @potiuk in https://github.com/apache/fory/pull/3734 +* chore(release): enforce OpenJDK 25 for JVM publishing by @chaokunyang in https://github.com/apache/fory/pull/3775 + +## New Contributors + +* @potiuk made their first contribution in https://github.com/apache/fory/pull/3734 + +**Full Changelog**: https://github.com/apache/fory/compare/v1.2.0...v1.3.0 diff --git a/docs/start/install.md b/docs/start/install.md index 5f4149333e7..76f33dad995 100644 --- a/docs/start/install.md +++ b/docs/start/install.md @@ -16,14 +16,14 @@ Use Maven to add Apache Fory™: org.apache.fory fory-core - 1.2.0 + 1.3.0 @@ -31,7 +31,7 @@ Use Maven to add Apache Fory™: org.apache.fory fory-simd - 1.2.0 + 1.3.0 --> ``` @@ -44,7 +44,7 @@ Scala 2.13 with Maven: org.apache.fory fory-scala_2.13 - 1.2.0 + 1.3.0 ``` @@ -54,20 +54,20 @@ Scala 3 with Maven: org.apache.fory fory-scala_3 - 1.2.0 + 1.3.0 ``` Scala 2.13 with sbt: ```sbt -libraryDependencies += "org.apache.fory" % "fory-scala_2.13" % "1.2.0" +libraryDependencies += "org.apache.fory" % "fory-scala_2.13" % "1.3.0" ``` Scala 3 with sbt: ```sbt -libraryDependencies += "org.apache.fory" % "fory-scala_3" % "1.2.0" +libraryDependencies += "org.apache.fory" % "fory-scala_3" % "1.3.0" ``` ## Kotlin @@ -78,7 +78,7 @@ Add Apache Fory™ Kotlin with Maven: org.apache.fory fory-kotlin - 1.2.0 + 1.3.0 ``` @@ -86,7 +86,7 @@ Add Apache Fory™ Kotlin with Maven: ```bash python -m pip install --upgrade pip -pip install pyfory==1.2.0 +pip install pyfory==1.3.0 ``` ## Go @@ -94,7 +94,7 @@ pip install pyfory==1.2.0 Use the full Go module path `github.com/apache/fory/go/fory`: ```bash -go get github.com/apache/fory/go/fory@v1.2.0 +go get github.com/apache/fory/go/fory@v1.3.0 ``` If your Go proxy has not picked up the new submodule tag yet, retry later or use `GOPROXY=direct` temporarily. @@ -103,13 +103,13 @@ If your Go proxy has not picked up the new submodule tag yet, retry later or use ```toml [dependencies] -fory = "1.2.0" +fory = "1.3.0" ``` Or use `cargo add`: ```bash -cargo add fory@1.2.0 +cargo add fory@1.3.0 ``` ## JavaScript / TypeScript @@ -132,7 +132,7 @@ Add Apache Fory™ Dart to `pubspec.yaml`: ```yaml dependencies: - fory: ^1.2.0 + fory: ^1.3.0 dev_dependencies: build_runner: ^2.4.13 @@ -149,12 +149,12 @@ dart run build_runner build --delete-conflicting-outputs Install the `Apache.Fory` NuGet package. It includes both the runtime and the source generator for `[ForyObject]` types. ```bash -dotnet add package Apache.Fory --version 1.2.0 +dotnet add package Apache.Fory --version 1.3.0 ``` ```xml - + ``` @@ -164,7 +164,7 @@ Add Apache Fory™ from the GitHub repository with Swift Package Manager: ```swift dependencies: [ - .package(url: "https://github.com/apache/fory.git", exact: "1.2.0") + .package(url: "https://github.com/apache/fory.git", exact: "1.3.0") ], targets: [ .target( diff --git a/docusaurus.config.ts b/docusaurus.config.ts index af340662e8a..354f946b250 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -48,7 +48,7 @@ const config: Config = { { docs: { sidebarCollapsible: true, - lastVersion: '1.2.0', + lastVersion: '1.3.0', versions: { current: { label: 'dev', diff --git a/i18n/en-US/docusaurus-plugin-content-docs/version-1.3.0/.keep b/i18n/en-US/docusaurus-plugin-content-docs/version-1.3.0/.keep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/i18n/zh-CN/docusaurus-plugin-content-blog/2026-06-25-fory_1_3_0_release.md b/i18n/zh-CN/docusaurus-plugin-content-blog/2026-06-25-fory_1_3_0_release.md new file mode 100644 index 00000000000..cb237103bc7 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-blog/2026-06-25-fory_1_3_0_release.md @@ -0,0 +1,165 @@ +--- +slug: fory_1_3_0_release +title: Fory v1.3.0 发布 +authors: [chaokunyang] +tags: [fory, java, kotlin, scala, android, python, rust, c++, go, c#, swift, dart, compiler] +--- + +Apache Fory 团队很高兴宣布 1.3.0 版本正式发布。本版本包含来自 3 位贡献者的 [8 个 PR](https://github.com/apache/fory/compare/v1.2.0...v1.3.0),并继续改进各支持语言的跨语言运行时。请访问 [Install 页面](https://fory.apache.org/docs/start/install) 获取各平台安装方式。 + +## 发布亮点 + +* Python gRPC 代码生成现在默认使用 `grpc.aio` AsyncIO API;同步 `grpcio` 输出仍可通过 `--grpc-python-mode=sync` 显式启用。 +* Dart 加入生成式 gRPC service 支持:`foryc --dart_out=... --grpc` 现在会生成 `package:grpc` client、service base、method descriptor,以及基于 Fory 的 payload 序列化逻辑。 +* Compiler gRPC 文档在多个语言上进一步细化,包括更清晰的生成 service 依赖和传输行为说明。 +* 运行时继续增强安全性,加入远端 schema metadata 限制,并修复 Java aligned-varint 与 type-checker cache 相关问题。 + +## Python Async gRPC 模式 + +Python gRPC 生成现在默认面向 AsyncIO。生成的 companion 使用 `grpc.aio`:servicer base 暴露 `async def` 方法,stub 搭配 `grpc.aio.Channel` 使用,streaming RPC 使用 async iterable。这让生成代码更适合现代 Python async service,同时仍保留既有 gRPC 支持中的 Fory-backed request/response 编码方式。 + +默认 async companion 生成命令如下: + +```bash +foryc service.fdl --python_out=./generated/python --grpc +``` + +对于简单 unary service,生成代码对应的 async server 形态如下: + +```python +import asyncio + +import grpc.aio + +import demo_greeter +import demo_greeter_grpc + + +class Greeter(demo_greeter_grpc.GreeterServicer): + async def say_hello(self, request, context): + return demo_greeter.HelloReply(reply=f"Hello, {request.name}") + + +async def serve(): + server = grpc.aio.server() + demo_greeter_grpc.add_servicer(Greeter(), server) + server.add_insecure_port("[::]:50051") + await server.start() + await server.wait_for_termination() + + +asyncio.run(serve()) +``` + +Client 使用 `grpc.aio` channel,并 `await` 生成的 stub 方法: + +```python +import grpc +import grpc.aio + +import demo_greeter +import demo_greeter_grpc + + +credentials = grpc.ssl_channel_credentials() +async with grpc.aio.secure_channel("api.example.com:443", credentials) as channel: + stub = demo_greeter_grpc.GreeterStub(channel) + reply = await stub.say_hello(demo_greeter.HelloRequest(name="Fory")) +``` + +已有同步应用仍可显式请求 sync companion: + +```bash +foryc service.fdl --python_out=./generated/python --grpc --grpc-python-mode=sync +``` + +Sync 模式保持相同的生成 public name 和 `_grpc.py` 文件名,但应用使用 `grpc.server(...)`、标准 `grpc.Channel` 实例,以及普通 `def` servicer 方法。 + +## Dart gRPC 代码生成 + +Fory 1.3.0 为包含 service 定义的 schema 增加 Dart gRPC service 生成。Service 定义可以来自 Fory IDL、protobuf IDL 或 FlatBuffers `rpc_service`。生成代码使用标准 grpc-dart API 来处理 client、service base、method descriptor、call option、deadline、取消、metadata 和 status code;每个 request/response 对象则使用 Fory 序列化,而不是 protobuf message bytes。 + +在 Dart 应用中,除 Fory package 外,还需要加入 `grpc` 和 `build_runner`: + +```yaml +dependencies: + fory: ^1.3.0 + grpc: ^4.0.0 + +dev_dependencies: + build_runner: ^2.4.0 +``` + +生成 Dart model 和 gRPC companion: + +```bash +foryc service.fdl --dart_out=./lib/generated --grpc +dart run build_runner build --delete-conflicting-outputs +``` + +对于 `demo.greeter` package,generator 会输出 model 文件、由 `build_runner` 生成的 serializer part,以及包含 `GreeterServiceBase` 和 `GreeterClient` 的 `_grpc.dart` companion。生成的 client 和 service base 会在首次使用时自动安装该 schema 的 Fory module,因此 service 实现不需要为生成的 message type 单独手动注册。 + +Unary Dart server 使用 grpc-dart 的 `Server` 和生成的 service base: + +```dart +import 'dart:io'; + +import 'package:grpc/grpc.dart'; +import 'demo/greeter/greeter.dart'; +import 'demo/greeter/greeter_grpc.dart'; + +class GreeterService extends GreeterServiceBase { + @override + Future sayHello(ServiceCall call, HelloRequest request) async { + return HelloReply()..reply = 'Hello, ${request.name}'; + } +} + +Future main() async { + final server = Server.create(services: [GreeterService()]); + await server.serve(address: InternetAddress.loopbackIPv4, port: 50051); +} +``` + +生成的 Dart client 使用标准 `ClientChannel`,并返回 grpc-dart 调用类型: + +```dart +import 'package:grpc/grpc.dart'; +import 'demo/greeter/greeter.dart'; +import 'demo/greeter/greeter_grpc.dart'; + +final channel = ClientChannel( + 'localhost', + port: 50051, + options: const ChannelOptions(credentials: ChannelCredentials.insecure()), +); +final client = GreeterClient(channel); + +final reply = await client.sayHello(HelloRequest()..name = 'Fory'); +await channel.shutdown(); +``` + +Dart 生成支持 unary、server-streaming、client-streaming 和 bidirectional streaming 四种 RPC 形态,并遵循 grpc-dart 约定。 + +## Features + +* feat(python): add async grpc mode for python by @chaokunyang in https://github.com/apache/fory/pull/3768 +* feat: limit remote schema metadata by @chaokunyang in https://github.com/apache/fory/pull/3770 +* feat(compiler): add dart gRPC codegen by @yash-agarwa-l in https://github.com/apache/fory/pull/3723 + +## Bug Fix + +* fix(java): guard aligned varint unsafe read by @chaokunyang in https://github.com/apache/fory/pull/3772 +* fix(java): cache accepted type checker classes by @chaokunyang in https://github.com/apache/fory/pull/3773 + +## Other Improvements + +* docs: refine gRPC support guides by @chaokunyang in https://github.com/apache/fory/pull/3767 +* docs: add threat model + SECURITY.md/AGENTS.md discoverability by @potiuk in https://github.com/apache/fory/pull/3734 +* chore(release): enforce OpenJDK 25 for JVM publishing by @chaokunyang in https://github.com/apache/fory/pull/3775 + +## New Contributors + +* @potiuk made their first contribution in https://github.com/apache/fory/pull/3734 + +**Full Changelog**: https://github.com/apache/fory/compare/v1.2.0...v1.3.0 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/community/DEVELOPMENT.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/community/DEVELOPMENT.md index 9e8a116d20e..1980387e772 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/community/DEVELOPMENT.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/community/DEVELOPMENT.md @@ -144,3 +144,4 @@ prettier --write "**/*.md" ## 贡献 更多信息,请参考[如何贡献到 Apache Fory™](https://github.com/apache/fory/blob/main/CONTRIBUTING.md)。 +对于 AI 辅助贡献,请遵循 [AI Contribution Policy](https://github.com/apache/fory/blob/main/AI_POLICY.md),包括 substantial AI assistance 所需的自审、双 reviewer AI review 循环、披露和验证证据。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/compiler/compiler-guide.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/compiler/compiler-guide.md index 16a2d40b270..215430e8d41 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/compiler/compiler-guide.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/compiler/compiler-guide.md @@ -72,7 +72,9 @@ foryc --scan-generated [OPTIONS] | `--swift_namespace_style` | Swift 命名空间方式:`enum` 或 `flatten` | `enum` | | `--emit-fdl` | 输出转换后的 FDL(用于非 FDL 输入) | `false` | | `--emit-fdl-path` | 将转换后的 FDL 写入此路径(文件或目录) | (stdout) | -| `--grpc` | 为 Java 和 Python 生成 gRPC service companion 代码 | `false` | +| `--grpc` | 为支持的输出生成 gRPC service companion 代码 | `false` | +| `--grpc-python-mode=MODE` | Python gRPC 模式:`async` 或 `sync` | `async` | +| `--grpc-web` | 生成 JavaScript gRPC-Web client companion | `false` | 支持 schema 级文件选项,用于控制特定语言的生成行为。 对于 `go_nested_type_style` 和 `swift_namespace_style`,当 CLI 标志和 @@ -139,22 +141,37 @@ foryc schema.fdl --output ./src/generated foryc user.fdl order.fdl product.fdl --output ./generated ``` -**编译包含 service 定义的简单 schema(Java + Python 模型):** +**编译包含 service 定义的简单 schema(Java + Python + Go + Rust + C# + Dart + Scala + Kotlin + JavaScript 模型):** ```bash -foryc compiler/examples/service.fdl --java_out=./generated/java --python_out=./generated/python +foryc compiler/examples/service.fdl --java_out=./generated/java --python_out=./generated/python --go_out=./generated/go --rust_out=./generated/rust --csharp_out=./generated/csharp --dart_out=./generated/dart --scala_out=./generated/scala --kotlin_out=./generated/kotlin --javascript_out=./generated/javascript ``` -**生成 Java 和 Python gRPC service companion 代码:** +**生成 Java、Python、Go、Rust、C#、Dart、Scala、Kotlin 和 Node.js JavaScript gRPC service companion 代码:** ```bash -foryc compiler/examples/service.fdl --java_out=./generated/java --python_out=./generated/python --grpc +foryc compiler/examples/service.fdl --java_out=./generated/java --python_out=./generated/python --go_out=./generated/go --rust_out=./generated/rust --csharp_out=./generated/csharp --dart_out=./generated/dart --scala_out=./generated/scala --kotlin_out=./generated/kotlin --javascript_out=./generated/javascript --grpc ``` 生成的 gRPC service 代码使用 Fory 序列化请求和响应载荷。Java 输出会导入 -grpc-java API,Python 输出会导入 `grpc`;编译或运行这些生成 service 文件的 -应用需要自行提供 gRPC 依赖。Fory 的 Java 和 Python 运行时包不会为此功能加入 -强制 gRPC 依赖。 +grpc-java API,Python 输出默认使用 `grpc.aio`,Go 输出会导入 grpc-go,Rust 输出会导入 +`tonic` 和 `bytes`,Scala 输出会导入 grpc-java API,Kotlin 输出会导入 grpc-java 和 +grpc-kotlin API 并使用 coroutine stub。C# 输出会导入 `Grpc.Core.Api` 类型,并可由 +`Grpc.AspNetCore` 等常规 .NET gRPC package 承载,或通过 `Grpc.Net.Client` 调用。Dart 输出 +会导入 `package:grpc`。JavaScript 输出会导入 `@grpc/grpc-js`。编译或运行这些生成 +service 文件的应用需要自行提供 gRPC 依赖。Fory package 不会为此功能加入强制 gRPC 依赖。 + +为已有同步 `grpcio` 应用生成同步 Python gRPC companion: + +```bash +foryc compiler/examples/service.fdl --python_out=./generated/python --grpc --grpc-python-mode=sync +``` + +**生成 JavaScript gRPC-Web 浏览器 client:** + +```bash +foryc compiler/examples/service.fdl --javascript_out=./generated/javascript --grpc --grpc-web +``` **使用 import 搜索路径:** @@ -397,6 +414,8 @@ generated/ - Package 片段映射为目录(例如 `demo.foo` → `demo/foo/`) - IDL module 类包含在主文件中;生成的 serializer 元数据包含在 part 文件中 - 非可选、非 `ref` 的 primitive list 使用类型化数组(例如 `Int32List`) +- 使用 `--grpc` 时,会在 model 文件旁为每个 schema 生成一个 `_grpc.dart` companion, + 其中包含各 service 的 `Client` 和 `ServiceBase`,并导入 `package:grpc` ### Scala @@ -667,7 +686,7 @@ cc_library( ```yaml dependencies: - fory: ^1.2.0 + fory: ^1.3.0 dev_dependencies: build_runner: ^2.4.0 @@ -859,5 +878,5 @@ fory = "x.y.z" ```yaml dependencies: - fory: ^1.2.0 + fory: ^1.3.0 ``` diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/compiler/flatbuffers-idl.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/compiler/flatbuffers-idl.md index f2500813134..78bf32de21c 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/compiler/flatbuffers-idl.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/compiler/flatbuffers-idl.md @@ -113,6 +113,23 @@ message Container { } ``` +### gRPC Service + +FlatBuffers `rpc_service` 定义会转换为 Fory service。使用 `--grpc` 时,compiler 会为 Java、Python、Go、Rust、C#、Dart、Scala、Kotlin 和 JavaScript 等支持的输出生成 gRPC service companion。JavaScript 浏览器 client 通过 `--grpc-web` 生成。这些 companion 使用 Fory 序列化 request 和 response payload。 + +```fbs +rpc_service SearchService { + Lookup(SearchRequest):SearchResponse; + StreamLookup(SearchRequest):SearchResponse (streaming: "server"); +} +``` + +```bash +foryc api.fbs --java_out=./generated/java --python_out=./generated/python --go_out=./generated/go --rust_out=./generated/rust --csharp_out=./generated/csharp --dart_out=./generated/dart --scala_out=./generated/scala --kotlin_out=./generated/kotlin --javascript_out=./generated/javascript --grpc +``` + +生成的 service 代码会导入 gRPC API,因此编译或运行这些文件的应用需要提供 grpc-java、grpc-kotlin、Scala grpc-java API、`grpcio`、grpc-go、Rust `tonic` 和 `bytes`、`@grpc/grpc-js`、C# `Grpc.Core.Api` 及 server/client 依赖,或 Dart `package:grpc`。Python companion 默认使用 `grpc.aio`,也可以通过 `--grpc-python-mode=sync` 生成同步模式。Fory package 不会把 gRPC 作为硬依赖。JavaScript 输出配合 `--grpc-web` 可生成导入 `grpc-web` 的浏览器 client。 + ### 默认值与元数据 - FlatBuffers 默认值会被解析,但不会作为 Fory 运行时默认值生效。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/compiler/generated-code.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/compiler/generated-code.md index 024eb203ca8..94944636526 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/compiler/generated-code.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/compiler/generated-code.md @@ -323,6 +323,41 @@ data = person.to_bytes() restored = Person.from_bytes(data) ``` +### gRPC Service Companion + +当 schema 包含 service,且 compiler 使用 `--grpc` 运行时,Python 会生成名为 +`_grpc.py` 的 companion module。Module 名称由 Fory package 的点号替换为下划线得到; +没有 package 时使用 `generated`。Python gRPC 输出默认使用 `grpc.aio` AsyncIO API。 + +```python +import grpc +import grpc.aio + + +class AddressBookServiceStub: + def __init__(self, channel): + self.lookup = channel.unary_unary( + "/example.AddressBookService/Lookup", + request_serializer=..., + response_deserializer=..., + ) + + +class AddressBookServiceServicer: + async def lookup(self, request, context): + await context.abort(grpc.StatusCode.UNIMPLEMENTED, "Method not implemented!") + + +def add_servicer(servicer, server): ... +``` + +编译或运行生成 companion module 的应用必须安装 `grpcio`;`pyfory` 不会加入硬 gRPC 依赖。 +Python API 使用 snake_case 方法名,同时在 gRPC wire path 中保留原始 IDL method 名称。 + +使用 `--grpc --grpc-python-mode=sync` 可以生成同步 Python `grpcio` companion。Sync 模式保持 +相同的生成文件名和 public name,但 servicer 方法使用普通 `def`,并使用同步 `grpc.Channel` +和 `grpc.Server` 实例。 + ## Rust ### 输出布局 @@ -1000,6 +1035,38 @@ void main() { } ``` +### gRPC Service Companion + +当 schema 包含 service,且 compiler 使用 `--grpc` 运行时,Dart 会在 model type 旁为每个 +schema 生成一个 `_grpc.dart` 文件。它面向 `package:grpc`。Request 和 response +序列化使用 companion 自动获取的 Fory runtime,并在首次使用时注册该 schema 的类型,因此不需要手动注册;应用也可以在第一次调用前通过 schema module 的 `install(...)` 注入自定义 `Fory`。 + +生成支持四种 RPC 模式:unary、server-streaming、client-streaming 和 bidirectional。Client +class 继承 `Client`;service base class 继承 `Service` 并通过 `$addMethod` 注册各方法。 + +```dart +class GreeterClient extends Client { + // Single response: ResponseFuture. Streaming response: ResponseStream. + ResponseFuture sayHello(HelloRequest request, {CallOptions? options}) { ... } + ResponseStream sayHellos(HelloRequest request, {CallOptions? options}) { ... } + ResponseFuture collectHellos(Stream request, {CallOptions? options}) { ... } + ResponseStream chatHellos(Stream request, {CallOptions? options}) { ... } +} + +abstract class GreeterServiceBase extends Service { + Future sayHello(ServiceCall call, HelloRequest request); + Stream sayHellos(ServiceCall call, HelloRequest request); + Future collectHellos(ServiceCall call, Stream request); + Stream chatHellos(ServiceCall call, Stream request); +} +``` + +单响应 client 方法返回 `ResponseFuture`(client-streaming 会用 `.single` 适配 streaming 调用); +streaming 响应方法返回 `ResponseStream`。Server 端实现会 override 抽象方法:单请求以 `Q` +传入,client-streaming 请求以 `Stream` 传入;单响应返回 `Future`,streaming 响应返回 +`Stream`。编译这些文件的应用必须提供 `grpc` 依赖;Fory Dart runtime 不会加入此依赖。原始 +IDL method 名称用于 gRPC wire path。 + ## 跨语言说明 ### 类型 ID 行为 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/compiler/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/compiler/index.md index 73caa1018b0..bb1809493c3 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/compiler/index.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/compiler/index.md @@ -20,7 +20,7 @@ license: | --- Fory IDL 是 Apache Fory 的 Schema 定义语言,可实现类型安全的跨语言序列化。 -你只需定义一次数据结构,即可为 Java、Python、Go、Rust、C++、C#、Swift、JavaScript 和 Dart 生成原生数据结构代码。 +你只需定义一次数据结构,即可为 Java、Python、Go、Rust、C++、C#、Swift、Dart、Scala、Kotlin 和 JavaScript/TypeScript 生成原生数据结构代码。Fory IDL 也可以描述 RPC service;对于 Java、Python、Go、Rust、C#、Dart、Scala、Kotlin 和 JavaScript,compiler 可以生成使用 Fory 序列化 request/response payload 的 gRPC service companion。 ## 示例 Schema @@ -143,6 +143,14 @@ foryc example.fdl --output ./generated foryc example.fdl --lang java,python,csharp,javascript,swift,dart --output ./generated ``` +为包含 service 定义的 schema 生成 Java、Python、Go、Rust、C#、Dart、Scala、Kotlin 和 JavaScript model 以及 gRPC service companion: + +```bash +foryc animals.fdl --java_out=./generated/java --python_out=./generated/python --go_out=./generated/go --rust_out=./generated/rust --csharp_out=./generated/csharp --dart_out=./generated/dart --scala_out=./generated/scala --kotlin_out=./generated/kotlin --javascript_out=./generated/javascript --grpc +``` + +生成的 service 代码使用标准 gRPC API,但 request 和 response 对象使用 Fory 序列化。应用需要自行提供 grpc-java、grpc-kotlin、Scala grpc-java API、`grpcio`、grpc-go、Rust `tonic` 和 `bytes`、C# `Grpc.Core.Api` 及 server/client 依赖,或 Dart `package:grpc`;Fory package 不会把 gRPC 作为硬依赖。Python companion 默认使用 `grpc.aio`,也可以通过 `--grpc-python-mode=sync` 生成同步模式。JavaScript Node.js companion 使用 `@grpc/grpc-js`;浏览器 client 通过 `--grpc-web` 单独生成并使用 `grpc-web`。 + ### 4. 使用生成代码 **Java:** diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/compiler/protobuf-idl.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/compiler/protobuf-idl.md index 13c0de69958..6b0c3ef9bfc 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/compiler/protobuf-idl.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/compiler/protobuf-idl.md @@ -48,9 +48,9 @@ license: | | 循环引用 | 不支持 | 支持 | | 未知字段 | 保留 | 不保留 | | 生成类型 | protobuf 专用模型类型 | 语言原生构造 | -| gRPC 生态 | 原生成熟 | 持续建设中(活跃开发) | +| gRPC 生态 | 原生成熟 | Java/Python/Go/Rust/C#/Dart/Scala/Kotlin/JavaScript service codegen | -Fory 的 gRPC 支持仍在持续开发中。当前生产级 gRPC 工作流里,protobuf 仍是更成熟的默认选择。 +Fory 可以通过 `--grpc` 生成 Java、Python、Go、Rust、C#、Dart、Scala、Kotlin 和 JavaScript gRPC service companion。JavaScript 浏览器 client 通过 `--grpc-web` 生成。这些 service 使用标准 gRPC 传输,但 request 和 response payload 使用 Fory 序列化,而不是 protobuf。对于广泛的 gRPC 生态工具、schema reflection 和 protobuf-native interceptor,protobuf 仍是更成熟的默认选择。 ## 为什么使用 Apache Fory @@ -295,6 +295,14 @@ message TreeNode { 将 protobuf 代码生成步骤替换为 Fory 编译器针对目标语言的生成命令。 +对于支持的 service 输出,添加 `--grpc` 以生成 gRPC companion 代码: + +```bash +foryc api.proto --java_out=./generated/java --python_out=./generated/python --go_out=./generated/go --rust_out=./generated/rust --csharp_out=./generated/csharp --dart_out=./generated/dart --scala_out=./generated/scala --kotlin_out=./generated/kotlin --javascript_out=./generated/javascript --grpc +``` + +生成的 Java service 文件依赖 grpc-java;Python service module 默认使用 `grpc.aio`;Rust service 文件导入 `tonic` 和 `bytes`;Go service 文件导入 grpc-go;JavaScript Node.js service 文件导入 `@grpc/grpc-js`;C# service 文件导入 `Grpc.Core.Api` 类型;Dart service 文件导入 `package:grpc`;Scala service 文件依赖 grpc-java;Kotlin service 文件依赖 grpc-java 和 grpc-kotlin。请在应用构建中加入这些依赖;Fory package 不会把 gRPC 作为硬依赖。同步 Python `grpcio` companion 可使用 `--grpc-python-mode=sync`。JavaScript 输出配合 `--grpc-web` 可生成导入 `grpc-web` 的浏览器 client。 + ### 第 5 步:执行兼容性验证 分阶段迁移时,可并行保留两种格式,并通过集成测试验证 payload 级一致性。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/compiler/schema-idl.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/compiler/schema-idl.md index baabf880e79..3e8fd2db192 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/compiler/schema-idl.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/compiler/schema-idl.md @@ -860,6 +860,20 @@ message Order [id=206] { } ``` +## Service 定义 + +Service 用于在 Fory IDL 中定义 RPC method 契约。它是可选的:包含 service 的 schema 仍会生成常规数据 model;只有在 compiler 使用 `--grpc` 且目标语言受支持时,才会生成 gRPC service 代码。支持的输出包括 Java、Python、Go、Rust、C#、Dart、Scala、Kotlin 和 JavaScript。JavaScript 浏览器 gRPC-Web client 通过 `--grpc-web` 生成。 + +```protobuf +service PetDirectory { + rpc Lookup (PetRequest) returns (PetResponse); + rpc Watch (PetRequest) returns (stream PetResponse); +} +``` + +- 生成的 gRPC companion 会对每个 RPC payload 使用 Fory 序列化。 +- 编译或运行这些 companion 的应用需要自行提供 gRPC 依赖,例如 grpc-java、grpc-kotlin、`grpcio`、grpc-go、Rust `tonic` 和 `bytes`、Scala grpc-java API、`@grpc/grpc-js`、`grpc-web`、C# `Grpc.Core.Api` 及 server/client package,或 Dart `package:grpc`。Python companion 默认使用 `grpc.aio`,也可以通过 `--grpc-python-mode=sync` 生成同步模式。 + ## 语法摘要 以下为简化文法(便于快速查阅,具体以编译器实现为准): diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/cpp/configuration.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/cpp/configuration.md index 62a93572f0c..f6518d611bb 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/cpp/configuration.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/cpp/configuration.md @@ -69,6 +69,10 @@ auto fory = Fory::builder() .xlang(true) .track_ref(true) .max_dyn_depth(10) + .max_type_fields(512) + .max_type_meta_bytes(4096) + .max_schema_versions_per_type(10) + .max_average_schema_versions_per_type(3) .check_struct_version(true) .build(); ``` @@ -136,6 +140,54 @@ auto fory = Fory::builder() - **增加**:对于合法的深度嵌套数据结构 - **减少**:对于更严格的安全要求或浅层数据结构 +### max_schema_versions_per_type(uint32_t) + +设置一个逻辑类型可接受的最大远端 metadata 版本数。 + +```cpp +auto fory = Fory::builder() + .max_schema_versions_per_type(10) + .build(); +``` + +**默认值:** `10` + +### max_type_fields(uint32_t) + +设置一个收到的远端 struct metadata body 中可接受的最大字段数。 + +```cpp +auto fory = Fory::builder() + .max_type_fields(512) + .build(); +``` + +**默认值:** `512` + +### max_type_meta_bytes(uint32_t) + +设置一个收到的 TypeDef body 可接受的最大编码 body 字节数,不包含 8 字节 header 和扩展 size varint。 + +```cpp +auto fory = Fory::builder() + .max_type_meta_bytes(4096) + .build(); +``` + +**默认值:** `4096` + +### max_average_schema_versions_per_type(uint32_t) + +设置所有已接受远端类型的平均 metadata 版本数限制。有效全局下限为 `8192` 个 schema。 + +```cpp +auto fory = Fory::builder() + .max_average_schema_versions_per_type(3) + .build(); +``` + +**默认值:** `3` + ### check_struct_version(bool) 启用/禁用结构体版本检查。 @@ -174,13 +226,27 @@ auto fory = Fory::builder() ## 配置摘要 -| 选项 | 说明 | 默认值 | -| ---------------------------- | ---------------------- | ------- | -| `xlang(bool)` | 启用跨语言模式 | `true` | -| `compatible(bool)` | 启用 schema 演化 | `false` | -| `track_ref(bool)` | 启用引用跟踪 | `true` | -| `max_dyn_depth(uint32_t)` | 动态类型的最大嵌套深度 | `5` | -| `check_struct_version(bool)` | 启用结构体版本检查 | `false` | +| 选项 | 说明 | 默认值 | +| ------------------------------------------------ | --------------------------------- | ------- | +| `xlang(bool)` | 启用跨语言模式 | `true` | +| `compatible(bool)` | 启用 schema 演化 | `false` | +| `track_ref(bool)` | 启用引用跟踪 | `true` | +| `max_dyn_depth(uint32_t)` | 动态类型的最大嵌套深度 | `5` | +| `max_type_fields(uint32_t)` | 一个收到的 struct metadata body 最大字段数 | `512` | +| `max_type_meta_bytes(uint32_t)` | 一个收到的 metadata body 最大编码字节数 | `4096` | +| `max_schema_versions_per_type(uint32_t)` | 一个逻辑类型最大远端 metadata 版本数 | `10` | +| `max_average_schema_versions_per_type(uint32_t)` | 所有远端类型的平均 metadata 版本数 | `3` | +| `check_struct_version(bool)` | 启用结构体版本检查 | `false` | + +## 安全 + +安全相关配置: + +- 在反序列化不可信 payload 前,只注册预期的类型。 +- 对 intentional same-schema payload,将 `check_struct_version(true)` 与 `compatible(false)` 配合使用。 +- 尽可能降低 `max_dyn_depth(...)`,以拒绝异常深的多态对象图。 +- 除非数据不是恶意输入,且可信 peer 会发送更大的 metadata 或大量 schema 版本,否则保持远端 schema metadata 限制的默认值。 +- 对不可信输入,优先使用具体字段,避免宽泛的多态字段。 ## 相关主题 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/cpp/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/cpp/index.md index 92599c21e3c..2b0805b1596 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/cpp/index.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/cpp/index.md @@ -61,7 +61,7 @@ include(FetchContent) FetchContent_Declare( fory GIT_REPOSITORY https://github.com/apache/fory.git - GIT_TAG v1.2.0 + GIT_TAG v1.3.0 SOURCE_SUBDIR cpp ) FetchContent_MakeAvailable(fory) @@ -91,11 +91,11 @@ module( bazel_dep(name = "rules_cc", version = "0.1.1") -bazel_dep(name = "fory", version = "1.2.0") +bazel_dep(name = "fory", version = "1.3.0") git_override( module_name = "fory", remote = "https://github.com/apache/fory.git", - commit = "v1.2.0", # 或使用特定 commit hash 以确保可复现性 + commit = "v1.3.0", # 或使用特定 commit hash 以确保可复现性 ) ``` @@ -126,7 +126,7 @@ bazel run //:my_app 对于本地开发,也可以改用 `local_path_override`: ```bazel -bazel_dep(name = "fory", version = "1.2.0") +bazel_dep(name = "fory", version = "1.3.0") local_path_override( module_name = "fory", path = "/path/to/fory", diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/csharp/configuration.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/csharp/configuration.md index 9af3ce18e24..b4a21f22696 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/csharp/configuration.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/csharp/configuration.md @@ -35,12 +35,16 @@ ThreadSafeFory threadSafe = Fory.Builder().BuildThreadSafe(); `Fory.Builder().Build()` 使用以下默认值: -| 选项 | 默认值 | 说明 | -| -------------------- | -------- | ---------------------------------- | -| `TrackRef` | `false` | 默认关闭引用跟踪 | -| `Compatible` | `false` | Schema 一致模式,不写入演进元数据 | -| `CheckStructVersion` | `false` | 默认关闭结构体 schema hash 校验 | -| `MaxDepth` | `20` | 动态对象图的最大嵌套深度 | +| 选项 | 默认值 | 说明 | +| --------------------------------- | ------- | --------------------------------- | +| `TrackRef` | `false` | 默认关闭引用跟踪 | +| `Compatible` | `false` | Schema 一致模式,不写入演进元数据 | +| `CheckStructVersion` | `false` | 默认关闭结构体 schema hash 校验 | +| `MaxDepth` | `20` | 动态对象图的最大嵌套深度 | +| `MaxTypeFields` | `512` | 一个收到的 struct metadata body 最大字段数 | +| `MaxTypeMetaBytes` | `4096` | 一个收到的 metadata body 最大编码字节数 | +| `MaxSchemaVersionsPerType` | `10` | 一个逻辑类型最大远端 metadata 版本数 | +| `MaxAverageSchemaVersionsPerType` | `3` | 所有远端类型的平均 metadata 版本数 | ## 构建器选项 @@ -88,6 +92,46 @@ Fory fory = Fory.Builder() `value` 必须大于 `0`。 +### `MaxTypeFields(int value)` + +设置一个收到的远端 struct metadata body 中可接受的最大字段数。 + +```csharp +Fory fory = Fory.Builder() + .MaxTypeFields(512) + .Build(); +``` + +### `MaxTypeMetaBytes(int value)` + +设置一个收到的 TypeMeta body 可接受的最大编码 body 字节数,不包含 8 字节 header 和扩展 size varint。 + +```csharp +Fory fory = Fory.Builder() + .MaxTypeMetaBytes(4096) + .Build(); +``` + +### `MaxSchemaVersionsPerType(int value)` + +设置一个逻辑类型可接受的最大远端 metadata 版本数。 + +```csharp +Fory fory = Fory.Builder() + .MaxSchemaVersionsPerType(10) + .Build(); +``` + +### `MaxAverageSchemaVersionsPerType(int value)` + +设置所有已接受远端类型的平均 metadata 版本数限制。有效全局下限为 `8192` 个 schema。 + +```csharp +Fory fory = Fory.Builder() + .MaxAverageSchemaVersionsPerType(3) + .Build(); +``` + ## 常见配置 ### 追求速度的 Schema 一致服务 @@ -117,6 +161,16 @@ ThreadSafeFory fory = Fory.Builder() .BuildThreadSafe(); ``` +## 安全 + +安全相关配置: + +- 在反序列化不可信 payload 前,只注册预期的类型。 +- 对 intentional same-schema payload,将 `CheckStructVersion(true)` 与 `Compatible(false)` 配合使用。 +- 设置 `MaxDepth(...)` 以拒绝异常深的动态对象图。 +- 除非数据不是恶意输入,且可信 peer 会发送更大的 metadata 或大量 schema 版本,否则保持远端 schema metadata 限制的默认值。 +- 对不可信输入,优先使用生成或已注册的具体 model,避免宽泛的动态字段。 + ## 相关主题 - [基础序列化](basic_serialization) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/csharp/grpc-support.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/csharp/grpc-support.md index d9d7636e07a..d68d388e6e1 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/csharp/grpc-support.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/csharp/grpc-support.md @@ -32,7 +32,7 @@ Server project: ```xml - + ``` @@ -41,7 +41,7 @@ Client project: ```xml - + diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/csharp/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/csharp/index.md index 94d9c269e5f..5e7137c8454 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/csharp/index.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/csharp/index.md @@ -43,7 +43,7 @@ Apache Fory™ C# 是面向 .NET 的高性能跨语言序列化运行时。它 ```xml - + ``` diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/dart/configuration.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/dart/configuration.md index b93aa2839ee..93d5a07eeba 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/dart/configuration.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/dart/configuration.md @@ -35,6 +35,10 @@ final fory = Fory(); final fory = Fory( compatible: true, maxDepth: 512, + maxTypeFields: 512, + maxTypeMetaBytes: 4096, + maxSchemaVersionsPerType: 10, + maxAverageSchemaVersionsPerType: 3, ); ``` @@ -80,6 +84,24 @@ final fory = Fory( final fory = Fory(maxDepth: 128); ``` +### 远端 Schema Metadata 限制 + +兼容模式可能接收用于 Schema 演进的远端 metadata。以下限制用于约束 metadata 大小和可接受的 schema 版本数: + +```dart +final fory = Fory( + maxTypeFields: 512, + maxTypeMetaBytes: 4096, + maxSchemaVersionsPerType: 10, + maxAverageSchemaVersionsPerType: 3, +); +``` + +- `maxTypeFields` 限制一个收到的 struct metadata body 中的字段数。 +- `maxTypeMetaBytes` 限制一个收到的 TypeMeta body 的编码 body 字节数,不包含 8 字节 header 和扩展 size varint。 +- `maxSchemaVersionsPerType` 限制一个逻辑类型可接受的远端 metadata 版本数。 +- `maxAverageSchemaVersionsPerType` 限制所有已接受远端类型的平均版本数;有效全局下限为 `8192` 个 schema。 + ### `maxCollectionSize` 任意单个 list、set 或 map 字段可接受的最大元素数。用于防止畸形消息触发失控的内存分配。 @@ -98,13 +120,17 @@ final fory = Fory(maxBinarySize: 8 * 1024 * 1024); ## 默认值 -| 选项 | 默认值 | -| -------------------- | --------- | -| `compatible` | `false` | -| `checkStructVersion` | `true` | -| `maxDepth` | 256 | -| `maxCollectionSize` | 1 048 576 | -| `maxBinarySize` | 64 MiB | +| 选项 | 默认值 | +| --------------------------------- | --------- | +| `compatible` | `false` | +| `checkStructVersion` | `true` | +| `maxDepth` | 256 | +| `maxTypeFields` | 512 | +| `maxTypeMetaBytes` | 4096 | +| `maxSchemaVersionsPerType` | 10 | +| `maxAverageSchemaVersionsPerType` | 3 | +| `maxCollectionSize` | 1 048 576 | +| `maxBinarySize` | 64 MiB | ## 跨语言说明 @@ -113,6 +139,7 @@ final fory = Fory(maxBinarySize: 8 * 1024 * 1024); - 如果任意一端需要 Schema 演进,则**所有**端都应设置 `compatible: true`。 - 每一端都要使用相同的数字 ID,或者相同的 `namespace + typeName` 组合。 - 写端和读端的 `compatible` 设置必须一致,模式不匹配会直接失败。 +- 除非数据不是恶意输入,且可信 peer 会发送更大的 metadata 或大量 schema 版本,否则保持远端 schema metadata 限制的默认值。 ## 相关主题 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/dart/grpc-support.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/dart/grpc-support.md new file mode 100644 index 00000000000..333a9b69b29 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/dart/grpc-support.md @@ -0,0 +1,284 @@ +--- +title: gRPC 支持 +sidebar_position: 12 +id: grpc_support +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. +--- + +Fory 可以为包含 service 定义的 schema 生成 Dart gRPC service companion。 +生成代码使用标准 `package:grpc` client、service base、method descriptor、 +call option、deadline、取消和 status code;request 和 response 对象则使用 +Fory 序列化,而不是 protobuf。 + +当 RPC 两端都由同一份 Fory IDL、protobuf IDL 或 FlatBuffers IDL 生成,并且 +两端都期望 Fory 编码的 message body 时,可以使用这种模式。如果 API 必须被通用 +protobuf client、reflection 工具或期望 protobuf message bytes 的组件消费,请使用 +标准 protobuf gRPC 代码生成。 + +## 添加依赖 + +`fory` package 不会加入 gRPC 依赖。编译或运行生成 service companion 的应用需要 +添加 `grpc`,同时添加用于生成 Fory serializer 代码的 `build_runner` dev dependency: + +```yaml +dependencies: + fory: ^1.3.0 + grpc: ^4.0.0 + +dev_dependencies: + build_runner: ^2.4.0 +``` + +client 和 server 应用使用同一组依赖。 + +## 定义 Service + +Service 定义可以来自 Fory IDL、protobuf IDL 或 FlatBuffers `rpc_service` 定义。 +Fory IDL service 示例: + +```protobuf +package demo.greeter; + +message HelloRequest { + string name = 1; +} + +message HelloReply { + string reply = 1; +} + +service Greeter { + rpc SayHello (HelloRequest) returns (HelloReply); +} +``` + +使用 `--grpc` 生成 Dart model 和 gRPC companion 代码: + +```bash +foryc service.fdl --dart_out=./lib/generated --grpc +``` + +然后运行一次 `build_runner`,为生成的 model 输出 Fory serializer part 文件。代码运行前 +必须执行这一步: + +```bash +dart run build_runner build --delete-conflicting-outputs +``` + +对这个 schema,Dart generator 会输出如下文件(model 文件和 module 名称来自 package +最后一段 `greeter`): + +| 文件 | 用途 | +| ------------------------------------------- | ----------------------------------------- | +| `demo/greeter/greeter.dart` | Fory model type 和 schema module | +| `demo/greeter/greeter.fory.dart` | Serializer 和注册逻辑,由 build_runner 生成 | +| `demo/greeter/greeter_grpc.dart` | gRPC client、service base 和 method descriptor | +| `GreeterForyModule` in `greeter.dart` | 生成类型的 Fory 注册 module | +| `GreeterServiceBase` in `greeter_grpc.dart` | server 实现使用的 base class | +| `GreeterClient` in `greeter_grpc.dart` | gRPC 调用使用的 client stub | + +生成的 client 和 service base 会自动获取可用的 `Fory`,并在首次使用时注册该 +schema 的类型,因此不需要手动注册。若需要共享自定义 `Fory`(例如已经配置了额外 +module 的实例),可在第一次 RPC 前调用一次 `GreeterForyModule.install(yourFory)`; +这是可选操作。 + +## 实现 Server + +继承生成的 `GreeterServiceBase`,并用 grpc-dart 的 `Server` 承载: + +```dart +import 'dart:io'; + +import 'package:grpc/grpc.dart'; +import 'demo/greeter/greeter.dart'; +import 'demo/greeter/greeter_grpc.dart'; + +class GreeterService extends GreeterServiceBase { + @override + Future sayHello(ServiceCall call, HelloRequest request) async { + final reply = HelloReply()..reply = 'Hello, ${request.name}'; + return reply; + } +} + +Future main() async { + final server = Server.create(services: [GreeterService()]); + await server.serve(address: InternetAddress.loopbackIPv4, port: 50051); +} +``` + +## 创建 Client + +通过 `ClientChannel` 使用生成的 client: + +```dart +import 'package:grpc/grpc.dart'; +import 'demo/greeter/greeter.dart'; +import 'demo/greeter/greeter_grpc.dart'; + +Future main() async { + final channel = ClientChannel( + 'localhost', + port: 50051, + options: const ChannelOptions( + credentials: ChannelCredentials.insecure(), + ), + ); + final client = GreeterClient(channel); + + final reply = await client.sayHello(HelloRequest()..name = 'Fory'); + print(reply.reply); + + await channel.shutdown(); +} +``` + +## Streaming RPC + +Fory service 定义可以使用相同的 gRPC streaming 形态: + +```protobuf +service Greeter { + rpc SayHello (HelloRequest) returns (HelloReply); + rpc LotsOfReplies (HelloRequest) returns (stream HelloReply); + rpc LotsOfGreetings (stream HelloRequest) returns (HelloReply); + rpc Chat (stream HelloRequest) returns (stream HelloReply); +} +``` + +生成的 Dart 方法遵循 grpc-dart 约定。单响应方法返回 `ResponseFuture` +(client-streaming 会用 `.single` 适配调用);streaming 响应方法返回 +`ResponseStream`。Server 端,单请求以 message type 传入,streaming 请求以 +`Stream` 传入;方法对单响应返回 `Future`,对 streaming 响应返回 `Stream`: + +| IDL shape | Client 方法 | Server 方法(override) | +| ----------------------------------------- | --------------------------------------------------- | ----------------------------------------------------- | +| `rpc A (Req) returns (Res)` | `ResponseFuture a(Req request, {CallOptions?})` | `Future a(ServiceCall call, Req request)` | +| `rpc A (Req) returns (stream Res)` | `ResponseStream a(Req request, {CallOptions?})` | `Stream a(ServiceCall call, Req request)` | +| `rpc A (stream Req) returns (Res)` | `ResponseFuture a(Stream request, {...})` | `Future a(ServiceCall call, Stream request)` | +| `rpc A (stream Req) returns (stream Res)` | `ResponseStream a(Stream request, {...})` | `Stream a(ServiceCall call, Stream request)` | + +Server 实现直接使用生成的 streaming 方法形态: + +```dart +class GreeterService extends GreeterServiceBase { + @override + Stream lotsOfReplies( + ServiceCall call, + HelloRequest request, + ) async* { + for (final greeting in ['Hello, ${request.name}', 'Welcome, ${request.name}']) { + yield HelloReply()..reply = greeting; + } + } + + @override + Future lotsOfGreetings( + ServiceCall call, + Stream request, + ) async { + final names = []; + await for (final message in request) { + names.add(message.name); + } + return HelloReply()..reply = names.join(', '); + } + + @override + Stream chat( + ServiceCall call, + Stream request, + ) async* { + await for (final message in request) { + yield HelloReply()..reply = 'Hello, ${message.name}'; + } + } +} +``` + +生成的 client 返回标准 grpc-dart 调用对象: + +```dart +// Server streaming. +await for (final reply in client.lotsOfReplies(HelloRequest()..name = 'Fory')) { + print(reply.reply); +} + +// Client streaming. +final summary = await client.lotsOfGreetings( + Stream.fromIterable([ + HelloRequest()..name = 'Ada', + HelloRequest()..name = 'Grace', + ]), +); +print(summary.reply); + +// Bidirectional streaming. +await for (final reply in client.chat( + Stream.fromIterable([HelloRequest()..name = 'Fory']), +)) { + print(reply.reply); +} +``` + +生成的 descriptor 会为 gRPC path 保留 IDL 中精确的 service 和 method 名称; +Dart 方法使用 camelCase 名称。 + +## 生成的 Module 名称 + +Dart model 文件和 schema module 按 package 的最后一段命名,而不是按 gRPC service +命名。(当 schema 没有 package 时,使用源文件 stem。) + +| Schema 输入(package) | Model 文件 | Schema module | +| ----------------------------- | ------------------- | ----------------------- | +| `service.fdl` (`demo.greeter`) | `greeter.dart` | `GreeterForyModule` | +| `api.fdl` (`demo.order_events`) | `order_events.dart` | `OrderEventsForyModule` | +| `greeter.fdl` (`demo.greeter`) | `greeter.dart` | `GreeterForyModule` | + +名为 `Greeter` 的 gRPC service 仍会生成 `_grpc.dart` companion,其中包含 +`GreeterClient` 和 `GreeterServiceBase`;它不会改变 schema module 名称。如果多个 +schema 文件使用同一个 package leaf,请将它们放到不同输出目录,或选择能生成不同 Dart +model 文件的 package/file 名称。 + +## gRPC 运行时行为 + +生成的 service 代码只替换 request 和 response 序列化。所有常规 gRPC 运行行为仍由你的 +gRPC stack 负责: + +- Deadline 和取消 +- TLS 和认证 +- 名称解析与负载均衡 +- Client 和 server interceptor +- Status code 和 metadata +- Channel 生命周期管理 + +## 故障排查 + +### 缺少 `package:grpc` 类型 + +将 `grpc` 加到应用依赖。生成的 Fory service 文件会 import grpc-dart API,但 `fory` +有意不依赖 gRPC。 + +### 生成代码引用缺失的 `.fory.dart` Part + +生成或重新生成 Dart source 后,运行 `dart run build_runner build --delete-conflicting-outputs`。 +Serializer part 文件由 `build_runner` 生成,不由 `foryc` 直接生成。 + +### Protobuf Client 无法解码 Service + +Fory gRPC companion 不使用 protobuf 编码格式。请为 Fory-generated service 使用 +Fory-generated client,或为通用 protobuf client 暴露单独的 protobuf service endpoint。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/dart/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/dart/index.md index 569fb7f5c1e..1d36db92336 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/dart/index.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/dart/index.md @@ -42,7 +42,7 @@ Apache Fory™ Dart 可以把 Dart 对象序列化为字节,再从字节反序 ```yaml dependencies: - fory: ^1.2.0 + fory: ^1.3.0 dev_dependencies: build_runner: ^2.4.0 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/go/configuration.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/go/configuration.md index 770e5a10228..87301e7ad86 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/go/configuration.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/go/configuration.md @@ -33,12 +33,16 @@ f := fory.New() 默认设置: -| Option | Default | Description | -| ---------- | ------- | ------------------------ | -| TrackRef | false | 关闭引用跟踪 | -| MaxDepth | 20 | 最大嵌套深度 | -| IsXlang | false | 关闭跨语言模式 | -| Compatible | false | 关闭 Schema 演进兼容模式 | +| Option | Default | Description | +| ------------------------------- | ------- | -------------------------------------- | +| TrackRef | false | 关闭引用跟踪 | +| MaxDepth | 20 | 最大嵌套深度 | +| IsXlang | false | 关闭跨语言模式 | +| Compatible | false | 关闭 Schema 演进兼容模式 | +| MaxTypeFields | 512 | 一个收到的 struct metadata body 最大字段数 | +| MaxTypeMetaBytes | 4096 | 一个收到的 metadata body 最大编码字节数 | +| MaxSchemaVersionsPerType | 10 | 一个逻辑类型最大远端 metadata 版本数 | +| MaxAverageSchemaVersionsPerType | 3 | 所有远端类型的平均 metadata 版本数 | ### 通过选项配置 @@ -47,6 +51,10 @@ f := fory.New( fory.WithTrackRef(true), fory.WithCompatible(true), fory.WithMaxDepth(10), + fory.WithMaxTypeFields(512), + fory.WithMaxTypeMetaBytes(4096), + fory.WithMaxSchemaVersionsPerType(10), + fory.WithMaxAverageSchemaVersionsPerType(3), ) ``` @@ -118,6 +126,38 @@ f := fory.New(fory.WithMaxDepth(30)) - 防护深层递归结构或恶意数据 - 超过限制会返回错误 +### WithMaxTypeFields + +设置一个收到的远端 struct metadata body 中可接受的最大字段数: + +```go +f := fory.New(fory.WithMaxTypeFields(512)) +``` + +### WithMaxTypeMetaBytes + +设置一个收到的 TypeDef body 可接受的最大编码 body 字节数,不包含 8 字节 header 和扩展 size varint: + +```go +f := fory.New(fory.WithMaxTypeMetaBytes(4096)) +``` + +### WithMaxSchemaVersionsPerType + +设置一个逻辑类型可接受的最大远端 metadata 版本数: + +```go +f := fory.New(fory.WithMaxSchemaVersionsPerType(10)) +``` + +### WithMaxAverageSchemaVersionsPerType + +设置所有已接受远端类型的平均 metadata 版本数限制。有效全局下限为 `8192` 个 schema: + +```go +f := fory.New(fory.WithMaxAverageSchemaVersionsPerType(3)) +``` + ### WithXlang 启用跨语言序列化模式: @@ -330,6 +370,7 @@ for req := range requests { 4. **需要长期保存数据时请复制**:默认实例返回的字节切片会在后续调用后失效。 5. **合理设置最大深度**:深层结构可适当调大,但需关注内存占用。 6. **Schema 可能演进时启用兼容模式**:服务版本存在结构差异时建议开启。 +7. **保留远端 metadata 限制默认值**:除非输入可信且 peer 确实会发送更大的 metadata 或大量 schema 版本。 ## 相关主题 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/go/grpc-support.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/go/grpc-support.md index d8cdd580bbf..21f5552dc98 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/go/grpc-support.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/go/grpc-support.md @@ -175,7 +175,7 @@ Fory service 支持 unary、server-streaming、client-streaming 和 bidirectiona - Bidirectional streaming 使用生成的 stream client/server interface。 - 每个 message frame 都使用生成 codec。 -## Service 行为 +## gRPC 运行时行为 生成的 service companion 只提供 Fory 序列化。deadline、取消、TLS、credential、unary/stream interceptor、status code、metadata、名称解析、负载均衡、连接生命周期和 backoff 等 Service 行为都遵循标准 grpc-go。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/java/compression.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/java/compression.md index e43ad76931a..35d44152f85 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/java/compression.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/java/compression.md @@ -84,7 +84,7 @@ CompressedArraySerializers.registerSerializers(fory); org.apache.fory fory-simd - 1.2.0 + 1.3.0 ``` diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/java/configuration.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/java/configuration.md index 49aa47f4a31..afdf2f5ef97 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/java/configuration.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/java/configuration.md @@ -38,6 +38,10 @@ license: | | `registerGuavaTypes` | 是否预注册 Guava 类型,例如 `RegularImmutableMap` / `RegularImmutableList`。这些类型不是公共 API,但看起来相当稳定。 | `true` | | `requireClassRegistration` | 关闭后可能允许未知类被反序列化,从而带来安全风险。 | `true` | | `maxDepth` | 设置反序列化的最大深度,超过时会抛出异常。可用于阻止反序列化 DDOS 攻击。 | `50` | +| `maxTypeFields` | 一个收到的远端 struct metadata body 中可接受的最大字段数。 | `512` | +| `maxTypeMetaBytes` | 一个收到的 TypeDef 或 TypeMeta body 可接受的最大编码 body 字节数,不包含 8 字节 header 和扩展 size varint。 | `4096` | +| `maxSchemaVersionsPerType` | 一个逻辑类型可接受的最大远端 metadata 版本数。 | `10` | +| `maxAverageSchemaVersionsPerType` | 所有已接受远端类型的平均 metadata 版本数限制;有效全局下限为 `8192` 个 metadata entry。 | `3` | | `suppressClassRegistrationWarnings` | 是否抑制类注册警告。这些警告可用于安全审计,但可能较为烦人,因此默认启用抑制。 | `true` | | `metaShareEnabled` | 启用或禁用元数据共享模式。 | 如果设置了 `CompatibleMode.COMPATIBLE` 则为 `true`,否则为 false。 | | `scopedMetaShareEnabled` | 作用域元数据共享只关注单次序列化过程。在该过程中创建或识别的元数据只归属于这次序列化,不会与其他序列化共享。 | 如果设置了 `CompatibleMode.COMPATIBLE` 则为 `true`,否则为 false。 | @@ -70,6 +74,17 @@ Fory fory = Fory.builder() .build(); ``` +## 安全 + +安全相关选项: + +- `requireClassRegistration(true)` 将反序列化限制为已注册类。 +- `withMaxDepth(...)` 拒绝异常深的对象图。 +- `withMaxTypeFields(...)` 和 `withMaxTypeMetaBytes(...)` 约束一个收到的远端 metadata body 的字段数和编码 body 大小。 +- `withMaxSchemaVersionsPerType(...)` 和 `withMaxAverageSchemaVersionsPerType(...)` 约束可接受的远端 metadata 版本数,但不改变注册、动态加载或 Schema 演进语义。 +- `withDeserializeUnknownClass(false)` 避免从 metadata 物化 unknown class。 +- `checkJdkClassSerializable(true)` 保持对 `java.*` class 的 JDK serializability 检查。 + ## 相关主题 - [字段配置](schema-metadata.md) - `@ForyField`、`@Ignore` 与整数编码注解 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/java/grpc-support.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/java/grpc-support.md index 23ede2278f2..15c26ea22f3 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/java/grpc-support.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/java/grpc-support.md @@ -354,7 +354,7 @@ final class StreamingClient { 生成 descriptor 会保留 IDL 中的 service 和 method 名称作为 gRPC path。 -## Service 行为 +## gRPC 运行时行为 生成的 service code 只替换 request/response 序列化。常规 gRPC service 行为仍由 grpc-java 提供: diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/javascript/configuration.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/javascript/configuration.md index e107aac1f53..0f52349daa8 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/javascript/configuration.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/javascript/configuration.md @@ -41,22 +41,30 @@ const fory = new Fory({ ref: true, compatible: true, maxDepth: 100, + maxTypeFields: 512, + maxTypeMetaBytes: 4096, + maxSchemaVersionsPerType: 10, + maxAverageSchemaVersionsPerType: 3, maxBinarySize: 64 * 1024 * 1024, maxCollectionSize: 1_000_000, hps, }); ``` -| 选项 | 默认值 | 说明 | -| -------------------------- | ----------- | ---------------------------------------------------------------- | -| `ref` | `false` | 为共享或循环对象图启用引用跟踪 | -| `compatible` | `true` | 允许新增或删除字段而不破坏现有消息 | -| `maxDepth` | `50` | 最大嵌套深度。必须 `>= 2`。对于深度嵌套结构可以调大 | -| `maxBinarySize` | 64 MiB | 任意单个二进制字段可接受的最大字节数 | -| `maxCollectionSize` | `1_000_000` | 任意 list、set 或 map 可接受的最大元素数 | -| `useSliceString` | `false` | Node.js 的可选字符串读取优化。除非已做基准测试,否则保持默认值 | -| `hps` | 未设置 | 来自 `@apache-fory/hps` 的可选快速字符串辅助库(Node.js 20+) | -| `hooks.afterCodeGenerated` | 未设置 | 用于检查生成的序列化器代码的回调,便于调试 | +| 选项 | 默认值 | 说明 | +| --------------------------------- | ----------- | ---------------------------------------------------------------- | +| `ref` | `false` | 为共享或循环对象图启用引用跟踪 | +| `compatible` | `true` | 允许新增或删除字段而不破坏现有消息 | +| `maxDepth` | `50` | 最大嵌套深度。必须 `>= 2`。对于深度嵌套结构可以调大 | +| `maxTypeFields` | `512` | 一个收到的远端 struct metadata body 最大字段数 | +| `maxTypeMetaBytes` | `4096` | 一个收到的 TypeMeta body 最大编码字节数 | +| `maxSchemaVersionsPerType` | `10` | 一个逻辑类型最大远端 metadata 版本数 | +| `maxAverageSchemaVersionsPerType` | `3` | 所有远端类型的平均 metadata 版本数 | +| `maxBinarySize` | 64 MiB | 任意单个二进制字段可接受的最大字节数 | +| `maxCollectionSize` | `1_000_000` | 任意 list、set 或 map 可接受的最大元素数 | +| `useSliceString` | `false` | Node.js 的可选字符串读取优化。除非已做基准测试,否则保持默认值 | +| `hps` | 未设置 | 来自 `@apache-fory/hps` 的可选快速字符串辅助库(Node.js 20+) | +| `hooks.afterCodeGenerated` | 未设置 | 用于检查生成的序列化器代码的回调,便于调试 | ## 引用跟踪 @@ -96,6 +104,8 @@ const fory = new Fory({ hps }); - 在反序列化不可信载荷前,只注册预期的 schema。 - 根据服务可接受的最大载荷形状设置 `maxDepth`、`maxBinarySize` 和 `maxCollectionSize`。 +- 除非数据不是恶意输入,且可信 peer 会发送更大的远端 metadata,否则保持 `maxTypeFields` 和 `maxTypeMetaBytes` 的默认值。 +- 除非数据不是恶意输入,且可信 peer 会发送大量远端 schema 版本,否则保持 `maxSchemaVersionsPerType` 和 `maxAverageSchemaVersionsPerType` 的默认值。 - 对不可信输入,优先使用显式的 `Type.struct(...)` schema,而不是 `Type.any()`。 - 只传入与你部署的运行时版本配套的官方包中的 `hps`。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/javascript/grpc-support.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/javascript/grpc-support.md index f0cde1f61cc..95e83fe1e42 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/javascript/grpc-support.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/javascript/grpc-support.md @@ -278,7 +278,7 @@ stream.on("end", () => { }); ``` -## Service 行为 +## gRPC 运行时行为 生成的 service code 只替换 request/response 序列化。常规 gRPC service 行为仍由 transport package 提供: diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/kotlin/configuration.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/kotlin/configuration.md index 67d1619599e..1586775c3bd 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/kotlin/configuration.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/kotlin/configuration.md @@ -107,6 +107,9 @@ val fory = ForyKotlin.builder().withXlang(false) .withRefTracking(true) // Enable schema evolution support for native-mode payloads .withCompatible(true) + // Bound remote schema metadata resource usage + .withMaxTypeFields(512) + .withMaxTypeMetaBytes(4096) // Enable async compilation for better startup performance .withAsyncCompilation(true) // Compression options @@ -114,3 +117,23 @@ val fory = ForyKotlin.builder().withXlang(false) .withLongCompressed(true) .build() ``` + +## 安全 + +生产环境以及任何不受信任的 payload 来源都应保持启用类注册: + +```kotlin +val fory = ForyKotlin.builder() + .requireClassRegistration(true) + .withMaxDepth(50) + .withMaxTypeFields(512) + .withMaxTypeMetaBytes(4096) + .build() +``` + +安全相关配置: + +- 保持 `requireClassRegistration(true)`,并注册应用类或生成的 module。 +- 使用 `withMaxDepth(...)` 拒绝异常深的对象图。 +- 除非数据不是恶意输入,且可信 peer 会发送更大的 metadata 或大量 schema 版本,否则保持 `withMaxTypeFields(...)`、`withMaxTypeMetaBytes(...)` 以及远端 schema-version 限制的默认值。 +- Allow-listing 和 unknown-class 控制请遵循 [Java 配置](../java/configuration.md#forybuilder-选项)。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/kotlin/grpc-support.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/kotlin/grpc-support.md index 89cc9893f2e..ce9b7b81fa5 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/kotlin/grpc-support.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/kotlin/grpc-support.md @@ -219,7 +219,7 @@ stub.chat( } ``` -## Service 行为 +## gRPC 运行时行为 生成的 service code 只替换 request/response 序列化。常规 gRPC service 行为仍由 grpc-java 和 grpc-kotlin 提供: diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/kotlin/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/kotlin/index.md index 859dfa00f9d..d5a9007d94a 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/kotlin/index.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/kotlin/index.md @@ -50,14 +50,14 @@ Fory Kotlin 继承了 Fory Java 的全部特性,并增加了 Kotlin 特定优 org.apache.fory fory-kotlin - 1.2.0 + 1.3.0 ``` ### Gradle ```kotlin -implementation("org.apache.fory:fory-kotlin:1.2.0") +implementation("org.apache.fory:fory-kotlin:1.3.0") ``` ## 快速开始 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/python/configuration.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/python/configuration.md index 203d7e5a576..4e5158b03b9 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/python/configuration.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/python/configuration.md @@ -33,7 +33,11 @@ class Fory: ref: bool = False, strict: bool = True, compatible: bool = False, - max_depth: int = 50 + max_depth: int = 50, + max_type_fields: int = 512, + max_type_meta_bytes: int = 4096, + max_schema_versions_per_type: int = 10, + max_average_schema_versions_per_type: int = 3 ) ``` @@ -49,19 +53,27 @@ class ThreadSafeFory: ref: bool = False, strict: bool = True, compatible: bool = False, - max_depth: int = 50 + max_depth: int = 50, + max_type_fields: int = 512, + max_type_meta_bytes: int = 4096, + max_schema_versions_per_type: int = 10, + max_average_schema_versions_per_type: int = 3 ) ``` ## 参数 -| 参数 | 类型 | 默认值 | 描述 | -| ------------ | ------ | ------- | --------------------------------------------------------------------------------------------------------------------------------------- | -| `xlang` | `bool` | `False` | 启用跨语言序列化。当为 `False` 时,启用 Python 原生模式,支持所有 Python 对象。当为 `True` 时,启用跨语言模式,兼容 Java、Go、Rust 等。 | -| `ref` | `bool` | `False` | 启用引用跟踪以支持共享/循环引用。如果数据没有共享引用,禁用此选项可获得更好性能。 | -| `strict` | `bool` | `True` | 出于安全考虑需要类型注册。**生产环境强烈推荐**。仅在受信任的环境中禁用。 | -| `compatible` | `bool` | `False` | 在跨语言模式中启用 schema 演化,允许在保持兼容性的同时添加/删除字段。 | -| `max_depth` | `int` | `50` | 反序列化的最大深度,用于安全防护,防止栈溢出攻击。 | +| 参数 | 类型 | 默认值 | 描述 | +| -------------------------------------- | ------ | ------- | --------------------------------------------------------------------------------------------------------------------------------------- | +| `xlang` | `bool` | `False` | 启用跨语言序列化。当为 `False` 时,启用 Python 原生模式,支持所有 Python 对象。当为 `True` 时,启用跨语言模式,兼容 Java、Go、Rust 等。 | +| `ref` | `bool` | `False` | 启用引用跟踪以支持共享/循环引用。如果数据没有共享引用,禁用此选项可获得更好性能。 | +| `strict` | `bool` | `True` | 出于安全考虑需要类型注册。**生产环境强烈推荐**。仅在受信任的环境中禁用。 | +| `compatible` | `bool` | `False` | 在跨语言模式中启用 schema 演化,允许在保持兼容性的同时添加/删除字段。 | +| `max_depth` | `int` | `50` | 反序列化的最大深度,用于安全防护,防止栈溢出攻击。 | +| `max_type_fields` | `int` | `512` | 一个收到的远端 struct metadata body 中可接受的最大字段数。 | +| `max_type_meta_bytes` | `int` | `4096` | 一个收到的 TypeDef body 可接受的最大编码 body 字节数,不包含 8 字节 header 和扩展 size varint。 | +| `max_schema_versions_per_type` | `int` | `10` | 一个逻辑类型可接受的最大远端 metadata 版本数。 | +| `max_average_schema_versions_per_type` | `int` | `3` | 所有已接受远端类型的平均 metadata 版本数限制;有效全局下限为 `8192` 个 schema。 | ## 核心方法 @@ -151,7 +163,11 @@ fory = pyfory.Fory( ref=False, # 如果有共享/循环引用则启用 strict=True, # 关键:生产环境始终为 True compatible=False, # 仅在需要 schema 演化时启用 - max_depth=20 # 根据数据结构深度调整 + max_depth=20, # 根据数据结构深度调整 + max_type_fields=512, + max_type_meta_bytes=4096, + max_schema_versions_per_type=10, + max_average_schema_versions_per_type=3, ) # 预先注册所有类型 @@ -160,6 +176,15 @@ fory.register(OrderModel, type_id=101) fory.register(ProductModel, type_id=102) ``` +收到的远端 metadata 也会受到限制: + +- `max_type_fields` 限制一个收到的 struct metadata body 中的字段数。 +- `max_type_meta_bytes` 限制一个收到的 TypeDef body 可接受的编码 body 字节数。 +- `max_schema_versions_per_type` 限制一个逻辑类型可接受的远端 metadata 版本数。 +- `max_average_schema_versions_per_type` 限制所有已接受远端类型的平均版本数。 + +这些限制不会改变 `strict`、policy、动态加载、unknown-class handling 或 Schema 演进语义。 + ### 开发环境配置 ```python diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/python/grpc-support.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/python/grpc-support.md index 0ee9beb11ed..c2579e7cfa9 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/python/grpc-support.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/python/grpc-support.md @@ -1,6 +1,6 @@ --- title: gRPC 支持 -sidebar_position: 9 +sidebar_position: 13 id: grpc_support license: | Licensed to the Apache Software Foundation (ASF) under one or more @@ -19,30 +19,31 @@ license: | limitations under the License. --- -Fory 可以为包含 service 定义的 schema 生成 Python gRPC companion module。生成代码使用 -`grpcio` 负责传输,request 和 response 对象使用 `pyfory` 序列化。 +Fory 可以为包含 service 定义的 schema 生成 Python gRPC service companion。生成 module +使用 `grpcio` 负责传输,并使用 Fory 序列化 request 和 response 对象。 -当两端都由同一份 Fory IDL、protobuf IDL 或 FlatBuffers IDL 生成,并且你希望使用 gRPC -传输语义与 Fory payload 编码时,可以使用这种模式。如果客户端或工具必须直接消费 protobuf -message bytes,请使用标准 protobuf gRPC 代码生成。 +当每个 RPC peer 都由同一份 Fory IDL、protobuf IDL 或 FlatBuffers IDL 生成,并且你希望 +使用 gRPC 传输语义与 Fory payload 编码时,可以使用这种模式。如果 client 或工具必须直接 +消费 protobuf message bytes,请使用标准 protobuf gRPC 代码生成。 -当前生成的 Python companion 面向同步 `grpcio` API。请使用普通 `def` servicer 方法、 -`grpc.server(...)`、标准 `grpc.Channel` 实例,并用 Python iterator/generator 处理 streaming RPC。 -生成的 stub 可以接收应用自行配置的任意 channel。Compiler 不会生成 `grpc.aio` stub 或 service -base,因此不要把生成 servicer 方法实现成 `async def`,除非你在生成 companion 外自行封装 adapter。 -基于 `grpc.aio` 的 Python gRPC async 支持将在下一个 Fory 版本提供。 +Python gRPC 生成默认使用 `grpc.aio` AsyncIO API。生成的 servicer base 使用 +`async def` 方法,生成的 stub 搭配 `grpc.aio.Channel` 实例使用,streaming RPC 使用 +async iterable。同步 `grpcio` companion 仍可通过 `--grpc-python-mode=sync` 生成。 -## 添加依赖 +## 安装依赖 + +将 `grpcio` 与 `pyfory` 一起安装。生成的 companion 会 import `grpc`,并且在默认模式下 +import `grpc.aio`;但 `pyfory` 不会把 gRPC 作为硬依赖。 ```bash pip install pyfory grpcio ``` -Fory Python package 不会把 gRPC 作为硬依赖;只有编译或运行生成 gRPC companion 的应用需要安装 -`grpcio`。 - ## 定义 Service +Service 定义可以来自 Fory IDL、protobuf IDL 或 FlatBuffers `rpc_service` 定义。 +Fory IDL service 示例: + ```protobuf package demo.greeter; @@ -59,88 +60,96 @@ service Greeter { } ``` -生成 Python model 和 gRPC companion: +使用 `--grpc` 生成 Python model 和 gRPC companion 代码: ```bash foryc service.fdl --python_out=./generated/python --grpc ``` -该 schema 会生成: +对这个 schema,Python generator 会输出: -| 文件 | 用途 | -| ---------------------- | --------------------------------------- | -| `demo_greeter.py` | Fory dataclass 和注册辅助逻辑 | -| `demo_greeter_grpc.py` | `grpcio` stub、servicer base 和注册函数 | +| 文件 | 用途 | +| ---------------------- | ----------------------------------------- | +| `demo_greeter.py` | Fory dataclass 和注册辅助逻辑 | +| `demo_greeter_grpc.py` | `grpc.aio` stub、servicer base 和注册函数 | -Module 名称来自 Fory package,点号会替换成下划线;没有 package 的 schema 使用 `generated.py` 和 -`generated_grpc.py`。 +Module 名称来自 Fory package,点号会替换为下划线。没有 package 的 schema 使用 +`generated.py` 和 `generated_grpc.py`。 -## 实现 Server +## 实现 Async Server + +继承生成的 servicer,并将它注册到 `grpc.aio` server。生成的 Python 方法名使用 +snake_case,而 gRPC wire path 保留原始 IDL method 名称。 ```python -from concurrent import futures +import asyncio -import grpc +import grpc.aio import demo_greeter import demo_greeter_grpc class Greeter(demo_greeter_grpc.GreeterServicer): - def say_hello(self, request, context): + async def say_hello(self, request, context): return demo_greeter.HelloReply(reply=f"Hello, {request.name}") -def serve(): - server = grpc.server(futures.ThreadPoolExecutor(max_workers=8)) +async def serve(): + server = grpc.aio.server() demo_greeter_grpc.add_servicer(Greeter(), server) server.add_insecure_port("[::]:50051") - server.start() - server.wait_for_termination() + await server.start() + await server.wait_for_termination() if __name__ == "__main__": - serve() + asyncio.run(serve()) ``` -## 创建 Client +生成的 request 和 response 类型由生成 companion 序列化,因此 service 实现不需要手动 +执行 Fory 注册。 + +## 创建 Async Client -使用生成的 stub 和普通 `grpcio` channel。生产 client 通常传入配置了 TLS/认证的 channel: +通过 `grpc.aio` channel 使用生成的 stub。生产 client 通常传入配置了 TLS/认证的 channel: ```python +import asyncio + import grpc +import grpc.aio import demo_greeter import demo_greeter_grpc -def main(): +async def main(): credentials = grpc.ssl_channel_credentials() - with grpc.secure_channel("api.example.com:443", credentials) as channel: + async with grpc.aio.secure_channel("api.example.com:443", credentials) as channel: stub = demo_greeter_grpc.GreeterStub(channel) - reply = stub.say_hello(demo_greeter.HelloRequest(name="Fory")) + reply = await stub.say_hello(demo_greeter.HelloRequest(name="Fory")) print(reply.reply) if __name__ == "__main__": - main() + asyncio.run(main()) ``` 本地测试和开发可以显式使用 insecure channel: ```python -# 仅用于本地测试和开发。 -# 生产环境请使用配置了 TLS/认证的 grpc.Channel。 -with grpc.insecure_channel("localhost:50051") as channel: +# Test-only channel. Use a TLS/auth-configured grpc.aio.Channel in production. +async with grpc.aio.insecure_channel("localhost:50051") as channel: stub = demo_greeter_grpc.GreeterStub(channel) ``` -Channel、credential、deadline、metadata、interceptor、retry 和 server lifecycle 都保持 `grpcio` -行为。 +Channel option、credential、deadline、metadata、retry 和 interceptor 仍由 `grpcio` 负责。 ## Streaming RPC -Fory service 可以使用 unary、server-streaming、client-streaming 和 bidirectional streaming: +Fory service 定义可以使用 unary、server-streaming、client-streaming 和 bidirectional +streaming RPC 形态: ```protobuf service Greeter { @@ -151,72 +160,127 @@ service Greeter { } ``` -生成 Python companion 遵循 `grpcio` 的 iterator/generator 约定: +默认 Python gRPC 输出遵循 `grpc.aio` 约定: -| IDL shape | Servicer 方法形态 | Stub 方法形态 | -| ----------------------------------------- | ----------------------------------------- | ---------------------- | -| `rpc A (Req) returns (Res)` | 返回一个 response 对象 | 返回一个 response 对象 | -| `rpc A (Req) returns (stream Res)` | yield 多个 response 对象 | 返回 response iterator | -| `rpc A (stream Req) returns (Res)` | 消费 request iterator 并返回一个 response | 接收 request iterator | -| `rpc A (stream Req) returns (stream Res)` | 消费 request iterator 并 yield response | 接收并返回 iterator | +| IDL shape | Servicer 方法形态 | Stub 方法形态 | +| ----------------------------------------- | ---------------------------------------------- | ------------------------------ | +| `rpc A (Req) returns (Res)` | `async def` 返回一个 response 对象 | awaitable 返回一个 response 对象 | +| `rpc A (Req) returns (stream Res)` | `async def` yield response 对象 | 返回 response async iterator | +| `rpc A (stream Req) returns (Res)` | 消费 async iterator 并返回 response | 接收 request async iterator | +| `rpc A (stream Req) returns (stream Res)` | 消费并 yield async iterator | 接收并返回 async iterator | -Servicer 方法使用 snake_case 名称;生成 descriptor 会保留 IDL 中的 service 和 method 名称作为 -gRPC path。每个 message frame 都通过 Fory serializer/deserializer 编码。 +Servicer 方法使用 snake_case 名称;生成 descriptor 会保留精确的 IDL service 和 +method 名称作为 gRPC path。 -Server 可以直接使用 Python iterator: +Server 实现使用 async 方法和 async iteration: ```python class Greeter(demo_greeter_grpc.GreeterServicer): - def lots_of_replies(self, request, context): + async def lots_of_replies(self, request, context): yield demo_greeter.HelloReply(reply=f"Hello, {request.name}") yield demo_greeter.HelloReply(reply=f"Welcome, {request.name}") - def lots_of_greetings(self, request_iterator, context): - names = [request.name for request in request_iterator] + async def lots_of_greetings(self, request_iterator, context): + names = [] + async for request in request_iterator: + names.append(request.name) return demo_greeter.HelloReply(reply=", ".join(names)) - def chat(self, request_iterator, context): - for request in request_iterator: + async def chat(self, request_iterator, context): + async for request in request_iterator: yield demo_greeter.HelloReply(reply=f"Hello, {request.name}") ``` -生成的 client 使用标准 `grpcio` streaming 调用形态: +生成的 client 使用 `grpc.aio` streaming 调用形态: ```python credentials = grpc.ssl_channel_credentials() -with grpc.secure_channel("api.example.com:443", credentials) as channel: +async with grpc.aio.secure_channel("api.example.com:443", credentials) as channel: stub = demo_greeter_grpc.GreeterStub(channel) - for reply in stub.lots_of_replies( + async for reply in stub.lots_of_replies( demo_greeter.HelloRequest(name="Fory") ): print(reply.reply) - def greeting_requests(): + async def greeting_requests(): yield demo_greeter.HelloRequest(name="Ada") yield demo_greeter.HelloRequest(name="Grace") - summary = stub.lots_of_greetings(greeting_requests()) + summary = await stub.lots_of_greetings(greeting_requests()) print(summary.reply) - def chat_requests(): + async def chat_requests(): yield demo_greeter.HelloRequest(name="Fory") yield demo_greeter.HelloRequest(name="RPC") - for reply in stub.chat(chat_requests()): + async for reply in stub.chat(chat_requests()): print(reply.reply) ``` -## Service 行为 +## Sync 模式 + +已有同步 `grpcio` 应用,或不运行 asyncio event loop 的环境,可以使用 sync 模式。显式生成 +sync companion: + +```bash +foryc service.fdl --python_out=./generated/python --grpc --grpc-python-mode=sync +``` + +Sync 模式输出相同的 `_grpc.py` 文件名和 public name,但 servicer 方法使用普通 +`def`,应用使用 `grpc.server(...)` 和标准 `grpc.Channel` 实例。 + +Unary sync server 示例: + +```python +from concurrent import futures + +import grpc + +import demo_greeter +import demo_greeter_grpc + + +class Greeter(demo_greeter_grpc.GreeterServicer): + def say_hello(self, request, context): + return demo_greeter.HelloReply(reply=f"Hello, {request.name}") + + +server = grpc.server(futures.ThreadPoolExecutor(max_workers=8)) +demo_greeter_grpc.add_servicer(Greeter(), server) +server.add_insecure_port("[::]:50051") +server.start() +server.wait_for_termination() +``` + +Unary sync client 示例: + +```python +import grpc + +import demo_greeter +import demo_greeter_grpc + + +with grpc.insecure_channel("localhost:50051") as channel: + stub = demo_greeter_grpc.GreeterStub(channel) + reply = stub.say_hello(demo_greeter.HelloRequest(name="Fory")) + print(reply.reply) +``` + +Sync streaming 遵循普通 `grpcio` iterator 和 generator 约定。 + +## gRPC 运行时行为 -生成的 service companion 只提供 Fory serialization callback。Service 行为仍遵循标准 `grpcio`: +生成的 service companion 只提供 Fory serialization callback。运行行为仍遵循标准 +`grpcio`: - Deadline 和取消 - TLS 和认证 credential - Client/server interceptor - Status code、details 和 metadata -- Channel/server 生命周期 -- 同步 server 的线程池大小 +- 默认模式下的 async event loop、channel 和 server 生命周期 +- Sync 模式下同步 server 的线程池大小 ## 故障排查 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/rust/basic-serialization.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/rust/basic-serialization.md index 76bfe93fa64..c9f65dda280 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/rust/basic-serialization.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/rust/basic-serialization.md @@ -143,7 +143,7 @@ let later = timestamp.checked_add_duration(duration)?; ```toml [dependencies] -fory = { version = "1.2.0", features = ["chrono"] } +fory = { version = "1.3.0", features = ["chrono"] } ``` ### 自定义类型 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/rust/configuration.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/rust/configuration.md index 254846b0d97..e0a8d06a7e7 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/rust/configuration.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/rust/configuration.md @@ -74,6 +74,24 @@ let fory = Fory::default().max_dyn_depth(10); // 允许最多 10 层 注意:静态数据类型(非动态类型)本质上是安全的,不受深度限制约束,因为它们的结构在编译时就已知。 +### 远端 Schema Metadata 限制 + +兼容模式可能接收用于 Schema 演进的远端 metadata。以下限制用于约束 metadata 大小和可接受的 schema 版本数: + +```rust +let fory = Fory::builder() + .max_type_fields(512) + .max_type_meta_bytes(4096) + .max_schema_versions_per_type(10) + .max_average_schema_versions_per_type(3) + .build(); +``` + +- `max_type_fields` 默认值为 `512`,限制一个收到的 struct metadata body 中的字段数。 +- `max_type_meta_bytes` 默认值为 `4096`,限制一个收到的 TypeDef 或 TypeMeta body 的编码 body 字节数,不包含 8 字节 header 和扩展 size varint。 +- `max_schema_versions_per_type` 默认值为 `10`,限制一个逻辑类型可接受的远端 metadata 版本数。 +- `max_average_schema_versions_per_type` 默认值为 `3`,限制所有已接受远端类型的平均版本数;有效全局下限为 `8192` 个 schema。 + ### 跨语言模式 启用跨语言序列化: @@ -112,11 +130,22 @@ let fory = Fory::default() ## 配置摘要 -| 选项 | 描述 | 默认值 | -| -------------------- | ---------------------- | ------- | -| `compatible(bool)` | 启用 schema 演化 | `false` | -| `xlang(bool)` | 启用跨语言模式 | `false` | -| `max_dyn_depth(u32)` | 动态类型的最大嵌套深度 | `5` | +| 选项 | 描述 | 默认值 | +| --------------------------------------------- | --------------------------------- | ------- | +| `compatible(bool)` | 启用 schema 演化 | `false` | +| `xlang(bool)` | 启用跨语言模式 | `false` | +| `max_dyn_depth(u32)` | 动态类型的最大嵌套深度 | `5` | +| `max_type_fields(usize)` | 一个收到的 struct metadata body 最大字段数 | `512` | +| `max_type_meta_bytes(usize)` | 一个收到的 metadata body 最大编码字节数 | `4096` | +| `max_schema_versions_per_type(usize)` | 一个逻辑类型最大远端 metadata 版本数 | `10` | +| `max_average_schema_versions_per_type(usize)` | 所有远端类型的平均 metadata 版本数 | `3` | + +## 安全建议 + +- 反序列化不可信 payload 前,先注册应用 struct 和 trait-object 实现。 +- 使用 `max_dyn_depth(...)` 拒绝异常深的动态对象图。 +- 除非数据不是恶意输入,且可信 peer 会发送更大的 metadata 或大量 schema 版本,否则保持远端 schema metadata 限制的默认值。 +- 对不可信输入,优先使用具体类型字段,避免宽泛的 `dyn Any` 或 trait-object 字段。 ## 相关主题 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/rust/grpc-support.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/rust/grpc-support.md index de234fbd4ab..f48fd2fa119 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/rust/grpc-support.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/rust/grpc-support.md @@ -34,7 +34,7 @@ streaming response 或 request stream,可添加 `tokio-stream`。 ```toml [dependencies] -fory = "1.2.0" +fory = "1.3.0" bytes = "1" tonic = { version = "0.14", features = ["transport"] } tokio = { version = "1", features = ["macros", "rt-multi-thread"] } @@ -283,7 +283,7 @@ while let Some(reply) = chat.message().await? { Rust gRPC payload 必须满足 `Send + 'static`,这样 tonic 才能在线程间移动 request/response。 如果 request 或 response schema 使用非线程安全的引用元信息,Rust gRPC 生成会拒绝该 service。 -## Service 行为 +## gRPC 运行时行为 生成的 service companion 只提供 Fory 序列化和 tonic binding。deadline、取消、TLS、认证、 Tower middleware、interceptor、status code、metadata、channel/server 生命周期和 backpressure diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/rust/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/rust/index.md index d6349d2e66e..4b05b6f92dc 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/rust/index.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/rust/index.md @@ -47,7 +47,7 @@ Rust 实现提供灵活而高性能的序列化能力,具备自动内存管理 ```toml [dependencies] -fory = "1.2.0" +fory = "1.3.0" ``` ### 基础示例 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/scala/configuration.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/scala/configuration.md index 54141e8b04b..59edf6cf951 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/scala/configuration.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/scala/configuration.md @@ -160,6 +160,8 @@ Scala 使用 Java 运行时配置表面。生产环境以及任何不受信任 val fory = ForyScala.builder() .requireClassRegistration(true) .withMaxDepth(50) + .withMaxTypeFields(512) + .withMaxTypeMetaBytes(4096) .build() ``` @@ -167,4 +169,5 @@ val fory = ForyScala.builder() - 保持 `requireClassRegistration(true)`,并注册应用类或生成的 modules。 - 使用 `withMaxDepth(...)` 拒绝异常深的对象图。 +- 除非数据不是恶意输入,且可信 peer 会发送更大的 metadata 或大量 schema 版本,否则保持 `withMaxTypeFields(...)`、`withMaxTypeMetaBytes(...)` 以及远端 schema-version 限制的默认值。 - Allow-listing 和 unknown-class 控制请遵循 [Java 配置](../java/configuration.md#forybuilder-选项)。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/scala/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/scala/index.md index f04891a0d4c..e6bc455afb6 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/scala/index.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/scala/index.md @@ -48,7 +48,7 @@ Fory Scala 继承了 Fory Java 的全部特性,并增加了 Scala 特定优化 使用 sbt 添加依赖: ```sbt -libraryDependencies += "org.apache.fory" %% "fory-scala" % "1.2.0" +libraryDependencies += "org.apache.fory" %% "fory-scala" % "1.3.0" ``` ## 快速开始 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/swift/configuration.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/swift/configuration.md index 108e2785f08..31b76aae328 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/swift/configuration.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/swift/configuration.md @@ -30,6 +30,12 @@ public struct ForyConfig { public var xlang: Bool public var trackRef: Bool public var compatible: Bool + public let checkClassVersion: Bool + public let maxDepth: Int + public let maxTypeFields: Int + public let maxTypeMetaBytes: Int + public let maxSchemaVersionsPerType: Int + public let maxAverageSchemaVersionsPerType: Int } ``` @@ -78,6 +84,25 @@ let fory = Fory(xlang: true, trackRef: true) let fory = Fory(xlang: true, trackRef: false, compatible: true) ``` +### Size 和 Depth 限制 + +`maxDepth` 限制解码 payload 的嵌套深度。兼容模式下的远端 metadata 也会被限制: + +- `maxTypeFields` 默认值为 `512`,限制一个收到的 struct metadata body 中的字段数。 +- `maxTypeMetaBytes` 默认值为 `4096`,限制一个收到的 TypeMeta body 的编码 body 字节数,不包含 8 字节 header 和扩展 size varint。 +- `maxSchemaVersionsPerType` 默认值为 `10`,限制一个逻辑类型可接受的远端 metadata 版本数。 +- `maxAverageSchemaVersionsPerType` 默认值为 `3`,限制所有已接受远端类型的平均版本数;有效全局下限为 `8192` 个 schema。 + +```swift +let fory = Fory( + maxDepth: 5, + maxTypeFields: 512, + maxTypeMetaBytes: 4096, + maxSchemaVersionsPerType: 10, + maxAverageSchemaVersionsPerType: 3 +) +``` + ## 推荐配置 ### 本地严格 Schema @@ -97,3 +122,12 @@ let fory = Fory(xlang: true, trackRef: false, compatible: true) ```swift let fory = Fory(xlang: true, trackRef: true, compatible: true) ``` + +## 安全 + +安全相关配置: + +- 在反序列化不可信 payload 前,只注册预期的生成 model。 +- 对 intentional same-schema payload,将 `checkClassVersion` 与 `compatible: false` 配合使用。 +- 根据服务接受的最大嵌套深度设置 `maxDepth`。 +- 除非数据不是恶意输入,且可信 peer 会发送更大的 metadata 或大量 schema 版本,否则保持远端 schema metadata 限制的默认值。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/xlang/getting-started.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/xlang/getting-started.md index f2b9a89e772..55a6900400e 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/xlang/getting-started.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/xlang/getting-started.md @@ -31,14 +31,14 @@ license: | org.apache.fory fory-core - 1.2.0 + 1.3.0 ``` **Gradle:** ```gradle -implementation 'org.apache.fory:fory-core:1.2.0' +implementation 'org.apache.fory:fory-core:1.3.0' ``` ### Python @@ -57,7 +57,7 @@ go get github.com/apache/fory/go/fory ```toml [dependencies] -fory = "1.2.0" +fory = "1.3.0" ``` ### JavaScript/TypeScript @@ -75,13 +75,13 @@ npm install @apache-fory/core @apache-fory/hps ### C\# ```bash -dotnet add package Apache.Fory --version 1.2.0 +dotnet add package Apache.Fory --version 1.3.0 ``` ### Dart ```bash -dart pub add fory:^1.2.0 +dart pub add fory:^1.3.0 dart pub add dev:build_runner ``` @@ -91,20 +91,20 @@ dart pub add dev:build_runner ```swift dependencies: [ - .package(url: "https://github.com/apache/fory.git", exact: "1.2.0") + .package(url: "https://github.com/apache/fory.git", exact: "1.3.0") ] ``` ### Scala ```scala -libraryDependencies += "org.apache.fory" %% "fory-scala" % "1.2.0" +libraryDependencies += "org.apache.fory" %% "fory-scala" % "1.3.0" ``` ### Kotlin ```kotlin -implementation("org.apache.fory:fory-kotlin:1.2.0") +implementation("org.apache.fory:fory-kotlin:1.3.0") ``` ### C++ diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/xlang/serialization.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/xlang/serialization.md index f5bb2ecfae7..14cc126d8ab 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/xlang/serialization.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/xlang/serialization.md @@ -21,6 +21,33 @@ license: | 本页演示常见的跨语言序列化模式。当各端使用匹配的类型标识、字段 schema 和兼容性设置时,在一种受支持语言中序列化的数据可以在任何其他受支持语言中反序列化。 +## 远端 Schema Metadata 限制 + +兼容模式可能为 reader 尚未知的类型接收远端 metadata(`TypeDef` 或 `TypeMeta`)。Fory 会限制可接受的不同远端 metadata 版本数量,并限制每个收到的 metadata body 大小: + +- `maxSchemaVersionsPerType`:一个逻辑类型可接受的最大远端 metadata 版本数,默认值为 `10`。 +- `maxAverageSchemaVersionsPerType`:所有已接受远端类型的平均 metadata 版本数,默认值为 `3`;有效全局下限为 `8192` 个 metadata entry。 +- `maxTypeFields`:一个收到的 struct metadata body 可声明的最大字段数,默认值为 `512`。 +- `maxTypeMetaBytes`:一个收到的 TypeDef 或 TypeMeta body 的最大编码 metadata body 字节数,不包含 8 字节 header 和扩展 size varint,默认值为 `4096`。 + +这些限制是资源保护。它们不会改变编码格式、注册要求、动态类型加载、unknown-type handling 或 Schema 演进兼容性。 + +仅当数据不是恶意输入,且可信 peer 会发送更大的 metadata 或大量 schema 版本时,才调高这些值。 + +| 语言 | 字段数选项 | Metadata 字节选项 | 单类型版本选项 | 平均版本选项 | +| --------------------- | ------------------- | --------------------- | ------------------------------ | ------------------------------------- | +| Java | `withMaxTypeFields` | `withMaxTypeMetaBytes` | `withMaxSchemaVersionsPerType` | `withMaxAverageSchemaVersionsPerType` | +| Scala | `withMaxTypeFields` | `withMaxTypeMetaBytes` | `withMaxSchemaVersionsPerType` | `withMaxAverageSchemaVersionsPerType` | +| Kotlin | `withMaxTypeFields` | `withMaxTypeMetaBytes` | `withMaxSchemaVersionsPerType` | `withMaxAverageSchemaVersionsPerType` | +| Python | `max_type_fields` | `max_type_meta_bytes` | `max_schema_versions_per_type` | `max_average_schema_versions_per_type` | +| JavaScript/TypeScript | `maxTypeFields` | `maxTypeMetaBytes` | `maxSchemaVersionsPerType` | `maxAverageSchemaVersionsPerType` | +| C++ | `max_type_fields` | `max_type_meta_bytes` | `max_schema_versions_per_type` | `max_average_schema_versions_per_type` | +| Go | `WithMaxTypeFields` | `WithMaxTypeMetaBytes` | `WithMaxSchemaVersionsPerType` | `WithMaxAverageSchemaVersionsPerType` | +| Rust | `max_type_fields` | `max_type_meta_bytes` | `max_schema_versions_per_type` | `max_average_schema_versions_per_type` | +| C# | `MaxTypeFields` | `MaxTypeMetaBytes` | `MaxSchemaVersionsPerType` | `MaxAverageSchemaVersionsPerType` | +| Swift | `maxTypeFields` | `maxTypeMetaBytes` | `maxSchemaVersionsPerType` | `maxAverageSchemaVersionsPerType` | +| Dart | `maxTypeFields` | `maxTypeMetaBytes` | `maxSchemaVersionsPerType` | `maxAverageSchemaVersionsPerType` | + ## 序列化内置类型 常见类型可以自动序列化,无需注册:原始数值类型、字符串、二进制、数组、列表、映射等。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/specification/java_serialization_spec.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/specification/java_serialization_spec.md index 8bf9b6ec15f..e1e3a9796b4 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/specification/java_serialization_spec.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/specification/java_serialization_spec.md @@ -210,6 +210,10 @@ class_layer: | namespace | type_name | field_infos | ``` +Reader 可以拒绝超过运行时资源限制的 TypeDef,例如 metadata body 字节数上限或一个 +TypeDef 中的最大字段数。这些限制是接收侧资源控制,不改变 TypeDef 编码格式、类型标识、 +动态类加载、unknown-class handling、注册策略或 Schema 演进语义。 + ### Field info 每个字段: diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/specification/xlang_implementation_guide.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/specification/xlang_implementation_guide.md index 641a6ebed0b..67051522c17 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/specification/xlang_implementation_guide.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/specification/xlang_implementation_guide.md @@ -312,13 +312,28 @@ struct 专属的 schema/version 包装与 compatible-field staging - 运行时会在 struct 载荷前写入 schema hash - 读取侧会在读字段之前检查该 hash +## 远端 Metadata 资源限制 + +实现应在 cold metadata parse path 上限制收到的 metadata body 和 struct 字段列表。 +`maxTypeMetaBytes` 限制一个 TypeDef 或 TypeMeta 的编码 body,不包含 8 字节 header 和扩展 +size varint,并且应在复制或解压 body 前检查。`maxTypeFields` 限制一个收到的 struct metadata +body 声明的字段数,并应在预留或分配字段列表前检查。 + +这些限制是运行时资源控制;它们不会改变编码格式、类型标识、动态加载、unknown-type 行为、 +反序列化策略或 Schema 演进语义。Metadata cache hit 和生成的字段 reader 仍是 hot path,不应为这些限制增加额外工作。 + +远端 schema-version 限制属于同一个 cold metadata owner path。Header cache hit 必须跳过剩余 +metadata body,并返回已缓存 metadata;不要重新执行 schema-limit 检查、hash 复验、分配或 policy 工作。Header miss 时,应在同一个具体 owner path 中完成处理:证明并读取 metadata body bytes,按 header 校验 body,校验字段数,通过现有注册与反序列化策略检查解析类型,必要时按原始编码字节比较精确本地 metadata,对非本地远端 metadata 检查 schema-version 限制,构建所需 read state,发布到持久 metadata cache,最后记录 schema count。失败或不兼容的 metadata 不得发布到持久 cache,也不得消耗 schema-version count。 + +编码字节与本地已注册 metadata 精确匹配的远端 metadata,在完成选择本地类型所需的现有类型与反序列化策略检查后,可以使用本地 metadata,且不消耗远端 schema-version 限制。这个 exact-local bypass 不只适用于 struct;当 named enum、ext 和 union metadata 带有 metadata body 且与本地编码字节匹配时,同样适用。纯 id-based enum、ext 和 typed-union value 不携带 TypeDef 或 TypeMeta body,必须继续走普通 type-id 加 user-type-id 路径。兼容 named enum、ext 和 union metadata 通常只有一个版本,但当它作为 shared metadata 发送且不精确匹配本地 metadata 时,仍会计入已接受远端 metadata 总数。`maxTypeFields` 只适用于 struct 字段列表。 + ## Meta string 与共享类型元信息 xlang 类型元信息由两类显式状态支撑: - `MetaStringWriter` 和 `MetaStringReader` 负责去重与解码命名空间和类型名字串 -- 共享 TypeDef 的读写状态负责跟踪已声明的 compatible struct 元数据 +- 共享 TypeDef 的读写状态负责跟踪已声明的 TypeDef 元数据 所有权规则: diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/specification/xlang_serialization_spec.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/specification/xlang_serialization_spec.md index 4e2bfdcc74a..99d177cd12c 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/specification/xlang_serialization_spec.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/specification/xlang_serialization_spec.md @@ -203,6 +203,13 @@ TypeDef 头部包含: - 每层字段数量与类型标识 - 字段级元信息(名称编码/tag、nullable/ref、field type) +Reader 可以拒绝超过运行时资源限制的 TypeDef,例如 metadata body 字节数上限或一个 struct +TypeDef 中的最大字段数。这些限制是接收侧资源控制,不改变 TypeDef 编码格式、类型标识、 +动态加载、unknown-type handling、注册策略或 Schema 演进语义。 + +纯 id-based enum、ext 和 typed-union value 不携带 TypeDef body。接收侧 TypeDef 资源限制只在 +stream 实际携带 shared TypeDef metadata 时适用。 + ## Meta String meta string 用于 namespace、typename、fieldname 的压缩表示。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/start/install.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/start/install.md index 5c43f4d64b2..509dcf52dba 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/start/install.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/start/install.md @@ -16,14 +16,14 @@ Apache Fory™ 同时提供源码发布物和各语言对应的软件包。 org.apache.fory fory-core - 1.2.0 + 1.3.0 @@ -31,7 +31,7 @@ Apache Fory™ 同时提供源码发布物和各语言对应的软件包。 org.apache.fory fory-simd - 1.2.0 + 1.3.0 --> ``` @@ -44,7 +44,7 @@ Scala 2.13 的 Maven 依赖: org.apache.fory fory-scala_2.13 - 1.2.0 + 1.3.0 ``` @@ -54,20 +54,20 @@ Scala 3 的 Maven 依赖: org.apache.fory fory-scala_3 - 1.2.0 + 1.3.0 ``` Scala 2.13 的 sbt 依赖: ```sbt -libraryDependencies += "org.apache.fory" % "fory-scala_2.13" % "1.2.0" +libraryDependencies += "org.apache.fory" % "fory-scala_2.13" % "1.3.0" ``` Scala 3 的 sbt 依赖: ```sbt -libraryDependencies += "org.apache.fory" % "fory-scala_3" % "1.2.0" +libraryDependencies += "org.apache.fory" % "fory-scala_3" % "1.3.0" ``` ## Kotlin @@ -78,7 +78,7 @@ libraryDependencies += "org.apache.fory" % "fory-scala_3" % "1.2.0" org.apache.fory fory-kotlin - 1.2.0 + 1.3.0 ``` @@ -86,7 +86,7 @@ libraryDependencies += "org.apache.fory" % "fory-scala_3" % "1.2.0" ```bash python -m pip install --upgrade pip -pip install pyfory==1.2.0 +pip install pyfory==1.3.0 ``` ## Go @@ -94,7 +94,7 @@ pip install pyfory==1.2.0 请使用完整的 Go 模块路径 `github.com/apache/fory/go/fory`: ```bash -go get github.com/apache/fory/go/fory@v1.2.0 +go get github.com/apache/fory/go/fory@v1.3.0 ``` 如果你的 Go proxy 还没有同步新的子模块 tag,请稍后重试,或者临时使用 `GOPROXY=direct`。 @@ -103,13 +103,13 @@ go get github.com/apache/fory/go/fory@v1.2.0 ```toml [dependencies] -fory = "1.2.0" +fory = "1.3.0" ``` 或者使用 `cargo add`: ```bash -cargo add fory@1.2.0 +cargo add fory@1.3.0 ``` ## JavaScript / TypeScript @@ -132,7 +132,7 @@ npm install @apache-fory/hps ```yaml dependencies: - fory: ^1.2.0 + fory: ^1.3.0 dev_dependencies: build_runner: ^2.4.13 @@ -149,12 +149,12 @@ dart run build_runner build --delete-conflicting-outputs 安装 `Apache.Fory` NuGet 包。它同时包含运行时以及 `[ForyObject]` 类型所需的源代码生成器。 ```bash -dotnet add package Apache.Fory --version 1.2.0 +dotnet add package Apache.Fory --version 1.3.0 ``` ```xml - + ``` @@ -164,7 +164,7 @@ dotnet add package Apache.Fory --version 1.2.0 ```swift dependencies: [ - .package(url: "https://github.com/apache/fory.git", exact: "1.2.0") + .package(url: "https://github.com/apache/fory.git", exact: "1.3.0") ], targets: [ .target( diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0.json b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0.json new file mode 100644 index 00000000000..36471a29e1b --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0.json @@ -0,0 +1,26 @@ +{ + "version.label": { + "message": "1.3.0", + "description": "The label for version 1.3.0" + }, + "sidebar.docsSidebar.category.Introduction": { + "message": "介绍", + "description": "The label for category Introduction in sidebar docsSidebar" + }, + "sidebar.docsSidebar.category.Start": { + "message": "快速开始", + "description": "The label for category Start in sidebar docsSidebar" + }, + "sidebar.docsSidebar.category.Schema IDL & Compiler": { + "message": "Schema IDL 与编译器", + "description": "The label for category Schema IDL & Compiler in sidebar docsSidebar" + }, + "sidebar.docsSidebar.category.Guide": { + "message": "用户指南", + "description": "The label for category Guide in sidebar docsSidebar" + }, + "sidebar.docsSidebar.category.Cross Language": { + "message": "跨语言", + "description": "The label for category Cross Language in sidebar docsSidebar" + } +} diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/cpp/README.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/cpp/README.md new file mode 100644 index 00000000000..53e1284bea8 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/cpp/README.md @@ -0,0 +1,78 @@ +# C++ 基准性能报告 + +_生成于 2026-05-08 17:54:45_ + +## 如何生成本报告 + +```bash +cd benchmarks/cpp/build +./fory_benchmark --benchmark_format=json --benchmark_out=benchmark_results.json +cd .. +python benchmark_report.py --json-file build/benchmark_results.json --output-dir report +``` + +## 基准图表 + +图表展示吞吐量(ops/sec);数值越高越好。 + +![Throughput](throughput.png) + +## 硬件与操作系统信息 + +| 键 | 值 | +| -------------------------- | ------------------------- | +| 操作系统 | Darwin 24.6.0 | +| 机器架构 | arm64 | +| 处理器 | arm | +| CPU 核心数(物理) | 12 | +| CPU 核心数(逻辑) | 12 | +| 总内存(GB) | 48.0 | +| 基准日期 | 2026-05-08T16:29:28+08:00 | +| CPU 核心数(基准采集) | 12 | + +## 基准结果 + +### 延迟结果(纳秒) + +| 数据类型 | 操作 | fory (ns) | protobuf (ns) | msgpack (ns) | 最快 | +| ----------------- | ---- | --------- | ------------- | ------------ | ------- | +| NumericStruct | 序列化 | 24.9 | 48.2 | 91.0 | fory | +| NumericStruct | 反序列化 | 26.6 | 33.0 | 1194.5 | fory | +| Sample | 序列化 | 62.3 | 97.3 | 314.6 | fory | +| Sample | 反序列化 | 371.1 | 689.0 | 2649.9 | fory | +| MediaContent | 序列化 | 115.0 | 857.2 | 311.7 | fory | +| MediaContent | 反序列化 | 406.5 | 1193.1 | 3311.1 | fory | +| NumericStructList | 序列化 | 81.7 | 495.0 | 485.6 | fory | +| NumericStructList | 反序列化 | 180.9 | 410.6 | 5733.1 | fory | +| SampleList | 序列化 | 284.9 | 5004.9 | 1579.6 | fory | +| SampleList | 反序列化 | 1928.7 | 5118.1 | 13396.8 | fory | +| MediaContentList | 序列化 | 464.8 | 4861.1 | 1671.1 | fory | +| MediaContentList | 反序列化 | 2099.8 | 6610.3 | 13963.4 | fory | + +### 吞吐结果(ops/sec) + +| 数据类型 | 操作 | fory TPS | protobuf TPS | msgpack TPS | 最快 | +| ----------------- | ---- | ---------- | ------------ | ---- | ------- | +| NumericStruct | 序列化 | 40,087,668 | 20,733,305 | 10,989,907 | fory | +| NumericStruct | 反序列化 | 37,606,127 | 30,296,744 | 837,189 | fory | +| Sample | 序列化 | 16,041,299 | 10,277,207 | 3,178,983 | fory | +| Sample | 反序列化 | 2,694,434 | 1,451,449 | 377,373 | fory | +| MediaContent | 序列化 | 8,698,574 | 1,166,539 | 3,208,626 | fory | +| MediaContent | 反序列化 | 2,460,094 | 838,185 | 302,013 | fory | +| NumericStructList | 序列化 | 12,240,275 | 2,020,102 | 2,059,276 | fory | +| NumericStructList | 反序列化 | 5,527,333 | 2,435,246 | 174,427 | fory | +| SampleList | 序列化 | 3,510,210 | 199,804 | 633,061 | fory | +| SampleList | 反序列化 | 518,490 | 195,386 | 74,645 | fory | +| MediaContentList | 序列化 | 2,151,560 | 205,715 | 598,396 | fory | +| MediaContentList | 反序列化 | 476,241 | 151,280 | 71,616 | fory | + +### 序列化数据大小(字节) + +| 数据类型 | fory | protobuf | msgpack | +| ----------------- | ---- | -------- | ------- | +| NumericStruct | 78 | 93 | 87 | +| Sample | 445 | 375 | 530 | +| MediaContent | 362 | 301 | 480 | +| NumericStructList | 255 | 475 | 449 | +| SampleList | 1978 | 1890 | 2664 | +| MediaContentList | 1531 | 1520 | 2421 | diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/cpp/throughput.png b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/cpp/throughput.png new file mode 100644 index 00000000000..4ae6119fa70 Binary files /dev/null and b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/cpp/throughput.png differ diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/csharp/README.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/csharp/README.md new file mode 100644 index 00000000000..f4c27a9e76f --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/csharp/README.md @@ -0,0 +1,89 @@ +# C# 基准性能报告 + +_生成于 2026-05-08 17:54:45_ + +## 如何生成本报告 + +```bash +cd benchmarks/csharp +dotnet run -c Release --project ./Fory.CSharpBenchmark.csproj -- --output build/benchmark_results.json +python3 benchmark_report.py --json-file build/benchmark_results.json --output-dir report +``` + +## 基准图表 + +图表展示吞吐量(ops/sec);数值越高越好。 + +![Throughput](throughput.png) + +## 硬件与操作系统信息 + +| 键 | 值 | +| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | +| 操作系统 | Darwin 24.6.0 Darwin Kernel Version 24.6.0: Wed Oct 15 21:12:15 PDT 2025; root:xnu-11417.140.69.703.14~1/RELEASE_ARM64_T6041 | +| 操作系统 Architecture | Arm64 | +| 机器架构 | Arm64 | +| 运行时版本 | 8.0.24 | +| 基准日期(UTC) | 2026-05-08T08:17:48.7871870Z | +| 预热秒数 | 1 | +| 持续秒数 | 3 | +| CPU 逻辑核心数(基准采集) | 12 | +| CPU 核心数(物理) | 12 | +| CPU 核心数(逻辑) | 12 | +| 总内存(GB) | 48.0 | + +## 基准覆盖范围 + +| 键 | 值 | +| ------------------- | ---------------------------------------------------------------------- | +| 输入 JSON 中的用例 | 36 / 36 | +| 序列化器 | fory, msgpack, protobuf | +| 数据类型 | struct, sample, mediacontent, structlist, samplelist, mediacontentlist | +| 操作 | serialize, deserialize | + +## 基准结果 + +### 延迟结果(纳秒) + +| 数据类型 | 操作 | fory (ns) | protobuf (ns) | msgpack (ns) | 最快 | +| ----------------- | ---- | --------- | ------------- | ------------ | ------- | +| NumericStruct | 序列化 | 50.3 | 170.8 | 107.8 | fory | +| NumericStruct | 反序列化 | 82.4 | 252.0 | 143.4 | fory | +| Sample | 序列化 | 263.2 | 607.1 | 377.1 | fory | +| Sample | 反序列化 | 199.4 | 1191.7 | 785.6 | fory | +| MediaContent | 序列化 | 379.7 | 509.6 | 417.6 | fory | +| MediaContent | 反序列化 | 450.3 | 846.6 | 791.4 | fory | +| NumericStructList | 序列化 | 183.7 | 641.8 | 447.8 | fory | +| NumericStructList | 反序列化 | 288.3 | 974.3 | 702.1 | fory | +| SampleList | 序列化 | 1205.7 | 3559.1 | 1864.1 | fory | +| SampleList | 反序列化 | 895.1 | 5710.3 | 2757.4 | fory | +| MediaContentList | 序列化 | 1495.4 | 2473.6 | 1812.4 | fory | +| MediaContentList | 反序列化 | 1946.7 | 3789.3 | 3778.4 | fory | + +### 吞吐结果(ops/sec) + +| 数据类型 | 操作 | fory TPS | protobuf TPS | msgpack TPS | 最快 | +| ----------------- | ---- | ---------- | ------------ | ---- | ------- | +| NumericStruct | 序列化 | 19,881,457 | 5,853,473 | 9,276,378 | fory | +| NumericStruct | 反序列化 | 12,137,374 | 3,968,585 | 6,973,504 | fory | +| Sample | 序列化 | 3,799,418 | 1,647,119 | 2,652,142 | fory | +| Sample | 反序列化 | 5,016,006 | 839,129 | 1,272,975 | fory | +| MediaContent | 序列化 | 2,633,704 | 1,962,428 | 2,394,549 | fory | +| MediaContent | 反序列化 | 2,220,537 | 1,181,222 | 1,263,568 | fory | +| NumericStructList | 序列化 | 5,445,002 | 1,558,156 | 2,232,996 | fory | +| NumericStructList | 反序列化 | 3,469,207 | 1,026,402 | 1,424,322 | fory | +| SampleList | 序列化 | 829,415 | 280,973 | 536,448 | fory | +| SampleList | 反序列化 | 1,117,133 | 175,122 | 362,663 | fory | +| MediaContentList | 序列化 | 668,732 | 404,272 | 551,755 | fory | +| MediaContentList | 反序列化 | 513,699 | 263,899 | 264,664 | fory | + +### 序列化数据大小(字节) + +| 数据类型 | fory | protobuf | msgpack | +| ----------------- | ---- | -------- | ------- | +| NumericStruct | 78 | 93 | 87 | +| Sample | 445 | 460 | 562 | +| MediaContent | 362 | 307 | 479 | +| NumericStructList | 255 | 475 | 444 | +| SampleList | 1978 | 2315 | 2819 | +| MediaContentList | 1531 | 1550 | 2404 | diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/csharp/throughput.png b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/csharp/throughput.png new file mode 100644 index 00000000000..2c6c7bdd8ce Binary files /dev/null and b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/csharp/throughput.png differ diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/dart/README.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/dart/README.md new file mode 100644 index 00000000000..b99b93440c4 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/dart/README.md @@ -0,0 +1,49 @@ +# Fory Dart 基准 + +该基准比较 Apache Fory、Protocol Buffers 和 JSON 在 Dart 中的序列化与反序列化吞吐量。 + +## 吞吐图表 + +![Throughput](throughput.png) + +## 硬件与运行时信息 + +| 键 | 值 | +| --------------------- | ----------------------------------------------------------------- | +| 时间戳 | 2026-05-08T08:23:10.201764Z | +| 操作系统 | Version 15.7.2 (Build 24G325) | +| 主机 | MacBook-Pro.local | +| CPU 核心数(逻辑) | 12 | +| 内存(GB) | 48.00 | +| Dart | 3.10.7 (stable) (Tue Dec 23 00:01:57 2025 -0800) on "macos_arm64" | +| 每个用例采样次数 | 5 | +| 每个用例预热时长(秒) | 1.0 | +| 每个用例持续时长(秒) | 1.5 | + +## 吞吐结果 + +| 数据类型 | 操作 | Fory TPS | Protobuf TPS | JSON TPS | 最快 | +| ----------------- | ---- | --------: | -----------: | --------: | ------------- | +| NumericStruct | 序列化 | 9,007,809 | 1,582,003 | 774,574 | fory (5.69x) | +| NumericStruct | 反序列化 | 9,039,403 | 3,343,459 | 1,391,036 | fory (2.70x) | +| Sample | 序列化 | 2,434,800 | 538,385 | 133,800 | fory (4.52x) | +| Sample | 反序列化 | 2,362,665 | 909,410 | 239,924 | fory (2.60x) | +| MediaContent | 序列化 | 1,167,225 | 423,564 | 223,387 | fory (2.76x) | +| MediaContent | 反序列化 | 1,987,141 | 770,107 | 254,156 | fory (2.58x) | +| NumericStructList | 序列化 | 2,551,102 | 283,827 | 139,615 | fory (8.99x) | +| NumericStructList | 反序列化 | 3,028,068 | 530,360 | 265,058 | fory (5.71x) | +| SampleList | 序列化 | 568,937 | 47,426 | 25,386 | fory (12.00x) | +| SampleList | 反序列化 | 542,871 | 108,349 | 48,058 | fory (5.01x) | +| MediaContentList | 序列化 | 226,507 | 81,828 | 41,780 | fory (2.77x) | +| MediaContentList | 反序列化 | 458,667 | 139,395 | 50,183 | fory (3.29x) | + +## 序列化大小(字节) + +| 数据类型 | Fory | Protobuf | JSON | +| ----------------- | ---: | -------: | ---: | +| NumericStruct | 78 | 93 | 159 | +| Sample | 445 | 377 | 791 | +| MediaContent | 362 | 307 | 619 | +| NumericStructList | 255 | 475 | 816 | +| SampleList | 1978 | 1900 | 3976 | +| MediaContentList | 1531 | 1550 | 3122 | diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/dart/throughput.png b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/dart/throughput.png new file mode 100644 index 00000000000..ee3833d8bc9 Binary files /dev/null and b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/dart/throughput.png differ diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/go/README.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/go/README.md new file mode 100644 index 00000000000..265c3d42d4b --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/go/README.md @@ -0,0 +1,58 @@ +# Go 序列化基准报告 + +生成时间: 2026-05-08 17:55:12 + +## 性能图表 + +![Throughput](throughput.png) + +## 系统信息 + +- **操作系统**: Darwin 24.6.0 +- **架构**: arm64 +- **Python**: 3.10.8 + +## 性能汇总 + +| 数据类型 | 操作 | Fory (ops/s) | Protobuf (ops/s) | Msgpack (ops/s) | Fory vs PB | Fory vs MP | +| ----------------- | ---- | ------------ | ---------------- | --------------- | ---------- | ---------- | +| NumericStruct | 序列化 | 12.74M | 7.16M | 3.63M | 1.78x | 3.51x | +| NumericStruct | 反序列化 | 10.63M | 8.40M | 1.78M | 1.27x | 5.98x | +| Sample | 序列化 | 7.16M | 2.53M | 646K | 2.84x | 11.10x | +| Sample | 反序列化 | 3.27M | 2.10M | 343K | 1.56x | 9.54x | +| MediaContent | 序列化 | 3.74M | 1.75M | 1.14M | 2.14x | 3.27x | +| MediaContent | 反序列化 | 2.03M | 1.23M | 646K | 1.66x | 3.15x | +| NumericStructList | 序列化 | 1.10M | 386K | 201K | 2.84x | 5.44x | +| NumericStructList | 反序列化 | 1.09M | 368K | 103K | 2.96x | 10.54x | +| SampleList | 序列化 | 496K | 126K | 36K | 3.93x | 13.83x | +| SampleList | 反序列化 | 195K | 96K | 17K | 2.04x | 11.73x | +| MediaContentList | 序列化 | 250K | 91K | 57K | 2.73x | 4.38x | +| MediaContentList | 反序列化 | 112K | 74K | 31K | 1.53x | 3.65x | + +## 详细耗时(ns/op) + +| 数据类型 | 操作 | Fory | Protobuf | Msgpack | +| ----------------- | ---- | ------ | -------- | ------- | +| NumericStruct | 序列化 | 78.5 | 139.6 | 275.5 | +| NumericStruct | 反序列化 | 94.0 | 119.0 | 562.5 | +| Sample | 序列化 | 139.6 | 395.9 | 1549.0 | +| Sample | 反序列化 | 306.0 | 475.9 | 2919.0 | +| MediaContent | 序列化 | 267.3 | 571.6 | 875.1 | +| MediaContent | 反序列化 | 492.4 | 815.8 | 1549.0 | +| NumericStructList | 序列化 | 912.8 | 2594.0 | 4970.0 | +| NumericStructList | 反序列化 | 919.9 | 2721.0 | 9698.0 | +| SampleList | 序列化 | 2018.0 | 7927.0 | 27909.0 | +| SampleList | 反序列化 | 5126.0 | 10460.0 | 60118.0 | +| MediaContentList | 序列化 | 4006.0 | 10939.0 | 17553.0 | +| MediaContentList | 反序列化 | 8893.0 | 13588.0 | 32439.0 | + +### 序列化数据大小(字节) + +| 数据类型 | Fory | Protobuf | Msgpack | +| ----------------- | ---- | -------- | ------- | +| NumericStruct | 78 | 93 | 88 | +| Sample | 445 | 375 | 524 | +| MediaContent | 340 | 301 | 400 | +| NumericStructList | 819 | 1900 | 1766 | +| SampleList | 7599 | 7560 | 10486 | +| MediaContentList | 5774 | 6080 | 8006 | diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/go/benchmark_results.txt b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/go/benchmark_results.txt new file mode 100644 index 00000000000..9f471bc01fb --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/go/benchmark_results.txt @@ -0,0 +1,326 @@ +============================================ +Serialized Sizes (bytes): +============================================ +NumericStruct: + Fory: 78 bytes + Protobuf: 93 bytes + Msgpack: 88 bytes +Sample: + Fory: 445 bytes + Protobuf: 375 bytes + Msgpack: 524 bytes +MediaContent: + Fory: 340 bytes + Protobuf: 301 bytes + Msgpack: 400 bytes +NumericStructList: + Fory: 819 bytes + Protobuf: 1900 bytes + Msgpack: 1766 bytes +SampleList: + Fory: 7599 bytes + Protobuf: 7560 bytes + Msgpack: 10486 bytes +MediaContentList: + Fory: 5774 bytes + Protobuf: 6080 bytes + Msgpack: 8006 bytes +============================================ +============================================ +Serialized Sizes (bytes): +============================================ +NumericStruct: + Fory: 78 bytes + Protobuf: 93 bytes + Msgpack: 88 bytes +Sample: + Fory: 445 bytes + Protobuf: 375 bytes + Msgpack: 524 bytes +MediaContent: + Fory: 340 bytes + Protobuf: 301 bytes + Msgpack: 400 bytes +NumericStructList: + Fory: 819 bytes + Protobuf: 1900 bytes + Msgpack: 1766 bytes +SampleList: + Fory: 7599 bytes + Protobuf: 7560 bytes + Msgpack: 10486 bytes +MediaContentList: + Fory: 5774 bytes + Protobuf: 6080 bytes + Msgpack: 8006 bytes +============================================ +============================================ +Serialized Sizes (bytes): +============================================ +NumericStruct: + Fory: 78 bytes + Protobuf: 93 bytes + Msgpack: 88 bytes +Sample: + Fory: 445 bytes + Protobuf: 375 bytes + Msgpack: 524 bytes +MediaContent: + Fory: 340 bytes + Protobuf: 301 bytes + Msgpack: 400 bytes +NumericStructList: + Fory: 819 bytes + Protobuf: 1900 bytes + Msgpack: 1766 bytes +SampleList: + Fory: 7599 bytes + Protobuf: 7560 bytes + Msgpack: 10486 bytes +MediaContentList: + Fory: 5774 bytes + Protobuf: 6080 bytes + Msgpack: 8006 bytes +============================================ +============================================ +Serialized Sizes (bytes): +============================================ +NumericStruct: + Fory: 78 bytes + Protobuf: 93 bytes + Msgpack: 88 bytes +Sample: + Fory: 445 bytes + Protobuf: 375 bytes + Msgpack: 524 bytes +MediaContent: + Fory: 340 bytes + Protobuf: 301 bytes + Msgpack: 400 bytes +NumericStructList: + Fory: 819 bytes + Protobuf: 1900 bytes + Msgpack: 1766 bytes +SampleList: + Fory: 7599 bytes + Protobuf: 7560 bytes + Msgpack: 10486 bytes +MediaContentList: + Fory: 5774 bytes + Protobuf: 6080 bytes + Msgpack: 8006 bytes +============================================ +============================================ +Serialized Sizes (bytes): +============================================ +NumericStruct: + Fory: 78 bytes + Protobuf: 93 bytes + Msgpack: 88 bytes +Sample: + Fory: 445 bytes + Protobuf: 375 bytes + Msgpack: 524 bytes +MediaContent: + Fory: 340 bytes + Protobuf: 301 bytes + Msgpack: 400 bytes +NumericStructList: + Fory: 819 bytes + Protobuf: 1900 bytes + Msgpack: 1766 bytes +SampleList: + Fory: 7599 bytes + Protobuf: 7560 bytes + Msgpack: 10486 bytes +MediaContentList: + Fory: 5774 bytes + Protobuf: 6080 bytes + Msgpack: 8006 bytes +============================================ +goos: darwin +goarch: arm64 +pkg: github.com/apache/fory/benchmarks/go +cpu: Apple M4 Pro +BenchmarkFory_NumericStruct_Serialize-12 15385461 78.49 ns/op 0 B/op 0 allocs/op +BenchmarkFory_NumericStruct_Serialize-12 14953550 78.78 ns/op 0 B/op 0 allocs/op +BenchmarkFory_NumericStruct_Serialize-12 15520209 76.33 ns/op 0 B/op 0 allocs/op +BenchmarkFory_NumericStruct_Serialize-12 15806354 76.43 ns/op 0 B/op 0 allocs/op +BenchmarkFory_NumericStruct_Serialize-12 15314667 78.51 ns/op 0 B/op 0 allocs/op +BenchmarkProtobuf_NumericStruct_Serialize-12 8704411 138.0 ns/op 192 B/op 2 allocs/op +BenchmarkProtobuf_NumericStruct_Serialize-12 8522941 138.8 ns/op 192 B/op 2 allocs/op +BenchmarkProtobuf_NumericStruct_Serialize-12 8285118 138.9 ns/op 192 B/op 2 allocs/op +BenchmarkProtobuf_NumericStruct_Serialize-12 8818959 139.6 ns/op 192 B/op 2 allocs/op +BenchmarkProtobuf_NumericStruct_Serialize-12 8463876 139.6 ns/op 192 B/op 2 allocs/op +BenchmarkMsgpack_NumericStruct_Serialize-12 4396588 274.9 ns/op 240 B/op 3 allocs/op +BenchmarkMsgpack_NumericStruct_Serialize-12 4438035 272.7 ns/op 240 B/op 3 allocs/op +BenchmarkMsgpack_NumericStruct_Serialize-12 4352302 272.0 ns/op 240 B/op 3 allocs/op +BenchmarkMsgpack_NumericStruct_Serialize-12 4346308 277.3 ns/op 240 B/op 3 allocs/op +BenchmarkMsgpack_NumericStruct_Serialize-12 4361384 275.5 ns/op 240 B/op 3 allocs/op +BenchmarkFory_NumericStruct_Deserialize-12 12784374 95.10 ns/op 48 B/op 1 allocs/op +BenchmarkFory_NumericStruct_Deserialize-12 12615007 94.35 ns/op 48 B/op 1 allocs/op +BenchmarkFory_NumericStruct_Deserialize-12 12597256 94.07 ns/op 48 B/op 1 allocs/op +BenchmarkFory_NumericStruct_Deserialize-12 12663748 93.35 ns/op 48 B/op 1 allocs/op +BenchmarkFory_NumericStruct_Deserialize-12 12668444 94.05 ns/op 48 B/op 1 allocs/op +BenchmarkProtobuf_NumericStruct_Deserialize-12 9892239 157.3 ns/op 96 B/op 1 allocs/op +BenchmarkProtobuf_NumericStruct_Deserialize-12 6950502 152.1 ns/op 96 B/op 1 allocs/op +BenchmarkProtobuf_NumericStruct_Deserialize-12 7466115 210.2 ns/op 96 B/op 1 allocs/op +BenchmarkProtobuf_NumericStruct_Deserialize-12 6987369 156.1 ns/op 96 B/op 1 allocs/op +BenchmarkProtobuf_NumericStruct_Deserialize-12 10052640 119.0 ns/op 96 B/op 1 allocs/op +BenchmarkMsgpack_NumericStruct_Deserialize-12 2397518 502.5 ns/op 96 B/op 2 allocs/op +BenchmarkMsgpack_NumericStruct_Deserialize-12 2379376 501.1 ns/op 96 B/op 2 allocs/op +BenchmarkMsgpack_NumericStruct_Deserialize-12 2390596 500.8 ns/op 96 B/op 2 allocs/op +BenchmarkMsgpack_NumericStruct_Deserialize-12 2387790 504.9 ns/op 96 B/op 2 allocs/op +BenchmarkMsgpack_NumericStruct_Deserialize-12 2404051 562.5 ns/op 96 B/op 2 allocs/op +BenchmarkFory_NumericStructList_Serialize-12 1000000 1081 ns/op 0 B/op 0 allocs/op +BenchmarkFory_NumericStructList_Serialize-12 1000000 1021 ns/op 0 B/op 0 allocs/op +BenchmarkFory_NumericStructList_Serialize-12 1293205 947.2 ns/op 0 B/op 0 allocs/op +BenchmarkFory_NumericStructList_Serialize-12 1278226 929.6 ns/op 0 B/op 0 allocs/op +BenchmarkFory_NumericStructList_Serialize-12 1316396 912.8 ns/op 0 B/op 0 allocs/op +BenchmarkProtobuf_NumericStructList_Serialize-12 467838 2594 ns/op 4192 B/op 23 allocs/op +BenchmarkProtobuf_NumericStructList_Serialize-12 462572 2743 ns/op 4192 B/op 23 allocs/op +BenchmarkProtobuf_NumericStructList_Serialize-12 453700 2589 ns/op 4192 B/op 23 allocs/op +BenchmarkProtobuf_NumericStructList_Serialize-12 460408 2607 ns/op 4192 B/op 23 allocs/op +BenchmarkProtobuf_NumericStructList_Serialize-12 452139 2594 ns/op 4192 B/op 23 allocs/op +BenchmarkMsgpack_NumericStructList_Serialize-12 247603 4879 ns/op 4106 B/op 8 allocs/op +BenchmarkMsgpack_NumericStructList_Serialize-12 249087 4844 ns/op 4106 B/op 8 allocs/op +BenchmarkMsgpack_NumericStructList_Serialize-12 248340 5142 ns/op 4106 B/op 8 allocs/op +BenchmarkMsgpack_NumericStructList_Serialize-12 206158 5846 ns/op 4106 B/op 8 allocs/op +BenchmarkMsgpack_NumericStructList_Serialize-12 238311 4970 ns/op 4106 B/op 8 allocs/op +BenchmarkFory_NumericStructList_Deserialize-12 1311939 932.1 ns/op 1072 B/op 3 allocs/op +BenchmarkFory_NumericStructList_Deserialize-12 1313912 937.1 ns/op 1072 B/op 3 allocs/op +BenchmarkFory_NumericStructList_Deserialize-12 1315593 915.4 ns/op 1072 B/op 3 allocs/op +BenchmarkFory_NumericStructList_Deserialize-12 1311540 927.6 ns/op 1072 B/op 3 allocs/op +BenchmarkFory_NumericStructList_Deserialize-12 1292565 919.9 ns/op 1072 B/op 3 allocs/op +BenchmarkProtobuf_NumericStructList_Deserialize-12 447052 2745 ns/op 3512 B/op 28 allocs/op +BenchmarkProtobuf_NumericStructList_Deserialize-12 429406 2743 ns/op 3512 B/op 28 allocs/op +BenchmarkProtobuf_NumericStructList_Deserialize-12 438819 2706 ns/op 3512 B/op 28 allocs/op +BenchmarkProtobuf_NumericStructList_Deserialize-12 447928 2785 ns/op 3512 B/op 28 allocs/op +BenchmarkProtobuf_NumericStructList_Deserialize-12 447502 2721 ns/op 3512 B/op 28 allocs/op +BenchmarkMsgpack_NumericStructList_Deserialize-12 122101 9764 ns/op 2193 B/op 7 allocs/op +BenchmarkMsgpack_NumericStructList_Deserialize-12 123258 9896 ns/op 2193 B/op 7 allocs/op +BenchmarkMsgpack_NumericStructList_Deserialize-12 121612 9738 ns/op 2193 B/op 7 allocs/op +BenchmarkMsgpack_NumericStructList_Deserialize-12 124760 9670 ns/op 2193 B/op 7 allocs/op +BenchmarkMsgpack_NumericStructList_Deserialize-12 125086 9698 ns/op 2193 B/op 7 allocs/op +BenchmarkFory_Sample_Serialize-12 8696760 147.5 ns/op 0 B/op 0 allocs/op +BenchmarkFory_Sample_Serialize-12 8567470 181.8 ns/op 0 B/op 0 allocs/op +BenchmarkFory_Sample_Serialize-12 8293867 139.5 ns/op 0 B/op 0 allocs/op +BenchmarkFory_Sample_Serialize-12 8486005 140.6 ns/op 0 B/op 0 allocs/op +BenchmarkFory_Sample_Serialize-12 8506568 139.6 ns/op 0 B/op 0 allocs/op +BenchmarkProtobuf_Sample_Serialize-12 3011733 387.9 ns/op 704 B/op 2 allocs/op +BenchmarkProtobuf_Sample_Serialize-12 3108796 390.8 ns/op 704 B/op 2 allocs/op +BenchmarkProtobuf_Sample_Serialize-12 2547630 393.6 ns/op 704 B/op 2 allocs/op +BenchmarkProtobuf_Sample_Serialize-12 3026661 392.1 ns/op 704 B/op 2 allocs/op +BenchmarkProtobuf_Sample_Serialize-12 3128582 395.9 ns/op 704 B/op 2 allocs/op +BenchmarkMsgpack_Sample_Serialize-12 777014 1553 ns/op 2321 B/op 7 allocs/op +BenchmarkMsgpack_Sample_Serialize-12 775314 1520 ns/op 2321 B/op 7 allocs/op +BenchmarkMsgpack_Sample_Serialize-12 757838 1525 ns/op 2321 B/op 7 allocs/op +BenchmarkMsgpack_Sample_Serialize-12 802612 1757 ns/op 2321 B/op 7 allocs/op +BenchmarkMsgpack_Sample_Serialize-12 790094 1549 ns/op 2321 B/op 7 allocs/op +BenchmarkFory_Sample_Deserialize-12 4167534 327.9 ns/op 676 B/op 9 allocs/op +BenchmarkFory_Sample_Deserialize-12 3184602 338.0 ns/op 676 B/op 9 allocs/op +BenchmarkFory_Sample_Deserialize-12 3629605 301.8 ns/op 676 B/op 9 allocs/op +BenchmarkFory_Sample_Deserialize-12 4008093 302.2 ns/op 676 B/op 9 allocs/op +BenchmarkFory_Sample_Deserialize-12 3992972 306.0 ns/op 676 B/op 9 allocs/op +BenchmarkProtobuf_Sample_Deserialize-12 2527548 477.1 ns/op 708 B/op 9 allocs/op +BenchmarkProtobuf_Sample_Deserialize-12 2476074 474.8 ns/op 708 B/op 9 allocs/op +BenchmarkProtobuf_Sample_Deserialize-12 2532358 475.8 ns/op 708 B/op 9 allocs/op +BenchmarkProtobuf_Sample_Deserialize-12 2408528 473.0 ns/op 708 B/op 9 allocs/op +BenchmarkProtobuf_Sample_Deserialize-12 2482132 475.9 ns/op 708 B/op 9 allocs/op +BenchmarkMsgpack_Sample_Deserialize-12 415698 2893 ns/op 1576 B/op 38 allocs/op +BenchmarkMsgpack_Sample_Deserialize-12 417212 2950 ns/op 1576 B/op 38 allocs/op +BenchmarkMsgpack_Sample_Deserialize-12 424497 2879 ns/op 1576 B/op 38 allocs/op +BenchmarkMsgpack_Sample_Deserialize-12 417026 2953 ns/op 1576 B/op 38 allocs/op +BenchmarkMsgpack_Sample_Deserialize-12 418338 2919 ns/op 1576 B/op 38 allocs/op +BenchmarkFory_SampleList_Serialize-12 599605 2041 ns/op 0 B/op 0 allocs/op +BenchmarkFory_SampleList_Serialize-12 582406 2046 ns/op 0 B/op 0 allocs/op +BenchmarkFory_SampleList_Serialize-12 563160 2029 ns/op 0 B/op 0 allocs/op +BenchmarkFory_SampleList_Serialize-12 607074 2067 ns/op 0 B/op 0 allocs/op +BenchmarkFory_SampleList_Serialize-12 613364 2018 ns/op 0 B/op 0 allocs/op +BenchmarkProtobuf_SampleList_Serialize-12 145861 8291 ns/op 14816 B/op 23 allocs/op +BenchmarkProtobuf_SampleList_Serialize-12 150979 7938 ns/op 14816 B/op 23 allocs/op +BenchmarkProtobuf_SampleList_Serialize-12 144135 8049 ns/op 14816 B/op 23 allocs/op +BenchmarkProtobuf_SampleList_Serialize-12 149202 8469 ns/op 14816 B/op 23 allocs/op +BenchmarkProtobuf_SampleList_Serialize-12 152338 7927 ns/op 14816 B/op 23 allocs/op +BenchmarkMsgpack_SampleList_Serialize-12 43825 27705 ns/op 32793 B/op 11 allocs/op +BenchmarkMsgpack_SampleList_Serialize-12 43051 29707 ns/op 32793 B/op 11 allocs/op +BenchmarkMsgpack_SampleList_Serialize-12 42046 27699 ns/op 32793 B/op 11 allocs/op +BenchmarkMsgpack_SampleList_Serialize-12 42596 37501 ns/op 32793 B/op 11 allocs/op +BenchmarkMsgpack_SampleList_Serialize-12 42518 27909 ns/op 32793 B/op 11 allocs/op +BenchmarkFory_SampleList_Deserialize-12 249300 4817 ns/op 13952 B/op 163 allocs/op +BenchmarkFory_SampleList_Deserialize-12 245127 5156 ns/op 13952 B/op 163 allocs/op +BenchmarkFory_SampleList_Deserialize-12 247790 4943 ns/op 13952 B/op 163 allocs/op +BenchmarkFory_SampleList_Deserialize-12 249534 4934 ns/op 13952 B/op 163 allocs/op +BenchmarkFory_SampleList_Deserialize-12 239244 5126 ns/op 13952 B/op 163 allocs/op +BenchmarkProtobuf_SampleList_Deserialize-12 111327 10797 ns/op 20872 B/op 188 allocs/op +BenchmarkProtobuf_SampleList_Deserialize-12 109178 10919 ns/op 20872 B/op 188 allocs/op +BenchmarkProtobuf_SampleList_Deserialize-12 92876 12674 ns/op 20872 B/op 188 allocs/op +BenchmarkProtobuf_SampleList_Deserialize-12 108414 10816 ns/op 20872 B/op 188 allocs/op +BenchmarkProtobuf_SampleList_Deserialize-12 114196 10460 ns/op 20872 B/op 188 allocs/op +BenchmarkMsgpack_SampleList_Deserialize-12 20566 58400 ns/op 37251 B/op 727 allocs/op +BenchmarkMsgpack_SampleList_Deserialize-12 20526 65792 ns/op 37251 B/op 727 allocs/op +BenchmarkMsgpack_SampleList_Deserialize-12 20550 58213 ns/op 37251 B/op 727 allocs/op +BenchmarkMsgpack_SampleList_Deserialize-12 19180 58170 ns/op 37251 B/op 727 allocs/op +BenchmarkMsgpack_SampleList_Deserialize-12 20426 60118 ns/op 37251 B/op 727 allocs/op +BenchmarkFory_MediaContent_Serialize-12 4481811 268.2 ns/op 0 B/op 0 allocs/op +BenchmarkFory_MediaContent_Serialize-12 4470292 269.4 ns/op 0 B/op 0 allocs/op +BenchmarkFory_MediaContent_Serialize-12 4524715 269.0 ns/op 0 B/op 0 allocs/op +BenchmarkFory_MediaContent_Serialize-12 4516789 270.0 ns/op 0 B/op 0 allocs/op +BenchmarkFory_MediaContent_Serialize-12 4391221 267.3 ns/op 0 B/op 0 allocs/op +BenchmarkProtobuf_MediaContent_Serialize-12 2135143 544.6 ns/op 1144 B/op 11 allocs/op +BenchmarkProtobuf_MediaContent_Serialize-12 2160157 561.0 ns/op 1144 B/op 11 allocs/op +BenchmarkProtobuf_MediaContent_Serialize-12 2221641 545.3 ns/op 1144 B/op 11 allocs/op +BenchmarkProtobuf_MediaContent_Serialize-12 2214582 565.7 ns/op 1144 B/op 11 allocs/op +BenchmarkProtobuf_MediaContent_Serialize-12 2115079 571.6 ns/op 1144 B/op 11 allocs/op +BenchmarkMsgpack_MediaContent_Serialize-12 1380163 869.0 ns/op 1168 B/op 6 allocs/op +BenchmarkMsgpack_MediaContent_Serialize-12 1380481 874.4 ns/op 1168 B/op 6 allocs/op +BenchmarkMsgpack_MediaContent_Serialize-12 1369536 873.7 ns/op 1168 B/op 6 allocs/op +BenchmarkMsgpack_MediaContent_Serialize-12 1376877 881.8 ns/op 1168 B/op 6 allocs/op +BenchmarkMsgpack_MediaContent_Serialize-12 1381296 875.1 ns/op 1168 B/op 6 allocs/op +BenchmarkFory_MediaContent_Deserialize-12 2537758 474.9 ns/op 656 B/op 13 allocs/op +BenchmarkFory_MediaContent_Deserialize-12 2491048 478.2 ns/op 656 B/op 13 allocs/op +BenchmarkFory_MediaContent_Deserialize-12 2500486 520.6 ns/op 656 B/op 13 allocs/op +BenchmarkFory_MediaContent_Deserialize-12 2419058 495.7 ns/op 656 B/op 13 allocs/op +BenchmarkFory_MediaContent_Deserialize-12 2404918 492.4 ns/op 656 B/op 13 allocs/op +BenchmarkProtobuf_MediaContent_Deserialize-12 1852849 647.5 ns/op 1088 B/op 21 allocs/op +BenchmarkProtobuf_MediaContent_Deserialize-12 1865282 638.1 ns/op 1088 B/op 21 allocs/op +BenchmarkProtobuf_MediaContent_Deserialize-12 1873513 649.3 ns/op 1088 B/op 21 allocs/op +BenchmarkProtobuf_MediaContent_Deserialize-12 1853527 635.6 ns/op 1088 B/op 21 allocs/op +BenchmarkProtobuf_MediaContent_Deserialize-12 1729706 815.8 ns/op 1088 B/op 21 allocs/op +BenchmarkMsgpack_MediaContent_Deserialize-12 801156 1553 ns/op 896 B/op 17 allocs/op +BenchmarkMsgpack_MediaContent_Deserialize-12 787426 1520 ns/op 896 B/op 17 allocs/op +BenchmarkMsgpack_MediaContent_Deserialize-12 750510 1538 ns/op 896 B/op 17 allocs/op +BenchmarkMsgpack_MediaContent_Deserialize-12 803448 1543 ns/op 896 B/op 17 allocs/op +BenchmarkMsgpack_MediaContent_Deserialize-12 780190 1549 ns/op 896 B/op 17 allocs/op +BenchmarkFory_MediaContentList_Serialize-12 304236 4158 ns/op 0 B/op 0 allocs/op +BenchmarkFory_MediaContentList_Serialize-12 300002 4008 ns/op 0 B/op 0 allocs/op +BenchmarkFory_MediaContentList_Serialize-12 301590 4026 ns/op 0 B/op 0 allocs/op +BenchmarkFory_MediaContentList_Serialize-12 304356 3955 ns/op 0 B/op 0 allocs/op +BenchmarkFory_MediaContentList_Serialize-12 305937 4006 ns/op 0 B/op 0 allocs/op +BenchmarkProtobuf_MediaContentList_Serialize-12 113460 10919 ns/op 22848 B/op 203 allocs/op +BenchmarkProtobuf_MediaContentList_Serialize-12 105204 12060 ns/op 22848 B/op 203 allocs/op +BenchmarkProtobuf_MediaContentList_Serialize-12 112503 10890 ns/op 22848 B/op 203 allocs/op +BenchmarkProtobuf_MediaContentList_Serialize-12 110593 10468 ns/op 22848 B/op 203 allocs/op +BenchmarkProtobuf_MediaContentList_Serialize-12 112423 10939 ns/op 22848 B/op 203 allocs/op +BenchmarkMsgpack_MediaContentList_Serialize-12 77541 15641 ns/op 16881 B/op 30 allocs/op +BenchmarkMsgpack_MediaContentList_Serialize-12 77178 16293 ns/op 16880 B/op 30 allocs/op +BenchmarkMsgpack_MediaContentList_Serialize-12 73228 16168 ns/op 16880 B/op 30 allocs/op +BenchmarkMsgpack_MediaContentList_Serialize-12 71780 16275 ns/op 16880 B/op 30 allocs/op +BenchmarkMsgpack_MediaContentList_Serialize-12 77145 17553 ns/op 16881 B/op 30 allocs/op +BenchmarkFory_MediaContentList_Deserialize-12 146653 8254 ns/op 13040 B/op 243 allocs/op +BenchmarkFory_MediaContentList_Deserialize-12 145420 8133 ns/op 13040 B/op 243 allocs/op +BenchmarkFory_MediaContentList_Deserialize-12 148918 8145 ns/op 13040 B/op 243 allocs/op +BenchmarkFory_MediaContentList_Deserialize-12 141998 8498 ns/op 13040 B/op 243 allocs/op +BenchmarkFory_MediaContentList_Deserialize-12 138174 8893 ns/op 13040 B/op 243 allocs/op +BenchmarkProtobuf_MediaContentList_Deserialize-12 90056 13521 ns/op 25400 B/op 428 allocs/op +BenchmarkProtobuf_MediaContentList_Deserialize-12 82902 13638 ns/op 25400 B/op 428 allocs/op +BenchmarkProtobuf_MediaContentList_Deserialize-12 91242 14083 ns/op 25400 B/op 428 allocs/op +BenchmarkProtobuf_MediaContentList_Deserialize-12 85706 13089 ns/op 25400 B/op 428 allocs/op +BenchmarkProtobuf_MediaContentList_Deserialize-12 91342 13588 ns/op 25400 B/op 428 allocs/op +BenchmarkMsgpack_MediaContentList_Deserialize-12 33295 31198 ns/op 20058 B/op 307 allocs/op +BenchmarkMsgpack_MediaContentList_Deserialize-12 40077 31423 ns/op 20058 B/op 307 allocs/op +BenchmarkMsgpack_MediaContentList_Deserialize-12 39219 30604 ns/op 20058 B/op 307 allocs/op +BenchmarkMsgpack_MediaContentList_Deserialize-12 39799 30377 ns/op 20058 B/op 307 allocs/op +BenchmarkMsgpack_MediaContentList_Deserialize-12 39267 32439 ns/op 20058 B/op 307 allocs/op +PASS +ok github.com/apache/fory/benchmarks/go 268.327s diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/go/serialized_sizes.txt b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/go/serialized_sizes.txt new file mode 100644 index 00000000000..a64d3e1c05c --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/go/serialized_sizes.txt @@ -0,0 +1,27 @@ +Serialized Sizes (bytes): +============================================ +NumericStruct: + Fory: 78 bytes + Protobuf: 93 bytes + Msgpack: 88 bytes +Sample: + Fory: 445 bytes + Protobuf: 375 bytes + Msgpack: 524 bytes +MediaContent: + Fory: 340 bytes + Protobuf: 301 bytes + Msgpack: 400 bytes +NumericStructList: + Fory: 819 bytes + Protobuf: 1900 bytes + Msgpack: 1766 bytes +SampleList: + Fory: 7599 bytes + Protobuf: 7560 bytes + Msgpack: 10486 bytes +MediaContentList: + Fory: 5774 bytes + Protobuf: 6080 bytes + Msgpack: 8006 bytes +============================================ diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/go/throughput.png b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/go/throughput.png new file mode 100644 index 00000000000..086d8b5533a Binary files /dev/null and b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/go/throughput.png differ diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/java/README.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/java/README.md new file mode 100644 index 00000000000..2475ee4c147 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/java/README.md @@ -0,0 +1,239 @@ +# Java 基准测试 + +## 系统环境 + +- Operation System:4.9.151-015.x86_64 +- CPU:Intel(R) Xeon(R) Platinum 8163 CPU @ 2.50GHz +- Byte Order:Little Endian +- L1d cache: 32K +- L1i cache:32K +- L2 cache: 1024K +- L3 cache: 33792K + +## JMH 参数 + +不要跳过 **warm up**,否则结果不准确。 + +```bash + -f 1 -wi 3 -i 3 -t 1 -w 2s -r 2s -rf cs +``` + +## 基准数据 + +### Struct + +Struct 是一个包含 100 个基础类型字段的类: + +```java +public class Struct { + public int f1; + public long f2; + public float f3; + public double f4; + // ... + public double f99; +} +``` + +### Struct2 + +Struct2 是一个包含 100 个装箱类型字段的类: + +```java +public class Struct { + public Integer f1; + public Long f2; + public Float f3; + public Double f4; + // ... + public Double f99; +} +``` + +### MediaContent + +MEDIA_CONTENT 来自 [jvm-serializers](https://github.com/eishay/jvm-serializers/blob/master/tpc/src/data/media/MediaContent.java). + +### Sample + +SAMPLE 来自 [kryo benchmark](https://github.com/EsotericSoftware/kryo/blob/master/benchmarks/src/main/java/com/esotericsoftware/kryo/benchmarks/data/Sample.java) + +## 基准图表 + +### 序列化到堆内缓冲区 + +将数据序列化到 Java 字节数组。 + +#### Java schema 一致性序列化 + +反序列化端必须与序列化端使用相同的类定义。 +该模式不支持类的前向/后向兼容。 + +![Java Heap Schema Consistent Serialization](java_heap_serialize_consistent.png) + +#### Java schema 兼容模式序列化 + +反序列化端可以与序列化端使用不同的类定义。 +该模式支持类的前向/后向兼容。 + +![Java Heap Schema Compatible Serialization](java_heap_serialize_compatible.png) + +#### Java schema 一致性反序列化 + +反序列化端必须与序列化端使用相同的类定义。 +该模式不支持类的前向/后向兼容。 + +![Java Heap Schema Consistent Deserialization](java_heap_deserialize_consistent.png) + +#### Java schema 兼容模式反序列化 + +反序列化端可以与序列化端使用不同的类定义。 +该模式支持类的前向/后向兼容。 + +![Java Heap Schema Compatible Deserialization](java_heap_deserialize_compatible.png) + +### 堆外序列化 + +将数据序列化到堆外内存。 + +#### Java schema 一致性序列化 + +反序列化端必须与序列化端使用相同的类定义。 +该模式不支持类的前向/后向兼容。 + +![Java Off Heap Schema Consistent Serialization](java_offheap_serialize_consistent.png) + +#### Java schema 兼容模式序列化 + +反序列化端可以与序列化端使用不同的类定义。 +该模式支持类的前向/后向兼容。 + +![Java Off Heap Schema Compatible Serialization](java_offheap_serialize_compatible.png) + +#### Java schema 一致性反序列化 + +反序列化端必须与序列化端使用相同的类定义。 +该模式不支持类的前向/后向兼容。 + +![Java Off Heap Schema Consistent Deserialization](java_offheap_deserialize_consistent.png) + +#### Java schema 兼容模式反序列化 + +反序列化端可以与序列化端使用不同的类定义。 +该模式支持类的前向/后向兼容。 + +![Java Off Heap Schema Compatible Deserialization](java_offheap_deserialize_compatible.png) + +### 零拷贝序列化 + +注意:零拷贝序列化只是在序列化阶段避免拷贝;如果将数据发送到其他机器,仍可能发生拷贝。 + +但如果在同一节点进程间序列化并使用共享内存,且数据在序列化前已位于堆外,那么其他进程可无拷贝读取该缓冲区。 + +#### Java 零拷贝序列化到堆内缓冲区 + +![Java Zero Copy Serialization](java_zero_copy_serialize.png) + +#### Java 零拷贝序列化到直接缓冲区 + +![Java Zero Copy Deserialization](java_zero_copy_deserialize.png) + +## 基准数据 + +### Java Serialization + +| Benchmark | objectType | bufferType | references | Fory | ForyMetaShared | Kryo | Fst | Hession | Jdk | Protostuff | +| ---------------------- | ------------- | ------------ | ---------- | -------------- | --------------- | -------------- | ------------- | ------------- | ------------- | ------------- | +| serialize | STRUCT | array | False | 7501415.567260 | | 558194.100861 | 882178.995727 | 258233.998931 | 155908.244240 | 330975.350403 | +| serialize | STRUCT | array | True | 6264439.154428 | | 557542.628765 | 757753.756691 | 260845.209485 | 151258.539369 | | +| serialize | STRUCT | directBuffer | False | 9834223.243204 | | 1078046.011115 | 807847.663261 | 266481.009225 | 154875.908438 | 340262.650047 | +| serialize | STRUCT | directBuffer | True | 7551780.823133 | | 853350.408656 | 762088.935404 | 261762.594966 | 156404.686214 | | +| serialize | STRUCT2 | array | False | 3586126.623874 | | 325172.969175 | 371762.982661 | 56056.080075 | 36846.049162 | 322563.440433 | +| serialize | STRUCT2 | array | True | 3306474.506382 | | 259863.332448 | 380638.700267 | 60038.879790 | 38183.705811 | | +| serialize | STRUCT2 | directBuffer | False | 2643155.135327 | | 355688.882786 | 365317.705376 | 55924.319442 | 37444.967981 | 325093.716261 | +| serialize | STRUCT2 | directBuffer | True | 2391110.083108 | | 338960.426033 | 370851.880711 | 56674.065604 | 35798.679246 | | +| serialize | MEDIA_CONTENT | array | False | 3031642.924542 | | 730792.521676 | 751892.023189 | 367782.358049 | 137989.198821 | 780618.761219 | +| serialize | MEDIA_CONTENT | array | True | 2250384.600246 | | 445251.084327 | 583859.907758 | 329427.470680 | 140260.668888 | | +| serialize | MEDIA_CONTENT | directBuffer | False | 2479862.129632 | | 608972.517580 | 728001.080250 | 372477.138150 | 138567.623369 | 805941.345157 | +| serialize | MEDIA_CONTENT | directBuffer | True | 1938527.588331 | | 359875.473951 | 595679.580108 | 353376.085025 | 140158.673910 | | +| serialize | SAMPLE | array | False | 3570966.469087 | | 1105365.931217 | 915907.574306 | 220386.502846 | 118374.836631 | 663272.710783 | +| serialize | SAMPLE | array | True | 1767693.835090 | | 734215.482291 | 731869.156376 | 192414.014211 | 119858.140625 | | +| serialize | SAMPLE | directBuffer | False | 3684487.760591 | | 1376560.302168 | 902302.261168 | 220981.308085 | 118273.584257 | 693641.589806 | +| serialize | SAMPLE | directBuffer | True | 1826456.709478 | | 932887.968348 | 723614.066770 | 211949.960255 | 108263.040839 | | +| serialize_compatible | STRUCT | array | False | 3530406.108869 | 9204444.777172 | 145964.199559 | | 258650.663523 | | | +| serialize_compatible | STRUCT | array | True | 3293059.098127 | 7064625.291374 | 136180.832879 | | 263564.913879 | | | +| serialize_compatible | STRUCT | directBuffer | False | 2653169.568374 | 11650229.648715 | 106695.800225 | | 249221.452137 | | | +| serialize_compatible | STRUCT | directBuffer | True | 2393817.762938 | 8702412.752357 | 106458.212005 | | 263623.143601 | | | +| serialize_compatible | STRUCT2 | array | False | 2773368.997680 | 2575824.143864 | 125807.748004 | | 58509.125342 | | | +| serialize_compatible | STRUCT2 | array | True | 2564174.550276 | 3543082.528217 | 114983.546343 | | 55552.977735 | | | +| serialize_compatible | STRUCT2 | directBuffer | False | 1912402.937879 | 2714748.572248 | 92130.672361 | | 58908.567439 | | | +| serialize_compatible | STRUCT2 | directBuffer | True | 1848338.968058 | 1866073.031851 | 88989.724768 | | 55524.373547 | | | +| serialize_compatible | MEDIA_CONTENT | array | False | 1679272.036223 | 2992288.235281 | 188911.259146 | | 377195.903772 | | | +| serialize_compatible | MEDIA_CONTENT | array | True | 1406736.538716 | 2058738.716953 | 145782.916427 | | 351657.879556 | | | +| serialize_compatible | MEDIA_CONTENT | directBuffer | False | 1710680.937387 | 2291443.556971 | 185363.714829 | | 371729.727192 | | | +| serialize_compatible | MEDIA_CONTENT | directBuffer | True | 1149999.473994 | 1804349.244125 | 142836.961878 | | 343834.954942 | | | +| serialize_compatible | SAMPLE | array | False | 3604596.465625 | 4409055.687063 | 378907.663184 | | 234454.975158 | | | +| serialize_compatible | SAMPLE | array | True | 1619648.337293 | 1840705.439334 | 320815.567701 | | 206174.173039 | | | +| serialize_compatible | SAMPLE | directBuffer | False | 3484533.218305 | 5043538.364886 | 296102.615094 | | 194761.224263 | | | +| serialize_compatible | SAMPLE | directBuffer | True | 1730822.630648 | 1859289.705838 | 276757.392449 | | 212840.483308 | | | +| deserialize | STRUCT | array | False | 4595230.434552 | | 607750.343557 | 357887.235311 | 84709.108821 | 29603.066599 | 517381.168594 | +| deserialize | STRUCT | array | True | 4634753.596131 | | 552802.227807 | 353480.554035 | 91050.370224 | 29727.744196 | | +| deserialize | STRUCT | directBuffer | False | 5012002.859236 | | 910534.169114 | 352441.597147 | 91151.633583 | 28717.004518 | 538922.947147 | +| deserialize | STRUCT | directBuffer | True | 4864329.316938 | | 914404.107564 | 334574.303484 | 91037.205901 | 29549.998286 | | +| deserialize | STRUCT2 | array | False | 1126298.359550 | | 275984.042401 | 280131.091068 | 69758.767783 | 14888.805111 | 416212.973861 | +| deserialize | STRUCT2 | array | True | 1046649.083082 | | 222710.554833 | 260649.308016 | 68616.029248 | 14034.100664 | | +| deserialize | STRUCT2 | directBuffer | False | 1117586.457565 | | 319247.256793 | 262519.858810 | 66866.108653 | 14652.043788 | 425523.315814 | +| deserialize | STRUCT2 | directBuffer | True | 1018277.848128 | | 249105.828416 | 234973.637096 | 65338.345185 | 14425.886048 | | +| deserialize | MEDIA_CONTENT | array | False | 2054066.903469 | | 577631.234369 | 363455.785182 | 118156.072284 | 38536.250402 | 951662.019963 | +| deserialize | MEDIA_CONTENT | array | True | 1507767.206603 | | 365530.417232 | 304371.728638 | 120016.594171 | 38957.191090 | | +| deserialize | MEDIA_CONTENT | directBuffer | False | 1502746.028159 | | 389473.174523 | 311691.658687 | 111067.942626 | 40512.632076 | 964664.641598 | +| deserialize | MEDIA_CONTENT | directBuffer | True | 1290593.975753 | | 306995.220799 | 251820.171513 | 121820.821260 | 37030.594632 | | +| deserialize | SAMPLE | array | False | 2069988.624415 | | 979173.981159 | 473409.796491 | 119471.518388 | 29309.573998 | 619338.385412 | +| deserialize | SAMPLE | array | True | 1797942.442313 | | 716438.884369 | 428315.502365 | 121106.002978 | 27466.003923 | | +| deserialize | SAMPLE | directBuffer | False | 2229791.078395 | | 983538.936801 | 441027.550809 | 117806.916589 | 28128.457935 | 624804.978534 | +| deserialize | SAMPLE | directBuffer | True | 1958815.397807 | | 762889.302732 | 420523.770904 | 121940.783597 | 28221.014735 | | +| deserialize_compatible | STRUCT | array | False | 2110335.039275 | 4978833.206806 | 78771.635309 | | 88617.486795 | | | +| deserialize_compatible | STRUCT | array | True | 2135681.982674 | 4807963.882520 | 72805.937649 | | 90206.654212 | | | +| deserialize_compatible | STRUCT | directBuffer | False | 1596464.248141 | 5149070.657830 | 58574.904225 | | 89580.561575 | | | +| deserialize_compatible | STRUCT | directBuffer | True | 1684681.074242 | 5137500.621288 | 60685.320299 | | 84407.472531 | | | +| deserialize_compatible | STRUCT2 | array | False | 849507.176263 | 1201998.142474 | 60602.285743 | | 63703.763814 | | | +| deserialize_compatible | STRUCT2 | array | True | 815120.319155 | 1058423.614156 | 62729.908347 | | 69521.573119 | | | +| deserialize_compatible | STRUCT2 | directBuffer | False | 784036.589363 | 1131212.586953 | 54637.329134 | | 69342.030965 | | | +| deserialize_compatible | STRUCT2 | directBuffer | True | 782679.662083 | 1089162.408165 | 51761.569591 | | 68542.055543 | | | +| deserialize_compatible | MEDIA_CONTENT | array | False | 1441671.706320 | 2279742.810882 | 180882.860363 | | 121619.090797 | | | +| deserialize_compatible | MEDIA_CONTENT | array | True | 1121136.039627 | 1623938.202345 | 154311.211540 | | 119994.104050 | | | +| deserialize_compatible | MEDIA_CONTENT | directBuffer | False | 1256034.732514 | 1718098.363961 | 134485.160300 | | 107594.474890 | | | +| deserialize_compatible | MEDIA_CONTENT | directBuffer | True | 1054942.751816 | 1333345.536684 | 119311.787329 | | 116531.023438 | | | +| deserialize_compatible | SAMPLE | array | False | 2296046.895861 | 2485564.396196 | 255086.928308 | | 121898.105768 | | | +| deserialize_compatible | SAMPLE | array | True | 1834139.395757 | 2002938.794909 | 238811.995510 | | 121297.485903 | | | +| deserialize_compatible | SAMPLE | directBuffer | False | 2308111.633661 | 2289261.533644 | 201993.787890 | | 124044.417439 | | | +| deserialize_compatible | SAMPLE | directBuffer | True | 1820490.585648 | 1927548.827586 | 174534.710870 | | 120276.449497 | | | + +### Java 零拷贝 + +| Benchmark | array_size | bufferType | dataType | Fory | Kryo | Fst | +| ---- | ---------- | ------------ | --------------- | -------------- | -------------- | -------------- | +| serialize | 200 | array | BUFFER | 5123572.914045 | 1985187.977633 | 2400193.220466 | +| serialize | 200 | array | PRIMITIVE_ARRAY | 8297232.942927 | 147342.606262 | 313986.053417 | +| serialize | 200 | directBuffer | BUFFER | 5400346.890126 | 1739454.519770 | 2282550.111756 | +| serialize | 200 | directBuffer | PRIMITIVE_ARRAY | 8335248.350301 | 972683.763633 | 294132.218623 | +| serialize | 1000 | array | BUFFER | 4979590.929127 | 1616159.671230 | 1805557.477810 | +| serialize | 1000 | array | PRIMITIVE_ARRAY | 8772856.921028 | 31395.721514 | 67209.107012 | +| serialize | 1000 | directBuffer | BUFFER | 5376191.775007 | 1377272.568510 | 1644789.427010 | +| serialize | 1000 | directBuffer | PRIMITIVE_ARRAY | 8207563.785251 | 209183.090868 | 66108.014322 | +| serialize | 5000 | array | BUFFER | 5018916.322770 | 711287.533377 | 811029.402136 | +| serialize | 5000 | array | PRIMITIVE_ARRAY | 8027439.580226 | 6248.006967 | 14997.400124 | +| serialize | 5000 | directBuffer | BUFFER | 5330897.682960 | 707092.956534 | 477148.540850 | +| serialize | 5000 | directBuffer | PRIMITIVE_ARRAY | 7695981.988316 | 43565.678616 | 15000.378818 | +| deserialize | 200 | array | BUFFER | 3302149.383135 | 1296284.787720 | 657754.887247 | +| deserialize | 200 | array | PRIMITIVE_ARRAY | 986136.067809 | 146675.360652 | 219333.990504 | +| deserialize | 200 | directBuffer | BUFFER | 3113115.471758 | 1004844.498712 | 598421.278941 | +| deserialize | 200 | directBuffer | PRIMITIVE_ARRAY | 991807.969328 | 518713.299422 | 179604.045774 | +| deserialize | 1000 | array | BUFFER | 2831942.848999 | 721266.541130 | 422147.154601 | +| deserialize | 1000 | array | PRIMITIVE_ARRAY | 205671.992736 | 30409.835023 | 53100.903684 | +| deserialize | 1000 | directBuffer | BUFFER | 3397690.327371 | 592972.713203 | 298929.116572 | +| deserialize | 1000 | directBuffer | PRIMITIVE_ARRAY | 202275.242341 | 112132.004609 | 38572.001768 | +| deserialize | 5000 | array | BUFFER | 3296658.120035 | 147251.846111 | 136934.604328 | +| deserialize | 5000 | array | PRIMITIVE_ARRAY | 40312.590172 | 6122.351228 | 10672.872798 | +| deserialize | 5000 | directBuffer | BUFFER | 3284441.570594 | 148614.476829 | 77950.612503 | +| deserialize | 5000 | directBuffer | PRIMITIVE_ARRAY | 40413.743717 | 21826.040410 | 8561.694533 | diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/java/data/jmh-jdk-11-deserialization.csv b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/java/data/jmh-jdk-11-deserialization.csv new file mode 100644 index 00000000000..98760a4721c --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/java/data/jmh-jdk-11-deserialization.csv @@ -0,0 +1,153 @@ +"Benchmark","Mode","Threads","Samples","Score","Score Error (99.9%)","Unit","Param: bufferType","Param: objectType","Param: references" +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fst_deserialize","thrpt",1,3,473409.796491,250793.361170,"ops/s",array,SAMPLE,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fst_deserialize","thrpt",1,3,428315.502365,191770.682411,"ops/s",array,SAMPLE,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fst_deserialize","thrpt",1,3,363455.785182,44717.666948,"ops/s",array,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fst_deserialize","thrpt",1,3,304371.728638,74831.791688,"ops/s",array,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fst_deserialize","thrpt",1,3,357887.235311,218092.594151,"ops/s",array,STRUCT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fst_deserialize","thrpt",1,3,353480.554035,229239.505256,"ops/s",array,STRUCT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fst_deserialize","thrpt",1,3,280131.091068,89472.347221,"ops/s",array,STRUCT2,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fst_deserialize","thrpt",1,3,260649.308016,165597.762734,"ops/s",array,STRUCT2,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fst_deserialize","thrpt",1,3,441027.550809,321712.847983,"ops/s",directBuffer,SAMPLE,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fst_deserialize","thrpt",1,3,420523.770904,122531.658151,"ops/s",directBuffer,SAMPLE,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fst_deserialize","thrpt",1,3,311691.658687,185300.664992,"ops/s",directBuffer,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fst_deserialize","thrpt",1,3,251820.171513,894743.730727,"ops/s",directBuffer,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fst_deserialize","thrpt",1,3,352441.597147,109488.413174,"ops/s",directBuffer,STRUCT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fst_deserialize","thrpt",1,3,334574.303484,89858.329407,"ops/s",directBuffer,STRUCT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fst_deserialize","thrpt",1,3,262519.858810,138990.948859,"ops/s",directBuffer,STRUCT2,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fst_deserialize","thrpt",1,3,234973.637096,568162.582389,"ops/s",directBuffer,STRUCT2,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize","thrpt",1,3,2069988.624415,1501625.089719,"ops/s",array,SAMPLE,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize","thrpt",1,3,1797942.442313,3541577.357052,"ops/s",array,SAMPLE,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize","thrpt",1,3,2054066.903469,3314161.094630,"ops/s",array,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize","thrpt",1,3,1507767.206603,1662320.958961,"ops/s",array,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize","thrpt",1,3,4595230.434552,678411.427346,"ops/s",array,STRUCT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize","thrpt",1,3,4634753.596131,582102.492158,"ops/s",array,STRUCT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize","thrpt",1,3,1126298.359550,856691.704987,"ops/s",array,STRUCT2,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize","thrpt",1,3,1046649.083082,1816153.563648,"ops/s",array,STRUCT2,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize","thrpt",1,3,2229791.078395,2950833.975767,"ops/s",directBuffer,SAMPLE,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize","thrpt",1,3,1958815.397807,381903.427430,"ops/s",directBuffer,SAMPLE,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize","thrpt",1,3,1502746.028159,1759759.267183,"ops/s",directBuffer,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize","thrpt",1,3,1290593.975753,246089.810346,"ops/s",directBuffer,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize","thrpt",1,3,5012002.859236,3360944.705659,"ops/s",directBuffer,STRUCT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize","thrpt",1,3,4864329.316938,4357833.300692,"ops/s",directBuffer,STRUCT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize","thrpt",1,3,1117586.457565,323438.118804,"ops/s",directBuffer,STRUCT2,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize","thrpt",1,3,1018277.848128,1141314.435153,"ops/s",directBuffer,STRUCT2,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize_compatible","thrpt",1,3,2296046.895861,2532372.600048,"ops/s",array,SAMPLE,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize_compatible","thrpt",1,3,1834139.395757,1021897.505849,"ops/s",array,SAMPLE,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize_compatible","thrpt",1,3,1441671.706320,317052.609754,"ops/s",array,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize_compatible","thrpt",1,3,1121136.039627,979316.642796,"ops/s",array,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize_compatible","thrpt",1,3,2110335.039275,389404.889706,"ops/s",array,STRUCT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize_compatible","thrpt",1,3,2135681.982674,1140328.709799,"ops/s",array,STRUCT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize_compatible","thrpt",1,3,849507.176263,77466.989680,"ops/s",array,STRUCT2,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize_compatible","thrpt",1,3,815120.319155,110547.743648,"ops/s",array,STRUCT2,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize_compatible","thrpt",1,3,2308111.633661,1699117.497565,"ops/s",directBuffer,SAMPLE,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize_compatible","thrpt",1,3,1820490.585648,244290.391445,"ops/s",directBuffer,SAMPLE,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize_compatible","thrpt",1,3,1256034.732514,811833.294307,"ops/s",directBuffer,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize_compatible","thrpt",1,3,1054942.751816,180122.424477,"ops/s",directBuffer,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize_compatible","thrpt",1,3,1596464.248141,1624412.248391,"ops/s",directBuffer,STRUCT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize_compatible","thrpt",1,3,1684681.074242,865262.572050,"ops/s",directBuffer,STRUCT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize_compatible","thrpt",1,3,784036.589363,189745.805570,"ops/s",directBuffer,STRUCT2,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize_compatible","thrpt",1,3,782679.662083,232459.929272,"ops/s",directBuffer,STRUCT2,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.forymetashared_deserialize_compatible","thrpt",1,3,2485564.396196,404678.689647,"ops/s",array,SAMPLE,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.forymetashared_deserialize_compatible","thrpt",1,3,2002938.794909,254819.284313,"ops/s",array,SAMPLE,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.forymetashared_deserialize_compatible","thrpt",1,3,2279742.810882,1984221.778254,"ops/s",array,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.forymetashared_deserialize_compatible","thrpt",1,3,1623938.202345,902985.831204,"ops/s",array,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.forymetashared_deserialize_compatible","thrpt",1,3,4978833.206806,1201415.339844,"ops/s",array,STRUCT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.forymetashared_deserialize_compatible","thrpt",1,3,4807963.882520,981981.561987,"ops/s",array,STRUCT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.forymetashared_deserialize_compatible","thrpt",1,3,1201998.142474,446807.356524,"ops/s",array,STRUCT2,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.forymetashared_deserialize_compatible","thrpt",1,3,1058423.614156,2254684.330438,"ops/s",array,STRUCT2,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.forymetashared_deserialize_compatible","thrpt",1,3,2289261.533644,330913.304260,"ops/s",directBuffer,SAMPLE,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.forymetashared_deserialize_compatible","thrpt",1,3,1927548.827586,2169684.221478,"ops/s",directBuffer,SAMPLE,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.forymetashared_deserialize_compatible","thrpt",1,3,1718098.363961,1096276.962216,"ops/s",directBuffer,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.forymetashared_deserialize_compatible","thrpt",1,3,1333345.536684,1110700.750020,"ops/s",directBuffer,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.forymetashared_deserialize_compatible","thrpt",1,3,5149070.657830,3249519.880480,"ops/s",directBuffer,STRUCT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.forymetashared_deserialize_compatible","thrpt",1,3,5137500.621288,3988987.937014,"ops/s",directBuffer,STRUCT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.forymetashared_deserialize_compatible","thrpt",1,3,1131212.586953,240674.468318,"ops/s",directBuffer,STRUCT2,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.forymetashared_deserialize_compatible","thrpt",1,3,1089162.408165,294119.059967,"ops/s",directBuffer,STRUCT2,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize","thrpt",1,3,119471.518388,29846.632785,"ops/s",array,SAMPLE,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize","thrpt",1,3,121106.002978,27710.578092,"ops/s",array,SAMPLE,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize","thrpt",1,3,118156.072284,4767.841380,"ops/s",array,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize","thrpt",1,3,120016.594171,41255.986380,"ops/s",array,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize","thrpt",1,3,84709.108821,132894.355932,"ops/s",array,STRUCT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize","thrpt",1,3,91050.370224,17939.461828,"ops/s",array,STRUCT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize","thrpt",1,3,69758.767783,10886.276794,"ops/s",array,STRUCT2,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize","thrpt",1,3,68616.029248,13625.851120,"ops/s",array,STRUCT2,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize","thrpt",1,3,117806.916589,35449.535259,"ops/s",directBuffer,SAMPLE,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize","thrpt",1,3,121940.783597,54381.355274,"ops/s",directBuffer,SAMPLE,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize","thrpt",1,3,111067.942626,136178.034974,"ops/s",directBuffer,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize","thrpt",1,3,121820.821260,36972.450957,"ops/s",directBuffer,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize","thrpt",1,3,91151.633583,18401.579776,"ops/s",directBuffer,STRUCT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize","thrpt",1,3,91037.205901,58868.732797,"ops/s",directBuffer,STRUCT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize","thrpt",1,3,66866.108653,38522.707093,"ops/s",directBuffer,STRUCT2,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize","thrpt",1,3,65338.345185,56556.914062,"ops/s",directBuffer,STRUCT2,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize_compatible","thrpt",1,3,121898.105768,55415.594537,"ops/s",array,SAMPLE,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize_compatible","thrpt",1,3,121297.485903,33990.621079,"ops/s",array,SAMPLE,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize_compatible","thrpt",1,3,121619.090797,6094.683470,"ops/s",array,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize_compatible","thrpt",1,3,119994.104050,24538.128667,"ops/s",array,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize_compatible","thrpt",1,3,88617.486795,40076.835119,"ops/s",array,STRUCT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize_compatible","thrpt",1,3,90206.654212,19313.248466,"ops/s",array,STRUCT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize_compatible","thrpt",1,3,63703.763814,132184.298414,"ops/s",array,STRUCT2,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize_compatible","thrpt",1,3,69521.573119,8875.645591,"ops/s",array,STRUCT2,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize_compatible","thrpt",1,3,124044.417439,41135.181705,"ops/s",directBuffer,SAMPLE,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize_compatible","thrpt",1,3,120276.449497,87827.771405,"ops/s",directBuffer,SAMPLE,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize_compatible","thrpt",1,3,107594.474890,81084.454215,"ops/s",directBuffer,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize_compatible","thrpt",1,3,116531.023438,141591.979641,"ops/s",directBuffer,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize_compatible","thrpt",1,3,89580.561575,23464.095972,"ops/s",directBuffer,STRUCT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize_compatible","thrpt",1,3,84407.472531,46007.731889,"ops/s",directBuffer,STRUCT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize_compatible","thrpt",1,3,69342.030965,17706.784671,"ops/s",directBuffer,STRUCT2,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize_compatible","thrpt",1,3,68542.055543,31793.805138,"ops/s",directBuffer,STRUCT2,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.jdk_deserialize","thrpt",1,3,29309.573998,765.998843,"ops/s",array,SAMPLE,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.jdk_deserialize","thrpt",1,3,27466.003923,14896.452629,"ops/s",array,SAMPLE,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.jdk_deserialize","thrpt",1,3,38536.250402,28217.590983,"ops/s",array,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.jdk_deserialize","thrpt",1,3,38957.191090,23931.545367,"ops/s",array,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.jdk_deserialize","thrpt",1,3,29603.066599,10373.666720,"ops/s",array,STRUCT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.jdk_deserialize","thrpt",1,3,29727.744196,15820.774882,"ops/s",array,STRUCT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.jdk_deserialize","thrpt",1,3,14888.805111,842.165916,"ops/s",array,STRUCT2,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.jdk_deserialize","thrpt",1,3,14034.100664,13936.787605,"ops/s",array,STRUCT2,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.jdk_deserialize","thrpt",1,3,28128.457935,5385.055284,"ops/s",directBuffer,SAMPLE,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.jdk_deserialize","thrpt",1,3,28221.014735,15281.277719,"ops/s",directBuffer,SAMPLE,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.jdk_deserialize","thrpt",1,3,40512.632076,8592.454839,"ops/s",directBuffer,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.jdk_deserialize","thrpt",1,3,37030.594632,9683.316145,"ops/s",directBuffer,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.jdk_deserialize","thrpt",1,3,28717.004518,17023.663871,"ops/s",directBuffer,STRUCT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.jdk_deserialize","thrpt",1,3,29549.998286,13823.854047,"ops/s",directBuffer,STRUCT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.jdk_deserialize","thrpt",1,3,14652.043788,2419.900104,"ops/s",directBuffer,STRUCT2,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.jdk_deserialize","thrpt",1,3,14425.886048,8723.095052,"ops/s",directBuffer,STRUCT2,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize","thrpt",1,3,979173.981159,178146.966897,"ops/s",array,SAMPLE,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize","thrpt",1,3,716438.884369,623966.984928,"ops/s",array,SAMPLE,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize","thrpt",1,3,577631.234369,221011.018380,"ops/s",array,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize","thrpt",1,3,365530.417232,214946.853015,"ops/s",array,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize","thrpt",1,3,607750.343557,99638.164976,"ops/s",array,STRUCT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize","thrpt",1,3,552802.227807,126286.013177,"ops/s",array,STRUCT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize","thrpt",1,3,275984.042401,9405.143983,"ops/s",array,STRUCT2,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize","thrpt",1,3,222710.554833,45168.519253,"ops/s",array,STRUCT2,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize","thrpt",1,3,983538.936801,1112062.949472,"ops/s",directBuffer,SAMPLE,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize","thrpt",1,3,762889.302732,279678.610328,"ops/s",directBuffer,SAMPLE,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize","thrpt",1,3,389473.174523,43058.395501,"ops/s",directBuffer,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize","thrpt",1,3,306995.220799,57411.579647,"ops/s",directBuffer,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize","thrpt",1,3,910534.169114,1888677.766640,"ops/s",directBuffer,STRUCT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize","thrpt",1,3,914404.107564,357717.816934,"ops/s",directBuffer,STRUCT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize","thrpt",1,3,319247.256793,217674.935649,"ops/s",directBuffer,STRUCT2,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize","thrpt",1,3,249105.828416,131690.474985,"ops/s",directBuffer,STRUCT2,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize_compatible","thrpt",1,3,255086.928308,23672.392381,"ops/s",array,SAMPLE,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize_compatible","thrpt",1,3,238811.995510,18059.118666,"ops/s",array,SAMPLE,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize_compatible","thrpt",1,3,180882.860363,69339.581545,"ops/s",array,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize_compatible","thrpt",1,3,154311.211540,23065.912064,"ops/s",array,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize_compatible","thrpt",1,3,78771.635309,45155.641241,"ops/s",array,STRUCT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize_compatible","thrpt",1,3,72805.937649,16849.888437,"ops/s",array,STRUCT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize_compatible","thrpt",1,3,60602.285743,61404.274046,"ops/s",array,STRUCT2,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize_compatible","thrpt",1,3,62729.908347,32805.651539,"ops/s",array,STRUCT2,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize_compatible","thrpt",1,3,201993.787890,41567.224827,"ops/s",directBuffer,SAMPLE,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize_compatible","thrpt",1,3,174534.710870,96357.498949,"ops/s",directBuffer,SAMPLE,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize_compatible","thrpt",1,3,134485.160300,104898.991148,"ops/s",directBuffer,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize_compatible","thrpt",1,3,119311.787329,66580.856795,"ops/s",directBuffer,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize_compatible","thrpt",1,3,58574.904225,140682.248378,"ops/s",directBuffer,STRUCT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize_compatible","thrpt",1,3,60685.320299,24159.853451,"ops/s",directBuffer,STRUCT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize_compatible","thrpt",1,3,54637.329134,14011.283798,"ops/s",directBuffer,STRUCT2,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize_compatible","thrpt",1,3,51761.569591,61030.640534,"ops/s",directBuffer,STRUCT2,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.protostuff_deserialize","thrpt",1,3,619338.385412,275705.847169,"ops/s",array,SAMPLE,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.protostuff_deserialize","thrpt",1,3,951662.019963,165160.853007,"ops/s",array,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.protostuff_deserialize","thrpt",1,3,517381.168594,313746.662696,"ops/s",array,STRUCT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.protostuff_deserialize","thrpt",1,3,416212.973861,119393.345217,"ops/s",array,STRUCT2,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.protostuff_deserialize","thrpt",1,3,624804.978534,147685.682690,"ops/s",directBuffer,SAMPLE,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.protostuff_deserialize","thrpt",1,3,964664.641598,218386.902856,"ops/s",directBuffer,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.protostuff_deserialize","thrpt",1,3,538922.947147,59168.303230,"ops/s",directBuffer,STRUCT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.protostuff_deserialize","thrpt",1,3,425523.315814,179264.359952,"ops/s",directBuffer,STRUCT2,false diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/java/data/jmh-jdk-11-serialization.csv b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/java/data/jmh-jdk-11-serialization.csv new file mode 100644 index 00000000000..2e985751a77 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/java/data/jmh-jdk-11-serialization.csv @@ -0,0 +1,153 @@ +"Benchmark","Mode","Threads","Samples","Score","Score Error (99.9%)","Unit","Param: bufferType","Param: objectType","Param: references" +"org.apache.fory.benchmark.UserTypeSerializeSuite.fst_serialize","thrpt",1,3,915907.574306,87627.907529,"ops/s",array,SAMPLE,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.fst_serialize","thrpt",1,3,731869.156376,37845.555465,"ops/s",array,SAMPLE,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.fst_serialize","thrpt",1,3,751892.023189,174327.010719,"ops/s",array,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.fst_serialize","thrpt",1,3,583859.907758,320047.618377,"ops/s",array,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.fst_serialize","thrpt",1,3,882178.995727,294884.724923,"ops/s",array,STRUCT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.fst_serialize","thrpt",1,3,757753.756691,605180.489112,"ops/s",array,STRUCT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.fst_serialize","thrpt",1,3,371762.982661,80513.482138,"ops/s",array,STRUCT2,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.fst_serialize","thrpt",1,3,380638.700267,101974.434105,"ops/s",array,STRUCT2,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.fst_serialize","thrpt",1,3,902302.261168,474054.277143,"ops/s",directBuffer,SAMPLE,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.fst_serialize","thrpt",1,3,723614.066770,77429.645491,"ops/s",directBuffer,SAMPLE,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.fst_serialize","thrpt",1,3,728001.080250,75699.111305,"ops/s",directBuffer,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.fst_serialize","thrpt",1,3,595679.580108,117269.848918,"ops/s",directBuffer,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.fst_serialize","thrpt",1,3,807847.663261,67842.801069,"ops/s",directBuffer,STRUCT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.fst_serialize","thrpt",1,3,762088.935404,575559.070335,"ops/s",directBuffer,STRUCT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.fst_serialize","thrpt",1,3,365317.705376,138313.634773,"ops/s",directBuffer,STRUCT2,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.fst_serialize","thrpt",1,3,370851.880711,66834.323719,"ops/s",directBuffer,STRUCT2,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize","thrpt",1,3,3570966.469087,152949.902180,"ops/s",array,SAMPLE,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize","thrpt",1,3,1767693.835090,263146.035836,"ops/s",array,SAMPLE,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize","thrpt",1,3,3031642.924542,567213.986117,"ops/s",array,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize","thrpt",1,3,2250384.600246,529709.299207,"ops/s",array,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize","thrpt",1,3,7501415.567260,6672023.542025,"ops/s",array,STRUCT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize","thrpt",1,3,6264439.154428,976363.317001,"ops/s",array,STRUCT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize","thrpt",1,3,3586126.623874,1966282.728305,"ops/s",array,STRUCT2,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize","thrpt",1,3,3306474.506382,869558.338568,"ops/s",array,STRUCT2,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize","thrpt",1,3,3684487.760591,882227.920611,"ops/s",directBuffer,SAMPLE,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize","thrpt",1,3,1826456.709478,1377648.673630,"ops/s",directBuffer,SAMPLE,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize","thrpt",1,3,2479862.129632,1498299.302699,"ops/s",directBuffer,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize","thrpt",1,3,1938527.588331,148125.034055,"ops/s",directBuffer,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize","thrpt",1,3,9834223.243204,5284494.290641,"ops/s",directBuffer,STRUCT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize","thrpt",1,3,7551780.823133,744016.111639,"ops/s",directBuffer,STRUCT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize","thrpt",1,3,2643155.135327,200764.008652,"ops/s",directBuffer,STRUCT2,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize","thrpt",1,3,2391110.083108,2608567.411194,"ops/s",directBuffer,STRUCT2,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize_compatible","thrpt",1,3,3604596.465625,1232634.245435,"ops/s",array,SAMPLE,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize_compatible","thrpt",1,3,1619648.337293,212055.067245,"ops/s",array,SAMPLE,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize_compatible","thrpt",1,3,1679272.036223,788041.322785,"ops/s",array,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize_compatible","thrpt",1,3,1406736.538716,263608.222325,"ops/s",array,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize_compatible","thrpt",1,3,3530406.108869,3125642.741982,"ops/s",array,STRUCT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize_compatible","thrpt",1,3,3293059.098127,96940.669016,"ops/s",array,STRUCT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize_compatible","thrpt",1,3,2773368.997680,503239.905176,"ops/s",array,STRUCT2,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize_compatible","thrpt",1,3,2564174.550276,978139.792610,"ops/s",array,STRUCT2,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize_compatible","thrpt",1,3,3484533.218305,476583.192148,"ops/s",directBuffer,SAMPLE,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize_compatible","thrpt",1,3,1730822.630648,410016.597939,"ops/s",directBuffer,SAMPLE,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize_compatible","thrpt",1,3,1710680.937387,307207.222026,"ops/s",directBuffer,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize_compatible","thrpt",1,3,1149999.473994,140915.968294,"ops/s",directBuffer,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize_compatible","thrpt",1,3,2653169.568374,535312.987476,"ops/s",directBuffer,STRUCT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize_compatible","thrpt",1,3,2393817.762938,380997.375838,"ops/s",directBuffer,STRUCT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize_compatible","thrpt",1,3,1912402.937879,284090.793301,"ops/s",directBuffer,STRUCT2,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize_compatible","thrpt",1,3,1848338.968058,108311.846780,"ops/s",directBuffer,STRUCT2,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.forymetashared_serialize_compatible","thrpt",1,3,4409055.687063,112740.443049,"ops/s",array,SAMPLE,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.forymetashared_serialize_compatible","thrpt",1,3,1840705.439334,516764.580627,"ops/s",array,SAMPLE,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.forymetashared_serialize_compatible","thrpt",1,3,2992288.235281,867622.163323,"ops/s",array,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.forymetashared_serialize_compatible","thrpt",1,3,2058738.716953,2388681.798594,"ops/s",array,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.forymetashared_serialize_compatible","thrpt",1,3,9204444.777172,1526628.258403,"ops/s",array,STRUCT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.forymetashared_serialize_compatible","thrpt",1,3,7064625.291374,2655795.498346,"ops/s",array,STRUCT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.forymetashared_serialize_compatible","thrpt",1,3,2575824.143864,2216634.295140,"ops/s",array,STRUCT2,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.forymetashared_serialize_compatible","thrpt",1,3,3543082.528217,393858.603613,"ops/s",array,STRUCT2,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.forymetashared_serialize_compatible","thrpt",1,3,5043538.364886,2797987.191909,"ops/s",directBuffer,SAMPLE,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.forymetashared_serialize_compatible","thrpt",1,3,1859289.705838,366893.710607,"ops/s",directBuffer,SAMPLE,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.forymetashared_serialize_compatible","thrpt",1,3,2291443.556971,3167882.958411,"ops/s",directBuffer,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.forymetashared_serialize_compatible","thrpt",1,3,1804349.244125,481995.792041,"ops/s",directBuffer,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.forymetashared_serialize_compatible","thrpt",1,3,11650229.648715,1101289.207239,"ops/s",directBuffer,STRUCT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.forymetashared_serialize_compatible","thrpt",1,3,8702412.752357,1116617.972427,"ops/s",directBuffer,STRUCT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.forymetashared_serialize_compatible","thrpt",1,3,2714748.572248,284462.787825,"ops/s",directBuffer,STRUCT2,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.forymetashared_serialize_compatible","thrpt",1,3,1866073.031851,1521156.537508,"ops/s",directBuffer,STRUCT2,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize","thrpt",1,3,220386.502846,60892.430853,"ops/s",array,SAMPLE,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize","thrpt",1,3,192414.014211,34217.359956,"ops/s",array,SAMPLE,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize","thrpt",1,3,367782.358049,54538.846197,"ops/s",array,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize","thrpt",1,3,329427.470680,530871.650379,"ops/s",array,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize","thrpt",1,3,258233.998931,145299.453488,"ops/s",array,STRUCT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize","thrpt",1,3,260845.209485,90601.426407,"ops/s",array,STRUCT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize","thrpt",1,3,56056.080075,3919.171009,"ops/s",array,STRUCT2,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize","thrpt",1,3,60038.879790,6808.083631,"ops/s",array,STRUCT2,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize","thrpt",1,3,220981.308085,153633.380796,"ops/s",directBuffer,SAMPLE,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize","thrpt",1,3,211949.960255,231322.136517,"ops/s",directBuffer,SAMPLE,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize","thrpt",1,3,372477.138150,39349.725456,"ops/s",directBuffer,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize","thrpt",1,3,353376.085025,11334.885856,"ops/s",directBuffer,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize","thrpt",1,3,266481.009225,113518.076189,"ops/s",directBuffer,STRUCT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize","thrpt",1,3,261762.594966,64652.028148,"ops/s",directBuffer,STRUCT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize","thrpt",1,3,55924.319442,28173.043405,"ops/s",directBuffer,STRUCT2,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize","thrpt",1,3,56674.065604,2667.957614,"ops/s",directBuffer,STRUCT2,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize_compatible","thrpt",1,3,234454.975158,5232.236462,"ops/s",array,SAMPLE,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize_compatible","thrpt",1,3,206174.173039,54217.176504,"ops/s",array,SAMPLE,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize_compatible","thrpt",1,3,377195.903772,33297.468886,"ops/s",array,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize_compatible","thrpt",1,3,351657.879556,10762.766962,"ops/s",array,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize_compatible","thrpt",1,3,258650.663523,64158.329095,"ops/s",array,STRUCT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize_compatible","thrpt",1,3,263564.913879,101476.568014,"ops/s",array,STRUCT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize_compatible","thrpt",1,3,58509.125342,10836.872797,"ops/s",array,STRUCT2,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize_compatible","thrpt",1,3,55552.977735,37802.538138,"ops/s",array,STRUCT2,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize_compatible","thrpt",1,3,194761.224263,8828.274695,"ops/s",directBuffer,SAMPLE,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize_compatible","thrpt",1,3,212840.483308,50730.019909,"ops/s",directBuffer,SAMPLE,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize_compatible","thrpt",1,3,371729.727192,136063.629978,"ops/s",directBuffer,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize_compatible","thrpt",1,3,343834.954942,13355.085558,"ops/s",directBuffer,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize_compatible","thrpt",1,3,249221.452137,261034.656030,"ops/s",directBuffer,STRUCT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize_compatible","thrpt",1,3,263623.143601,32854.227410,"ops/s",directBuffer,STRUCT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize_compatible","thrpt",1,3,58908.567439,4137.470432,"ops/s",directBuffer,STRUCT2,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize_compatible","thrpt",1,3,55524.373547,668.406873,"ops/s",directBuffer,STRUCT2,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.jdk_serialize","thrpt",1,3,118374.836631,34704.407553,"ops/s",array,SAMPLE,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.jdk_serialize","thrpt",1,3,119858.140625,49308.850176,"ops/s",array,SAMPLE,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.jdk_serialize","thrpt",1,3,137989.198821,40017.148924,"ops/s",array,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.jdk_serialize","thrpt",1,3,140260.668888,57308.595910,"ops/s",array,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.jdk_serialize","thrpt",1,3,155908.244240,20385.278504,"ops/s",array,STRUCT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.jdk_serialize","thrpt",1,3,151258.539369,12676.385578,"ops/s",array,STRUCT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.jdk_serialize","thrpt",1,3,36846.049162,5491.880967,"ops/s",array,STRUCT2,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.jdk_serialize","thrpt",1,3,38183.705811,14778.659439,"ops/s",array,STRUCT2,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.jdk_serialize","thrpt",1,3,118273.584257,18950.629048,"ops/s",directBuffer,SAMPLE,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.jdk_serialize","thrpt",1,3,108263.040839,14982.910444,"ops/s",directBuffer,SAMPLE,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.jdk_serialize","thrpt",1,3,138567.623369,24365.255940,"ops/s",directBuffer,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.jdk_serialize","thrpt",1,3,140158.673910,54416.908878,"ops/s",directBuffer,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.jdk_serialize","thrpt",1,3,154875.908438,11984.781345,"ops/s",directBuffer,STRUCT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.jdk_serialize","thrpt",1,3,156404.686214,58131.602098,"ops/s",directBuffer,STRUCT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.jdk_serialize","thrpt",1,3,37444.967981,14948.012339,"ops/s",directBuffer,STRUCT2,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.jdk_serialize","thrpt",1,3,35798.679246,36641.428549,"ops/s",directBuffer,STRUCT2,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize","thrpt",1,3,1105365.931217,162251.803619,"ops/s",array,SAMPLE,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize","thrpt",1,3,734215.482291,79297.387490,"ops/s",array,SAMPLE,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize","thrpt",1,3,730792.521676,678674.266715,"ops/s",array,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize","thrpt",1,3,445251.084327,85646.179264,"ops/s",array,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize","thrpt",1,3,558194.100861,257156.321315,"ops/s",array,STRUCT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize","thrpt",1,3,557542.628765,63084.390134,"ops/s",array,STRUCT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize","thrpt",1,3,325172.969175,20774.334333,"ops/s",array,STRUCT2,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize","thrpt",1,3,259863.332448,86373.633851,"ops/s",array,STRUCT2,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize","thrpt",1,3,1376560.302168,147424.342310,"ops/s",directBuffer,SAMPLE,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize","thrpt",1,3,932887.968348,249800.928765,"ops/s",directBuffer,SAMPLE,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize","thrpt",1,3,608972.517580,249598.971835,"ops/s",directBuffer,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize","thrpt",1,3,359875.473951,274870.064607,"ops/s",directBuffer,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize","thrpt",1,3,1078046.011115,249527.480472,"ops/s",directBuffer,STRUCT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize","thrpt",1,3,853350.408656,126642.692106,"ops/s",directBuffer,STRUCT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize","thrpt",1,3,355688.882786,37587.645927,"ops/s",directBuffer,STRUCT2,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize","thrpt",1,3,338960.426033,36185.446014,"ops/s",directBuffer,STRUCT2,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize_compatible","thrpt",1,3,378907.663184,280309.649766,"ops/s",array,SAMPLE,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize_compatible","thrpt",1,3,320815.567701,455179.720989,"ops/s",array,SAMPLE,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize_compatible","thrpt",1,3,188911.259146,97827.317807,"ops/s",array,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize_compatible","thrpt",1,3,145782.916427,81270.905462,"ops/s",array,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize_compatible","thrpt",1,3,145964.199559,88578.667153,"ops/s",array,STRUCT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize_compatible","thrpt",1,3,136180.832879,167433.069232,"ops/s",array,STRUCT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize_compatible","thrpt",1,3,125807.748004,23178.451287,"ops/s",array,STRUCT2,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize_compatible","thrpt",1,3,114983.546343,7341.380140,"ops/s",array,STRUCT2,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize_compatible","thrpt",1,3,296102.615094,18485.738321,"ops/s",directBuffer,SAMPLE,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize_compatible","thrpt",1,3,276757.392449,61173.636852,"ops/s",directBuffer,SAMPLE,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize_compatible","thrpt",1,3,185363.714829,121440.885632,"ops/s",directBuffer,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize_compatible","thrpt",1,3,142836.961878,12450.918452,"ops/s",directBuffer,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize_compatible","thrpt",1,3,106695.800225,13463.570576,"ops/s",directBuffer,STRUCT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize_compatible","thrpt",1,3,106458.212005,2994.065295,"ops/s",directBuffer,STRUCT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize_compatible","thrpt",1,3,92130.672361,9515.661170,"ops/s",directBuffer,STRUCT2,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize_compatible","thrpt",1,3,88989.724768,9379.338279,"ops/s",directBuffer,STRUCT2,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.protostuff_serialize","thrpt",1,3,663272.710783,522116.492895,"ops/s",array,SAMPLE,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.protostuff_serialize","thrpt",1,3,780618.761219,406063.945348,"ops/s",array,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.protostuff_serialize","thrpt",1,3,330975.350403,196475.862643,"ops/s",array,STRUCT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.protostuff_serialize","thrpt",1,3,322563.440433,201276.713653,"ops/s",array,STRUCT2,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.protostuff_serialize","thrpt",1,3,693641.589806,43883.803566,"ops/s",directBuffer,SAMPLE,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.protostuff_serialize","thrpt",1,3,805941.345157,91281.699006,"ops/s",directBuffer,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.protostuff_serialize","thrpt",1,3,340262.650047,152117.387028,"ops/s",directBuffer,STRUCT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.protostuff_serialize","thrpt",1,3,325093.716261,56485.972228,"ops/s",directBuffer,STRUCT2,false diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/java/data/jmh-jdk-11-zerocopy.csv b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/java/data/jmh-jdk-11-zerocopy.csv new file mode 100644 index 00000000000..503298291f9 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/java/data/jmh-jdk-11-zerocopy.csv @@ -0,0 +1,73 @@ +"Benchmark","Mode","Threads","Samples","Score","Score Error (99.9%)","Unit","Param: array_size","Param: bufferType","Param: dataType","Param: references" +"org.apache.fory.benchmark.ZeroCopySuite.fst_deserialize","thrpt",1,3,219333.990504,40075.312705,"ops/s",200,array,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.fst_deserialize","thrpt",1,3,657754.887247,408901.031753,"ops/s",200,array,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.fst_deserialize","thrpt",1,3,179604.045774,45387.509931,"ops/s",200,directBuffer,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.fst_deserialize","thrpt",1,3,598421.278941,135411.381215,"ops/s",200,directBuffer,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.fst_deserialize","thrpt",1,3,53100.903684,17709.358858,"ops/s",1000,array,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.fst_deserialize","thrpt",1,3,422147.154601,528539.529166,"ops/s",1000,array,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.fst_deserialize","thrpt",1,3,38572.001768,24685.893533,"ops/s",1000,directBuffer,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.fst_deserialize","thrpt",1,3,298929.116572,135040.133488,"ops/s",1000,directBuffer,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.fst_deserialize","thrpt",1,3,10672.872798,11519.184075,"ops/s",5000,array,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.fst_deserialize","thrpt",1,3,136934.604328,19269.032565,"ops/s",5000,array,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.fst_deserialize","thrpt",1,3,8561.694533,2137.867210,"ops/s",5000,directBuffer,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.fst_deserialize","thrpt",1,3,77950.612503,83587.484192,"ops/s",5000,directBuffer,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.fst_serialize","thrpt",1,3,313986.053417,29311.641179,"ops/s",200,array,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.fst_serialize","thrpt",1,3,2400193.220466,242732.420524,"ops/s",200,array,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.fst_serialize","thrpt",1,3,294132.218623,60640.778775,"ops/s",200,directBuffer,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.fst_serialize","thrpt",1,3,2282550.111756,93004.652618,"ops/s",200,directBuffer,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.fst_serialize","thrpt",1,3,67209.107012,5947.935958,"ops/s",1000,array,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.fst_serialize","thrpt",1,3,1805557.477810,5232839.112456,"ops/s",1000,array,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.fst_serialize","thrpt",1,3,66108.014322,16869.542536,"ops/s",1000,directBuffer,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.fst_serialize","thrpt",1,3,1644789.427010,54907.475118,"ops/s",1000,directBuffer,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.fst_serialize","thrpt",1,3,14997.400124,12425.308762,"ops/s",5000,array,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.fst_serialize","thrpt",1,3,811029.402136,39593.863849,"ops/s",5000,array,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.fst_serialize","thrpt",1,3,15000.378818,2219.583522,"ops/s",5000,directBuffer,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.fst_serialize","thrpt",1,3,477148.540850,34970.176073,"ops/s",5000,directBuffer,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.fory_deserialize","thrpt",1,3,986136.067809,274606.792449,"ops/s",200,array,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.fory_deserialize","thrpt",1,3,3302149.383135,3166455.521704,"ops/s",200,array,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.fory_deserialize","thrpt",1,3,991807.969328,296490.837932,"ops/s",200,directBuffer,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.fory_deserialize","thrpt",1,3,3113115.471758,4861106.592529,"ops/s",200,directBuffer,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.fory_deserialize","thrpt",1,3,205671.992736,127494.951753,"ops/s",1000,array,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.fory_deserialize","thrpt",1,3,2831942.848999,1720214.698856,"ops/s",1000,array,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.fory_deserialize","thrpt",1,3,202275.242341,56270.238293,"ops/s",1000,directBuffer,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.fory_deserialize","thrpt",1,3,3397690.327371,146058.097383,"ops/s",1000,directBuffer,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.fory_deserialize","thrpt",1,3,40312.590172,11836.614114,"ops/s",5000,array,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.fory_deserialize","thrpt",1,3,3296658.120035,2199676.238015,"ops/s",5000,array,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.fory_deserialize","thrpt",1,3,40413.743717,9893.735875,"ops/s",5000,directBuffer,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.fory_deserialize","thrpt",1,3,3284441.570594,998247.999163,"ops/s",5000,directBuffer,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.fory_serialize","thrpt",1,3,8297232.942927,6613235.386482,"ops/s",200,array,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.fory_serialize","thrpt",1,3,5123572.914045,9342104.794019,"ops/s",200,array,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.fory_serialize","thrpt",1,3,8335248.350301,2796789.944593,"ops/s",200,directBuffer,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.fory_serialize","thrpt",1,3,5400346.890126,7654935.504676,"ops/s",200,directBuffer,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.fory_serialize","thrpt",1,3,8772856.921028,1984208.178343,"ops/s",1000,array,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.fory_serialize","thrpt",1,3,4979590.929127,5568756.926202,"ops/s",1000,array,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.fory_serialize","thrpt",1,3,8207563.785251,4153794.222735,"ops/s",1000,directBuffer,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.fory_serialize","thrpt",1,3,5376191.775007,9167580.616022,"ops/s",1000,directBuffer,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.fory_serialize","thrpt",1,3,8027439.580226,6148996.477681,"ops/s",5000,array,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.fory_serialize","thrpt",1,3,5018916.322770,1424045.598203,"ops/s",5000,array,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.fory_serialize","thrpt",1,3,7695981.988316,578662.201123,"ops/s",5000,directBuffer,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.fory_serialize","thrpt",1,3,5330897.682960,8055585.038901,"ops/s",5000,directBuffer,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.kryo_deserialize","thrpt",1,3,146675.360652,23502.675233,"ops/s",200,array,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.kryo_deserialize","thrpt",1,3,1296284.787720,297204.673569,"ops/s",200,array,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.kryo_deserialize","thrpt",1,3,518713.299422,600548.228171,"ops/s",200,directBuffer,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.kryo_deserialize","thrpt",1,3,1004844.498712,552313.083272,"ops/s",200,directBuffer,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.kryo_deserialize","thrpt",1,3,30409.835023,9562.322209,"ops/s",1000,array,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.kryo_deserialize","thrpt",1,3,721266.541130,62426.822891,"ops/s",1000,array,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.kryo_deserialize","thrpt",1,3,112132.004609,6625.327605,"ops/s",1000,directBuffer,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.kryo_deserialize","thrpt",1,3,592972.713203,511777.226637,"ops/s",1000,directBuffer,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.kryo_deserialize","thrpt",1,3,6122.351228,3021.683209,"ops/s",5000,array,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.kryo_deserialize","thrpt",1,3,147251.846111,278375.379465,"ops/s",5000,array,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.kryo_deserialize","thrpt",1,3,21826.040410,2246.004993,"ops/s",5000,directBuffer,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.kryo_deserialize","thrpt",1,3,148614.476829,87146.409451,"ops/s",5000,directBuffer,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.kryo_serialize","thrpt",1,3,147342.606262,138864.952435,"ops/s",200,array,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.kryo_serialize","thrpt",1,3,1985187.977633,70426.638387,"ops/s",200,array,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.kryo_serialize","thrpt",1,3,972683.763633,604464.052899,"ops/s",200,directBuffer,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.kryo_serialize","thrpt",1,3,1739454.519770,1053963.541481,"ops/s",200,directBuffer,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.kryo_serialize","thrpt",1,3,31395.721514,3652.506399,"ops/s",1000,array,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.kryo_serialize","thrpt",1,3,1616159.671230,2722436.839807,"ops/s",1000,array,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.kryo_serialize","thrpt",1,3,209183.090868,25323.702489,"ops/s",1000,directBuffer,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.kryo_serialize","thrpt",1,3,1377272.568510,203974.331559,"ops/s",1000,directBuffer,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.kryo_serialize","thrpt",1,3,6248.006967,783.226602,"ops/s",5000,array,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.kryo_serialize","thrpt",1,3,711287.533377,86363.201941,"ops/s",5000,array,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.kryo_serialize","thrpt",1,3,43565.678616,4221.267495,"ops/s",5000,directBuffer,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.kryo_serialize","thrpt",1,3,707092.956534,298737.951680,"ops/s",5000,directBuffer,BUFFER,false diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/java/java_heap_deserialize_compatible.png b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/java/java_heap_deserialize_compatible.png new file mode 100644 index 00000000000..52dd292a3c0 Binary files /dev/null and b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/java/java_heap_deserialize_compatible.png differ diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/java/java_heap_deserialize_consistent.png b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/java/java_heap_deserialize_consistent.png new file mode 100644 index 00000000000..a21445421a7 Binary files /dev/null and b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/java/java_heap_deserialize_consistent.png differ diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/java/java_heap_serialize_compatible.png b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/java/java_heap_serialize_compatible.png new file mode 100644 index 00000000000..2142dfdbab0 Binary files /dev/null and b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/java/java_heap_serialize_compatible.png differ diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/java/java_heap_serialize_consistent.png b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/java/java_heap_serialize_consistent.png new file mode 100644 index 00000000000..5703f724733 Binary files /dev/null and b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/java/java_heap_serialize_consistent.png differ diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/java/java_offheap_deserialize_compatible.png b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/java/java_offheap_deserialize_compatible.png new file mode 100644 index 00000000000..ce77bf6fb7c Binary files /dev/null and b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/java/java_offheap_deserialize_compatible.png differ diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/java/java_offheap_deserialize_consistent.png b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/java/java_offheap_deserialize_consistent.png new file mode 100644 index 00000000000..04c3e3d04aa Binary files /dev/null and b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/java/java_offheap_deserialize_consistent.png differ diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/java/java_offheap_serialize_compatible.png b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/java/java_offheap_serialize_compatible.png new file mode 100644 index 00000000000..8d9197121c8 Binary files /dev/null and b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/java/java_offheap_serialize_compatible.png differ diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/java/java_offheap_serialize_consistent.png b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/java/java_offheap_serialize_consistent.png new file mode 100644 index 00000000000..8bdea8f5598 Binary files /dev/null and b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/java/java_offheap_serialize_consistent.png differ diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/java/java_repo_deserialization_throughput.png b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/java/java_repo_deserialization_throughput.png new file mode 100644 index 00000000000..d64f5e77a19 Binary files /dev/null and b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/java/java_repo_deserialization_throughput.png differ diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/java/java_repo_serialization_throughput.png b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/java/java_repo_serialization_throughput.png new file mode 100644 index 00000000000..8f9824ffe1a Binary files /dev/null and b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/java/java_repo_serialization_throughput.png differ diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/java/java_zero_copy_deserialize.png b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/java/java_zero_copy_deserialize.png new file mode 100644 index 00000000000..a91315eb345 Binary files /dev/null and b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/java/java_zero_copy_deserialize.png differ diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/java/java_zero_copy_serialize.png b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/java/java_zero_copy_serialize.png new file mode 100644 index 00000000000..a3f1b983464 Binary files /dev/null and b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/java/java_zero_copy_serialize.png differ diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/java/throughput.png b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/java/throughput.png new file mode 100644 index 00000000000..025f8b4329d Binary files /dev/null and b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/java/throughput.png differ diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/javascript/README.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/javascript/README.md new file mode 100644 index 00000000000..574bb7dfe21 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/javascript/README.md @@ -0,0 +1,82 @@ +# JavaScript 基准性能报告 + +_生成于 2026-05-08 17:55:12_ + +## 如何生成本报告 + +```bash +cd benchmarks/javascript +./run.sh +``` + +## 基准语义 + +计时的序列化器循环使用各序列化器原生的类型化值。Fory 接收其 schema 使用的预归一化 Fory 值,protobuf 接收预构造的 protobuf 形态值,JSON 接收基准 JavaScript 对象。Protobuf 耗时不包含 `toProto`、`fromProto`、`protobufjs.create` 或 `toObject` 转换工作。 + +## 基准图表 + +图表展示吞吐量(ops/sec);数值越高越好。 + +![Throughput](throughput.png) + +## 硬件与操作系统信息 + +| 键 | 值 | +| -------------------------- | ------------------------ | +| 操作系统 | Darwin 24.6.0 | +| 机器架构 | arm64 | +| 处理器 | arm | +| CPU 核心数(物理) | 12 | +| CPU 核心数(逻辑) | 12 | +| 总内存(GB) | 48.0 | +| 基准日期 | 2026-05-08T08:07:36.073Z | +| CPU 核心数(基准采集) | 12 | +| Node.js | v22.20.0 | +| V8 | 12.4.254.21-node.33 | + +## 基准结果 + +### 延迟结果(纳秒) + +| 数据类型 | 操作 | fory (ns) | protobuf (ns) | json (ns) | 最快 | +| ----------------- | ---- | --------- | ------------- | --------- | -------- | +| NumericStruct | 序列化 | 76.0 | 613.0 | 496.0 | fory | +| NumericStruct | 反序列化 | 56.9 | 94.8 | 333.0 | fory | +| Sample | 序列化 | 318.0 | 2016.6 | 1409.3 | fory | +| Sample | 反序列化 | 496.0 | 902.5 | 1609.6 | fory | +| MediaContent | 序列化 | 494.1 | 1358.5 | 803.5 | fory | +| MediaContent | 反序列化 | 539.3 | 628.3 | 1134.3 | fory | +| NumericStructList | 序列化 | 195.3 | 3019.3 | 2013.5 | fory | +| NumericStructList | 反序列化 | 183.7 | 606.9 | 1944.0 | fory | +| SampleList | 序列化 | 1681.9 | 19346.7 | 11870.3 | fory | +| SampleList | 反序列化 | 2571.9 | 5730.6 | 9074.5 | fory | +| MediaContentList | 序列化 | 2785.9 | 7616.6 | 3611.5 | fory | +| MediaContentList | 反序列化 | 3709.7 | 3018.6 | 5294.5 | protobuf | + +### 吞吐结果(ops/sec) + +| 数据类型 | 操作 | fory TPS | protobuf TPS | json TPS | 最快 | +| ----------------- | ---- | ---------- | ------------ | --------- | -------- | +| NumericStruct | 序列化 | 13,162,466 | 1,631,271 | 2,016,097 | fory | +| NumericStruct | 反序列化 | 17,568,418 | 10,543,763 | 3,002,971 | fory | +| Sample | 序列化 | 3,144,194 | 495,893 | 709,593 | fory | +| Sample | 反序列化 | 2,015,942 | 1,108,010 | 621,285 | fory | +| MediaContent | 序列化 | 2,023,719 | 736,097 | 1,244,512 | fory | +| MediaContent | 反序列化 | 1,854,348 | 1,591,617 | 881,572 | fory | +| NumericStructList | 序列化 | 5,121,376 | 331,201 | 496,645 | fory | +| NumericStructList | 反序列化 | 5,444,504 | 1,647,728 | 514,414 | fory | +| SampleList | 序列化 | 594,551 | 51,688 | 84,244 | fory | +| SampleList | 反序列化 | 388,820 | 174,503 | 110,199 | fory | +| MediaContentList | 序列化 | 358,954 | 131,293 | 276,891 | fory | +| MediaContentList | 反序列化 | 269,561 | 331,275 | 188,876 | protobuf | + +### 序列化数据大小(字节) + +| 数据类型 | fory | protobuf | json | +| ----------------- | ---- | -------- | ---- | +| NumericStruct | 78 | 93 | 159 | +| Sample | 445 | 377 | 724 | +| MediaContent | 388 | 307 | 596 | +| NumericStructList | 255 | 475 | 817 | +| SampleList | 1978 | 1900 | 3642 | +| MediaContentList | 1661 | 1550 | 3009 | diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/javascript/throughput.png b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/javascript/throughput.png new file mode 100644 index 00000000000..54c24051d66 Binary files /dev/null and b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/javascript/throughput.png differ diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/python/README.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/python/README.md new file mode 100644 index 00000000000..fa2de3f06f5 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/python/README.md @@ -0,0 +1,87 @@ +# Python 基准性能报告 + +_生成于 2026-05-08 17:54:45_ + +## 如何生成本报告 + +```bash +cd benchmarks/python +./run.sh +``` + +## 基准图表 + +图表展示吞吐量(ops/sec);数值越高越好。 + +![Throughput](throughput.png) + +## 硬件与操作系统信息 + +| 键 | 值 | +| --------------------- | ---------------------------- | +| 操作系统 | Darwin 24.6.0 | +| 机器架构 | arm64 | +| 处理器 | arm | +| Python | 3.10.8 | +| CPU 核心数(物理) | 12 | +| CPU 核心数(逻辑) | 12 | +| 总内存(GB) | 48.0 | +| Python 实现 | CPython | +| 基准平台 | macOS-15.7.2-arm64-arm-64bit | + +## 基准配置 + +| 键 | 值 | +| ---------- | ----- | +| warmup | 3 | +| iterations | 15 | +| repeat | 5 | +| number | 1000 | +| list_size | 5 | + +## 基准结果 + +### 延迟结果(纳秒) + +| 数据类型 | 操作 | fory (ns) | protobuf (ns) | pickle (ns) | 最快 | +| ----------------- | ---- | --------- | ------------- | ---- | ------- | +| NumericStruct | 序列化 | 491.4 | 802.3 | 1119.8 | fory | +| NumericStruct | 反序列化 | 522.2 | 1211.6 | 1788.7 | fory | +| Sample | 序列化 | 1096.4 | 3315.8 | 10185.2 | fory | +| Sample | 反序列化 | 2772.0 | 6659.7 | 7061.9 | fory | +| MediaContent | 序列化 | 989.2 | 3433.2 | 4392.7 | fory | +| MediaContent | 反序列化 | 1518.7 | 4381.2 | 4305.1 | fory | +| NumericStructList | 序列化 | 1111.2 | 4707.9 | 3235.8 | fory | +| NumericStructList | 反序列化 | 1891.7 | 6891.0 | 3974.9 | fory | +| SampleList | 序列化 | 3447.2 | 18719.1 | 32125.7 | fory | +| SampleList | 反序列化 | 13131.6 | 35264.2 | 24154.4 | fory | +| MediaContentList | 序列化 | 2996.5 | 17597.4 | 11087.8 | fory | +| MediaContentList | 反序列化 | 6228.7 | 21562.0 | 10459.3 | fory | + +### 吞吐结果(ops/sec) + +| 数据类型 | 操作 | fory TPS | protobuf TPS | pickle TPS | 最快 | +| ----------------- | ---- | --------- | ------------ | ---------- | ------- | +| NumericStruct | 序列化 | 2,035,025 | 1,246,379 | 893,009 | fory | +| NumericStruct | 反序列化 | 1,915,112 | 825,344 | 559,055 | fory | +| Sample | 序列化 | 912,072 | 301,590 | 98,182 | fory | +| Sample | 反序列化 | 360,751 | 150,158 | 141,605 | fory | +| MediaContent | 序列化 | 1,010,939 | 291,275 | 227,652 | fory | +| MediaContent | 反序列化 | 658,462 | 228,247 | 232,281 | fory | +| NumericStructList | 序列化 | 899,960 | 212,407 | 309,040 | fory | +| NumericStructList | 反序列化 | 528,636 | 145,116 | 251,580 | fory | +| SampleList | 序列化 | 290,092 | 53,421 | 31,128 | fory | +| SampleList | 反序列化 | 76,152 | 28,357 | 41,400 | fory | +| MediaContentList | 序列化 | 333,720 | 56,826 | 90,189 | fory | +| MediaContentList | 反序列化 | 160,547 | 46,378 | 95,609 | fory | + +### 序列化数据大小(字节) + +| 数据类型 | fory | protobuf | pickle | +| ----------------- | ---- | -------- | ------ | +| NumericStruct | 78 | 93 | 169 | +| Sample | 445 | 375 | 1176 | +| MediaContent | 366 | 301 | 624 | +| NumericStructList | 219 | 475 | 582 | +| SampleList | 1914 | 1890 | 3546 | +| MediaContentList | 1614 | 1520 | 1415 | diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/python/throughput.png b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/python/throughput.png new file mode 100644 index 00000000000..7bd99a82e6d Binary files /dev/null and b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/python/throughput.png differ diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/rust/README.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/rust/README.md new file mode 100644 index 00000000000..76ff3c4a575 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/rust/README.md @@ -0,0 +1,77 @@ +# Rust 基准性能报告 + +_生成于 2026-05-08 17:55:12_ + +## 如何生成本报告 + +```bash +cd benchmarks/rust +cargo bench --bench serialization_bench 2>&1 | tee results/cargo_bench.log +cargo run --release --bin fory_profiler -- --print-all-serialized-sizes | tee results/serialized_sizes.txt +python benchmark_report.py --log-file results/cargo_bench.log --size-file results/serialized_sizes.txt --output-dir results +``` + +## 基准图表 + +图表展示吞吐量(ops/sec);数值越高越好。 + +![Throughput](throughput.png) + +## 硬件与操作系统信息 + +| 键 | 值 | +| -------------------- | ------------------- | +| 操作系统 | Darwin 24.6.0 | +| 机器架构 | arm64 | +| 处理器 | arm | +| CPU 核心数(物理) | 12 | +| CPU 核心数(逻辑) | 12 | +| 总内存(GB) | 48.0 | +| 基准日期 | 2026-05-08T16:47:49 | + +## 基准结果 + +### 延迟结果(纳秒) + +| 数据类型 | 操作 | fory (ns) | protobuf (ns) | msgpack (ns) | 最快 | +| ----------------- | ---- | --------- | ------------- | ------------ | ------- | +| NumericStruct | 序列化 | 38.1 | 94.6 | 239.5 | fory | +| NumericStruct | 反序列化 | 32.6 | 62.4 | 107.3 | fory | +| Sample | 序列化 | 95.3 | 591.8 | 601.1 | fory | +| Sample | 反序列化 | 410.1 | 925.8 | 805.9 | fory | +| MediaContent | 序列化 | 120.0 | 553.9 | 446.9 | fory | +| MediaContent | 反序列化 | 566.7 | 713.0 | 902.6 | fory | +| NumericStructList | 序列化 | 121.5 | 512.0 | 618.0 | fory | +| NumericStructList | 反序列化 | 137.9 | 404.9 | 615.9 | fory | +| SampleList | 序列化 | 267.7 | 2920.2 | 2011.1 | fory | +| SampleList | 反序列化 | 1831.9 | 4636.4 | 4141.4 | fory | +| MediaContentList | 序列化 | 367.1 | 2835.6 | 1441.7 | fory | +| MediaContentList | 反序列化 | 2703.8 | 3622.3 | 4832.3 | fory | + +### 吞吐结果(ops/sec) + +| 数据类型 | 操作 | fory TPS | protobuf TPS | msgpack TPS | 最快 | +| ----------------- | ---- | ---------- | ------------ | ---- | ------- | +| NumericStruct | 序列化 | 26,237,767 | 10,572,613 | 4,174,668 | fory | +| NumericStruct | 反序列化 | 30,720,079 | 16,035,920 | 9,322,271 | fory | +| Sample | 序列化 | 10,494,611 | 1,689,874 | 1,663,700 | fory | +| Sample | 反序列化 | 2,438,311 | 1,080,170 | 1,240,895 | fory | +| MediaContent | 序列化 | 8,331,945 | 1,805,445 | 2,237,687 | fory | +| MediaContent | 反序列化 | 1,764,633 | 1,402,426 | 1,107,960 | fory | +| NumericStructList | 序列化 | 8,232,485 | 1,953,125 | 1,618,071 | fory | +| NumericStructList | 反序列化 | 7,250,580 | 2,469,563 | 1,623,535 | fory | +| SampleList | 序列化 | 3,735,664 | 342,442 | 497,240 | fory | +| SampleList | 反序列化 | 545,881 | 215,685 | 241,464 | fory | +| MediaContentList | 序列化 | 2,724,350 | 352,659 | 693,626 | fory | +| MediaContentList | 反序列化 | 369,850 | 276,068 | 206,941 | fory | + +### 序列化数据大小(字节) + +| 数据类型 | fory | protobuf | msgpack | +| ----------------- | ---- | -------- | ------- | +| NumericStruct | 78 | 93 | 87 | +| Sample | 445 | 375 | 590 | +| MediaContent | 362 | 301 | 500 | +| NumericStructList | 255 | 475 | 449 | +| SampleList | 1978 | 1890 | 2964 | +| MediaContentList | 1531 | 1520 | 2521 | diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/rust/throughput.png b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/rust/throughput.png new file mode 100644 index 00000000000..5640e734e40 Binary files /dev/null and b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/rust/throughput.png differ diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/swift/README.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/swift/README.md new file mode 100644 index 00000000000..f0840cc9cb4 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/swift/README.md @@ -0,0 +1,46 @@ +# Fory Swift 基准 + +该基准比较 Apache Fory、Protocol Buffers 和 JSON 在 Swift 中的序列化与反序列化吞吐量。 + +## 吞吐图表 + +![Throughput](throughput.png) + +## 硬件与运行时信息 + +| 键 | 值 | +| --------------------- | ----------------------------- | +| 时间戳 | 2026-05-08T09:05:32Z | +| 操作系统 | Version 15.7.2 (Build 24G325) | +| 主机 | macbook-pro.local | +| CPU 核心数(逻辑) | 12 | +| 内存(GB) | 48.00 | +| 每个用例持续时长(秒) | 3 | + +## 吞吐结果 + +| 数据类型 | 操作 | Fory TPS | Protobuf TPS | JSON TPS | 最快 | +| ----------------- | ---- | ---------: | -----------: | -------: | ------------ | +| NumericStruct | 序列化 | 9,435,623 | 6,175,939 | 408,960 | fory (1.53x) | +| NumericStruct | 反序列化 | 11,037,225 | 6,842,676 | 328,302 | fory (1.61x) | +| Sample | 序列化 | 3,596,835 | 1,257,100 | 79,781 | fory (2.86x) | +| Sample | 反序列化 | 982,255 | 733,588 | 41,274 | fory (1.34x) | +| MediaContent | 序列化 | 1,561,376 | 609,896 | 98,677 | fory (2.56x) | +| MediaContent | 反序列化 | 523,836 | 395,202 | 70,528 | fory (1.33x) | +| NumericStructList | 序列化 | 2,910,846 | 918,363 | 82,965 | fory (3.17x) | +| NumericStructList | 反序列化 | 2,436,636 | 701,656 | 69,353 | fory (3.47x) | +| SampleList | 序列化 | 694,557 | 202,040 | 16,679 | fory (3.44x) | +| SampleList | 反序列化 | 187,109 | 131,947 | 8,236 | fory (1.42x) | +| MediaContentList | 序列化 | 348,238 | 98,007 | 18,698 | fory (3.55x) | +| MediaContentList | 反序列化 | 104,990 | 74,422 | 16,298 | fory (1.41x) | + +## 序列化大小(字节) + +| 数据类型 | Fory | Protobuf | JSON | +| ----------------- | ---: | -------: | ---: | +| NumericStruct | 78 | 93 | 159 | +| Sample | 445 | 375 | 696 | +| MediaContent | 362 | 301 | 608 | +| NumericStructList | 255 | 475 | 816 | +| SampleList | 1978 | 1890 | 3501 | +| MediaContentList | 1531 | 1520 | 3067 | diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/swift/throughput.png b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/swift/throughput.png new file mode 100644 index 00000000000..c8d7f75e350 Binary files /dev/null and b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/benchmarks/swift/throughput.png differ diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/community/DEVELOPMENT.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/community/DEVELOPMENT.md new file mode 100644 index 00000000000..1980387e772 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/community/DEVELOPMENT.md @@ -0,0 +1,147 @@ +--- +title: 开发指南 +sidebar_position: 10 +id: development +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. +--- + +## 如何构建 Apache Fory™ + +请从 https://github.com/apache/fory 检出源代码树。 + +### 构建 Apache Fory™ Java + +```bash +cd java +mvn clean compile -DskipTests +``` + +#### 环境要求 + +- java 1.8+ +- maven 3.6.3+ + +### 构建 Apache Fory™ Python + +```bash +cd python +# 首先卸载 numpy,以便在安装 pyarrow 时自动安装正确的 numpy 版本。 +# 对于 Python 版本低于 3.13,目前不支持 numpy 2。 +pip uninstall -y numpy +# 为 Python < 3.13 安装必要的环境。 +pip install pyarrow Cython wheel pytest +# pip install pyarrow Cython wheel pytest +pip install -v -e . +``` + +#### 环境要求 + +- python 3.6+ + +### 构建 Apache Fory™ C++ + +构建 fory 行格式: + +```bash +bazel build //cpp/fory/row:fory_row_format +``` + +构建 fory 行格式编码器: + +```bash +bazel build //cpp/fory/encoder:fory_encoder +``` + +#### 环境要求 + +- 支持 C++17 的编译器 +- bazel 6.3.2 + +### 构建 Apache Fory™ GoLang + +```bash +cd go/fory +# 运行测试 +go test -v ./... +# 运行跨语言测试 +go test -v fory_xlang_test.go +``` + +#### 环境要求 + +- go 1.13+ + +### 构建 Apache Fory™ Rust + +```bash +cd rust +# 构建 +cargo build +# 运行测试 +cargo test +# 运行特定测试 +cargo test -p tests --test $test_file $test_method +# 运行子目录下的特定测试 +cargo test --test mod $dir$::$test_file::$test_method +# 调试子目录下的特定测试并获取回溯信息 +RUST_BACKTRACE=1 FORY_PANIC_ON_ERROR=1 cargo test --test mod $dir$::$test_file::$test_method +# 检查 fory derive 宏生成的代码 +cargo expand --test mod $mod$::$file$ > expanded.rs +``` + +#### 环境要求 + +```bash +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +``` + +### 构建 Apache Fory™ JavaScript + +```bash +cd javascript +npm install + +# 运行构建 +npm run build +# 运行测试 +npm run test +``` + +#### 环境要求 + +- node 14+ +- npm 8+ + +### Lint Markdown 文档 + +```bash +# 全局安装 prettier +npm install -g prettier + +# 格式化 markdown 文件 +prettier --write "**/*.md" +``` + +#### 环境要求 + +- node 14+ +- npm 8+ + +## 贡献 + +更多信息,请参考[如何贡献到 Apache Fory™](https://github.com/apache/fory/blob/main/CONTRIBUTING.md)。 +对于 AI 辅助贡献,请遵循 [AI Contribution Policy](https://github.com/apache/fory/blob/main/AI_POLICY.md),包括 substantial AI assistance 所需的自审、双 reviewer AI review 循环、披露和验证证据。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/community/community.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/community/community.md new file mode 100644 index 00000000000..25b8db4021d --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/community/community.md @@ -0,0 +1,85 @@ +--- +title: 社区 +sidebar_position: 0 +id: community +--- + +Apache Fory™ 是一个由社区驱动的开源项目,项目的蓬勃发展得益于社区贡献。 +我们邀请您根据自己的意愿尽可能地参与项目。以下是几种贡献方式: + +- 使用 Apache Fory™ 并分享使用体验和反馈问题; +- 为项目提供最佳实践示例; +- 报告错误并修复; +- 贡献代码和参与文档建设。 + +## 邮件列表 + +| 邮件列表 | 描述 | 订阅 | 取消订阅 | 发送邮件 | 活动 | +| ----------------------- | ------------------ | ------------------------------------------------ | ------------------------------------------------------ | -------------------------------------- | -------------------------------------------------------------------------- | +| dev@fory.apache.org | 开发相关讨论 | [订阅](mailto:dev-subscribe@fory.apache.org) | [取消订阅](mailto:dev-unsubscribe@fory.apache.org) | [发送邮件](mailto:dev@fory.apache.org) | [邮件列表活动](https://lists.apache.org/list.html?dev@fory.apache.org) | +| commits@fory.apache.org | 仓库的所有 commits | [订阅](mailto:commits-subscribe@fory.apache.org) | [取消订阅](mailto:commits-unsubscribe@fory.apache.org) | 只读的邮件列表 | [邮件列表活动](https://lists.apache.org/list.html?commits@fory.apache.org) | + +在尝试发送邮件之前,请确保订阅上述的邮件列表。 + +**如果您没有订阅邮件列表,您的邮件将被拒绝或不会收到回复。** + +### 如何订阅邮件列表 + +要发送邮件至邮件列表,请先通过以下方式订阅: + +1. 发送电子邮件至 listname-subscribe@fory.apache.org,并相应替换 `listname`; +2. 回复您将收到的确认电子邮件,保持邮件主题行完整; +3. 然后您将收到一封欢迎的电子邮件,订阅成功。 + +在讨论电子邮件中的代码片段时,请确保: + +- 您不要链接到外部服务中的文件,因为此类文件可能会更改、被删除或链接可能会中断,从而使存档的电子邮件线程变得无用; +- 您粘贴文本而不是文本屏幕截图; +- 粘贴代码时保持格式,以保持代码可读; +- 有足够的导入语句以避免产生代码歧义。 + +## Slack + +您可以加入[Slack 上的 Apache Fory™ 社区](https://join.slack.com/t/fory-project/shared_invite/zt-1u8soj4qc-ieYEu7ciHOqA2mo47llS8A)。 + +这里有一些社区规则: + +- 保持尊重和友善; +- 所有重要的决定和结论都必须反映到邮件列表中。 “如果这没有在邮件列表中有相关的讨论记录,则代表它不生效” ; +- [The Apache Way](https://theapacheway.com/on-list/); +- 使用 Slack 线程来防止并行对话淹没当前的对话频道; +- 请不要直接向邮件列表发送 Bug fix、Issue 分配和 Code Review 消息。这些内容应该被社区贡献者自愿处理并分配。 + +## Issue 跟踪 + +我们使用 GitHub Issues 来跟踪所有 Issues: + +- 代码相关问题:https://github.com/apache/fory/issues +- 网站相关问题:https://github.com/apache/fory-site/issues + +您需要有一个 [GitHub 帐号](https://github.com/signup) 才能创建问题。 +如果您没有 [GitHub 帐号](https://github.com/signup),您可以发送电子邮件至 dev@fory.apache.org。 + +### 报告 Bug + +您在报告 Bug 之前,应该: + +- 验证该 Bug 确实存在; +- 搜索 [Issue List](https://github.com/apache/fory/issues) 以确保不存在相关 Bug。 +- 在 Issue List 中创建 [bug 报告](https://github.com/apache/fory/issues/new?assignees=&labels=bug&projects=&template=bug_report.yml)。 +- 如果可能的话,深入研究 Apache Fory™ 的源代码,并针对您报告的 Bug 提交补丁,这有助于快速修复 Bug。 + +### 报告安全漏洞 + +Apache Fory™ 是 [Apache 软件基金会](https://apache.org/) 的一个项目,遵循 [ASF 漏洞处理流程](https://apache.org/security/#vulnerability-handling)。 + +要报告您发现的新的安全漏洞,请遵循 [ASF 漏洞报告流程](https://apache.org/security/#reporting-a-vulnerability),该流程解释了如何私下向社区维护者发送详细的漏洞信息。 + +### New Feature + +欢迎您增强功能或新功能建议。提案越具体、越合理,您在 Apache Fory™ 社区的影响力就越大。它有可能在之后版本发布。 + +### 项目源代码 + +- Apache Fory™ Core 存储库:https://github.com/apache/fory +- Apache Fory™ 网站存储库:https://github.com/apache/fory-site diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/community/how_to_join_community.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/community/how_to_join_community.md new file mode 100644 index 00000000000..e24a3667343 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/community/how_to_join_community.md @@ -0,0 +1,108 @@ +--- +title: 如何加入 Apache Fory™ 社区 +sidebar_position: 0 +id: how_to_join_community +--- + +首先为你选择加入开源贡献行列的行为点赞 👍🏻。再者,十分感谢你选择参与到 Apache Fory™ 社区,为这个开源项目做出贡献。 + +## Apache Fory™ 贡献指南 + +Apache Fory™ 团队通常在 github 上进行开发和 issue 维护,请打开 [Github 网站](https://github.com/),点击右上角 `Sign up` 按钮,注册一个自己的账号,开启你开源之旅的第一步。 + +在 [Apache Fory™ 仓库](https://github.com/apache/fory)中,我们有一份面向所有开源贡献者的[指南](https://fory.apache.org/zh-CN/docs/community/),介绍了有关版本管理、分支管理等内容,**请花几分钟时间阅读了解一下**。 + +## 你的第一个 Pull Request + +### Step0:安装 Git + +Git 是一种版本控制系统,用于跟踪和管理软件开发项目中的代码变更。它帮助开发者记录和管理代码的历史记录,方便团队协作、代码版本控制、合并代码等操作。通过 Git,您可以追踪每个文件的每个版本,并轻松地在不同版本之间进行切换和比较。Git 还提供了分支管理功能,使得可以同时进行多个并行开发任务。 + +- 访问 Git 官方网站:[https://git-scm.com](https://git-scm.com) +- 下载最新版本的 Git 安装程序。 +- 运行下载的安装程序,按照安装向导的提示进行安装。 +- 安装完成后,你可以通过命令行使用 `git version` 命令确认安装成功。 + +### Step1:Fork 项目 + +- 首先需要 fork 这个项目,进入[Apache Fory™ 项目页面](https://github.com/apache/fory),点击右上角的 Fork 按钮 +- 你的 github 帐号中会出现 xxxx(你的 github 用户名)/fory 这个项目 +- 在本地电脑上使用以下命令: 得到一个 fory 文件夹 + +``` +// ssh +git clone git@github.com:xxxx(你的github用户名)/fory.git +// https +git clone https://github.com/xxxx(你的github用户名)/fory.git +``` + +### Step2:获取项目代码 + +- 进入 fory 文件夹,添加 fory 的远程地址 + +``` +git remote add upstream https://github.com/apache/fory.git +``` + +### Step3:创建分支 + +- 好了,现在可以开始贡献我们的代码了。fory 默认分支为 main 分支。无论是功能开发、bug 修复、文档编写,都请新建立一个分支,再合并到 main 分支上。使用以下代码创建分支: + +```shell +// 创建功能开发分支 +git checkout -b feat/xxxx + +// 创建问题修复开发分支 +git checkout -b fix/xxxx + +// 创建文档、demo分支 +git checkout -b docs/add-java-demo +``` + +假设我们创建了文档修改分支 `docs/add-java-demo` + +- 假设我们已经添加了一些代码,提交到代码库 + +- git add . + +- git commit -a -m "docs: add java demo and related docs" 。 + +### Step4:合并修改 + +- 切换回自己的开发分支: + +``` +git checkout docs/add-java-demo +``` + +- 把更新代码提交到自己的分支中: + +``` +git push origin docs/add-java-demo +``` + +### Step5:提交 Pull Request + +你可以在你的 github 代码仓库页面点击 `Compare & pull request` 按钮。或通过 `contribute` 按钮创建。 + +- 填写这是什么类型的修改 +- 填写关联的 issue +- 若有复杂变更,请说明背景和解决方案 + +相关信息填写完成后,点击 Create pull request 提交。 + +## **轻松步入 Apache Fory™ 开源贡献之旅** + +"**good first issue**" 是一个在开源社区常见的标签,这个标签的目的是帮助新贡献者找到适合入门的问题。 + +Apache Fory™ 的入门问题,你可以通过 [issue 列表](https://github.com/apache/fory/issues)查看。 + +如果你当前**有时间和意愿**参与到社区贡献,可以在 issue 里看一看 **good first issue**,选择一个感兴趣、适合自己的认领。 + +## 拥抱 Apache Fory™ 社区 + +在你为 Apache Fory™ 贡献代码之余,我们鼓励你参与其他让社区更加繁荣的事情,比如: + +- 为项目的发展、功能规划 等提建议。 +- 创作文章、视频,开办讲座来宣传 Apache Fory™ +- 撰写推广计划,同团队一同执行。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/community/how_to_release.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/community/how_to_release.md new file mode 100644 index 00000000000..0f0f2cd6b76 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/community/how_to_release.md @@ -0,0 +1,449 @@ +--- +title: 如何发布 +sidebar_position: 0 +id: how_to_release +--- + +本文主要介绍如何发布新版本的 Apache Fory™。 + +## 介绍 + +源代码发布是 Apache 最重视以及最重要的部分。 + +请注意许可证和发布的软件签名问题。发布软件是一件严肃的事情,并会产生相应的法律后果。 + +## release manager 第一次发布 + +### 环境要求 + +此发布过程在 Ubuntu 系统中运行,需要以下几个环境依赖: + +- OpenJDK 25+ +- Apache Maven 3.6.3+ +- Python 3.8+ +- GnuPG 2.x +- Git +- SVN(Apache 基金会使用 svn 来托管项目发布) +- 可选的包发布和验证工具:Node.js LTS 和 npm、Rust via rustup、Go 1.24+、Dart、.NET SDK 8.0+、sbt +- **设置环境变量**:如果您在不同的目录下配置了 gpg 密钥,请执行 `export GNUPGHOME=$(xxx)` 导出环境变量。 + +### 准备 GPG 密钥 + +如果您是第一次作为软件发布者,您需要准备一个 GPG 密钥。 + +您可以参考这里的[快速开始](https://infra.apache.org/openpgp.html)获取一个 GPG 密钥或者获取更多相关信息。 + +#### 安装 GPG + +```bash +sudo apt install gnupg2 +``` + +#### 生成 GPG 密钥 + +请使用您的 Apache 名字和电子邮件地址生成 GPG 密钥: + +```bash +$ gpg --full-gen-key +gpg (GnuPG) 2.2.20; Copyright (C) 2020 Free Software Foundation, Inc. +This is free software: you are free to change and redistribute it. +There is NO WARRANTY, to the extent permitted by law. + +Please select what kind of key you want: + (1) RSA and RSA (default) + (2) DSA and Elgamal + (3) DSA (sign only) + (4) RSA (sign only) + (14) Existing key from card +Your selection? 1 # input 1 +RSA keys may be between 1024 and 4096 bits long. +What keysize do you want? (2048) 4096 # input 4096 +Requested keysize is 4096 bits +Please specify how long the key should be valid. + 0 = key does not expire + = key expires in n days + w = key expires in n weeks + m = key expires in n months + y = key expires in n years +Key is valid for? (0) 0 # input 0 +Key does not expire at all +Is this correct? (y/N) y # input y + +GnuPG needs to construct a user ID to identify your key. + +Real name: Chaokun Yang # input your name +Email address: chaokunyang@apache.org # input your email +Comment: CODE SIGNING KEY # input some annotations, optional +You selected this USER-ID: + "Chaokun " + +Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O # input O +We need to generate a lot of random bytes. It is a good idea to perform +some other action (type on the keyboard, move the mouse, utilize the +disks) during the prime generation; this gives the random number +generator a better chance to gain enough entropy. +We need to generate a lot of random bytes. It is a good idea to perform +some other action (type on the keyboard, move the mouse, utilize the +disks) during the prime generation; this gives the random number +generator a better chance to gain enough entropy. + +# Input the security key +┌──────────────────────────────────────────────────────┐ +│ Please enter this passphrase │ +│ │ +│ Passphrase: _______________________________ │ +│ │ +│ │ +└──────────────────────────────────────────────────────┘ +# key generation will be done after your inputting the key with the following output +gpg: key E49B00F626B marked as ultimately trusted +gpg: revocation certificate stored as '/Users/chaokunyang/.gnupg/openpgp-revocs.d/1E2CDAE4C08AD7D694D1CB139D7BE8E45E580BA4.rev' +public and secret key created and signed. + +pub rsa4096 2022-07-12 [SC] + 1E2CDAE4C08AD7D694D1CB139D7BE8E45E580BA4 +uid [ultimate] Chaokun +sub rsa4096 2022-07-12 [E] +``` + +#### 上传公钥至 GPG 密钥服务器 + +首先,列出您所创建的 GPG 密钥: + +```bash +gpg --list-keys +``` + +执行相关命令之后,您将看到如下输出: + +```bash +-------------------------------------------------- +pub rsa4096 2024-03-27 [SC] + 1E2CDAE4C08AD7D694D1CB139D7BE8E45E580BA4 +uid [ultimate] chaokunyang (CODE SIGNING KEY) +sub rsa4096 2024-03-27 [E] +``` + +然后,将您的密钥 ID 发送到密钥服务器: + +```bash +gpg --keyserver keys.openpgp.org --send-key # e.g., 1E2CDAE4C08AD7D694D1CB139D7BE8E45E580BA4 +``` + +其中,`keys.openpgp.org` 是一个随机选择的密钥服务器,可以使用 keyserver.ubuntu.com 或任何其他功能完备的密钥服务器。 + +#### 检查密钥是否创建成功 + +上传大约需要一分钟;之后,您可以通过电子邮件在相应的密钥服务器上检查。 + +将密钥上传到密钥服务器的主要目的是为了加入一个可信的[信任网络](https://infra.apache.org/release-signing.html#web-of-trust)。 + +#### 将 GPG 公钥添加到项目 KEYS 文件中 + +发布分支的 svn 仓库是:https://dist.apache.org/repos/dist/release/fory + +请在发布分支的 KEYS 中添加公钥: + +```bash +svn co https://dist.apache.org/repos/dist/release/fory fory-dist +# As this step will copy all the versions, it will take some time. If the network is broken, please use svn cleanup to delete the lock before re-execute it. +cd fory-dist +(gpg --list-sigs YOUR_NAME@apache.org && gpg --export --armor YOUR_NAME@apache.org) >> KEYS # Append your key to the KEYS file +svn add . # It is not needed if the KEYS document exists before. +svn ci -m "add gpg key for YOUR_NAME" # Later on, if you are asked to enter a username and password, just use your apache username and password. +``` + +#### 将 GPG 公钥上传到您的 GitHub 帐户 + +- 输入 `https://github.com/settings/keys` 以添加您的 GPG 密钥。 +- 如果添加后发现“未验证”字样,请将 GPG 密钥中使用的电子邮件地址绑定到您的 GitHub 帐户(https://github.com/settings/emails)。 + +### 延伸阅读 + +建议您在发布之前阅读以下文档,了解有关 Apache 基金会发布软件的更多详细信息,但这不是必须的: + +- 发布政策:https://www.apache.org/legal/release-policy.html +- TLP 版本:https://infra.apache.org/release-distribution +- 发布标志:https://infra.apache.org/release-signing.html +- 发布发布:https://infra.apache.org/release-publishing.html +- 发布下载页面:https://infra.apache.org/release-download-pages.html +- 发布 maven artifacts:https://infra.apache.org/publishing-maven-artifacts.html + +## 开始有关发布的讨论 + +通过发送电子邮件至以下地址发起有关下一个版本的讨论:dev@fory.apache.org: + +标题: + +``` +[DISCUSS] Release Apache Fory ${release_version} +``` + +内容: + +``` +Hello, Apache Fory Community, + +This is a call for a discussion to release Apache Fory version ${release_version}. + +The planned change list for this release: + +https://github.com/apache/fory/compare/v${previous_release_version}...main + +Please leave your comments here about this release plan. We will bump the version in repo and start the release process after the discussion. + +Thanks, + +${name} +``` + +## 准备发布 + +如果讨论结果中没有出现反对声音,您需要做一些发布版本的准备工作。 + +### GitHub 分支和标签 + +- 创建一个名为 `releases-${release_version}` 的分支。也可以运行 `python ci/release.py prepare -v ${release_version}`,让脚本创建分支、更新所有版本并创建准备提交。 +- 如果没有使用 `prepare`,通过执行命令 `python ci/release.py bump_version -l all -version ${release_version}` 将版本升级到 `${release_version}` +- 执行 git commit 并将分支推送到 `git@github.com:apache/fory.git` +- 通过 `git tag v${release_version}-${rc_version}` 创建一个 release candidate 标签,然后将其推送到 `git@github.com:apache/fory.git` +- 如果本次发布包含 `go/fory` 这个 Go 子模块,请在投票通过后额外创建并推送 Go 子模块标签。例如,对于最终版 `${release_version}`,执行: + +```bash +git tag go/fory/v${release_version} +git push apache go/fory/v${release_version} +``` + +### 构建 artifacts 并上传到 SVN dist/dev 仓库 + +首先,您需要通过 `python ci/release.py build -v ${release_version}` 构建预发布 artifacts。 + +然后您需要把它上传到 svn dist repo。dev 分支的 dist 仓库地址是:https://dist.apache.org/repos/dist/dev/fory + +```bash +# As this step will copy all the versions, it will take some time. If the network is broken, please use svn cleanup to delete the lock before re-execute it. +svn co https://dist.apache.org/repos/dist/dev/fory fory-dist-dev +``` + +然后,上传项目: + +```bash +cd fory-dist-dev +# create a directory named by version +mkdir ${release_version}-${rc_version} +# copy source code and signature package to the versioned directory +cp ${repo_dir}/dist/* ${release_version}-${rc_version} +# check svn status +svn status +# add to svn +svn add ${release_version}-${rc_version} +# check svn status +svn status +# commit to SVN remote server +svn commit -m "Prepare for fory ${release_version}-${rc_version}" +``` + +访问 https://dist.apache.org/repos/dist/dev/fory/ 以检查 artifacts 是否正确上传。 + +### 如果出现问题该怎么办 + +如果某些文件是意外出现或者发生某些错误,则需要删除相关内容并执行 `svn delete`,然后重复上述上传过程。 + +## 投票 + +新版本发布需要 Apache Fory 社区的投票。 + +- release_version:Fory 的版本,如 1.0.0。 +- release_candidate_version:投票的版本,如 1.0.0-rc1。 +- maven_artifact_number:Maven 暂存 artifacts 的数量。如 1001. 具体来说,可以通过搜索 “fory” 来找到 maven_artifact_number https://repository.apache.org/#stagingRepositories. + +### Fory 社区投票 + +发送电子邮件至 Fory Community:dev@fory.apache.org: + +标题: + +``` +[VOTE] Release Apache Fory v${release_version}-${rc_version} +``` + +内容: + +``` +Hello, Apache Fory Community: + +This is a call for vote to release Apache Fory +v${release_version}-${rc_version}. + +Apache Fory is a blazingly fast multi-language serialization framework +for idiomatic domain objects, schema IDL, and cross-language data +exchange. + +The change lists about this release: + +https://github.com/apache/fory/compare/v${previous_release_version}...v${release_version}-${rc_version} + +The release candidates: +https://dist.apache.org/repos/dist/dev/fory/${release_version}-${rc_version}/ + +The maven staging for this release: +https://repository.apache.org/content/repositories/orgapachefory-${maven_artifact_number} + +Git tag for the release: +https://github.com/apache/fory/releases/tag/v${release_version}-${rc_version} + +如果本次发布还包含 Go 模块,请同时附上 Go 子模块标签: +https://github.com/apache/fory/releases/tag/go/fory/v${release_version} + +Git commit for the release: +https://github.com/apache/fory/commit/${release_commit} + +The artifacts signed with PGP key [${gpg_key_id}], corresponding to +[${apache_email}], that can be found in keys file: +https://downloads.apache.org/fory/KEYS + +The vote will be open for at least 72 hours until the necessary number of votes are reached. + +Please vote accordingly: + +[ ] +1 approve +[ ] +0 no opinion +[ ] -1 disapprove with the reason + +To learn more about Fory, please see https://fory.apache.org/ + +*Valid check is a requirement for a vote. *Checklist for reference: + +[ ] Download Fory is valid. +[ ] Checksums and PGP signatures are valid. +[ ] Source code distributions have correct names matching the current release. +[ ] LICENSE and NOTICE files are correct. +[ ] All files have license headers if necessary. +[ ] No compiled archives bundled in source archive. +[ ] Can compile from source. + +How to Build and Test, please refer to: https://github.com/apache/fory/blob/main/docs/DEVELOPMENT.md + +Thanks, +Chaokun Yang +``` + +在至少获得 3 个来自 Apache Fory PMC member 的 binding +1,并且没有收到否决票之后,发布投票结果: + +标题: + +``` +[RESULT][VOTE] Release Apache Fory v${release_version}-${rc_version} +``` + +内容: + +``` +Hello, Apache Fory Community, + +The vote to release Apache Fory v${release_version}-${rc_version} has passed. + +The vote PASSED with 3 binding +1 votes and 0 -1 votes: + +Binding votes: + +- xxx +- yyy +- zzz + +Vote thread: ${vote_thread_url} + +Thanks, + +${name} +``` + +### 如果投票失败怎么办 + +如果投票失败,请单击“删除”以删除暂存的 Maven artifacts。 + +解决提出的问题,然后再次提出 `rc_version` 的新投票。 + +## 官方发布 + +### 将 artifacts 发布到 SVN 发布目录 + +- release_version:Fory 的发布版本,如 1.0.0 +- release_candidate_version:投票版本,如 1.0.0-rc1 + +```bash +svn mv https://dist.apache.org/repos/dist/dev/fory/${release_version}-${rc_version} https://dist.apache.org/repos/dist/release/fory/${release_version} -m "Release fory ${release_version}" +``` + +### 更改 Fory 网站下载链接 + +提交 PR 到 https://github.com/apache/fory-site 仓库更新 Fory 版本,[下载页面](https://fory.apache.org/download) + +同时更新 release blog、download page、checksum/signature 示例、release notes 链接、current docs、zh-CN 翻译、`${release_version}` 的 versioned docs snapshot、`versions.json` 以及 `docusaurus.config.ts` 默认 docs 版本。 + +### GitHub 正式发布 + +投票通过后,从通过投票的 commit 创建并推送最终 tag `v${release_version}`。该 tag 会触发 Python、compiler、JavaScript、Rust、Dart 和 C# 的最终包发布 workflows。发送公告前,请监控所有 workflows 直到完成。 + +### 发布 Maven artifacts + +- maven_artifact_number:Maven 暂存 artifacts 的数量。如 1001。 +- 打开https://repository.apache.org/#stagingRepositories. +- 找到 artifacts `orgapachefory-${maven_artifact_number}`,点击“发布”。 + +### 发送公告 + +将发布公告发送给 dev@fory.apache.org 并且抄送给 announce@apache.org。 + +标题: + +``` +[ANNOUNCE] Apache Fory ${release_version} released +``` + +内容: + +``` +Hi all, + +The Apache Fory community is pleased to announce +that Apache Fory ${release_version} is now available. + +Apache Fory is a blazingly fast multi-language serialization framework +for idiomatic domain objects, schema IDL, and cross-language data +exchange. + +This release includes ${pr_count} PRs from ${contributor_count} contributors. + +Highlights in ${release_version} include: + +- ... + +Release blog, with details and examples: +https://fory.apache.org/blog/fory_${release_version_with_underscores}_release + +The release notes are available here: +https://github.com/apache/fory/releases/tag/v${release_version} + +For the complete list of changes: +https://github.com/apache/fory/compare/v${previous_release_version}...v${release_version} + +Apache Fory website: https://fory.apache.org/ + +Download Links: https://fory.apache.org/download + +Fory Resources: +- Fory github repo: https://github.com/apache/fory +- Issue: https://github.com/apache/fory/issues +- Mailing list: dev@fory.apache.org + +We are looking to grow our community and welcome new contributors. If +you are interested in contributing to Fory, please contact us on the +mailing list or on GitHub. We will be happy to help you get started. + +------------------ +Best Regards, +${your_name} +``` + +至此,整个发布流程结束。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/community/how_to_verify.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/community/how_to_verify.md new file mode 100644 index 00000000000..150fb90cf66 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/community/how_to_verify.md @@ -0,0 +1,119 @@ +--- +title: 如何验证 Apache Fory™ +sidebar_position: 0 +id: how_to_verify +--- + +## 下载 Apache Fory™ + +```bash +# If there is svn locally, you can clone to the local +svn co https://dist.apache.org/repos/dist/dev/incubator/fory/${release_version}-${rc_version}/ +# You can download the material file directly +wget https://dist.apache.org/repos/dist/dev/incubator/fory/${release_version}-${rc_version}/xxx.xxx +``` + +## 验证 checksums 和 signatures + +首先,您需要安装 gpg: + +```bash +apt-get install gnupg +# or +yum install gnupg +# or +brew install gnupg +``` + +之后,导入 Apache Fory release manager 的公钥: + +```bash +curl https://downloads.apache.org/incubator/fory/KEYS > KEYS # Download KEYS +gpg --import KEYS # Import KEYS to local +# Then, trust the public key: +gpg --edit-key # Edit the key(mentioned in vote email) +# It will enter the interactive mode, use the following command to trust the key: +gpg (GnuPG) 2.0.22; Copyright (C) 2013 Free Software Foundation, Inc. +This is free software: you are free to change and redistribute it. +There is NO WARRANTY, to the extent permitted by law. + + +pub 4096R/5E580BA4 created: 2024-03-27 expires: never usage: SC + trust: unknown validity: unknown +sub 4096R/A31EF728 created: 2024-03-27 expires: never usage: E +[ unknown] (1). chaokunyang (CODE SIGNING KEY) + +gpg> trust +pub 4096R/5E580BA4 created: 2024-03-27 expires: never usage: SC + trust: unknown validity: unknown +sub 4096R/A31EF728 created: 2024-03-27 expires: never usage: E +[ unknown] (1). chaokunyang (CODE SIGNING KEY) + +Please decide how far you trust this user to correctly verify other users' keys +(by looking at passports, checking fingerprints from different sources, etc.) + + 1 = I don't know or won't say + 2 = I do NOT trust + 3 = I trust marginally + 4 = I trust fully + 5 = I trust ultimately + m = back to the main menu + +Your decision? 5 +Do you really want to set this key to ultimate trust? (y/N) y + +pub 4096R/5E580BA4 created: 2024-03-27 expires: never usage: SC + trust: ultimate validity: unknown +sub 4096R/A31EF728 created: 2024-03-27 expires: never usage: E +[ unknown] (1). chaokunyang (CODE SIGNING KEY) +Please note that the shown key validity is not necessarily correct +unless you restart the program. +``` + +接下来验证签名: + +```bash +for i in *.tar.gz; do echo $i; gpg --verify $i.asc $i; done +``` + +如果出现如下内容,则表示签名正确: + +```bash +apache-fory-0.5.0-src.tar.gz +gpg: Signature made Wed 17 Apr 2024 11:49:45 PM CST using RSA key ID 5E580BA4 +gpg: checking the trustdb +gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model +gpg: depth: 0 valid: 1 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 1u +gpg: Good signature from "chaokunyang (CODE SIGNING KEY) " +``` + +然后验证 checksum: + +```bash +for i in *.tar.gz; do echo $i; sha512sum --check $i.sha512; done +``` + +它应该输出如下内容: + +```bash +apache-fory-0.12.0-src.tar.gz +apache-fory-0.12.0-src.tar.gz: OK +``` + +## 检查源码包中的文件 + +解压缩 `apache-fory-${release_version}-${rc_version}-src.tar.gz` 并检查以下内容: + +- 此存储库 LICENSE 和 NOTICE 文件是正确的; +- 如有必要,所有文件都有 ASF 许可证标头; +- 项目构建通过。 + +## 检查 fory-java 的 Maven artifacts + +下载 Apache Fory™:https://repository.apache.org/content/repositories/orgapachefory-${maven_artifact_number}/. + +您可以检查以下内容: + +- JAR 的 Checksum 与项目绑定的 checksum 文件一致。 +- JAR 的 signature 与项目绑定的 signature 文件一致。 +- JAR 在本地是可重复的。这意味着您可以在计算机上构建 JAR,并验证 checksum 和与项目绑定的相同。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/compiler/_category_.json b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/compiler/_category_.json new file mode 100644 index 00000000000..abf19e9fcb1 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/compiler/_category_.json @@ -0,0 +1,6 @@ +{ + "position": 3, + "label": "Schema IDL 与编译器", + "collapsible": true, + "collapsed": true +} diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/compiler/compiler-guide.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/compiler/compiler-guide.md new file mode 100644 index 00000000000..215430e8d41 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/compiler/compiler-guide.md @@ -0,0 +1,882 @@ +--- +title: 编译器指南 +sidebar_position: 3 +id: compiler_guide +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. +--- + +本指南介绍 Fory IDL 编译器的安装、使用方式和集成方法。 + +## 安装 + +### 从源码安装 + +```bash +cd compiler +pip install -e . +``` + +### 验证安装 + +```bash +foryc --help +``` + +## 命令行接口 + +### 基本用法 + +```bash +foryc [OPTIONS] FILES... +``` + +```bash +foryc --scan-generated [OPTIONS] +``` + +### 选项 + +编译选项: + +| 选项 | 说明 | 默认值 | +| ------------------------------------- | -------------------------------------------------- | ------------- | +| `--lang` | 目标语言列表,使用逗号分隔 | `all` | +| `--output`, `-o` | 输出目录 | `./generated` | +| `-I`, `--proto_path`, `--import_path` | 添加 import 搜索路径(可重复指定) | (无) | +| `--java_out=DST_DIR` | 在 DST_DIR 中生成 Java 代码 | (无) | +| `--python_out=DST_DIR` | 在 DST_DIR 中生成 Python 代码 | (无) | +| `--cpp_out=DST_DIR` | 在 DST_DIR 中生成 C++ 代码 | (无) | +| `--go_out=DST_DIR` | 在 DST_DIR 中生成 Go 代码 | (无) | +| `--rust_out=DST_DIR` | 在 DST_DIR 中生成 Rust 代码 | (无) | +| `--csharp_out=DST_DIR` | 在 DST_DIR 中生成 C# 代码 | (无) | +| `--javascript_out=DST_DIR` | 在 DST_DIR 中生成 JavaScript/TypeScript 代码 | (无) | +| `--swift_out=DST_DIR` | 在 DST_DIR 中生成 Swift 代码 | (无) | +| `--dart_out=DST_DIR` | 在 DST_DIR 中生成 Dart 代码 | (无) | +| `--scala_out=DST_DIR` | 在 DST_DIR 中生成 Scala 3 代码 | (无) | +| `--kotlin_out=DST_DIR` | 在 DST_DIR 中生成 Kotlin 代码 | (无) | +| `--go_nested_type_style` | Go 嵌套类型命名方式:`camelcase` 或 `underscore` | `underscore` | +| `--swift_namespace_style` | Swift 命名空间方式:`enum` 或 `flatten` | `enum` | +| `--emit-fdl` | 输出转换后的 FDL(用于非 FDL 输入) | `false` | +| `--emit-fdl-path` | 将转换后的 FDL 写入此路径(文件或目录) | (stdout) | +| `--grpc` | 为支持的输出生成 gRPC service companion 代码 | `false` | +| `--grpc-python-mode=MODE` | Python gRPC 模式:`async` 或 `sync` | `async` | +| `--grpc-web` | 生成 JavaScript gRPC-Web client companion | `false` | + +支持 schema 级文件选项,用于控制特定语言的生成行为。 +对于 `go_nested_type_style` 和 `swift_namespace_style`,当 CLI 标志和 +schema 选项同时存在时,CLI 标志优先生效。Rust temporal 代码生成没有 CLI 标志: +需要在 schema 中设置 `option rust_use_chrono_temporal_types = true;`,以生成 +`chrono::NaiveDate`、`chrono::NaiveDateTime` 和 `chrono::Duration`,而不是默认的 +`fory::Date`、`fory::Timestamp` 和 `fory::Duration`。编译基于 chrono 的 Rust +生成代码的 crate 必须依赖 `chrono`,并启用 Fory 的 `chrono` feature。 + +扫描选项(配合 `--scan-generated`): + +| 选项 | 说明 | 默认值 | +| ------------ | ---------------------- | ------- | +| `--root` | 要扫描的根目录 | `.` | +| `--relative` | 输出相对于根目录的路径 | `false` | +| `--delete` | 删除匹配的生成文件 | `false` | +| `--dry-run` | 仅扫描/打印,不删除 | `false` | + +### 扫描生成文件 + +使用 `--scan-generated` 查找 `foryc` 生成的文件。扫描器会递归遍历目录树,跳过 +`build/`、`target/` 和隐藏目录,并在发现每个生成文件时打印出来。 + +```bash +# Scan current directory +foryc --scan-generated + +# Scan a specific root +foryc --scan-generated --root ./src + +# Print paths relative to the scan root +foryc --scan-generated --root ./src --relative + +# Delete scanned generated files +foryc --scan-generated --root ./src --delete + +# Dry-run (scan and print only) +foryc --scan-generated --root ./src --dry-run +``` + +### 示例 + +**为所有语言编译:** + +```bash +foryc schema.fdl +``` + +**为选定的一组语言编译:** + +```bash +foryc schema.fdl --lang java,python,csharp,javascript,swift,dart,kotlin +``` + +**指定输出目录:** + +```bash +foryc schema.fdl --output ./src/generated +``` + +**编译多个文件:** + +```bash +foryc user.fdl order.fdl product.fdl --output ./generated +``` + +**编译包含 service 定义的简单 schema(Java + Python + Go + Rust + C# + Dart + Scala + Kotlin + JavaScript 模型):** + +```bash +foryc compiler/examples/service.fdl --java_out=./generated/java --python_out=./generated/python --go_out=./generated/go --rust_out=./generated/rust --csharp_out=./generated/csharp --dart_out=./generated/dart --scala_out=./generated/scala --kotlin_out=./generated/kotlin --javascript_out=./generated/javascript +``` + +**生成 Java、Python、Go、Rust、C#、Dart、Scala、Kotlin 和 Node.js JavaScript gRPC service companion 代码:** + +```bash +foryc compiler/examples/service.fdl --java_out=./generated/java --python_out=./generated/python --go_out=./generated/go --rust_out=./generated/rust --csharp_out=./generated/csharp --dart_out=./generated/dart --scala_out=./generated/scala --kotlin_out=./generated/kotlin --javascript_out=./generated/javascript --grpc +``` + +生成的 gRPC service 代码使用 Fory 序列化请求和响应载荷。Java 输出会导入 +grpc-java API,Python 输出默认使用 `grpc.aio`,Go 输出会导入 grpc-go,Rust 输出会导入 +`tonic` 和 `bytes`,Scala 输出会导入 grpc-java API,Kotlin 输出会导入 grpc-java 和 +grpc-kotlin API 并使用 coroutine stub。C# 输出会导入 `Grpc.Core.Api` 类型,并可由 +`Grpc.AspNetCore` 等常规 .NET gRPC package 承载,或通过 `Grpc.Net.Client` 调用。Dart 输出 +会导入 `package:grpc`。JavaScript 输出会导入 `@grpc/grpc-js`。编译或运行这些生成 +service 文件的应用需要自行提供 gRPC 依赖。Fory package 不会为此功能加入强制 gRPC 依赖。 + +为已有同步 `grpcio` 应用生成同步 Python gRPC companion: + +```bash +foryc compiler/examples/service.fdl --python_out=./generated/python --grpc --grpc-python-mode=sync +``` + +**生成 JavaScript gRPC-Web 浏览器 client:** + +```bash +foryc compiler/examples/service.fdl --javascript_out=./generated/javascript --grpc --grpc-web +``` + +**使用 import 搜索路径:** + +```bash +# Add a single import path +foryc src/main.fdl -I libs/common + +# Add multiple import paths (repeated option) +foryc src/main.fdl -I libs/common -I libs/types + +# Add multiple import paths (comma-separated) +foryc src/main.fdl -I libs/common,libs/types,third_party/ + +# Using --proto_path (protoc-compatible alias) +foryc src/main.fdl --proto_path=libs/common + +# Mix all styles +foryc src/main.fdl -I libs/common,libs/types --proto_path third_party/ +``` + +**语言专属输出目录(protoc 风格):** + +```bash +# Generate only Java code to a specific directory +foryc schema.fdl --java_out=./src/main/java + +# Generate multiple languages to different directories +foryc schema.fdl --java_out=./java/gen --python_out=./python/src --cpp_out=./cpp/gen --go_out=./go/gen --rust_out=./rust/gen --csharp_out=./csharp/gen --javascript_out=./javascript/src --swift_out=./swift/gen --dart_out=./dart/gen --scala_out=./scala/gen --kotlin_out=./kotlin/gen + +# Combine with import paths +foryc schema.fdl --java_out=./gen/java -I proto/ -I common/ + +# Generate Scala 3 code to a specific directory +foryc schema.fdl --scala_out=./src/main/scala + +# Generate Kotlin code to a specific directory +foryc schema.fdl --kotlin_out=./src/main/kotlin +``` + +使用 `--{lang}_out` 选项时: + +- 只生成显式指定的语言(不会生成全部语言) +- 编译器会写入指定目录(特定语言的生成器仍可能创建 package/module 子目录) +- 兼容 protoc 风格工作流 + +**查看从 proto/fbs 输入转换后的 Fory IDL:** + +```bash +# Print translated Fory IDL to stdout +foryc schema.proto --emit-fdl + +# Write translated Fory IDL to a directory +foryc schema.fbs --emit-fdl --emit-fdl-path ./translated +``` + +## Import 路径解析 + +编译带有 import 的 Fory IDL 文件时,编译器按以下顺序查找被导入文件: + +1. **相对于导入者文件(默认)** - 始终会首先自动搜索包含 import 语句的文件所在目录。同目录 import 不需要 `-I` 标志。 +2. **每个 `-I` 路径的顺序** - 命令行上指定的额外搜索路径 + +**同目录 import 会自动生效:** + +```protobuf +// main.fdl +import "common.fdl"; // Found if common.fdl is in the same directory +``` + +```bash +# No -I needed for same-directory imports +foryc main.fdl +``` + +**示例项目结构:** + +``` +project/ +├── src/ +│ └── main.fdl # import "common.fdl"; +└── libs/ + └── common.fdl +``` + +**不使用 `-I`(失败):** + +```bash +$ foryc src/main.fdl +Import error: Import not found: common.fdl + Searched in: /project/src +``` + +**使用 `-I`(成功):** + +```bash +$ foryc src/main.fdl -I libs/ +Compiling src/main.fdl... + Resolved 1 import(s) +``` + +## 支持语言 + +| 语言 | 标志 | 输出后缀 | 说明 | +| --------------------- | ------------ | -------- | ------------------------------------ | +| Java | `java` | `.java` | 带 Fory 注解的 POJO | +| Python | `python` | `.py` | 带类型提示的 dataclass | +| Go | `go` | `.go` | 带 struct tag 的结构体 | +| Rust | `rust` | `.rs` | 带 derive 宏的结构体 | +| C++ | `cpp` | `.h` | 带 FORY 宏的结构体 | +| C# | `csharp` | `.cs` | 带 Fory attribute 的类 | +| JavaScript/TypeScript | `javascript` | `.ts` | 带注册函数的 interface | +| Swift | `swift` | `.swift` | Fory Swift 模型宏 | +| Dart | `dart` | `.dart` | 带注解的 `@ForyStruct` 类 | +| Scala | `scala` | `.scala` | 使用宏派生的 Scala 3 模型 | +| Kotlin | `kotlin` | `.kt` | 使用 KSP serializer 的 Kotlin 模型 | + +## 输出结构 + +### Java + +``` +generated/ +└── java/ + └── com/ + └── example/ + ├── User.java + ├── Order.java + ├── Status.java + └── ExampleForyModule.java +``` + +- 每个类型(enum 或 message)一个文件 +- Package 结构与 Fory IDL package 一致 +- 生成 schema module 类 + +### Python + +``` +generated/ +└── python/ + └── example.py +``` + +- 所有类型位于单个 module +- Module 名称由 package 派生 +- 包含注册函数 + +### Go + +``` +generated/ +└── go/ + └── example/ + └── example.go +``` + +- 所有类型位于单个文件 +- 目录名和 package 名称由 `go_package` 或 Fory IDL package 派生 +- 包含注册函数 + +### Rust + +``` +generated/ +└── rust/ + └── example.rs +``` + +- 所有类型位于单个 module +- Module 名称由 package 派生 +- 包含注册函数 + +### C++ + +``` +generated/ +└── cpp/ + └── example.h +``` + +- 单个头文件 +- Namespace 与 package 匹配(点号转换为 `::`) +- Header guard 和前向声明 + +### JavaScript/TypeScript + +``` +generated/ +└── javascript/ + └── example.ts +``` + +- 每个 schema 生成单个 `.ts` 文件 +- message 生成为 `export interface` 声明 +- enum 生成为 `export enum` 声明 +- union 生成为带 case enum 的判别联合 +- 包含注册辅助函数 + +### C\# + +``` +generated/ +└── csharp/ + └── example/ + └── example.cs +``` + +- 每个 schema 生成单个 `.cs` 文件 +- Namespace 使用 `csharp_namespace`(如已设置)或 Fory IDL package +- 包含以源文件为前缀的 `XXXForyModule` 安装 helper,以及 `ToBytes`/`FromBytes` 方法 +- 被导入的 schema 会由生成的 helper 传递安装(例如 `root.idl` 导入 `addressbook.fdl` 和 `tree.fdl`) + +### Swift + +``` +generated/ +└── swift/ + └── addressbook/ + └── addressbook.swift +``` + +- 每个 schema 生成单个 `.swift` 文件 +- Package 片段会映射为嵌套 Swift enum(例如 `addressbook.*` -> `Addressbook.*`) +- 生成的 message 使用 `@ForyStruct`,enum 使用 `@ForyEnum`,union 使用 `@ForyUnion`/`@ForyCase` +- Union 类型生成为带关联载荷值的 tagged enum +- 每个 schema 都包含 schema 文件 module owner 和 `toBytes`/`fromBytes` helper +- 被导入的 schema 会由生成的 module helper 传递安装 + +### Dart + +``` +generated/ +└── dart/ + └── package/ + ├── package.dart + └── package.fory.dart +``` + +- 每个 schema 生成两个文件:包含带注解类型的主 `.dart` 文件,以及包含生成 serializer 的 `.fory.dart` part 文件 +- Package 片段映射为目录(例如 `demo.foo` → `demo/foo/`) +- IDL module 类包含在主文件中;生成的 serializer 元数据包含在 part 文件中 +- 非可选、非 `ref` 的 primitive list 使用类型化数组(例如 `Int32List`) +- 使用 `--grpc` 时,会在 model 文件旁为每个 schema 生成一个 `_grpc.dart` companion, + 其中包含各 service 的 `Client` 和 `ServiceBase`,并导入 `package:grpc` + +### Scala + +``` +generated/ +└── scala/ + └── example/ + ├── User.scala + ├── Status.scala + ├── Animal.scala + └── ExampleForyModule.scala +``` + +- 每个生成类型一个 Scala 3 源文件 +- Package 结构与 Fory IDL package 一致 +- Message 派生 `org.apache.fory.scala.ForySerializer` +- `optional T` 字段使用 `Option[T]` +- Enum 使用 Scala 3 `enum` +- Union 使用 Scala 3 ADT `enum`,并带 `@ForyUnion`、`@ForyCase` 和一个 `Unknown` +- 包含 schema module object + +### Kotlin + +``` +generated/ +└── kotlin/ + └── example/ + ├── User.kt + ├── Status.kt + ├── Animal.kt + └── ExampleForyModule.kt +``` + +- 每个生成类型一个 Kotlin 源文件 +- Package 结构在设置 `kotlin_package` 时使用该选项,否则使用 Fory IDL package +- Message 使用 `@ForyStruct` 和 KSP 生成的 serializer +- Enum 使用稳定的 Fory enum ID +- Union 使用 sealed class,并带 `@ForyUnion`、`@ForyCase` 和 unknown-case carrier +- 包含 schema module object + +### C# IDL 矩阵验证 + +运行端到端 C# IDL 矩阵(FDL/IDL/Proto/FBS 生成以及 roundtrip 测试): + +```bash +cd integration_tests/idl_tests +./run_csharp_tests.sh +``` + +此 runner 会执行以下场景的 schema-consistent 和 compatible roundtrip: + +- `addressbook`、`auto_id`、`complex_pb` primitives +- `collection` 和 union/list 变体 +- `optional_types` +- `any_example`(`.fdl`)和 `any_example`(`.proto`) +- `tree` 和 `graph` 引用跟踪场景 +- `monster.fbs` 和 `complex_fbs.fbs` +- `root.idl` 跨 package import 覆盖 +- 演进 schema 兼容性场景 + +### Swift IDL 矩阵验证 + +运行端到端 Swift IDL 矩阵(FDL/IDL/Proto/FBS 生成以及 roundtrip 测试): + +```bash +cd integration_tests/idl_tests +./run_swift_tests.sh +``` + +此脚本会运行: + +- compatible 和 schema-consistent 模式下的本地 Swift IDL roundtrip 测试 +- 使用 `IDL_PEER_LANG=swift` 的 Java 驱动 peer roundtrip 验证 + +脚本还会设置 `DATA_FILE*` 变量,以便覆盖基于文件的 roundtrip 路径。 + +## 构建集成 + +### Maven (Java) + +添加到 `pom.xml`: + +```xml + + + + org.codehaus.mojo + exec-maven-plugin + 3.1.0 + + + generate-fory-types + generate-sources + + exec + + + foryc + + ${project.basedir}/src/main/fdl/schema.fdl + --java_out + ${project.build.directory}/generated-sources/fory + + + + + + + +``` + +添加生成源码: + +```xml + + + + org.codehaus.mojo + build-helper-maven-plugin + 3.4.0 + + + generate-sources + + add-source + + + + ${project.build.directory}/generated-sources/fory + + + + + + + +``` + +### Gradle (Java/Kotlin) + +添加到 `build.gradle`: + +```groovy +task generateForyTypes(type: Exec) { + commandLine 'foryc', + "${projectDir}/src/main/fdl/schema.fdl", + '--java_out', "${buildDir}/generated/sources/fory" +} + +compileJava.dependsOn generateForyTypes + +sourceSets { + main { + java { + srcDir "${buildDir}/generated/sources/fory" + } + } +} +``` + +### Python (setuptools) + +添加到 `setup.py` 或 `pyproject.toml`: + +```python +# setup.py +from setuptools import setup +from setuptools.command.build_py import build_py +import subprocess + +class BuildWithForyIdl(build_py): + def run(self): + subprocess.run([ + 'foryc', + 'schema.fdl', + '--python_out', 'src/generated' + ], check=True) + super().run() + +setup( + cmdclass={'build_py': BuildWithForyIdl}, + # ... +) +``` + +### Go (go generate) + +添加到你的 Go 文件: + +```go +//go:generate foryc ../schema.fdl --lang go --output . +package models +``` + +运行: + +```bash +go generate ./... +``` + +### Rust (build.rs) + +添加到 `build.rs`: + +```rust +use std::process::Command; + +fn main() { + println!("cargo:rerun-if-changed=schema.fdl"); + + let status = Command::new("foryc") + .args(&["schema.fdl", "--rust_out", "src/generated"]) + .status() + .expect("Failed to run foryc"); + + if !status.success() { + panic!("Fory IDL compilation failed"); + } +} +``` + +### CMake (C++) + +添加到 `CMakeLists.txt`: + +```cmake +find_program(FORY_COMPILER foryc) + +add_custom_command( + OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/generated/example.h + COMMAND ${FORY_COMPILER} + ${CMAKE_CURRENT_SOURCE_DIR}/schema.fdl + --cpp_out ${CMAKE_CURRENT_SOURCE_DIR}/generated + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/schema.fdl + COMMENT "Generating Fory IDL types" +) + +add_custom_target(generate_fory_idl DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/generated/example.h) + +add_library(mylib ...) +add_dependencies(mylib generate_fory_idl) +target_include_directories(mylib PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/generated) +``` + +### Bazel + +在 `BUILD` 中创建规则: + +```python +genrule( + name = "generate_fdl", + srcs = ["schema.fdl"], + outs = ["generated/example.h"], + cmd = "$(location //:fory_compiler) $(SRCS) --cpp_out $(RULEDIR)/generated", + tools = ["//:fory_compiler"], +) + +cc_library( + name = "models", + hdrs = [":generate_fdl"], + # ... +) +``` + +### Dart / Flutter + +在 `pubspec.yaml` 中添加 Fory 依赖: + +```yaml +dependencies: + fory: ^1.3.0 + +dev_dependencies: + build_runner: ^2.4.0 +``` + +使用编译器生成 schema 类型: + +```bash +foryc schema.fdl --dart_out=lib/generated +``` + +然后运行 `build_runner` 生成 serializer: + +```bash +dart run build_runner build +``` + +## 错误处理 + +### 语法错误 + +``` +Error: Line 5, Column 12: Expected ';' after field declaration +``` + +修复方式:检查提示的行是否缺少分号或存在语法问题。 + +### 类型名重复 + +``` +Error: Duplicate type name: User +``` + +修复方式:确保同一文件中的每个 enum 和 message 名称唯一。 + +### 类型 ID 重复 + +``` +Error: Duplicate type ID 100: User and Order +``` + +修复方式:为每个类型分配唯一类型 ID。 + +### 未知类型引用 + +``` +Error: Unknown type 'Address' in Customer.address +``` + +修复方式:先定义被引用的类型,或检查是否存在拼写错误。 + +Service RPC 的请求和响应类型也按相同方式校验:像 +`rpc SayHello (HelloRequest) returns (HelloReply);` 这样的 RPC 必须引用已定义的 +message 类型,否则校验器会在 RPC 所在行报告 `Unknown type '...'` 错误。 + +### 字段号重复 + +``` +Error: Duplicate field number 1 in User: name and id +``` + +修复方式:在每个 message 内分配唯一字段号。 + +## 最佳实践 + +### 项目结构 + +``` +project/ +├── fdl/ +│ ├── common.fdl # Shared types +│ ├── user.fdl # User domain +│ └── order.fdl # Order domain +├── src/ +│ └── generated/ # Generated code (git-ignored) +└── build.gradle +``` + +### 版本控制 + +- **纳入版本控制**:Fory IDL schema 文件 +- **忽略**:生成代码(可重新生成) + +添加到 `.gitignore`: + +``` +# Generated Fory IDL code +src/generated/ +generated/ +``` + +### CI/CD 集成 + +构建时始终重新生成: + +```yaml +# GitHub Actions example +steps: + - name: Install Fory IDL Compiler + run: pip install ./compiler + + - name: Generate Types + run: foryc fdl/*.fdl --output src/generated + + - name: Build + run: ./gradlew build +``` + +### Schema 演进 + +修改 schema 时: + +1. **不要复用字段号** - 改为标记为 reserved +2. **不要修改类型 ID** - 它们是二进制格式的一部分 +3. **新增字段** - 使用新的字段号 +4. **使用 `optional`** - 保持向后兼容 + +```protobuf +message User [id=100] { + string id = 1; + string name = 2; + // Field 3 was removed, don't reuse + optional string email = 4; // New field +} +``` + +## 故障排查 + +### Command Not Found + +``` +foryc: command not found +``` + +**解决方式:** 确保编译器已安装且在 PATH 中: + +```bash +pip install -e ./compiler +# Or add to PATH +export PATH=$PATH:~/.local/bin +``` + +### Permission Denied + +``` +Permission denied: ./generated +``` + +**解决方式:** 确保输出目录具备写权限: + +```bash +chmod -R u+w ./generated +``` + +### 生成代码中的 Import 错误 + +**Java:** 确保项目中包含 Fory 依赖: + +```xml + + org.apache.fory + fory-core + ${fory.version} + +``` + +**Python:** 确保已安装 pyfory: + +```bash +pip install pyfory +``` + +**Go:** 确保 fory module 可用: + +```bash +go get github.com/apache/fory/go/fory +``` + +**Rust:** 确保 `Cargo.toml` 中包含 fory crate: + +```toml +[dependencies] +fory = "x.y.z" +``` + +**C++:** 确保 Fory 头文件位于 include path 中。 + +**Dart:** 确保 `pubspec.yaml` 中包含 fory package: + +```yaml +dependencies: + fory: ^1.3.0 +``` diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/compiler/flatbuffers-idl.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/compiler/flatbuffers-idl.md new file mode 100644 index 00000000000..78bf32de21c --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/compiler/flatbuffers-idl.md @@ -0,0 +1,202 @@ +--- +title: FlatBuffers IDL 支持 +sidebar_position: 7 +id: flatbuffers_idl +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. +--- + +本页说明 Apache Fory 如何读取 FlatBuffers schema(`.fbs`)并将其转换为 Fory IR 以生成代码。 + +## 本页内容 + +- 何时应在 Fory 中使用 FlatBuffers 输入 +- FlatBuffers 到 Fory 的精确映射行为 +- `.fbs` 中支持的 Fory 扩展属性 +- 迁移注意事项与生成代码差异 + +## 为什么使用 Apache Fory + +- **生成代码更符合语言习惯**:Fory 会生成各语言常用风格的类/结构体,可直接作为领域对象使用。 +- **Java 性能更优**:在 Fory 的 Java 对象序列化基准中,Fory 通常快于 FlatBuffers。 +- **其他语言性能相近**:序列化性能通常处于同一量级。 +- **实际反序列化链路更高效**:FlatBuffers 默认并不做原生对象反序列化,因此看起来更快;但当业务需要原生对象时,还需额外转换,这一步往往成为主要开销。在这种场景下,Fory 端到端反序列化通常更快。 +- **API 更简单**:Fory 直接操作原生对象,无需反向构建 table 或手动管理 offset。 +- **对象图建模能力更强**:Fory 原生支持共享引用和循环引用。 + +## 快速决策指南 + +| 场景 | 建议路径 | +| ----------------------------------------------- | ----------------------- | +| 已有 `.fbs` schema,想接入 Fory 运行时/代码生成 | 使用 FlatBuffers 输入 | +| 新建 schema,希望完整使用 Fory 语法能力 | 使用原生 Fory IDL | +| 运行时必须保持 FlatBuffers 线格式兼容 | 继续使用 FlatBuffers 栈 | +| 需要 Fory 对象图语义(`ref`、弱引用等) | 使用 Fory | + +## FlatBuffers 到 Fory 的映射 + +### Schema 级规则 + +- `namespace` 映射为 Fory package 命名空间。 +- `include` 映射为 Fory import。 +- `table` 会被翻译为 `evolving=true`。 +- `struct` 会被翻译为 `evolving=false`。 +- `root_type` 会被解析,但 Fory 运行时/代码生成不会使用。 +- `file_identifier` 与 `file_extension` 会被解析,但 Fory 代码生成不会使用。 + +### 字段编号 + +FlatBuffers 字段没有显式 field ID。Fory 会按源码声明顺序分配字段号,从 `1` 开始。 + +### 标量类型映射 + +| FlatBuffers | Fory Type | +| ----------- | --------- | +| `byte` | `int8` | +| `ubyte` | `uint8` | +| `short` | `int16` | +| `ushort` | `uint16` | +| `int` | `int32` | +| `uint` | `uint32` | +| `long` | `int64` | +| `ulong` | `uint64` | +| `float` | `float32` | +| `double` | `float64` | +| `bool` | `bool` | +| `string` | `string` | + +向量(`[T]`)映射为 Fory 列表类型。 + +### 联合类型 + +FlatBuffers union 映射为 Fory union。 + +- case ID 按声明顺序分配,从 `1` 开始。 +- case 名称由类型名转换为 snake_case 字段名。 + +**FlatBuffers** + +```fbs +union Payload { + Note, + Metric +} + +table Container { + payload: Payload; +} +``` + +**转换后的 Fory 结构** + +```protobuf +union Payload { + Note note = 1; + Metric metric = 2; +} + +message Container { + Payload payload = 1; +} +``` + +### gRPC Service + +FlatBuffers `rpc_service` 定义会转换为 Fory service。使用 `--grpc` 时,compiler 会为 Java、Python、Go、Rust、C#、Dart、Scala、Kotlin 和 JavaScript 等支持的输出生成 gRPC service companion。JavaScript 浏览器 client 通过 `--grpc-web` 生成。这些 companion 使用 Fory 序列化 request 和 response payload。 + +```fbs +rpc_service SearchService { + Lookup(SearchRequest):SearchResponse; + StreamLookup(SearchRequest):SearchResponse (streaming: "server"); +} +``` + +```bash +foryc api.fbs --java_out=./generated/java --python_out=./generated/python --go_out=./generated/go --rust_out=./generated/rust --csharp_out=./generated/csharp --dart_out=./generated/dart --scala_out=./generated/scala --kotlin_out=./generated/kotlin --javascript_out=./generated/javascript --grpc +``` + +生成的 service 代码会导入 gRPC API,因此编译或运行这些文件的应用需要提供 grpc-java、grpc-kotlin、Scala grpc-java API、`grpcio`、grpc-go、Rust `tonic` 和 `bytes`、`@grpc/grpc-js`、C# `Grpc.Core.Api` 及 server/client 依赖,或 Dart `package:grpc`。Python companion 默认使用 `grpc.aio`,也可以通过 `--grpc-python-mode=sync` 生成同步模式。Fory package 不会把 gRPC 作为硬依赖。JavaScript 输出配合 `--grpc-web` 可生成导入 `grpc-web` 的浏览器 client。 + +### 默认值与元数据 + +- FlatBuffers 默认值会被解析,但不会作为 Fory 运行时默认值生效。 +- 非 Fory 的 metadata 属性会作为通用 option 保留在 IR 中,供下游工具按需消费。 + +## FlatBuffers 中的 Fory 扩展属性 + +FlatBuffers metadata 属性写法为 `key:value`。对于 Fory 扩展选项,在 `.fbs` 中使用 `fory_`(或 `fory.`)前缀;解析时会去掉该前缀。 + +### 支持的字段属性 + +| FlatBuffers Attribute | 在 Fory 中的效果 | +| ------------------------------- | ------------------------------------------------------------------------ | +| `fory_ref:true` | 为字段启用引用跟踪 | +| `fory_nullable:true` | 将字段标记为 optional/nullable | +| `fory_weak_ref:true` | 启用弱引用语义,并隐含开启 `ref` | +| `fory_thread_safe_pointer:true` | 对 ref 字段使用 Rust `Arc`/`ArcWeak`,而不是默认的 `Rc`/`RcWeak` | + +语义说明: + +- `fory_weak_ref:true` 隐含 `ref`。 +- `fory_thread_safe_pointer` 默认为 `false`,仅在字段启用 ref 跟踪时生效,且不会改变编码格式。 +- 在 Rust 代码生成中,`fory_weak_ref:true` 默认使用 `RcWeak`;只有同时设置 + `fory_thread_safe_pointer:true` 时才会切换为 `ArcWeak`。 +- 对列表字段,`fory_ref:true` 作用于列表元素。 + +示例: + +```fbs +table Node { + parent: Node (fory_weak_ref: true); + children: [Node] (fory_ref: true); + cached: Node (fory_ref: true, fory_thread_safe_pointer: true); +} +``` + +## 生成代码差异 + +即使用 `.fbs` 作为 Fory 输入,生成的依然是标准 Fory 代码,而不是 FlatBuffers 的 `ByteBuffer` 风格 API。 + +- Java:带 Fory 元数据的 POJO/record +- Python:dataclass 与注册辅助代码 +- Go/Rust/C++:原生结构体与 Fory 元数据 + +最终序列化格式是 Fory 二进制协议,而不是 FlatBuffers 线格式。 + +## 用法 + +直接编译 FlatBuffers schema: + +```bash +foryc schema.fbs --lang java,python --output ./generated +``` + +输出转换后的 Fory schema 语法以便调试: + +```bash +foryc schema.fbs --emit-fdl --emit-fdl-path ./translated +``` + +## 迁移注意事项 + +1. 保持现有 `namespace` 稳定,以保证类型注册稳定。 +2. 检查依赖 FlatBuffers 字面量默认值的字段,并在业务代码中补充显式默认值。 +3. 在需要对象图语义的字段上添加 `fory_ref`/`fory_weak_ref`。 +4. 替换现有序列化路径前,先用 roundtrip 测试验证生成模型行为。 + +## 总结 + +FlatBuffers 输入模式可让你复用现有 `.fbs` schema,同时迁移到 Fory 运行时与代码生成模型。它适合渐进式迁移场景:既保留既有 schema 投入,又能采用 Fory 原生对象 API。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/compiler/generated-code.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/compiler/generated-code.md new file mode 100644 index 00000000000..94944636526 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/compiler/generated-code.md @@ -0,0 +1,1104 @@ +--- +title: 生成代码 +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. +--- + +本文档说明各目标语言的代码生成结果与结构。 + +Fory IDL 生成的类型遵循宿主语言习惯,可直接作为领域对象使用。生成代码同时包含 `to/from bytes` 辅助方法和类型注册辅助逻辑。 + +## 参考 Schema + +下面示例基于两个真实 schema: + +1. `addressbook.fdl`(显式类型 ID) +2. `auto_id.fdl`(未显式声明类型 ID) + +### `addressbook.fdl`(节选) + +```protobuf +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 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 people = 1; + map people_by_name = 2; +} +``` + +### `auto_id.fdl`(节选) + +```protobuf +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 + +### 输出布局 + +对于 `package addressbook`,Java 输出通常位于: + +- `/addressbook/` +- 类型文件:`AddressBook.java`、`Person.java`、`Dog.java`、`Cat.java`、`Animal.java` +- 注册辅助类:`AddressbookForyRegistration.java` + +### 类型生成 + +message 会生成带 `@ForyField`、默认构造器、getter/setter 以及字节辅助方法的 Java 类: + +```java +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) { ... } +} +``` + +union 会生成继承 `org.apache.fory.type.union.Union` 的类: + +```java +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) { ... } +} +``` + +### 注册 + +生成的注册辅助方法: + +```java +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); +} +``` + +对于未显式 `[id=...]` 的 schema,注册代码会使用计算得到的数值 ID(例如 `auto_id.fdl`): + +```java +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); +``` + +若设置 `option enable_auto_type_id = false;`,则按命名空间与类型名注册: + +```java +resolver.register(Config.class, "myapp.models", "Config"); +resolver.registerUnion( + Holder.class, + "myapp.models", + "Holder", + new org.apache.fory.serializer.UnionSerializer(fory, Holder.class)); +``` + +### 使用示例 + +```java +Person person = new Person(); +person.setName("Alice"); +person.setPet(Animal.ofDog(new Dog())); + +byte[] data = person.toBytes(); +Person restored = Person.fromBytes(data); +``` + +## Python + +### 输出布局 + +Python 每个 schema 文件生成一个模块,例如: + +- `/addressbook.py` + +### 类型生成 + +union 生成 case 枚举与 `Union` 子类,并提供类型化辅助方法: + +```python +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: ... +``` + +message 生成 `@pyfory.dataclass` 类型,嵌套类型保持嵌套: + +```python +@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": ... +``` + +### 注册 + +生成注册函数: + +```python +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) +``` + +未显式 `[id=...]` 时,注册代码使用计算得到的数值 ID: + +```python +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) +``` + +若设置 `option enable_auto_type_id = false;`: + +```python +fory.register_type(Config, namespace="myapp.models", typename="Config") +fory.register_union( + Holder, + namespace="myapp.models", + typename="Holder", + serializer=HolderSerializer(fory), +) +``` + +### 使用示例 + +```python +person = Person(name="Alice", pet=Animal.dog(Dog(name="Rex", bark_volume=10))) + +data = person.to_bytes() +restored = Person.from_bytes(data) +``` + +### gRPC Service Companion + +当 schema 包含 service,且 compiler 使用 `--grpc` 运行时,Python 会生成名为 +`_grpc.py` 的 companion module。Module 名称由 Fory package 的点号替换为下划线得到; +没有 package 时使用 `generated`。Python gRPC 输出默认使用 `grpc.aio` AsyncIO API。 + +```python +import grpc +import grpc.aio + + +class AddressBookServiceStub: + def __init__(self, channel): + self.lookup = channel.unary_unary( + "/example.AddressBookService/Lookup", + request_serializer=..., + response_deserializer=..., + ) + + +class AddressBookServiceServicer: + async def lookup(self, request, context): + await context.abort(grpc.StatusCode.UNIMPLEMENTED, "Method not implemented!") + + +def add_servicer(servicer, server): ... +``` + +编译或运行生成 companion module 的应用必须安装 `grpcio`;`pyfory` 不会加入硬 gRPC 依赖。 +Python API 使用 snake_case 方法名,同时在 gRPC wire path 中保留原始 IDL method 名称。 + +使用 `--grpc --grpc-python-mode=sync` 可以生成同步 Python `grpcio` companion。Sync 模式保持 +相同的生成文件名和 public name,但 servicer 方法使用普通 `def`,并使用同步 `grpc.Channel` +和 `grpc.Server` 实例。 + +## Rust + +### 输出布局 + +Rust 每个 schema 生成一个模块文件,例如: + +- `/addressbook.rs` + +### 类型生成 + +union 映射为 Rust enum,并用 `#[fory(id = ...)]` 声明 case ID: + +```rust +#[derive(ForyObject, Debug, Clone, PartialEq)] +pub enum Animal { + #[fory(id = 1)] + Dog(Dog), + #[fory(id = 2)] + Cat(Cat), +} +``` + +嵌套类型生成嵌套 module: + +```rust +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, + } +} +``` + +message 会 `derive(ForyObject)` 并生成 `to_bytes`/`from_bytes`: + +```rust +#[derive(ForyObject, Debug, Clone, PartialEq, Default)] +pub struct Person { + #[fory(id = 1)] + pub name: String, + #[fory(id = 7)] + pub phones: Vec, + #[fory(id = 8, type_id = "union")] + pub pet: Animal, +} +``` + +### 注册 + +生成注册函数: + +```rust +pub fn register_types(fory: &mut Fory) -> Result<(), fory::Error> { + fory.register_union::(106)?; + fory.register::(101)?; + fory.register::(102)?; + fory.register::(100)?; + fory.register::(104)?; + fory.register::(105)?; + fory.register::(103)?; + Ok(()) +} +``` + +未显式 `[id=...]` 时,注册代码使用计算得到的数值 ID: + +```rust +fory.register::(1124725126)?; +fory.register_union::(1471345060)?; +fory.register::(3022445236)?; +fory.register_union::(1609214087)?; +fory.register::(2862577837)?; +``` + +若设置 `option enable_auto_type_id = false;`: + +```rust +fory.register_by_namespace::("myapp.models", "Config")?; +fory.register_union_by_namespace::("myapp.models", "Holder")?; +``` + +### 使用示例 + +```rust +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++ + +### 输出布局 + +C++ 每个 schema 文件生成一个头文件,例如: + +- `/addressbook.h` + +### 类型生成 + +message 会生成 `final` 类,并包含类型化访问器与字节辅助方法: + +```cpp +class Person final { + public: + class PhoneNumber final { + public: + const std::string& number() const; + std::string* mutable_number(); + template + void set_number(Arg&& arg, Args&&... args); + + fory::Result, fory::Error> to_bytes() const; + static fory::Result from_bytes(const std::vector& data); + }; + + const std::string& name() const; + std::string* mutable_name(); + template + void set_name(Arg&& arg, Args&&... args); + + const Animal& pet() const; + Animal* mutable_pet(); +}; +``` + +可选 message 字段会生成 `has_xxx`、`mutable_xxx`、`clear_xxx` API: + +```cpp +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(); + } + return payload_.get(); + } + void clear_payload() { payload_.reset(); } + + private: + std::unique_ptr payload_; +}; +``` + +union 会生成基于 `std::variant` 的封装: + +```cpp +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 + decltype(auto) visit(Visitor&& vis) const; + + private: + std::variant value_; +}; +``` + +生成头文件还会包含 `FORY_UNION`、`FORY_FIELD_CONFIG`、`FORY_ENUM`、`FORY_STRUCT` 等序列化元信息宏。 + +### 注册 + +生成注册函数: + +```cpp +inline void register_types(fory::serialization::BaseFory& fory) { + fory.register_union(106); + fory.register_enum(101); + fory.register_struct(102); + fory.register_struct(100); + fory.register_struct(104); + fory.register_struct(105); + fory.register_struct(103); +} +``` + +未显式 `[id=...]` 时,注册代码使用计算得到的数值 ID: + +```cpp +fory.register_enum(1124725126); +fory.register_union(1471345060); +fory.register_struct(3022445236); +fory.register_union(1609214087); +fory.register_struct(2862577837); +``` + +若设置 `option enable_auto_type_id = false;`: + +```cpp +fory.register_struct("myapp.models", "Config"); +fory.register_union("myapp.models", "Holder"); +``` + +### 使用示例 + +```cpp +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 + +### 输出布局 + +Go 输出路径受 schema 选项与 `--go_out` 共同影响。 + +对于 `addressbook.fdl`,若配置了 `go_package`,生成结果会遵循对应 import path/package(位于 `--go_out` 根目录下)。 + +若未配置 `go_package`,则使用 `--go_out` 指定目录,并按 package 规则生成文件名与包名。 + +### 类型生成 + +嵌套类型默认使用下划线命名(`Person_PhoneType`、`Person_PhoneNumber`): + +```go +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"` +} +``` + +message 会生成带 `fory` tag 的 struct 与字节辅助方法: + +```go +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 { ... } +``` + +union 会生成 case struct,并提供构造器/访问器/visitor API: + +```go +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 { ... } +``` + +### 注册 + +生成注册函数: + +```go +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 +} +``` + +未显式 `[id=...]` 时,注册代码使用计算得到的数值 ID: + +```go +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 { ... } +``` + +若设置 `option enable_auto_type_id = false;`: + +```go +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` 可控制嵌套类型命名风格: + +```protobuf +option go_nested_type_style = "camelcase"; +``` + +### 使用示例 + +```go +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\# + +### 输出布局 + +C# 输出通常是每个 schema 对应一个 `.cs` 文件,例如: + +- `/addressbook/addressbook.cs` + +### 类型生成 + +message 会生成带 `[ForyObject]` 的类、C# 属性以及字节辅助方法: + +```csharp +[ForyObject] +public sealed partial class Person +{ + public string Name { get; set; } = string.Empty; + public int Id { get; set; } + public List Phones { get; set; } = new(); + public Animal Pet { get; set; } = null!; + + public byte[] ToBytes() { ... } + public static Person FromBytes(byte[] data) { ... } +} +``` + +union 会生成带类型化 case 辅助方法的 `Union` 子类: + +```csharp +public sealed class Animal : Union +{ + public static Animal Dog(Dog value) { ... } + public static Animal Cat(Cat value) { ... } + public bool IsDog => ...; + public Dog DogValue() { ... } +} +``` + +### 注册 + +每个 schema 都会生成一个注册辅助类: + +```csharp +public static class AddressbookForyRegistration +{ + public static void Register(Fory fory) + { + fory.Register((uint)106); + fory.Register((uint)100); + // ... + } +} +``` + +若未显式提供类型 ID,生成的注册代码会像其他目标语言一样使用计算得到的数值 ID。 + +## JavaScript + +### 输出布局 + +JavaScript 输出通常是每个 schema 对应一个 `.ts` 文件,例如: + +- `/addressbook.ts` + +### 类型生成 + +message 会生成使用 camelCase 字段名的 `export interface` 声明: + +```typescript +export interface Person { + name: string; + id: number; + phones: PhoneNumber[]; + pet?: Animal | null; +} +``` + +enum 会生成 `export enum` 声明: + +```typescript +export enum PhoneType { + MOBILE = 0, + HOME = 1, + WORK = 2, +} +``` + +union 会生成带 case enum 的判别联合类型: + +```typescript +export enum AnimalCase { + DOG = 1, + CAT = 2, +} + +export type Animal = + | { case: AnimalCase.DOG; value: Dog } + | { case: AnimalCase.CAT; value: Cat }; +``` + +## Swift + +### 输出布局 + +Swift 输出通常是每个 schema 对应一个 `.swift` 文件,例如: + +- `/addressbook/addressbook.swift` + +### 类型生成 + +生成器会创建带 `@ForyObject` 和字段 ID 的 Swift 模型。 + +当 package/namespace 非空时,命名空间形态由 `swift_namespace_style` 控制: + +- `enum`(默认):生成嵌套 enum 命名空间包装 +- `flatten`:在顶层类型名上加上由 package 派生的前缀(例如 `Demo_Foo_User`) + +当 package/namespace 为空时,不会生成 enum 包装或 flatten 前缀。 + +对于非空 package 且使用默认 `enum` 风格时: + +```swift +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() + } +} +``` + +对于非空 package 且使用 `flatten` 风格时: + +```swift +@ForyObject +public struct Addressbook_Person: Equatable { ... } +``` + +当命令行和 schema 选项同时设置时,CLI 参数 `--swift_namespace_style` 会覆盖 schema 中的 `swift_namespace_style`。 + +union 会生成带关联值的标记 Swift enum。 +带 `ref`/`weak_ref` 字段的 message 会生成 `final class` 模型,以保留引用语义。 + +### 注册 + +每个 schema 都会生成带传递性 import 注册的辅助类: + +```swift +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) + } +} +``` + +对于非空 package 且使用 `flatten` 风格时,辅助类名称也会带前缀(例如 `Addressbook_ForyRegistration`)。 + +若未显式提供 `[id=...]`,注册会使用计算得到的数值 ID。 +若设置 `option enable_auto_type_id = false;`,则生成代码会改用基于名称的注册 API。 + +## Dart + +### 输出布局 + +Dart 输出通常是每个 schema 对应两个文件:一个带注解类型的主 `.dart` 文件,以及一个包含生成序列化器和注册辅助逻辑的 `.fory.dart` part 文件。 + +- `/package/package.dart` +- `/package/package.fory.dart` + +### 类型生成 + +message 会生成带 `@ForyStruct` 注解的 `final class`,并在每个字段上加 `@ForyField`: + +```dart +@ForyStruct() +final class Person { + Person(); + + @ForyField(id: 1) + String name = ''; + + @ForyField(id: 2) + Int32 id = Int32(0); + + @ForyField(id: 7) + List phones = []; + + @ForyField(id: 8) + Animal pet = Animal._empty(); +} +``` + +enum 会生成带 `rawValue` getter 和 `fromRawValue` factory 的 Dart `enum`: + +```dart +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.'), + }; +} +``` + +union 会生成带 `@ForyUnion` 注解的类,包含工厂构造器、case enum 以及自定义序列化器: + +```dart +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; + // ... +} +``` + +嵌套类型使用扁平下划线命名(例如 `Person_PhoneNumber`、`Person_PhoneType`)。 + +非 optional、非 ref 的原始类型列表会使用 typed array 以获得零拷贝性能(例如 `list` -> `Int32List`)。 + +列表元素或 map value 上的引用跟踪通过 `@ListType` / `@MapType` 注解表达: + +```dart +@ListType(element: ValueType.ref()) +@ForyField(id: 3) +List children = []; + +@MapType(value: ValueType.ref()) +@ForyField(id: 2) +Map byName = {}; +``` + +### 注册 + +每个 schema 都会生成一个注册辅助类,负责处理该文件中的所有类型,并传递性注册 import 进来的类型: + +```dart +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 + } +} +``` + +### 使用示例 + +```dart +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(bytes); +} +``` + +### gRPC Service Companion + +当 schema 包含 service,且 compiler 使用 `--grpc` 运行时,Dart 会在 model type 旁为每个 +schema 生成一个 `_grpc.dart` 文件。它面向 `package:grpc`。Request 和 response +序列化使用 companion 自动获取的 Fory runtime,并在首次使用时注册该 schema 的类型,因此不需要手动注册;应用也可以在第一次调用前通过 schema module 的 `install(...)` 注入自定义 `Fory`。 + +生成支持四种 RPC 模式:unary、server-streaming、client-streaming 和 bidirectional。Client +class 继承 `Client`;service base class 继承 `Service` 并通过 `$addMethod` 注册各方法。 + +```dart +class GreeterClient extends Client { + // Single response: ResponseFuture. Streaming response: ResponseStream. + ResponseFuture sayHello(HelloRequest request, {CallOptions? options}) { ... } + ResponseStream sayHellos(HelloRequest request, {CallOptions? options}) { ... } + ResponseFuture collectHellos(Stream request, {CallOptions? options}) { ... } + ResponseStream chatHellos(Stream request, {CallOptions? options}) { ... } +} + +abstract class GreeterServiceBase extends Service { + Future sayHello(ServiceCall call, HelloRequest request); + Stream sayHellos(ServiceCall call, HelloRequest request); + Future collectHellos(ServiceCall call, Stream request); + Stream chatHellos(ServiceCall call, Stream request); +} +``` + +单响应 client 方法返回 `ResponseFuture`(client-streaming 会用 `.single` 适配 streaming 调用); +streaming 响应方法返回 `ResponseStream`。Server 端实现会 override 抽象方法:单请求以 `Q` +传入,client-streaming 请求以 `Stream` 传入;单响应返回 `Future`,streaming 响应返回 +`Stream`。编译这些文件的应用必须提供 `grpc` 依赖;Fory Dart runtime 不会加入此依赖。原始 +IDL method 名称用于 gRPC wire path。 + +## 跨语言说明 + +### 类型 ID 行为 + +- 显式 `[id=...]` 会直接用于生成注册代码。 +- 未声明类型 ID 时,生成代码会使用计算得到的数值 ID(参见 `auto_id.*` 输出)。 +- 若设置 `option enable_auto_type_id = false;`,则生成代码改为使用 namespace/type-name 注册 API,而非数值 ID。 + +### 嵌套类型形态 + +| 语言 | 嵌套类型形式 | +| ---------- | ---------------------------- | +| Java | `Person.PhoneNumber` | +| Python | `Person.PhoneNumber` | +| Rust | `person::PhoneNumber` | +| C++ | `Person::PhoneNumber` | +| Go | `Person_PhoneNumber`(默认) | +| C# | `Person.PhoneNumber` | +| JavaScript | `Person.PhoneNumber` | +| Swift | `Person.PhoneNumber` | +| Dart | `Person_PhoneNumber` | + +### 字节辅助方法命名 + +| 语言 | 辅助方法名称 | +| ---------- | ------------------------- | +| 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 | (通过 `fory.serialize()`) | +| Swift | `toBytes` / `fromBytes` | +| Dart | (通过 `fory.serialize()`) | diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/compiler/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/compiler/index.md new file mode 100644 index 00000000000..bb1809493c3 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/compiler/index.md @@ -0,0 +1,224 @@ +--- +title: 概览 +sidebar_position: 1 +id: index +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. +--- + +Fory IDL 是 Apache Fory 的 Schema 定义语言,可实现类型安全的跨语言序列化。 +你只需定义一次数据结构,即可为 Java、Python、Go、Rust、C++、C#、Swift、Dart、Scala、Kotlin 和 JavaScript/TypeScript 生成原生数据结构代码。Fory IDL 也可以描述 RPC service;对于 Java、Python、Go、Rust、C#、Dart、Scala、Kotlin 和 JavaScript,compiler 可以生成使用 Fory 序列化 request/response payload 的 gRPC service companion。 + +## 示例 Schema + +Fory IDL 提供了简单直观的语法来定义跨语言数据结构: + +```protobuf +package example; + +enum Status { + PENDING = 0; + ACTIVE = 1; + COMPLETED = 2; +} + +message User { + string name = 1; + int32 age = 2; + optional string email = 3; + list tags = 4; +} + +message Item { + string sku = 1; + int32 quantity = 2; +} + +message Order { + ref User customer = 1; + list items = 2; + Status status = 3; + map metadata = 4; +} + +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; +} +``` + +## 为什么选择 Fory IDL? + +### Schema 优先开发 + +在 Fory IDL 中一次定义数据模型,即可在所有语言中生成一致且类型安全的代码。这样可以确保: + +- **类型安全**:在编译期而不是运行期发现类型错误 +- **一致性**:各语言使用相同字段名、类型和结构 +- **文档性**:Schema 本身就是可持续演进的文档 +- **可演进性**:可在所有实现中受控地进行 Schema 变更 + +### Fory 原生能力 + +与通用 IDL 不同,Fory IDL 专门为 Fory 序列化设计: + +- **引用跟踪**:通过 `ref` 一等支持共享引用和循环引用 +- **可空字段**:通过 `optional` 显式声明可空类型 +- **类型注册**:内置支持数值 ID 与基于命名空间的注册 +- **原生代码生成**:生成带 Fory 注解/宏的语言惯用代码 + +### 集成开销低 + +生成代码直接使用各语言原生构造: + +- Java:带 `@ForyField` 注解的普通 POJO +- Python:带类型提示的 dataclass +- Go:带 struct tag 的结构体 +- Rust:带 `#[derive(ForyObject)]` 的结构体 +- C++:带 `FORY_STRUCT` 宏的结构体 +- C#:带 `[ForyObject]` 的类及注册辅助函数 +- JavaScript:带注册函数的接口 +- Swift:带 `@ForyObject` 模型、`@ForyField` 元数据和注册辅助函数 +- Dart:带 `@ForyStruct` 类、`@ForyField` 注解和注册辅助函数 + +## 快速开始 + +### 1. 安装编译器 + +```bash +pip install fory-compiler +``` + +或从源码安装: + +```bash +cd compiler +pip install -e . +``` + +### 2. 编写 Schema + +创建 `example.fdl`: + +```protobuf +package example; + +message Person { + string name = 1; + int32 age = 2; + optional string email = 3; +} +``` + +### 3. 生成代码 + +```bash +# 为所有语言生成 +foryc example.fdl --output ./generated + +# 为指定语言生成 +foryc example.fdl --lang java,python,csharp,javascript,swift,dart --output ./generated +``` + +为包含 service 定义的 schema 生成 Java、Python、Go、Rust、C#、Dart、Scala、Kotlin 和 JavaScript model 以及 gRPC service companion: + +```bash +foryc animals.fdl --java_out=./generated/java --python_out=./generated/python --go_out=./generated/go --rust_out=./generated/rust --csharp_out=./generated/csharp --dart_out=./generated/dart --scala_out=./generated/scala --kotlin_out=./generated/kotlin --javascript_out=./generated/javascript --grpc +``` + +生成的 service 代码使用标准 gRPC API,但 request 和 response 对象使用 Fory 序列化。应用需要自行提供 grpc-java、grpc-kotlin、Scala grpc-java API、`grpcio`、grpc-go、Rust `tonic` 和 `bytes`、C# `Grpc.Core.Api` 及 server/client 依赖,或 Dart `package:grpc`;Fory package 不会把 gRPC 作为硬依赖。Python companion 默认使用 `grpc.aio`,也可以通过 `--grpc-python-mode=sync` 生成同步模式。JavaScript Node.js companion 使用 `@grpc/grpc-js`;浏览器 client 通过 `--grpc-web` 单独生成并使用 `grpc-web`。 + +### 4. 使用生成代码 + +**Java:** + +```java +Person person = new Person(); +person.setName("Alice"); +person.setAge(30); +byte[] data = person.toBytes(); +``` + +**Python:** + +```python +import pyfory +from example import Person + +person = Person(name="Alice", age=30) +data = bytes(person) # 或 `person.to_bytes()` +``` + +## 文档导航 + +| 文档 | 说明 | +| -------------------------------------------- | ---------------------------------- | +| [Fory IDL 语法](schema-idl.md) | 完整语言语法与文法 | +| [类型系统](schema-idl.md#type-system) | 基础类型、集合类型与类型规则 | +| [编译器指南](compiler-guide.md) | CLI 选项与构建集成 | +| [生成代码](generated-code.md) | 各目标语言的输出格式 | +| [Protocol Buffers IDL 支持](protobuf-idl.md) | 与 protobuf 的对比及迁移指南 | +| [FlatBuffers IDL 支持](flatbuffers-idl.md) | FlatBuffers 映射规则与代码生成差异 | + +## 核心概念 + +### 字段修饰符 + +- **`optional`**:字段可为 null/None +- **`ref`**:为共享/循环引用启用引用跟踪 +- **`list`**:字段为列表/数组(别名:`repeated`) + +```protobuf +message Example { + optional string nullable = 1; + ref Node parent = 2; + list numbers = 3; +} +``` + +### 跨语言兼容 + +Fory IDL 类型会映射为各语言原生类型: + +| Fory IDL 类型 | Java | Python | Go | Rust | C++ | C# | JavaScript | Swift | Dart | +| ------------- | --------- | -------------- | -------- | -------- | ------------- | -------- | ---------- | -------- | -------- | +| `int32` | `int` | `pyfory.int32` | `int32` | `i32` | `int32_t` | `int` | `number` | `Int32` | `Int32` | +| `string` | `String` | `str` | `string` | `String` | `std::string` | `string` | `string` | `String` | `String` | +| `bool` | `boolean` | `bool` | `bool` | `bool` | `bool` | `bool` | `boolean` | `Bool` | `bool` | + +完整映射请参见 [类型系统](schema-idl.md#type-system)。 + +## 最佳实践 + +1. **使用有意义的 package 名称**:将相关类型分组管理 +2. **为性能分配类型 ID**:数值 ID 比基于名称的注册更高效 +3. **预留 ID 范围**:为后续扩展保留空隙(例如用户 100-199,订单 200-299) +4. **显式使用 `optional`**:清晰表达可空语义 +5. **共享对象使用 `ref`**:对象可能被多处引用时开启引用跟踪 + +## 示例 + +完整可运行示例见 [examples](https://github.com/apache/fory/tree/main/compiler/examples) 目录。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/compiler/protobuf-idl.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/compiler/protobuf-idl.md new file mode 100644 index 00000000000..6b0c3ef9bfc --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/compiler/protobuf-idl.md @@ -0,0 +1,332 @@ +--- +title: Protobuf IDL 支持 +sidebar_position: 10 +id: protobuf_idl_support +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. +--- + +本页说明 Apache Fory 如何处理 Protocol Buffers(`.proto`)schema、protobuf 概念如何映射到 Fory,以及 protobuf 专用 Fory 扩展选项的使用方式。 + +## 本页内容 + +- 如何在具体场景下选择 protobuf 或 Fory +- 迁移时需要关注的语法与语义差异 +- `.proto` 文件中支持的 Fory 扩展选项 +- 从 protobuf 迁移到 Fory 的实践路径 + +## 快速决策指南 + +| 场景 | 建议格式 | +| ----------------------------------------------------- | ---------------- | +| 主要构建 gRPC API,依赖 protobuf 工具链 | Protocol Buffers | +| 需要极致对象图性能与引用跟踪 | Fory | +| 需要在序列化数据中表达循环/共享引用 | Fory | +| 需要强 unknown-field 语义保证线格式兼容 | Protocol Buffers | +| 希望直接使用原生 struct/class,而非 protobuf 包装类型 | Fory | + +## Protobuf 与 Fory 对比 + +| 维度 | Protocol Buffers | Fory | +| --------- | --------------------- | ---------------------- | +| 主要目标 | RPC/消息契约 | 高性能对象序列化 | +| 编码模型 | Tag-Length-Value | Fory 二进制协议 | +| 引用跟踪 | 非内建 | 一等支持(`ref`) | +| 循环引用 | 不支持 | 支持 | +| 未知字段 | 保留 | 不保留 | +| 生成类型 | protobuf 专用模型类型 | 语言原生构造 | +| gRPC 生态 | 原生成熟 | Java/Python/Go/Rust/C#/Dart/Scala/Kotlin/JavaScript service codegen | + +Fory 可以通过 `--grpc` 生成 Java、Python、Go、Rust、C#、Dart、Scala、Kotlin 和 JavaScript gRPC service companion。JavaScript 浏览器 client 通过 `--grpc-web` 生成。这些 service 使用标准 gRPC 传输,但 request 和 response payload 使用 Fory 序列化,而不是 protobuf。对于广泛的 gRPC 生态工具、schema reflection 和 protobuf-native interceptor,protobuf 仍是更成熟的默认选择。 + +## 为什么使用 Apache Fory + +- **代码更贴近语言习惯**:Fory IDL 生成的类和结构体可直接作为业务领域对象使用。 +- **序列化性能更高**:在 Fory 基准中,Fory 在对象序列化场景中可显著快于 protobuf。 +- **对象图表达更自然**:共享引用和循环引用是内建能力,无需通过业务层 ID 关联绕过。 + +性能细节请参见 [性能参考](#性能参考)。 + +## 语法与语义映射 + +### Package 与文件级选项 + +**Protocol Buffers** + +```protobuf +syntax = "proto3"; +package example.models; +option java_package = "com.example.models"; +option go_package = "example.com/models"; +``` + +**Fory** + +```protobuf +package example.models; +``` + +Fory 使用统一 package 命名空间做跨语言注册。语言特定的包路径仍可在代码生成阶段单独配置。 + +### Message 与 Enum 定义 + +**Protocol Buffers** + +```protobuf +message User { + string id = 1; + string name = 2; + optional string email = 3; + int32 age = 4; + repeated string tags = 5; + map metadata = 6; +} + +enum Status { + STATUS_UNSPECIFIED = 0; + STATUS_ACTIVE = 1; +} +``` + +**Fory** + +```protobuf +message User [id=101] { + string id = 1; + string name = 2; + optional string email = 3; + int32 age = 4; + list tags = 5; + map metadata = 6; +} + +enum Status [id=102] { + UNKNOWN = 0; + ACTIVE = 1; +} +``` + +关键差异: + +- Fory 可直接分配稳定类型 ID(`[id=...]`)。 +- Fory 使用 `list`(`repeated T` 为兼容别名)。 +- 枚举命名更偏向语言习惯,而非 protobuf 前缀风格。 + +### `oneof` 到 `union` + +protobuf 的 `oneof` 会被翻译为嵌套 Fory `union`,并增加一个可选字段指向该 union。 + +**Protocol Buffers** + +```protobuf +message Event { + oneof payload { + string text = 1; + int32 number = 2; + } +} +``` + +**转换后的 Fory 结构** + +```protobuf +message Event { + union payload { + string text = 1; + int32 number = 2; + } + optional payload payload = 1; +} +``` + +说明: + +- union case ID 来自原 `oneof` 字段号。 +- 自动生成的 union 引用字段使用 `oneof` 中最小字段号。 + +### Import 与 Well-Known Types + +支持 protobuf import。常见 well-known types 会直接映射: + +- `google.protobuf.Timestamp` -> `timestamp` +- `google.protobuf.Duration` -> `duration` +- `google.protobuf.Any` -> `any` + +## 类型映射要点 + +| Protobuf Type | Fory 映射 | +| ---------------------------------------- | ----------------------------- | +| `bool` | `bool` | +| `int32`, `uint32` | 可变长 32 位整型家族 | +| `sint32` | zigzag 32 位整型 | +| `int64`, `uint64` | 可变长 64 位整型家族 | +| `sint64` | zigzag 64 位整型 | +| `fixed32`, `fixed64` | 定长无符号整型家族 | +| `sfixed32`, `sfixed64` | 定长有符号整型家族 | +| `float`, `double` | `float32`, `float64` | +| `string`, `bytes` | `string`, `bytes` | +| `repeated T` | `list` | +| `map` | `map` | +| `optional T` | `optional T` | +| `oneof` | `union` + 可选 union 引用字段 | +| `int64 [(fory).type = "tagged_int64"]` | `tagged_int64` 编码 | +| `uint64 [(fory).type = "tagged_uint64"]` | `tagged_uint64` 编码 | + +## Fory 扩展选项(Protobuf) + +`.proto` 文件中的 Fory 专用选项使用 `(fory).` 前缀。 + +```protobuf +option (fory).enable_auto_type_id = true; + +message TreeNode { + TreeNode parent = 1 [(fory).weak_ref = true]; + repeated TreeNode children = 2 [(fory).ref = true]; +} +``` + +### 文件级选项 + +| 选项 | 类型 | 说明 | +| ------------------------------------ | ------ | --------------------------------------------------- | +| `(fory).use_record_for_java_message` | bool | 对该文件所有 message 生成 Java record | +| `(fory).polymorphism` | bool | 默认开启多态序列化元信息 | +| `(fory).enable_auto_type_id` | bool | 缺失时自动生成类型 ID(编译器默认 true) | +| `(fory).evolving` | bool | message 的默认 schema 演进行为 | +| `(fory).go_nested_type_style` | string | Go 嵌套命名风格:`underscore`(默认)或 `camelcase` | + +### Message 与 Enum 级选项 + +| 选项 | 作用对象 | 类型 | 说明 | +| ---------------------------- | ------------- | ------ | ----------------------------- | +| `(fory).id` | message, enum | int | 显式类型 ID(用于注册) | +| `(fory).alias` | message, enum | string | 自动 ID 哈希使用的别名 | +| `(fory).evolving` | message | bool | 覆盖文件级演进设置 | +| `(fory).use_record_for_java` | message | bool | 为该 message 生成 Java record | +| `(fory).deprecated` | message, enum | bool | 标记类型为弃用 | +| `(fory).namespace` | message | string | 覆盖默认 package 命名空间 | + +### 字段级选项 + +| 选项 | 类型 | 说明 | +| ---------------------------- | ------ | --------------------------------------------------------------------- | +| `(fory).ref` | bool | 为该字段启用引用跟踪 | +| `(fory).nullable` | bool | 将字段视为可空(`optional`) | +| `(fory).weak_ref` | bool | 生成弱指针语义(C++/Rust 代码生成) | +| `(fory).thread_safe_pointer` | bool | 对 ref 字段使用 Rust `Arc`/`ArcWeak`;默认 `false` 使用 `Rc`/`RcWeak` | +| `(fory).deprecated` | bool | 标记字段为弃用 | +| `(fory).type` | string | 基础类型覆盖,目前支持 `tagged_int64`/`tagged_uint64` | + +引用相关行为: + +- `weak_ref = true` 隐含开启 ref 跟踪。 +- 对 `repeated` 字段,`(fory).ref = true` 作用于列表元素。 +- 对 `map` 字段,`(fory).ref = true` 作用于 map value。 +- `weak_ref` 与 `thread_safe_pointer` 是 C++/Rust 代码生成提示。 +- `thread_safe_pointer` 默认为 `false`;它只改变生成的 Rust 指针承载类型,不改变编码格式。 +- 在 Rust 代码生成中,`(fory).weak_ref = true` 默认使用 `RcWeak`;只有同时设置 + `(fory).thread_safe_pointer = true` 时才会切换为 `ArcWeak`。 + +### 典型选项组合示例 + +```protobuf +message Graph { + Node root = 1 [(fory).ref = true, (fory).thread_safe_pointer = true]; + repeated Node nodes = 2 [(fory).ref = true]; + map cache = 3 [(fory).ref = true]; + Node parent = 4 [(fory).weak_ref = true]; +} +``` + +## 引用跟踪 vs Protobuf ID 关联 + +protobuf 本身不保留共享/循环对象图。借助 Fory protobuf 扩展,可以显式启用对象图语义。 + +**不使用 Fory ref 选项(protobuf 风格 ID 关联):** + +```protobuf +message TreeNode { + string id = 1; + string parent_id = 2; + repeated string child_ids = 3; +} +``` + +**使用 Fory ref 选项(对象图语义):** + +```protobuf +message TreeNode { + TreeNode parent = 1 [(fory).weak_ref = true]; + repeated TreeNode children = 2 [(fory).ref = true]; +} +``` + +## 迁移指南:Protobuf 到 Fory + +### 第 1 步:转换 schema 语法 + +- 保持 package 名称稳定。 +- 将 `repeated T` 改为 `list`(或保留 `repeated` 别名)。 +- 在需要稳定数值注册的位置添加显式 `[id=...]`。 + +### 第 2 步:处理 `oneof` 与特殊类型 + +- `oneof` -> `union` + 可选 union 字段。 +- 将 protobuf well-known types 映射到 Fory 基础类型(`timestamp`、`duration`、`any`)。 + +### 第 3 步:用 `ref` 替换 protobuf 的对象图绕过方案 + +若 protobuf 里通过手工 ID 关联表达对象图,迁移到 Fory 后应改用 `ref` 修饰符(必要时使用 `ref(weak=true)`)。 + +### 第 4 步:更新构建与代码生成 + +将 protobuf 代码生成步骤替换为 Fory 编译器针对目标语言的生成命令。 + +对于支持的 service 输出,添加 `--grpc` 以生成 gRPC companion 代码: + +```bash +foryc api.proto --java_out=./generated/java --python_out=./generated/python --go_out=./generated/go --rust_out=./generated/rust --csharp_out=./generated/csharp --dart_out=./generated/dart --scala_out=./generated/scala --kotlin_out=./generated/kotlin --javascript_out=./generated/javascript --grpc +``` + +生成的 Java service 文件依赖 grpc-java;Python service module 默认使用 `grpc.aio`;Rust service 文件导入 `tonic` 和 `bytes`;Go service 文件导入 grpc-go;JavaScript Node.js service 文件导入 `@grpc/grpc-js`;C# service 文件导入 `Grpc.Core.Api` 类型;Dart service 文件导入 `package:grpc`;Scala service 文件依赖 grpc-java;Kotlin service 文件依赖 grpc-java 和 grpc-kotlin。请在应用构建中加入这些依赖;Fory package 不会把 gRPC 作为硬依赖。同步 Python `grpcio` companion 可使用 `--grpc-python-mode=sync`。JavaScript 输出配合 `--grpc-web` 可生成导入 `grpc-web` 的浏览器 client。 + +### 第 5 步:执行兼容性验证 + +分阶段迁移时,可并行保留两种格式,并通过集成测试验证 payload 级一致性。 + +## 共存策略 + +迁移期间可以并行运行 protobuf 与 Fory: + +```java +public byte[] serialize(Object obj, Format format) { + if (format == Format.PROTOBUF) { + return ((MessageLite) obj).toByteArray(); + } + return fory.serialize(obj); +} +``` + +可在服务边界设置转换层,并优先迁移内部对象图较重的链路。 + +## 性能参考 + +- Benchmarks: https://fory.apache.org/docs/introduction/benchmark +- Benchmark code: https://github.com/apache/fory/tree/main/benchmarks + +## 总结 + +当主要关注 API 契约与 gRPC 生态时,建议使用 protobuf。若主要关注对象图性能、原生数据模型与引用语义,建议使用 Fory。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/compiler/schema-idl.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/compiler/schema-idl.md new file mode 100644 index 00000000000..3e8fd2db192 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/compiler/schema-idl.md @@ -0,0 +1,913 @@ +--- +title: Schema IDL 语法 +sidebar_position: 2 +id: syntax +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. +--- + +本文档给出 Fory IDL 的语法与语义参考,覆盖文件结构、类型系统、字段规则、选项与类型注册策略。 + +编译器使用方式与构建集成请参见 [Compiler Guide](compiler-guide.md)。 +protobuf/FlatBuffers 前端映射请参见 [Protocol Buffers IDL Support](protobuf-idl.md) 与 [FlatBuffers IDL Support](flatbuffers-idl.md)。 + +## 文件结构 + +一个 Fory IDL 文件通常包含: + +1. 可选 `package` 声明 +2. 可选文件级 `option` +3. 可选 `import` 语句 +4. 类型定义(`enum`、`message`、`union`) + +```protobuf +// Optional package declaration +package com.example.models; + +// Optional file-level options +option java_package = "com.example.models"; + +// Import statements +import "common/types.fdl"; + +// Type definitions +enum Color [id=100] { ... } +message User [id=101] { ... } +message Order [id=102] { ... } +union Event [id=103] { ... } +``` + +## 注释 + +支持单行注释与块注释: + +```protobuf +// This is a single-line comment + +/* + * This is a block comment + * that spans multiple lines + */ + +message Example { + string name = 1; // Inline comment +} +``` + +## Package 声明 + +`package` 定义文件中所有类型的命名空间。 + +```protobuf +package com.example.models; +``` + +也可以配置 alias(用于自动类型 ID 计算): + +```protobuf +package com.example.models alias models_v1; +``` + +规则: + +- 可选但推荐 +- 必须位于任何类型定义之前 +- 每个文件最多一个 `package` +- 用于命名空间注册 +- `alias` 会参与 auto-ID 哈希 + +语言映射: + +| 语言 | package 用法 | +| ---------- | ------------------------- | +| Java | Java package | +| Python | 模块名(`.` 转 `_`) | +| Go | 包名(通常取最后一段) | +| Rust | 模块名(`.` 转 `_`) | +| C++ | 命名空间(`.` 转 `::`) | +| C# | 命名空间 | +| JavaScript | 模块名(取最后一段) | +| Dart | 库名(保留 package 各段) | + +## 文件级选项 + +文件级选项用于控制语言定制代码生成。 + +### 语法 + +```protobuf +option option_name = value; +``` + +### Java Package 选项 + +通过 `java_package` 覆盖 Java 输出包名: + +```protobuf +package payment; +option java_package = "com.mycorp.payment.v1"; + +message Payment { + string id = 1; +} +``` + +效果: + +- Java 文件输出到 `com/mycorp/payment/v1/` +- Java `package` 声明使用该值 +- 跨语言类型注册仍以 Fory package(如 `payment`)为准 + +### Go Package 选项 + +通过 `go_package` 指定 Go import path 与包名: + +```protobuf +package payment; +option go_package = "github.com/mycorp/apis/gen/payment/v1;paymentv1"; + +message Payment { + string id = 1; +} +``` + +格式:`"import/path;package_name"` 或仅 `"import/path"`(包名取最后一段)。 + +### Java Outer Classname 选项 + +将多个类型包装到一个外层类: + +```protobuf +package payment; +option java_outer_classname = "DescriptorProtos"; + +enum Status { + UNKNOWN = 0; + ACTIVE = 1; +} + +message Payment { + string id = 1; + Status status = 2; +} +``` + +默认会生成单文件,枚举与消息作为静态内部类型。 + +### Java Multiple Files 选项 + +控制 Java 是否拆分多文件: + +```protobuf +package payment; +option java_outer_classname = "PaymentProtos"; +option java_multiple_files = true; + +message Payment { + string id = 1; +} + +message Receipt { + string id = 1; +} +``` + +行为: + +| `java_outer_classname` | `java_multiple_files` | 结果 | +| ---------------------- | --------------------- | ---------------- | +| 未设置 | 任意 | 每个类型一个文件 | +| 已设置 | `false`(默认) | 单文件 + 内部类 | +| 已设置 | `true` | 强制拆分为多文件 | + +### 多个选项组合 + +```protobuf +package payment; +option java_package = "com.mycorp.payment.v1"; +option go_package = "github.com/mycorp/apis/gen/payment/v1;paymentv1"; +option deprecated = true; +``` + +### Protobuf 扩展语法说明 + +在 `.fdl` 中请使用 Fory 原生语法(如 `[id=100]`、`ref`、`optional`、`nullable=true`)。 +`(fory).xxx` 形式仅用于 `.proto`(protobuf 前端)。 + +### 选项优先级 + +语言包路径优先级: + +1. 命令行覆盖(最高) +2. 语言选项(`java_package`、`go_package`) +3. Fory IDL `package`(兜底) + +### 跨语言类型注册 + +默认情况下,注册名由 `package + type name`(或类型 ID)确定。建议长期保持 `package` 稳定,以避免跨版本注册不一致。 + +## Import 语句 + +### 基本语法 + +```protobuf +import "common/types.fdl"; +``` + +### 多个导入 + +```protobuf +import "common/types.fdl"; +import "domain/user.fdl"; +import "domain/order.fdl"; +``` + +### 路径解析 + +导入解析顺序: + +1. 导入者文件所在目录 +2. 命令行 `-I/--proto_path/--import_path` 指定目录(按给定顺序) + +### 完整示例 + +```protobuf +// src/main.fdl +package app; + +import "common.fdl"; +import "models/user.fdl"; + +message Main { + common.Meta meta = 1; + models.User user = 2; +} +``` + +### 不支持的 import 写法 + +- URL 形式(如 `https://...`) +- 绝对路径依赖(不推荐,会破坏可移植性) + +### import 错误 + +典型错误: + +- 文件不存在 +- 搜索路径未包含依赖目录 +- 同名文件冲突导致解析到错误版本 + +## Enum 定义 + +### 基本语法 + +```protobuf +enum Status { + UNKNOWN = 0; + ACTIVE = 1; + DISABLED = 2; +} +``` + +### 显式类型 ID + +```protobuf +enum Status [id=101] { + UNKNOWN = 0; + ACTIVE = 1; + DISABLED = 2; +} +``` + +### 预留值 + +```protobuf +enum Status { + UNKNOWN = 0; + ACTIVE = 1; + reserved 2, 3; + reserved 10 to 20; +} +``` + +### enum 类型选项 + +常见:`id`、`alias`、`deprecated`。 + +### 语言映射 + +| 语言 | 实现形式 | +| ---------- | -------------------------------------- | +| Java | `enum Status { UNKNOWN, ACTIVE, ... }` | +| Python | `class Status(IntEnum): UNKNOWN = 0` | +| Go | `type Status int32` 配合常量 | +| Rust | `#[repr(i32)] enum Status { Unknown }` | +| C++ | `enum class Status : int32_t { ... }` | +| JavaScript | `export enum Status { UNKNOWN, ... }` | +| Dart | `enum Status { unknown, active, ... }` | + +### 枚举前缀处理 + +针对 protobuf 风格 `TYPE_NAME_VALUE`,生成器通常会按语言习惯去除冗余前缀,使 API 更自然。 + +| 语言 | 输出示例 | 风格 | +| ---------- | ----------------------------------------- | ---------------- | +| Java | `UNKNOWN, TIER1, TIER2` | 作用域枚举 | +| Rust | `Unknown, Tier1, Tier2` | 作用域枚举 | +| C++ | `UNKNOWN, TIER1, TIER2` | 作用域枚举 | +| Python | `UNKNOWN, TIER1, TIER2` | 作用域 `IntEnum` | +| Go | `DeviceTierUnknown, DeviceTierTier1, ...` | 非作用域常量 | +| JavaScript | `UNKNOWN, TIER1, TIER2` | 作用域枚举 | +| Dart | `unknown, tier1, tier2` | 作用域枚举 | + +## Message 定义 + +### 基本语法 + +```protobuf +message User { + string name = 1; + int32 age = 2; +} +``` + +### 显式类型 ID + +```protobuf +message User [id=100] { + string name = 1; + int32 age = 2; +} +``` + +### 无显式类型 ID + +未声明 `[id=...]` 时,编译器可按配置自动生成类型 ID,或使用 namespace/name 注册。 + +### 语言映射 + +| 语言 | 实现形式 | +| ---------- | --------------------------------- | +| Java | 带 getter/setter 的 POJO | +| Python | `@dataclass` 类 | +| Go | 导出字段的 struct | +| Rust | `#[derive(ForyObject)]` 的 struct | +| C++ | 带 `FORY_STRUCT` 宏的 struct | +| JavaScript | `export interface` 声明 | +| Dart | `@ForyStruct` `final class` | + +### 预留字段 + +```protobuf +message User { + string name = 1; + reserved 2, 3; + reserved 10 to 20; +} +``` + +### message 类型选项 + +常见选项:`id`、`alias`、`evolving`、`deprecated`、`namespace`、`use_record_for_java`。 + +## 嵌套类型 + +### 嵌套 message + +```protobuf +message Person { + message PhoneNumber { + string number = 1; + } + PhoneNumber phone = 1; +} +``` + +### 嵌套 enum + +```protobuf +message Person { + enum PhoneType { + MOBILE = 0; + HOME = 1; + } + PhoneType type = 1; +} +``` + +### 限定类型名 + +可使用完整限定名引用嵌套类型,例如 `Person.PhoneNumber`。 + +### 深层嵌套类型 + +支持多层嵌套,但建议控制层级,避免可读性下降。 + +### 各语言生成形态 + +| 语言 | 嵌套类型形态 | +| ---------- | ----------------------------------------------- | +| Java | `Outer.Inner` | +| Python | `Outer.Inner` | +| Rust | `outer::Inner` | +| C++ | `Outer::Inner` | +| Go | `Outer_Inner`(默认,可配置为 camelcase) | +| JavaScript | 扁平名称(如 `Result`) | +| Dart | 带下划线的扁平类名(如 `SearchResponse_Result`) | + +### 嵌套规则 + +- 嵌套类型名在其父作用域内必须唯一 +- 可被同文件后续类型引用 +- 可通过 import + 限定名跨文件引用 + +## Union 定义 + +### 基本语法 + +```protobuf +union Animal { + Dog dog = 1; + Cat cat = 2; +} +``` + +### 在 message 中使用 union + +```protobuf +message Person { + Animal pet = 1; +} +``` + +### 规则 + +- case 字段号必须唯一 +- case 类型通常为消息类型或可序列化复合类型 +- 各语言会生成带 case 判别和访问器的 union 表达 + +## 字段定义 + +### 基本语法 + +```protobuf +string name = 1; +``` + +### 带修饰符语法 + +```protobuf +optional string nickname = 2; +ref Node parent = 3; +list scores = 4; +``` + +### 字段修饰符 + +#### `optional` + +声明字段可为空(null/None): + +```protobuf +message User { + optional string email = 1; +} +``` + +建议在跨语言场景显式使用,以避免默认值差异。 + +**生成代码:** + +| 语言 | 非 optional | optional | +| ---------- | ------------------- | ----------------------------------------------- | +| Java | `String name` | `String email` 配合 `@ForyField(nullable=true)` | +| Python | `name: str` | `name: Optional[str]` | +| Go | `Name string` | `Name *string` | +| Rust | `name: String` | `name: Option` | +| C++ | `std::string name` | `std::optional name` | +| JavaScript | `name: string` | `name?: string \| null` | +| Dart | `String name` | `String? email` | + +#### `ref` + +开启引用跟踪,用于共享对象与循环结构: + +```protobuf +message Node { + string name = 1; + ref Node parent = 2; + list children = 3; +} +``` + +当运行时全局 ref tracking 开启时,字段级 `ref` 才会生效。 + +**生成代码:** + +| 语言 | 不使用 `ref` | 使用 `ref` | +| ---------- | ---------------- | ------------------------------------------ | +| Java | `Node parent` | `Node parent` 配合 `@ForyField(ref=true)` | +| Python | `parent: Node` | `parent: Node = pyfory.field(ref=True)` | +| Go | `Parent Node` | `Parent *Node` 配合 `fory:"ref"` | +| Rust | `parent: Node` | `parent: Rc` | +| C++ | `Node parent` | `std::shared_ptr parent` | +| JavaScript | `parent: Node` | `parent: Node`(无额外 `ref` 区分) | +| Dart | `Node parent` | `Node parent` 配合 `@ForyField(ref: true)` | + +Rust 对启用 ref 跟踪的字段默认使用 `Rc` 和 `RcWeak`。当生成的 Rust 类型需要使用 +`Arc` 或 `ArcWeak` 进行跨线程共享所有权时,可以使用 `ref(thread_safe=true)`。这个 +设置只是 Rust 代码生成中的承载类型选择;它不会改变编码格式,也不会让被引用值本身 +变成线程安全。protobuf 选项语法见 +[Protocol Buffers IDL Support](protobuf-idl.md#字段级选项)。 + +Rust 指针承载类型映射: + +| Fory IDL | Rust type | +| ---------------------------------------------- | --------------- | +| `ref Node parent` | `Rc` | +| `ref(thread_safe=true) Node parent` | `Arc` | +| `ref(weak=true) Node parent` | `RcWeak` | +| `ref(weak=true, thread_safe=true) Node parent` | `ArcWeak` | + +#### `list` + +列表字段(`repeated` 为等价别名): + +```protobuf +message Group { + list tags = 1; +} +``` + +**生成代码:** + +| 语言 | 类型 | +| ---------- | -------------------------- | +| Java | `List` | +| Python | `List[str]` | +| Go | `[]string` | +| Rust | `Vec` | +| C++ | `std::vector` | +| JavaScript | `string[]` | +| Dart | `List` | + +### 组合修饰符 + +可组合使用: + +```protobuf +message Graph { + optional ref Node root = 1; + list nodes = 2; +} +``` + +## 字段号 + +字段号规则: + +- 同一 message 内必须唯一 +- 必须为正整数 +- 不应复用已删除字段号(建议使用 `reserved`) +- 建议预留编号区间以便演进 + +## Type System {#type-system} + +Fory IDL 类型系统包括基础类型、命名类型和集合类型。 + +### Primitive Types + +| 类型族 | 示例 | +| -------- | --------------------------------- | +| 布尔 | `bool` | +| 整数 | `int8/int16/int32/int64`、`uint*` | +| 浮点 | `float32`、`float64` | +| 字符串 | `string` | +| 字节数组 | `bytes` | +| 时间 | `date`、`timestamp`、`duration` | +| 动态类型 | `any` | + +#### Boolean + +`bool` 表示布尔值。 + +| 语言 | 类型 | 说明 | +| ---------- | --------- | ---- | +| Java | `boolean` | | +| Python | `bool` | | +| Go | `bool` | | +| Rust | `bool` | | +| C++ | `bool` | | +| JavaScript | `boolean` | | +| Dart | `bool` | | + +#### Integer Types + +支持有符号/无符号与不同位宽。跨语言场景建议明确编码策略并保持字段语义稳定。 + +**有符号整数映射:** + +| Fory IDL | Java | Python | Go | Rust | C++ | JavaScript | Dart | +| -------- | ------- | -------------- | ------- | ----- | --------- | ------------------ | ------- | +| `int8` | `byte` | `pyfory.int8` | `int8` | `i8` | `int8_t` | `number` | `Int8` | +| `int16` | `short` | `pyfory.int16` | `int16` | `i16` | `int16_t` | `number` | `Int16` | +| `int32` | `int` | `pyfory.int32` | `int32` | `i32` | `int32_t` | `number` | `Int32` | +| `int64` | `long` | `pyfory.int64` | `int64` | `i64` | `int64_t` | `bigint \| number` | `int` | + +**无符号整数映射:** + +| Fory IDL | Java | Python | Go | Rust | C++ | JavaScript | Dart | +| -------- | ------- | --------------- | -------- | ----- | ---------- | ------------------ | -------- | +| `uint8` | `short` | `pyfory.uint8` | `uint8` | `u8` | `uint8_t` | `number` | `UInt8` | +| `uint16` | `int` | `pyfory.uint16` | `uint16` | `u16` | `uint16_t` | `number` | `UInt16` | +| `uint32` | `long` | `pyfory.uint32` | `uint32` | `u32` | `uint32_t` | `number` | `UInt32` | +| `uint64` | `long` | `pyfory.uint64` | `uint64` | `u64` | `uint64_t` | `bigint \| number` | `int` | + +#### Integer Encoding Variants + +常见编码: + +- `varint`:小值更省空间 +- `fixed`:固定长度,性能稳定 +- `tagged`:混合编码(特定类型可用) + +#### Floating-Point Types + +- `float32` +- `float64` + +| Fory IDL | Java | Python | Go | Rust | C++ | JavaScript | Dart | +| --------- | -------- | ---------------- | --------- | ----- | -------- | ---------- | --------- | +| `float32` | `float` | `pyfory.float32` | `float32` | `f32` | `float` | `number` | `Float32` | +| `float64` | `double` | `pyfory.float64` | `float64` | `f64` | `double` | `number` | `double` | + +#### String Type + +`string` 使用 UTF-8 文本语义。 + +| 语言 | 类型 | 说明 | +| ---------- | ------------- | -------------------- | +| Java | `String` | 不可变 | +| Python | `str` | | +| Go | `string` | 不可变 | +| Rust | `String` | 所有权字符串,堆分配 | +| C++ | `std::string` | | +| JavaScript | `string` | | +| Dart | `String` | | + +#### Bytes Type + +`bytes` 用于原始二进制载荷。 + +| 语言 | 类型 | 说明 | +| ---------- | ---------------------- | ------ | +| Java | `byte[]` | | +| Python | `bytes` | 不可变 | +| Go | `[]byte` | | +| Rust | `Vec` | | +| C++ | `std::vector` | | +| JavaScript | `Uint8Array` | | +| Dart | `Uint8List` | | + +#### Temporal Types + +##### Date + +`date` 表示日期(不含时区时间部分)。 + +| 语言 | 类型 | 说明 | +| ---------- | --------------------------- | ----------------------- | +| Java | `java.time.LocalDate` | | +| Python | `datetime.date` | | +| Go | `time.Time` | 会忽略时间部分 | +| Rust | `chrono::NaiveDate` | 需依赖 `chrono` crate | +| C++ | `fory::serialization::Date` | | +| JavaScript | `Date` | | +| Dart | `LocalDate` | Fory package 提供的类型 | + +##### Timestamp + +`timestamp` 表示时间点(跨语言应统一时间语义与精度预期)。 + +| 语言 | 类型 | 说明 | +| ---------- | -------------------------------- | ----------------------- | +| Java | `java.time.Instant` | 基于 UTC | +| Python | `datetime.datetime` | | +| Go | `time.Time` | | +| Rust | `chrono::NaiveDateTime` | 需依赖 `chrono` crate | +| C++ | `fory::serialization::Timestamp` | | +| JavaScript | `Date` | | +| Dart | `Timestamp` | Fory package 提供的类型 | + +#### Any + +`any` 允许存储动态类型值。使用 `any` 时建议配合清晰的业务约束,避免滥用导致模型不稳定。 + +| 语言 | 类型 | 说明 | +| ---------- | -------------- | -------------- | +| Java | `Object` | 写入运行时类型 | +| Python | `Any` | 写入运行时类型 | +| Go | `any` | 写入运行时类型 | +| Rust | `Box` | 写入运行时类型 | +| C++ | `std::any` | 写入运行时类型 | +| JavaScript | `any` | 写入运行时类型 | +| Dart | `Object?` | 写入运行时类型 | + +### Named Types + +命名类型包括: + +- `enum` +- `message` +- `union` + +支持跨文件 import 和限定名引用。 + +### Collection Types + +#### List (`list`) + +```protobuf +list +``` + +等价别名:`repeated T`。 + +#### Map + +```protobuf +map +``` + +约束: + +- `K` 一般应为可稳定比较的标量类型 +- `V` 可为任意支持类型 + +| Fory IDL | Java | Python | Go | Rust | C++ | JavaScript | Dart | +| -------------------- | ---------------------- | ----------------- | ------------------ | ----------------------- | -------------------------------- | --------------------- | -------------------- | +| `map` | `Map` | `Dict[str, int]` | `map[string]int32` | `HashMap` | `std::map` | `Map` | `Map` | +| `map` | `Map` | `Dict[str, User]` | `map[string]User` | `HashMap` | `std::map` | `Map` | `Map` | + +### Type Compatibility Matrix + +跨语言建议: + +- 使用各语言都稳定支持的公共子集 +- 尽量避免平台相关宽度/语义差异 +- 对整数编码与可空语义显式声明 + +### Best Practices + +1. 优先使用显式字段号与稳定命名 +2. 需要可空就显式 `optional` +3. 存在共享/循环关系时使用 `ref` +4. 降低 `any` 使用范围,优先强类型建模 +5. 预留字段号与枚举值区间 + +## Type IDs + +类型 ID 用于跨语言快速注册与解码匹配。 + +### 显式类型 ID + +```protobuf +message User [id=100] { + string name = 1; +} +``` + +### 无显式类型 ID + +未显式声明时可: + +- 自动生成数值 ID(默认配置) +- 禁用自动 ID,改用 namespace/type-name 注册 + +### 实践说明 + +- 类型 ID 在协议层面应视为稳定标识 +- 一经发布,不建议更改 +- 建议按域规划 ID 段(如 100-199 用户域) + +### ID 分配策略 + +- 核心高频模型优先分配固定 ID +- 团队统一管理 ID 区间与分配规范 +- 在 CI 中增加 ID 冲突检查 + +## 完整示例 + +```protobuf +package demo.order; + +option java_package = "com.example.demo.order"; +option go_package = "github.com/example/demo/gen/order;order"; + +import "demo/common.fdl"; + +enum Status [id=200] { + UNKNOWN = 0; + CREATED = 1; + PAID = 2; + SHIPPED = 3; +} + +message Item [id=201] { + string sku = 1; + int32 quantity = 2; +} + +message User [id=202] { + string id = 1; + string name = 2; + optional string email = 3; +} + +union Animal [id=203] { + Dog dog = 1; + Cat cat = 2; +} + +message Dog [id=204] { + string name = 1; +} + +message Cat [id=205] { + string name = 1; +} + +message Order [id=206] { + string id = 1; + ref User buyer = 2; + list items = 3; + Status status = 4; + map metadata = 5; + optional Animal pet = 6; +} +``` + +## Service 定义 + +Service 用于在 Fory IDL 中定义 RPC method 契约。它是可选的:包含 service 的 schema 仍会生成常规数据 model;只有在 compiler 使用 `--grpc` 且目标语言受支持时,才会生成 gRPC service 代码。支持的输出包括 Java、Python、Go、Rust、C#、Dart、Scala、Kotlin 和 JavaScript。JavaScript 浏览器 gRPC-Web client 通过 `--grpc-web` 生成。 + +```protobuf +service PetDirectory { + rpc Lookup (PetRequest) returns (PetResponse); + rpc Watch (PetRequest) returns (stream PetResponse); +} +``` + +- 生成的 gRPC companion 会对每个 RPC payload 使用 Fory 序列化。 +- 编译或运行这些 companion 的应用需要自行提供 gRPC 依赖,例如 grpc-java、grpc-kotlin、`grpcio`、grpc-go、Rust `tonic` 和 `bytes`、Scala grpc-java API、`@grpc/grpc-js`、`grpc-web`、C# `Grpc.Core.Api` 及 server/client package,或 Dart `package:grpc`。Python companion 默认使用 `grpc.aio`,也可以通过 `--grpc-python-mode=sync` 生成同步模式。 + +## 语法摘要 + +以下为简化文法(便于快速查阅,具体以编译器实现为准): + +```ebnf +file = [packageDecl] {optionDecl} {importDecl} {typeDecl} ; + +packageDecl = "package" qualifiedName ["alias" identifier] ";" ; +optionDecl = "option" identifier "=" optionValue ";" ; +importDecl = "import" stringLiteral ";" ; + +typeDecl = enumDecl | messageDecl | unionDecl ; + +enumDecl = "enum" identifier [typeOptions] "{" {enumField | reservedDecl} "}" ; +enumField = identifier "=" intLiteral ";" ; + +messageDecl = "message" identifier [typeOptions] "{" {fieldDecl | nestedTypeDecl | reservedDecl} "}" ; +unionDecl = "union" identifier [typeOptions] "{" {unionCaseDecl} "}" ; + +fieldDecl = [fieldModifier] typeRef identifier "=" intLiteral [fieldOptions] ";" ; +unionCaseDecl = typeRef identifier "=" intLiteral [fieldOptions] ";" ; + +fieldModifier = "optional" | "ref" | "list" ; + +typeRef = primitiveType | qualifiedName | listType | mapType ; +listType = "list" "<" typeRef ">" ; +mapType = "map" "<" typeRef "," typeRef ">" ; + +typeOptions = "[" optionPair {"," optionPair} "]" ; +fieldOptions = "[" optionPair {"," optionPair} "]" ; +optionPair = identifier "=" optionValue ; + +reservedDecl = "reserved" reservedItem {"," reservedItem} ";" ; +reservedItem = intLiteral | intLiteral "to" intLiteral | stringLiteral ; +``` + +实现建议:如需严谨验证,请以编译器语法解析器和测试用例为准。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/_category_.json b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/_category_.json new file mode 100644 index 00000000000..ab7f160ead2 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/_category_.json @@ -0,0 +1 @@ +{"position": 3, "label": "用户指南"} diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/cpp/_category_.json b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/cpp/_category_.json new file mode 100644 index 00000000000..9660bafa82e --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/cpp/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "C++", + "position": 4, + "collapsible": true, + "collapsed": true +} diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/cpp/basic-serialization.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/cpp/basic-serialization.md new file mode 100644 index 00000000000..d047b5b9913 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/cpp/basic-serialization.md @@ -0,0 +1,238 @@ +--- +title: 基础序列化 +sidebar_position: 2 +id: basic_serialization +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. +--- + +本页介绍基础对象图序列化和核心序列化 API。 + +## 对象图序列化 + +Apache Fory™ 提供复杂对象图的自动序列化,保留对象之间的结构和关系。`FORY_STRUCT` 宏在编译时生成高效的序列化代码,消除运行时开销。 + +**核心功能:** + +- 支持任意深度的嵌套结构体序列化 +- 集合类型(vector、set、map) +- 使用 `std::optional` 的可选字段 +- 智能指针(`std::shared_ptr`、`std::unique_ptr`) +- 自动处理基本类型和字符串 +- 使用变长整数的高效二进制编码 + +```cpp +#include "fory/serialization/fory.h" +#include +#include + +using namespace fory::serialization; + +// 定义结构体 +struct Address { + std::string street; + std::string city; + std::string country; + + bool operator==(const Address &other) const { + return street == other.street && city == other.city && + country == other.country; + } +}; +FORY_STRUCT(Address, street, city, country); + +struct Person { + std::string name; + int32_t age; + Address address; + std::vector hobbies; + std::map metadata; + + bool operator==(const Person &other) const { + return name == other.name && age == other.age && + address == other.address && hobbies == other.hobbies && + metadata == other.metadata; + } +}; +FORY_STRUCT(Person, name, age, address, hobbies, metadata); + +int main() { + auto fory = Fory::builder().xlang(true).build(); + fory.register_struct
(100); + fory.register_struct(200); + + Person person{ + "John Doe", + 30, + {"123 Main St", "New York", "USA"}, + {"reading", "coding"}, + {{"role", "developer"}} + }; + + auto result = fory.serialize(person); + auto decoded = fory.deserialize(result.value()); + assert(person == decoded.value()); +} +``` + +## 序列化 API + +### 序列化到新 Vector + +```cpp +auto fory = Fory::builder().xlang(true).build(); +fory.register_struct(1); + +MyStruct obj{/* ... */}; + +// 序列化 - 返回 Result, Error> +auto result = fory.serialize(obj); +if (result.ok()) { + std::vector bytes = std::move(result).value(); + // 使用 bytes... +} else { + // 处理错误 + std::cerr << result.error().to_string() << std::endl; +} +``` + +### 序列化到现有缓冲区 + +```cpp +// 序列化到现有 Buffer(最快路径) +Buffer buffer; +auto result = fory.serialize_to(buffer, obj); +if (result.ok()) { + size_t bytes_written = result.value(); + // buffer 现在包含序列化数据 +} + +// 序列化到现有 vector(零拷贝) +std::vector output; +auto result = fory.serialize_to(output, obj); +if (result.ok()) { + size_t bytes_written = result.value(); + // output 现在包含序列化数据 +} +``` + +### 从字节数组反序列化 + +```cpp +// 从原始指针反序列化 +auto result = fory.deserialize(data_ptr, data_size); +if (result.ok()) { + MyStruct obj = std::move(result).value(); +} + +// 从 vector 反序列化 +std::vector data = /* ... */; +auto result = fory.deserialize(data); + +// 从 Buffer 反序列化(更新 reader_index) +Buffer buffer(data); +auto result = fory.deserialize(buffer); +``` + +## 错误处理 + +Fory 使用 `Result` 类型进行错误处理: + +```cpp +auto result = fory.serialize(obj); + +// 检查操作是否成功 +if (result.ok()) { + auto value = std::move(result).value(); + // 使用 value... +} else { + Error error = result.error(); + std::cerr << "Error: " << error.to_string() << std::endl; +} + +// 或使用 FORY_TRY 宏进行提前返回 +FORY_TRY(bytes, fory.serialize(obj)); +// 直接使用 bytes... +``` + +常见错误类型: + +- `Error::type_mismatch` - 反序列化时类型 ID 不匹配 +- `Error::invalid_data` - 无效或损坏的数据 +- `Error::buffer_out_of_bound` - 缓冲区溢出/下溢 +- `Error::type_error` - 类型注册错误 + +## FORY_STRUCT 宏 + +`FORY_STRUCT` 宏用于注册结构体以进行序列化: + +```cpp +struct MyStruct { + int32_t x; + std::string y; + std::vector z; +}; + +// 必须与结构体在同一命名空间中 +FORY_STRUCT(MyStruct, x, y, z); +``` + +该宏: + +1. 生成编译时字段元数据 +2. 启用 ADL(参数依赖查找)进行序列化 +3. 通过模板特化创建高效的序列化代码 + +**要求:** + +- 必须放在与结构体相同的命名空间中(用于 ADL) +- 所有列出的字段必须是可序列化类型 +- 宏中的字段顺序决定序列化顺序 + +## 嵌套结构体 + +完全支持嵌套结构体: + +```cpp +struct Inner { + int32_t value; +}; +FORY_STRUCT(Inner, value); + +struct Outer { + Inner inner; + std::string label; +}; +FORY_STRUCT(Outer, inner, label); + +// 两者都必须注册 +fory.register_struct(1); +fory.register_struct(2); +``` + +## 性能提示 + +- **缓冲区复用**:使用 `serialize_to(buffer, obj)` 配合预分配的缓冲区 +- **预注册**:在序列化开始前注册所有类型 +- **单线程**:尽可能使用 `build()` 而不是 `build_thread_safe()` +- **禁用跟踪**:当不需要引用跟踪时使用 `track_ref(false)` +- **紧凑编码**:使用变长编码提高空间效率 + +## 相关主题 + +- [配置](configuration.md) - 构建器选项 +- [类型注册](type-registration.md) - 注册类型 +- [支持的类型](supported-types.md) - 所有支持的类型 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/cpp/configuration.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/cpp/configuration.md new file mode 100644 index 00000000000..f6518d611bb --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/cpp/configuration.md @@ -0,0 +1,255 @@ +--- +title: 配置 +sidebar_position: 1 +id: configuration +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. +--- + +本页介绍 Fory 配置选项和序列化模式。 + +## 序列化模式 + +Apache Fory™ 支持两种序列化模式: + +### SchemaConsistent 模式(默认) + +类型声明必须在通信双方完全匹配: + +```cpp +auto fory = Fory::builder().build(); // 默认为 SchemaConsistent +``` + +### Compatible 模式 + +允许独立的 schema 演化: + +```cpp +auto fory = Fory::builder().compatible(true).build(); +``` + +## 构建器模式 + +使用 `ForyBuilder` 构造具有自定义配置的 Fory 实例: + +```cpp +#include "fory/serialization/fory.h" + +using namespace fory::serialization; + +// 默认配置 +auto fory = Fory::builder().build(); + +// 用于 schema 演化的兼容模式 +auto fory = Fory::builder() + .compatible(true) + .build(); + +// 跨语言模式 +auto fory = Fory::builder() + .xlang(true) + .build(); + +// 完整配置 +auto fory = Fory::builder() + .compatible(true) + .xlang(true) + .track_ref(true) + .max_dyn_depth(10) + .max_type_fields(512) + .max_type_meta_bytes(4096) + .max_schema_versions_per_type(10) + .max_average_schema_versions_per_type(3) + .check_struct_version(true) + .build(); +``` + +## 配置选项 + +### xlang(bool) + +启用/禁用跨语言(xlang)序列化模式。 + +```cpp +auto fory = Fory::builder() + .xlang(true) // 启用跨语言兼容性 + .build(); +``` + +启用后,包含用于与 Java、Python、Go、Rust 和 JavaScript 跨语言兼容的元数据。 + +**默认值:** `true` + +### compatible(bool) + +启用/禁用用于 schema 演化的兼容模式。 + +```cpp +auto fory = Fory::builder() + .compatible(true) // 启用 schema 演化 + .build(); +``` + +启用后,支持读取使用不同 schema 版本序列化的数据。 + +**默认值:** `false` + +### track_ref(bool) + +启用/禁用共享引用和循环引用的引用跟踪。 + +```cpp +auto fory = Fory::builder() + .track_ref(true) // 启用引用跟踪 + .build(); +``` + +启用后,避免重复序列化共享对象并处理循环引用。 + +**默认值:** `true` + +### max_dyn_depth(uint32_t) + +设置动态类型对象的最大允许嵌套深度。 + +```cpp +auto fory = Fory::builder() + .max_dyn_depth(10) // 允许最多 10 层 + .build(); +``` + +这限制了嵌套多态对象序列化的最大深度(例如 `shared_ptr`、`unique_ptr`)。防止深度嵌套结构在动态序列化场景中导致栈溢出。 + +**默认值:** `5` + +**何时调整:** + +- **增加**:对于合法的深度嵌套数据结构 +- **减少**:对于更严格的安全要求或浅层数据结构 + +### max_schema_versions_per_type(uint32_t) + +设置一个逻辑类型可接受的最大远端 metadata 版本数。 + +```cpp +auto fory = Fory::builder() + .max_schema_versions_per_type(10) + .build(); +``` + +**默认值:** `10` + +### max_type_fields(uint32_t) + +设置一个收到的远端 struct metadata body 中可接受的最大字段数。 + +```cpp +auto fory = Fory::builder() + .max_type_fields(512) + .build(); +``` + +**默认值:** `512` + +### max_type_meta_bytes(uint32_t) + +设置一个收到的 TypeDef body 可接受的最大编码 body 字节数,不包含 8 字节 header 和扩展 size varint。 + +```cpp +auto fory = Fory::builder() + .max_type_meta_bytes(4096) + .build(); +``` + +**默认值:** `4096` + +### max_average_schema_versions_per_type(uint32_t) + +设置所有已接受远端类型的平均 metadata 版本数限制。有效全局下限为 `8192` 个 schema。 + +```cpp +auto fory = Fory::builder() + .max_average_schema_versions_per_type(3) + .build(); +``` + +**默认值:** `3` + +### check_struct_version(bool) + +启用/禁用结构体版本检查。 + +```cpp +auto fory = Fory::builder() + .check_struct_version(true) // 启用版本检查 + .build(); +``` + +启用后,验证类型哈希以检测 schema 不匹配。 + +**默认值:** `false` + +## 线程安全 vs 单线程 + +### 单线程(最快) + +```cpp +auto fory = Fory::builder() + .xlang(true) + .build(); // 返回 Fory +``` + +单线程 `Fory` 是最快的选项,但非线程安全。每个线程使用一个实例。 + +### 线程安全 + +```cpp +auto fory = Fory::builder() + .xlang(true) + .build_thread_safe(); // 返回 ThreadSafeFory +``` + +`ThreadSafeFory` 使用 Fory 实例池提供线程安全的序列化。由于池开销略慢,但可以从多个线程并发安全使用。 + +## 配置摘要 + +| 选项 | 说明 | 默认值 | +| ------------------------------------------------ | --------------------------------- | ------- | +| `xlang(bool)` | 启用跨语言模式 | `true` | +| `compatible(bool)` | 启用 schema 演化 | `false` | +| `track_ref(bool)` | 启用引用跟踪 | `true` | +| `max_dyn_depth(uint32_t)` | 动态类型的最大嵌套深度 | `5` | +| `max_type_fields(uint32_t)` | 一个收到的 struct metadata body 最大字段数 | `512` | +| `max_type_meta_bytes(uint32_t)` | 一个收到的 metadata body 最大编码字节数 | `4096` | +| `max_schema_versions_per_type(uint32_t)` | 一个逻辑类型最大远端 metadata 版本数 | `10` | +| `max_average_schema_versions_per_type(uint32_t)` | 所有远端类型的平均 metadata 版本数 | `3` | +| `check_struct_version(bool)` | 启用结构体版本检查 | `false` | + +## 安全 + +安全相关配置: + +- 在反序列化不可信 payload 前,只注册预期的类型。 +- 对 intentional same-schema payload,将 `check_struct_version(true)` 与 `compatible(false)` 配合使用。 +- 尽可能降低 `max_dyn_depth(...)`,以拒绝异常深的多态对象图。 +- 除非数据不是恶意输入,且可信 peer 会发送更大的 metadata 或大量 schema 版本,否则保持远端 schema metadata 限制的默认值。 +- 对不可信输入,优先使用具体字段,避免宽泛的多态字段。 + +## 相关主题 + +- [基础序列化](basic-serialization.md) - 使用配置的 Fory +- [跨语言](xlang-serialization.md) - XLANG 模式详情 +- [类型注册](type-registration.md) - 注册类型 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/cpp/custom-serializers.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/cpp/custom-serializers.md new file mode 100644 index 00000000000..1043febb220 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/cpp/custom-serializers.md @@ -0,0 +1,371 @@ +--- +title: 自定义序列化器 +sidebar_position: 6 +id: custom_serializers +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. +--- + +对于不支持 `FORY_STRUCT` 的类型,需要手动实现 `Serializer` 模板特化。 + +## 何时使用自定义序列化器 + +- 来自第三方库的外部类型 +- 有特殊序列化要求的类型 +- 兼容遗留数据格式 +- 性能关键的自定义编码 +- 使用自定义协议实现跨语言互操作 + +## 实现 Serializer 模板 + +要创建自定义序列化器,请在 `fory::serialization` 命名空间内为你的类型特化 `Serializer` 模板: + +```cpp +#include "fory/serialization/fory.h" + +using namespace fory::serialization; + +// Define your custom type +struct MyExt { + int32_t id; + bool operator==(const MyExt &other) const { return id == other.id; } +}; + +namespace fory { +namespace serialization { + +template <> +struct Serializer { + // Declare as extension type for custom serialization + static constexpr TypeId type_id = TypeId::EXT; + + // Main write method - handles null checking and type info + static void write(const MyExt &value, WriteContext &ctx, RefMode ref_mode, + bool write_type, bool has_generics = false) { + (void)has_generics; + write_not_null_ref_flag(ctx, ref_mode); + if (write_type) { + auto result = ctx.write_any_type_info( + static_cast(TypeId::UNKNOWN), + std::type_index(typeid(MyExt))); + if (!result.ok()) { + ctx.set_error(std::move(result).error()); + return; + } + } + write_data(value, ctx); + } + + // write only the data (no type info) + static void write_data(const MyExt &value, WriteContext &ctx) { + Serializer::write_data(value.id, ctx); + } + + // write data with generics support + static void write_data_generic(const MyExt &value, WriteContext &ctx, + bool has_generics) { + (void)has_generics; + write_data(value, ctx); + } + + // Main read method - handles null checking and type info + static MyExt read(ReadContext &ctx, RefMode ref_mode, bool read_type) { + bool has_value = read_null_only_flag(ctx, ref_mode); + if (ctx.has_error() || !has_value) { + return MyExt{}; + } + if (read_type) { + const TypeInfo *type_info = ctx.read_any_type_info(ctx.error()); + if (ctx.has_error()) { + return MyExt{}; + } + if (!type_info) { + ctx.set_error(Error::type_error("TypeInfo for MyExt not found")); + return MyExt{}; + } + } + return read_data(ctx); + } + + // Read only the data (no type info) + static MyExt read_data(ReadContext &ctx) { + MyExt value; + value.id = Serializer::read_data(ctx); + return value; + } + + // Read data with generics support + static MyExt read_data_generic(ReadContext &ctx, bool has_generics) { + (void)has_generics; + return read_data(ctx); + } + + // Read with pre-resolved type info + static MyExt read_with_type_info(ReadContext &ctx, RefMode ref_mode, + const TypeInfo &type_info) { + (void)type_info; + return read(ctx, ref_mode, false); + } +}; + +} // namespace serialization +} // namespace fory +``` + +## 必需方法 + +自定义序列化器必须实现以下静态方法: + +| 方法 | 用途 | +| --------------------- | ----------------------------------------------- | +| `write` | 带类型信息的主序列化入口 | +| `write_data` | 只序列化数据(不含类型信息) | +| `write_data_generic` | 序列化数据并支持泛型 | +| `read` | 带类型信息的主反序列化入口 | +| `read_data` | 只反序列化数据(不含类型信息) | +| `read_data_generic` | 反序列化数据并支持泛型 | +| `read_with_type_info` | 使用预先解析的 TypeInfo 反序列化 | + +对于自定义扩展类型,`type_id` 常量应设置为 `TypeId::EXT`。 + +## 注册自定义序列化器 + +使用前需要先向 Fory 注册自定义序列化器: + +```cpp +auto fory = Fory::builder().xlang(true).build(); + +// Register with numeric type ID (must match across languages) +auto result = fory.register_extension_type(103); +if (!result.ok()) { + std::cerr << "Failed to register: " << result.error().to_string() << std::endl; +} + +// Or register with type name for named type systems +fory.register_extension_type("my_ext"); + +// Or with namespace and type name +fory.register_extension_type("com.example", "MyExt"); +``` + +## 完整示例 + +```cpp +#include "fory/serialization/fory.h" +#include + +using namespace fory::serialization; + +struct CustomType { + int32_t value; + std::string name; + + bool operator==(const CustomType &other) const { + return value == other.value && name == other.name; + } +}; + +namespace fory { +namespace serialization { + +template <> +struct Serializer { + static constexpr TypeId type_id = TypeId::EXT; + + static void write(const CustomType &value, WriteContext &ctx, + RefMode ref_mode, bool write_type, bool has_generics = false) { + (void)has_generics; + write_not_null_ref_flag(ctx, ref_mode); + if (write_type) { + auto result = ctx.write_any_type_info( + static_cast(TypeId::UNKNOWN), + std::type_index(typeid(CustomType))); + if (!result.ok()) { + ctx.set_error(std::move(result).error()); + return; + } + } + write_data(value, ctx); + } + + static void write_data(const CustomType &value, WriteContext &ctx) { + // write value as varint for compact encoding + Serializer::write_data(value.value, ctx); + // Delegate string serialization to built-in serializer + Serializer::write_data(value.name, ctx); + } + + static void write_data_generic(const CustomType &value, WriteContext &ctx, + bool has_generics) { + (void)has_generics; + write_data(value, ctx); + } + + static CustomType read(ReadContext &ctx, RefMode ref_mode, bool read_type) { + bool has_value = read_null_only_flag(ctx, ref_mode); + if (ctx.has_error() || !has_value) { + return CustomType{}; + } + if (read_type) { + const TypeInfo *type_info = ctx.read_any_type_info(ctx.error()); + if (ctx.has_error()) { + return CustomType{}; + } + if (!type_info) { + ctx.set_error(Error::type_error("TypeInfo for CustomType not found")); + return CustomType{}; + } + } + return read_data(ctx); + } + + static CustomType read_data(ReadContext &ctx) { + CustomType value; + value.value = Serializer::read_data(ctx); + value.name = Serializer::read_data(ctx); + return value; + } + + static CustomType read_data_generic(ReadContext &ctx, bool has_generics) { + (void)has_generics; + return read_data(ctx); + } + + static CustomType read_with_type_info(ReadContext &ctx, RefMode ref_mode, + const TypeInfo &type_info) { + (void)type_info; + return read(ctx, ref_mode, false); + } +}; + +} // namespace serialization +} // namespace fory + +int main() { + auto fory = Fory::builder().xlang(true).build(); + fory.register_extension_type(100); + + CustomType original{42, "test"}; + + auto serialized = fory.serialize(original); + if (!serialized.ok()) { + std::cerr << "Serialization failed" << std::endl; + return 1; + } + + auto deserialized = fory.deserialize(serialized.value()); + if (!deserialized.ok()) { + std::cerr << "Deserialization failed" << std::endl; + return 1; + } + + assert(original == deserialized.value()); + std::cout << "Custom serializer works!" << std::endl; + return 0; +} +``` + +## WriteContext Methods + +The `WriteContext` provides methods for writing data: + +```cpp +// Primitive types +ctx.write_uint8(value); +ctx.write_int8(value); +ctx.write_uint16(value); + +// Variable-length integers (compact encoding) +ctx.write_var_uint32(value); // Unsigned varint +ctx.write_varint32(value); // Signed zigzag varint +ctx.write_var_uint64(value); // Unsigned varint +ctx.write_varint64(value); // Signed zigzag varint + +// Tagged integers (for mixed-size encoding) +ctx.write_tagged_uint64(value); +ctx.write_tagged_int64(value); + +// Raw bytes +ctx.write_bytes(data_ptr, length); + +// Access underlying buffer for advanced operations +ctx.buffer().write_int32(value); +ctx.buffer().write_float(value); +ctx.buffer().write_double(value); +``` + +## ReadContext Methods + +The `ReadContext` provides methods for reading data: + +```cpp +// Primitive types (use error reference pattern) +uint8_t u8 = ctx.read_uint8(ctx.error()); +int8_t i8 = ctx.read_int8(ctx.error()); + +// Variable-length integers +uint32_t u32 = ctx.read_var_uint32(ctx.error()); +int32_t i32 = ctx.read_varint32(ctx.error()); +uint64_t u64 = ctx.read_var_uint64(ctx.error()); +int64_t i64 = ctx.read_varint64(ctx.error()); + +// Check for errors after read operations +if (ctx.has_error()) { + return MyType{}; // Return default on error +} + +// Access underlying buffer for advanced operations +int32_t value = ctx.buffer().read_int32(ctx.error()); +float f = ctx.buffer().read_float(ctx.error()); +double d = ctx.buffer().read_double(ctx.error()); +``` + +## Delegating to Built-in Serializers + +Reuse existing serializers for nested types: + +```cpp +static void write_data(const MyType &value, WriteContext &ctx) { + // Delegate to built-in serializers + Serializer::write_data(value.int_field, ctx); + Serializer::write_data(value.string_field, ctx); + Serializer>::write_data(value.vec_field, ctx); +} + +static MyType read_data(ReadContext &ctx) { + MyType value; + value.int_field = Serializer::read_data(ctx); + value.string_field = Serializer::read_data(ctx); + value.vec_field = Serializer>::read_data(ctx); + return value; +} +``` + +## Best Practices + +1. **Use variable-length encoding** for integers that may be small +2. **Check errors after read operations** using `ctx.has_error()` +3. **Return default values on error** to maintain consistent behavior +4. **Delegate to built-in serializers** for standard types +5. **Match type IDs across languages** for cross-language compatibility +6. **Use `(void)param`** to suppress unused parameter warnings + +## Related Topics + +- [Type Registration](type_registration) - Registering serializers +- [Basic Serialization](basic_serialization) - Using FORY_STRUCT macro +- [Schema Evolution](schema_evolution) - Compatible mode +- [Cross-Language](xlang_serialization) - Cross-language serialization diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/cpp/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/cpp/index.md new file mode 100644 index 00000000000..2b0805b1596 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/cpp/index.md @@ -0,0 +1,273 @@ +--- +title: C++ 序列化指南 +sidebar_position: 0 +id: serialization_index +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. +--- + +**Apache Fory™** 是一个高性能的多语言序列化框架,基于 **JIT 编译**与**零拷贝**技术,在保持易用性和安全性的同时提供出色性能。 + +C++ 实现基于现代 C++17 特性和模板元编程,提供具备编译时类型安全的高性能序列化能力。 + +## 为什么选择 Apache Fory™ C++? + +- **高性能**:快速序列化与优化的二进制协议 +- **跨语言**:可在 Java、Python、C++、Go、JavaScript 和 Rust 之间无缝序列化与反序列化数据 +- **类型安全**:通过基于宏的结构体注册实现编译时类型检查 +- **引用跟踪**:自动跟踪共享引用和循环引用 +- **Schema 演进**:兼容模式支持独立的 Schema 变更 +- **双格式支持**:对象图序列化与零拷贝行格式 +- **线程安全**:同时提供单线程(最快)和线程安全两种变体 + +## 安装 + +C++ 实现同时支持 CMake 和 Bazel 构建系统。 + +### 前置条件 + +- CMake 3.16+(用于 CMake 构建)或 Bazel 8+(用于 Bazel 构建) +- 支持 C++17 的编译器(GCC 7+、Clang 5+、MSVC 2017+) + +使用 MSVC 构建时,需要配置构建系统传入 `/Zc:preprocessor`。 + +### 使用 CMake(推荐) + +最简单的集成方式是使用 CMake 的 `FetchContent` 模块: + +```cmake +cmake_minimum_required(VERSION 3.16) +project(my_project LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +if(MSVC) + add_compile_options(/Zc:preprocessor) +endif() +include(FetchContent) +FetchContent_Declare( + fory + GIT_REPOSITORY https://github.com/apache/fory.git + GIT_TAG v1.3.0 + SOURCE_SUBDIR cpp +) +FetchContent_MakeAvailable(fory) + +add_executable(my_app main.cc) +target_link_libraries(my_app PRIVATE fory::serialization) +``` + +然后构建并运行: + +```bash +mkdir build && cd build +cmake .. -DCMAKE_BUILD_TYPE=Release +cmake --build . --parallel +./my_app +``` + +### 使用 Bazel + +在项目根目录创建 `MODULE.bazel` 文件: + +```bazel +module( + name = "my_project", + version = "1.0.0", +) + +bazel_dep(name = "rules_cc", version = "0.1.1") + +bazel_dep(name = "fory", version = "1.3.0") +git_override( + module_name = "fory", + remote = "https://github.com/apache/fory.git", + commit = "v1.3.0", # 或使用特定 commit hash 以确保可复现性 +) +``` + +为应用程序创建 `BUILD` 文件: + +```bazel +cc_binary( + name = "my_app", + srcs = ["main.cc"], + deps = ["@fory//cpp/fory/serialization:fory_serialization"], +) +``` + +使用 MSVC 构建时,在 Bazel 配置中加入符合标准的预处理器选项: + +```bazel +# .bazelrc +build --cxxopt=/Zc:preprocessor +``` + +然后构建并运行: + +```bash +bazel build //:my_app +bazel run //:my_app +``` + +对于本地开发,也可以改用 `local_path_override`: + +```bazel +bazel_dep(name = "fory", version = "1.3.0") +local_path_override( + module_name = "fory", + path = "/path/to/fory", +) +``` + +### 示例 + +完整可运行示例请参见 [examples/cpp](https://github.com/apache/fory/tree/main/examples/cpp) 目录: + +- [hello_world](https://github.com/apache/fory/tree/main/examples/cpp/hello_world) - 对象图序列化 +- [hello_row](https://github.com/apache/fory/tree/main/examples/cpp/hello_row) - 行格式编码 + +## 快速开始 + +### 基础示例 + +```cpp +#include "fory/serialization/fory.h" +#include +#include + +using namespace fory::serialization; + +// 定义结构体 +struct Person { + std::string name; + int32_t age; + std::vector hobbies; + + bool operator==(const Person &other) const { + return name == other.name && age == other.age && hobbies == other.hobbies; + } +}; +FORY_STRUCT(Person, name, age, hobbies); + +int main() { + // 创建 Fory 实例 + auto fory = Fory::builder() + .xlang(true) // 启用跨语言模式 + .track_ref(false) // 对简单类型禁用引用跟踪 + .build(); + + // 为类型注册唯一 ID + fory.register_struct(1); + + // 创建对象 + Person person{"Alice", 30, {"reading", "coding"}}; + + // 序列化 + auto result = fory.serialize(person); + if (!result.ok()) { + // 处理错误 + return 1; + } + std::vector bytes = std::move(result).value(); + + // 反序列化 + auto deser_result = fory.deserialize(bytes); + if (!deser_result.ok()) { + // 处理错误 + return 1; + } + Person decoded = std::move(deser_result).value(); + + assert(person == decoded); + return 0; +} +``` + +### 继承字段 + +如果希望派生类型包含基类字段,请在 `FORY_STRUCT` 中写入 `FORY_BASE(Base)`。基类本身也必须定义自己的 `FORY_STRUCT`,这样其字段才能被引用。 + +```cpp +struct Base { + int32_t id; + FORY_STRUCT(Base, id); +}; + +struct Derived : Base { + std::string name; + FORY_STRUCT(Derived, FORY_BASE(Base), name); +}; +``` + +## 线程安全 + +Apache Fory™ C++ 面向不同线程需求提供两种变体: + +### 单线程(最快) + +```cpp +// 单线程 Fory:速度最快,但不是线程安全的 +auto fory = Fory::builder() + .xlang(true) + .build(); +``` + +### 线程安全 + +```cpp +// 线程安全 Fory:使用上下文池 +auto fory = Fory::builder() + .xlang(true) + .build_thread_safe(); + +// 可安全用于多线程 +std::thread t1([&]() { + auto result = fory.serialize(obj1); +}); +std::thread t2([&]() { + auto result = fory.serialize(obj2); +}); +``` + +**提示:** 请在启动线程之前完成类型注册,以确保每个工作线程都能看到一致的元数据。 + +## 使用场景 + +### 对象序列化 + +- 含嵌套对象和引用的复杂数据结构 +- 微服务中的跨语言通信 +- 具备完整类型安全的通用序列化 +- 使用兼容模式进行 Schema 演进 + +### 行格式序列化 + +- 高吞吐数据处理 +- 需要快速字段访问的分析型负载 +- 内存受限环境 +- 零拷贝场景 + +## 后续步骤 + +- [配置](configuration.md) - 构建器选项与模式 +- [基础序列化](basic-serialization.md) - 对象图序列化 +- [Schema 演进](schema-evolution.md) - 兼容模式与 Schema 变更 +- [类型注册](type-registration.md) - 注册类型 +- [字段配置](schema-metadata.md) - 字段级元数据(可空、引用跟踪) +- [支持的类型](supported-types.md) - 全部支持的类型 +- [跨语言](xlang-serialization.md) - XLANG 模式 +- [行格式](row-format.md) - 零拷贝行格式 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/cpp/native-serialization.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/cpp/native-serialization.md new file mode 100644 index 00000000000..922d163c3d7 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/cpp/native-serialization.md @@ -0,0 +1,194 @@ +--- +title: 原生序列化 +sidebar_position: 3 +id: native_serialization +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. +--- + +C++ 原生序列化是通过 `.xlang(false)` 选择的 C++ 专用编码模式。当所有写入端和读取端都是 C++,并且载荷应遵循 C++ 类型行为而不是可移植 xlang 类型系统时,可以使用它。 + +当字节需要由 Java、Python、Go、Rust、JavaScript 或其他非 C++ Fory 运行时读取时,请使用默认的 C++ 模式 [Xlang Serialization](xlang-serialization.md)。 + +## 何时使用原生序列化 + +在以下场景使用原生序列化: + +- 载荷只由 C++ 应用生成和消费。 +- 数据模型使用 C++ 特有类型,例如字符类型、原生无符号类型 ID、`std::tuple`、智能指针或 C++ 多态模型。 +- 需要面向同步发布服务的 Schema 一致 C++ 载荷。 +- 需要为仅 C++ 的滚动部署提供兼容的 Schema 演进。 +- 希望在 C++ 边界避开可移植 xlang 类型映射约束。 + +## 创建原生运行时 + +```cpp +#include "fory/serialization/fory.h" +#include +#include +#include + +using namespace fory::serialization; + +struct Order { + int64_t id; + double amount; + + bool operator==(const Order &other) const { + return id == other.id && amount == other.amount; + } +}; +FORY_STRUCT(Order, id, amount); + +int main() { + auto fory = Fory::builder() + .xlang(false) + .build(); + fory.register_struct(100); + + Order order{1, 42.5}; + auto bytes = fory.serialize(order).value(); + auto decoded = fory.deserialize(bytes).value(); + assert(order == decoded); +} +``` + +每个线程使用一个配置好的 `Fory` 实例;如果同一个运行时会被多个线程共享,则构建线程安全运行时: + +```cpp +auto fory = Fory::builder() + .xlang(false) + .track_ref(true) + .build_thread_safe(); +``` + +在并发序列化开始前注册类型。 + +## Schema 演进 + +原生序列化默认使用 Schema 一致模式。当仅 C++ 的写入端和读取端 Schema 可能不同时,启用兼容模式: + +```cpp +auto fory = Fory::builder() + .xlang(false) + .compatible(true) + .build(); +``` + +兼容模式会写入 Schema 元数据;只要字段身份保持兼容,读取端就能容忍字段新增、删除或重排。参见 [Schema Evolution](schema-evolution.md)。 + +## 注册 + +序列化前使用稳定 ID 或名称注册结构体: + +```cpp +fory.register_struct(100); +fory.register_struct("example", "Order"); +``` + +使用数字 ID 可以获得更紧凑的载荷;当独立团队通过名称协调类型身份时,使用命名空间/类型名注册。 + +## C++ 对象范围 + +原生序列化覆盖 C++ 特有的对象范围: + +- 由 `FORY_STRUCT` 描述的结构体和类。 +- 标准容器,例如 `std::vector`、`std::map`、`std::unordered_map`、`std::set` 和 `std::unordered_set`。 +- `std::optional`、`std::variant` 以及类似 tuple 的值。 +- `std::shared_ptr` 和 `std::unique_ptr`。 +- 字符类型,例如 `char`、`char16_t` 和 `char32_t`。 +- 带原生模式类型 ID 的无符号整数类型。 +- 通过 C++ 运行时注册的多态序列化。 + +完整类型范围和 xlang 映射说明请参见 [Supported Types](supported-types.md)。 + +## 引用和智能指针 + +原生序列化支持智能指针和引用跟踪: + +```cpp +auto fory = Fory::builder() + .xlang(false) + .track_ref(true) + .build(); +``` + +启用引用跟踪时,可以保留共享指针身份,并通过受支持的指针模式表示循环对象图。对于不以身份作为模型一部分的值型数据,可以禁用引用跟踪。 + +## 仅原生模式支持的标量形态 + +部分 C++ 标量形态不是可移植的 xlang 载荷。当这些形态必须作为 C++ 值往返时,请使用原生序列化: + +```cpp +auto fory = Fory::builder().xlang(false).build(); + +auto char_bytes = fory.serialize(char32_t{U'A'}).value(); +auto value = fory.deserialize(char_bytes).value(); + +auto unsigned_bytes = fory.serialize(uint64_t{42}).value(); +auto unsigned_value = fory.deserialize(unsigned_bytes).value(); +``` + +对于 xlang 载荷,请使用 Schema 元数据和共享的 xlang 类型映射,而不是依赖仅 C++ 原生模式支持的类型 ID。 + +## 性能建议 + +- 复用配置好的 `Fory` 实例。 +- 为了获得最快路径,每个线程使用单线程 `Fory`;共享并发使用时使用 `build_thread_safe()`。 +- 对同步发布的 C++ 服务保持原生 Schema 一致模式。 +- 仅在需要 C++ 专用 Schema 演进时启用 `.compatible(true)`。 +- 使用显式数字 ID 注册结构体,以获得紧凑载荷。 +- 对值型图禁用引用跟踪。 +- 在热点路径上优先使用具体类型,而不是多态/动态字段。 + +## 原生模式与 Xlang 对比 + +| 需求 | 使用原生序列化 | 使用 xlang 序列化 | +| ---------------------------------------- | ------------------------ | ----------------------- | +| 仅 C++ 载荷 | 是 | 可选 | +| 非 C++ 读取端或写入端 | 否 | 是 | +| C++ 原生字符和无符号形态 | 是 | 有限 | +| 智能指针和 C++ 对象图 | 是 | 有限 | +| Schema 一致的同语言载荷 | 是 | 否 | +| 默认兼容 Schema 演进 | 否 | 是 | +| 跨运行时可移植类型映射 | 否 | 是 | + +## 故障排查 + +### 非 C++ 运行时无法读取载荷 + +写入端正在使用原生序列化。请用 `.xlang(true)` 重新构建,并与每个对等运行时对齐类型注册。 + +### 字段变更后滚动部署失败 + +原生序列化默认使用 Schema 一致模式。当 Schema 可能不同时,请在写入端和读取端都使用 `.compatible(true)`。 + +### 仅原生模式支持的标量无法映射到其他语言 + +对于可移植载荷,请使用带显式 Schema 元数据的 xlang 序列化。C++ 原生类型 ID 仅供 C++ 读取端使用。 + +### 共享指针图丢失身份 + +启用 `.track_ref(true)`,并确认图使用受支持的指针模式。 + +## 相关主题 + +- [Xlang Serialization](xlang-serialization.md) - 跨运行时 C++ 载荷 +- [Configuration](configuration.md) - 构建器选项 +- [Basic Serialization](basic-serialization.md) - 对象图序列化 +- [Supported Types](supported-types.md) - C++ 类型支持 +- [Polymorphic Serialization](polymorphism.md) - 多态对象模型 +- [Schema Evolution](schema-evolution.md) - 兼容模式 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/cpp/polymorphism.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/cpp/polymorphism.md new file mode 100644 index 00000000000..14cfbc19431 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/cpp/polymorphism.md @@ -0,0 +1,480 @@ +--- +title: 多态序列化 +sidebar_position: 5 +id: polymorphism +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. +--- + +Apache Fory™ 通过智能指针(`std::shared_ptr` 与 `std::unique_ptr`)支持多态序列化,为继承体系提供动态分派与类型灵活性。 + +## 支持的多态类型 + +- `std::shared_ptr` - 共享所有权并支持多态分派 +- `std::unique_ptr` - 独占所有权并支持多态分派 +- 集合:`std::vector>`、`std::map>` +- 可选值:`std::optional>` + +## 基础多态序列化 + +```cpp +#include "fory/serialization/fory.h" + +using namespace fory::serialization; + +// Define base class with virtual methods +struct Animal { + virtual ~Animal() = default; + virtual std::string speak() const = 0; + int32_t age = 0; +}; +FORY_STRUCT(Animal, age); + +// Define derived classes +struct Dog : Animal { + std::string speak() const override { return "Woof!"; } + std::string breed; +}; +FORY_STRUCT(Dog, age, breed); + +struct Cat : Animal { + std::string speak() const override { return "Meow!"; } + std::string color; +}; +FORY_STRUCT(Cat, age, color); + +// Struct with polymorphic field +struct Zoo { + std::shared_ptr star_animal; +}; +FORY_STRUCT(Zoo, star_animal); + +int main() { + auto fory = Fory::builder().track_ref(true).build(); + + // Register all types with unique type IDs + fory.register_struct(100); + fory.register_struct(101); + fory.register_struct(102); + + // Create object with polymorphic field + Zoo zoo; + zoo.star_animal = std::make_shared(); + zoo.star_animal->age = 3; + static_cast(zoo.star_animal.get())->breed = "Labrador"; + + // Serialize + auto bytes_result = fory.serialize(zoo); + assert(bytes_result.ok()); + + // Deserialize - runtime type is preserved + auto decoded_result = fory.deserialize(bytes_result.value()); + assert(decoded_result.ok()); + + auto decoded = std::move(decoded_result).value(); + assert(decoded.star_animal->speak() == "Woof!"); + assert(decoded.star_animal->age == 3); + + auto* dog_ptr = dynamic_cast(decoded.star_animal.get()); + assert(dog_ptr != nullptr); + assert(dog_ptr->breed == "Labrador"); +} +``` + +## 多态类型注册 + +对于多态序列化,需要使用唯一类型 ID 注册派生类型: + +```cpp +// Register with numeric type ID +fory.register_struct(100); +fory.register_struct(101); +``` + +**为什么要注册类型 ID?** + +- 二进制表示更紧凑 +- 类型查找和分派更快 +- 与非多态类型注册保持一致 + +## 自动多态检测 + +Fory 使用 `std::is_polymorphic` 自动检测多态类型: + +```cpp +struct Base { + virtual ~Base() = default; // Virtual destructor makes it polymorphic + int32_t value = 0; +}; + +struct NonPolymorphic { + int32_t value = 0; // No virtual methods +}; + +// Polymorphic field - type info written automatically +struct Container1 { + std::shared_ptr ptr; // Auto-detected as polymorphic +}; + +// Non-polymorphic field - no type info written +struct Container2 { + std::shared_ptr ptr; // Not polymorphic +}; +``` + +## 控制动态分派 + +使用 `fory::dynamic` 覆盖自动多态检测: + +```cpp +struct Animal { + virtual ~Animal() = default; + virtual std::string speak() const = 0; +}; + +struct Pet { + // Auto-detected: type info written (Animal has virtual methods) + std::shared_ptr animal1; + + // Force dynamic: type info written explicitly + fory::field, 0, fory::dynamic> animal2; + + // Force non-dynamic: skip type info (faster but no runtime subtyping) + fory::field, 1, fory::dynamic> animal3; +}; +FORY_STRUCT(Pet, animal1, animal2, animal3); +``` + +**何时使用 `fory::dynamic`:** + +- 确定运行时类型始终与声明类型一致 +- 对性能要求很高且不需要子类型支持 +- 虽然有多态基类,但实际处理的是单态数据 + +### 不使用包装类型的字段配置 + +使用 `FORY_FIELD_CONFIG` 配置字段,无需 `fory::field<>` 包装器: + +```cpp +struct Zoo { + std::shared_ptr star; // Auto-detected as polymorphic + std::shared_ptr backup; // Nullable polymorphic field + std::shared_ptr mascot; // Non-dynamic (no subtype dispatch) +}; +FORY_STRUCT(Zoo, star, backup, mascot); + +// Configure fields with tag IDs and options +FORY_FIELD_CONFIG(Zoo, + (star, fory::F(0)), // Tag ID 0, default options + (backup, fory::F(1).nullable()), // Tag ID 1, allow nullptr + (mascot, fory::F(2).dynamic(false)) // Tag ID 2, disable polymorphism +); +``` + +关于 `fory::nullable`、`fory::ref` 和其他字段级选项的完整细节,请参见[字段配置](schema_metadata) + +## std::unique_ptr 多态 + +对于多态类型,`std::unique_ptr` 的工作方式与 `std::shared_ptr` 相同: + +```cpp +struct Container { + std::unique_ptr pet; +}; +FORY_STRUCT(Container, pet); + +auto fory = Fory::builder().track_ref(true).build(); +fory.register_struct(200); +fory.register_struct(201); + +Container container; +container.pet = std::make_unique(); +static_cast(container.pet.get())->breed = "Beagle"; + +auto bytes = fory.serialize(container).value(); +auto decoded = fory.deserialize(bytes).value(); + +// Runtime type preserved +auto* dog = dynamic_cast(decoded.pet.get()); +assert(dog != nullptr); +assert(dog->breed == "Beagle"); +``` + +## 多态对象集合 + +```cpp +#include +#include + +struct AnimalShelter { + std::vector> animals; + std::map> registry; +}; +FORY_STRUCT(AnimalShelter, animals, registry); + +auto fory = Fory::builder().track_ref(true).build(); +fory.register_struct(100); +fory.register_struct(101); +fory.register_struct(102); + +AnimalShelter shelter; +shelter.animals.push_back(std::make_shared()); +shelter.animals.push_back(std::make_shared()); +shelter.registry["pet1"] = std::make_unique(); + +auto bytes = fory.serialize(shelter).value(); +auto decoded = fory.deserialize(bytes).value(); + +// All runtime types preserved +assert(dynamic_cast(decoded.animals[0].get()) != nullptr); +assert(dynamic_cast(decoded.animals[1].get()) != nullptr); +assert(dynamic_cast(decoded.registry["pet1"].get()) != nullptr); +``` + +## 引用跟踪 + +多态类型中的 `std::shared_ptr` 引用跟踪行为相同。 +详情和示例请参见[支持的类型](supported_types)。 + +## 嵌套多态深度限制 + +为了防止深度嵌套的多态结构导致栈溢出,Fory 会限制最大动态嵌套深度: + +```cpp +struct Container { + virtual ~Container() = default; + int32_t value = 0; + std::shared_ptr nested; +}; +FORY_STRUCT(Container, value, nested); + +// Default max_dyn_depth is 5 +auto fory1 = Fory::builder().build(); +assert(fory1.config().max_dyn_depth == 5); + +// Increase limit for deeper nesting +auto fory2 = Fory::builder().max_dyn_depth(10).build(); +fory2.register_struct(1); + +// Create deeply nested structure +auto level3 = std::make_shared(); +level3->value = 3; + +auto level2 = std::make_shared(); +level2->value = 2; +level2->nested = level3; + +auto level1 = std::make_shared(); +level1->value = 1; +level1->nested = level2; + +// Serialization succeeds +auto bytes = fory2.serialize(level1).value(); + +// Deserialization succeeds with sufficient depth +auto decoded = fory2.deserialize>(bytes).value(); +``` + +**Depth exceeded error:** + +```cpp +auto fory_shallow = Fory::builder().max_dyn_depth(2).build(); +fory_shallow.register_struct(1); + +// 3 levels exceeds max_dyn_depth=2 +auto result = fory_shallow.deserialize>(bytes); +assert(!result.ok()); // Fails with depth exceeded error +``` + +**When to adjust:** + +- **Increase `max_dyn_depth`**: For legitimate deeply nested polymorphic data structures +- **Decrease `max_dyn_depth`**: For stricter security requirements or shallow data structures + +## Nullability for Polymorphic Fields + +By default, `std::shared_ptr` and `std::unique_ptr` fields are treated as +non-nullable in the schema. To allow `nullptr`, wrap the field with +`fory::field<>` (or `FORY_FIELD_TAGS`) and opt in with `fory::nullable`. + +```cpp +struct Pet { + // Non-nullable (default) + std::shared_ptr primary; + + // Nullable via explicit field metadata + fory::field, 0, fory::nullable> optional; +}; +FORY_STRUCT(Pet, primary, optional); +``` + +See [Field Configuration](schema_metadata) for more details. + +## Combining Polymorphism with Other Features + +### Polymorphism + Reference Tracking + +```cpp +struct GraphNode { + virtual ~GraphNode() = default; + int32_t id = 0; + std::vector> neighbors; +}; +FORY_STRUCT(GraphNode, id, neighbors); + +struct WeightedNode : GraphNode { + double weight = 0.0; +}; +FORY_STRUCT(WeightedNode, id, neighbors, weight); + +// Enable ref tracking to handle shared references and cycles +auto fory = Fory::builder().track_ref(true).build(); +fory.register_struct(100); +fory.register_struct(101); + +// Create cyclic graph +auto node1 = std::make_shared(); +node1->id = 1; + +auto node2 = std::make_shared(); +node2->id = 2; + +node1->neighbors.push_back(node2); +node2->neighbors.push_back(node1); // Cycle + +auto bytes = fory.serialize(node1).value(); +auto decoded = fory.deserialize>(bytes).value(); +// Cycle handled correctly +``` + +### Polymorphism + Schema Evolution + +Use compatible mode for schema evolution with polymorphic types: + +```cpp +auto fory = Fory::builder() + .compatible(true) // Enable schema evolution + .track_ref(true) + .build(); +``` + +## Best Practices + +1. **Use type ID registration** for polymorphic types: + + ```cpp + fory.register_struct(100); + ``` + +2. **Enable reference tracking** for polymorphic types: + + ```cpp + auto fory = Fory::builder().track_ref(true).build(); + ``` + +3. **Virtual destructors required**: Ensure base classes have virtual destructors: + + ```cpp + struct Base { + virtual ~Base() = default; // Required for polymorphism + }; + ``` + +4. **Register all concrete types** before serialization/deserialization: + + ```cpp + fory.register_struct(100); + fory.register_struct(101); + ``` + +5. **Use `dynamic_cast`** to downcast after deserialization: + + ```cpp + auto* derived = dynamic_cast(base_ptr.get()); + if (derived) { + // Use derived-specific members + } + ``` + +6. **Adjust `max_dyn_depth`** based on your data structure depth: + + ```cpp + auto fory = Fory::builder().max_dyn_depth(10).build(); + ``` + +7. **Use `fory::nullable`** for optional polymorphic fields: + + ```cpp + fory::field, 0, fory::nullable> optional_ptr; + ``` + +## Error Handling + +```cpp +auto bytes_result = fory.serialize(obj); +if (!bytes_result.ok()) { + std::cerr << "Serialization failed: " + << bytes_result.error().to_string() << std::endl; + return; +} + +auto decoded_result = fory.deserialize(bytes_result.value()); +if (!decoded_result.ok()) { + std::cerr << "Deserialization failed: " + << decoded_result.error().to_string() << std::endl; + return; +} +``` + +**Common errors:** + +- **Type not registered**: Register all concrete types with unique IDs before use +- **Depth exceeded**: Increase `max_dyn_depth` for deeply nested structures +- **Type ID conflict**: Ensure each type has a unique type ID across all registered types + +## Performance Considerations + +**Polymorphic serialization overhead:** + +- Type metadata written for each polymorphic object (~16-32 bytes) +- Dynamic type resolution during deserialization +- Virtual function calls for runtime dispatch + +**Optimization tips:** + +1. **Use `fory::dynamic`** when runtime type matches declared type: + + ```cpp + fory::field, 0, fory::dynamic> fixed_type; + ``` + +2. **Minimize nesting depth** to reduce metadata overhead + +3. **Batch polymorphic objects** in collections rather than individual fields + +4. **Consider non-polymorphic alternatives** when polymorphism isn't needed: + + ```cpp + std::variant animal; // Type-safe union instead of polymorphism + ``` + +## Related Topics + +- [Type Registration](type_registration) - Registering types for serialization +- [Field Configuration](schema_metadata) - Field-level metadata and options +- [Supported Types](supported_types) - Smart pointers and collections +- [Configuration](configuration) - `max_dyn_depth` and other settings +- [Basic Serialization](basic_serialization) - Core serialization concepts diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/cpp/row-format.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/cpp/row-format.md new file mode 100644 index 00000000000..76b1ad42cde --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/cpp/row-format.md @@ -0,0 +1,516 @@ +--- +title: 行格式 +sidebar_position: 7 +id: row_format +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. +--- + +本页介绍用于高性能、缓存友好的数据访问的行格式序列化。 + +## 概述 + +Apache Fory™ 行格式是一种针对以下场景优化的二进制格式: + +- **随机访问**:无需反序列化整个对象即可读取任意字段 +- **零拷贝**:无数据复制的直接内存访问 +- **缓存友好**:连续内存布局提高 CPU 缓存效率 +- **列式转换**:易于转换为 Apache Arrow 格式 +- **部分序列化**:只序列化需要的字段 + +## 何时使用行格式 + +| 使用场景 | 行格式 | 对象图 | +| ------------------ | ------ | ------ | +| 分析/OLAP | ✅ | ❌ | +| 随机字段访问 | ✅ | ❌ | +| 完整对象序列化 | ❌ | ✅ | +| 复杂对象图 | ❌ | ✅ | +| 引用跟踪 | ❌ | ✅ | +| 跨语言(简单类型) | ✅ | ✅ | + +## 快速开始 + +```cpp +#include "fory/encoder/row_encoder.h" +#include "fory/row/writer.h" + +using namespace fory::row; +using namespace fory::row::encoder; + +// 定义结构体 +struct Person { + int32_t id; + std::string name; + float score; +}; + +// 注册字段元数据(行编码必需) +FORY_FIELD_INFO(Person, id, name, score); + +int main() { + // 创建编码器 + RowEncoder encoder; + + // 编码一个 person + Person person{1, "Alice", 95.5f}; + encoder.Encode(person); + + // 获取编码后的行 + auto row = encoder.GetWriter().ToRow(); + + // 随机访问字段 + int32_t id = row->GetInt32(0); + std::string name = row->GetString(1); + float score = row->GetFloat(2); + + assert(id == 1); + assert(name == "Alice"); + assert(score == 95.5f); + + return 0; +} +``` + +## 行编码器 + +### 基本用法 + +`RowEncoder` 模板类提供类型安全的编码: + +```cpp +#include "fory/encoder/row_encoder.h" + +// 使用 FORY_FIELD_INFO 定义结构体 +struct Point { + double x; + double y; +}; +FORY_FIELD_INFO(Point, x, y); + +// 创建编码器 +RowEncoder encoder; + +// 访问 schema(用于检查) +const Schema& schema = encoder.GetSchema(); +std::cout << "Fields: " << schema.field_names().size() << std::endl; + +// 编码值 +Point p{1.0, 2.0}; +encoder.Encode(p); + +// 获取结果作为 Row +auto row = encoder.GetWriter().ToRow(); +``` + +### 嵌套结构体 + +```cpp +struct Address { + std::string city; + std::string country; +}; +FORY_FIELD_INFO(Address, city, country); + +struct Person { + std::string name; + Address address; +}; +FORY_FIELD_INFO(Person, name, address); + +// 编码嵌套结构体 +RowEncoder encoder; +Person person{"Alice", {"New York", "USA"}}; +encoder.Encode(person); + +auto row = encoder.GetWriter().ToRow(); +std::string name = row->GetString(0); + +// 访问嵌套结构体 +auto address_row = row->GetStruct(1); +std::string city = address_row->GetString(0); +std::string country = address_row->GetString(1); +``` + +### 数组 / 列表 + +```cpp +struct Record { + std::vector values; + std::string label; +}; +FORY_FIELD_INFO(Record, values, label); + +RowEncoder encoder; +Record record{{1, 2, 3, 4, 5}, "test"}; +encoder.Encode(record); + +auto row = encoder.GetWriter().ToRow(); +auto array = row->GetArray(0); + +int count = array->num_elements(); +for (int i = 0; i < count; i++) { + int32_t value = array->GetInt32(i); +} +``` + +### 直接编码数组 + +```cpp +// 直接编码 vector(不在结构体内) +std::vector people{ + {"Alice", {"NYC", "USA"}}, + {"Bob", {"London", "UK"}} +}; + +RowEncoder encoder; +encoder.Encode(people); + +// 获取数组数据 +auto array = encoder.GetWriter().CopyToArrayData(); +auto first_person = array->GetStruct(0); +std::string first_name = first_person->GetString(0); +``` + +## 行数据访问 + +### Row 类 + +`Row` 类提供对结构体字段的随机访问: + +```cpp +class Row { +public: + // 空值检查 + bool IsNullAt(int i) const; + + // 基本类型 getter + bool GetBoolean(int i) const; + int8_t GetInt8(int i) const; + int16_t GetInt16(int i) const; + int32_t GetInt32(int i) const; + int64_t GetInt64(int i) const; + float GetFloat(int i) const; + double GetDouble(int i) const; + + // 字符串/二进制 getter + std::string GetString(int i) const; + std::vector GetBinary(int i) const; + + // 嵌套类型 + std::shared_ptr GetStruct(int i) const; + std::shared_ptr GetArray(int i) const; + std::shared_ptr GetMap(int i) const; + + // 元数据 + int num_fields() const; + SchemaPtr schema() const; + + // 调试 + std::string ToString() const; +}; +``` + +### ArrayData 类 + +`ArrayData` 类提供对列表/数组元素的访问: + +```cpp +class ArrayData { +public: + // 空值检查 + bool IsNullAt(int i) const; + + // 元素数量 + int num_elements() const; + + // 基本类型 getter(与 Row 相同) + int32_t GetInt32(int i) const; + // ... 其他基本类型 + + // 字符串 getter + std::string GetString(int i) const; + + // 嵌套类型 + std::shared_ptr GetStruct(int i) const; + std::shared_ptr GetArray(int i) const; + std::shared_ptr GetMap(int i) const; + + // 类型信息 + ListTypePtr type() const; +}; +``` + +### MapData 类 + +`MapData` 类提供对 map 键值对的访问: + +```cpp +class MapData { +public: + // 元素数量 + int num_elements(); + + // 以数组形式访问键和值 + std::shared_ptr keys_array(); + std::shared_ptr values_array(); + + // 类型信息 + MapTypePtr type(); +}; +``` + +## Schema 和类型 + +### Schema 定义 + +Schema 定义行数据的结构: + +```cpp +#include "fory/row/schema.h" + +using namespace fory::row; + +// 以编程方式创建 schema +auto person_schema = schema({ + field("id", int32()), + field("name", utf8()), + field("score", float32()), + field("active", boolean()) +}); + +// 访问 schema 信息 +for (const auto& f : person_schema->fields()) { + std::cout << f->name() << ": " << f->type()->name() << std::endl; +} +``` + +### 类型系统 + +行格式可用的类型: + +```cpp +// 基本类型 +DataTypePtr boolean(); // bool +DataTypePtr int8(); // int8_t +DataTypePtr int16(); // int16_t +DataTypePtr int32(); // int32_t +DataTypePtr int64(); // int64_t +DataTypePtr float32(); // float +DataTypePtr float64(); // double + +// 字符串和二进制 +DataTypePtr utf8(); // std::string +DataTypePtr binary(); // std::vector + +// 复杂类型 +DataTypePtr list(DataTypePtr element_type); +DataTypePtr map(DataTypePtr key_type, DataTypePtr value_type); +DataTypePtr struct_(std::vector fields); +``` + +### 类型推断 + +`RowEncodeTrait` 模板自动推断类型: + +```cpp +// 基本类型的类型推断 +RowEncodeTrait::Type(); // 返回 int32() +RowEncodeTrait::Type(); // 返回 float32() +RowEncodeTrait::Type(); // 返回 utf8() + +// 集合的类型推断 +RowEncodeTrait>::Type(); // 返回 list(int32()) + +// map 的类型推断 +RowEncodeTrait>::Type(); +// 返回 map(utf8(), int32()) + +// 结构体的类型推断(需要 FORY_FIELD_INFO) +RowEncodeTrait::Type(); // 返回 struct_({...}) +RowEncodeTrait::Schema(); // 返回 schema({...}) +``` + +## 行写入器 + +### RowWriter + +用于手动构造行: + +```cpp +#include "fory/row/writer.h" + +// 创建 schema +auto my_schema = schema({ + field("x", int32()), + field("y", float64()), + field("name", utf8()) +}); + +// 创建写入器 +RowWriter writer(my_schema); +writer.Reset(); + +// 写入字段 +writer.Write(0, 42); // x = 42 +writer.Write(1, 3.14); // y = 3.14 +writer.WriteString(2, "test"); // name = "test" + +// 获取结果 +auto row = writer.ToRow(); +``` + +### ArrayWriter + +用于手动构造数组: + +```cpp +// 创建数组类型 +auto array_type = list(int32()); + +// 创建写入器 +ArrayWriter writer(array_type); +writer.Reset(5); // 5 个元素 + +// 写入元素 +for (int i = 0; i < 5; i++) { + writer.Write(i, i * 10); +} + +// 获取结果 +auto array = writer.CopyToArrayData(); +``` + +### 空值 + +```cpp +// 在特定索引处设置空值 +writer.SetNullAt(2); // 字段 2 为空 + +// 读取时检查空值 +if (!row->IsNullAt(2)) { + std::string value = row->GetString(2); +} +``` + +## 内存布局 + +### 行布局 + +``` ++------------------+--------------------+--------------------+ +| 空值位图 | 固定大小数据 | 变长数据 | ++------------------+--------------------+--------------------+ +| ceil(n/8) B | 8 * n 字节 | 可变 | ++------------------+--------------------+--------------------+ +``` + +- **空值位图**:每字段一位,指示空值 +- **固定大小数据**:每字段 8 字节(基本类型直接存储,变长类型存储偏移量+大小) +- **变长数据**:字符串、数组、嵌套结构体 + +### 数组布局 + +``` ++------------+------------------+--------------------+--------------------+ +| 元素数量 | 空值位图 | 固定大小数据 | 变长数据 | ++------------+------------------+--------------------+--------------------+ +| 8 字节 | ceil(n/8) 字节 | elem_size * n | 可变 | ++------------+------------------+--------------------+--------------------+ +``` + +### Map 布局 + +``` ++------------------+------------------+ +| 键数组 | 值数组 | ++------------------+------------------+ +``` + +## 性能提示 + +### 1. 复用编码器 + +```cpp +RowEncoder encoder; + +// 编码多条记录 +for (const auto& person : people) { + encoder.Encode(person); + auto row = encoder.GetWriter().ToRow(); + // 处理 row... +} +``` + +### 2. 预分配缓冲区 + +```cpp +// 获取缓冲区引用进行预分配 +auto& buffer = encoder.GetWriter().buffer(); +buffer->Reserve(expected_size); +``` + +### 3. 批量处理 + +```cpp +// 批量处理以提高缓存利用率 +std::vector batch; +batch.reserve(BATCH_SIZE); + +while (hasMore()) { + batch.clear(); + fillBatch(batch); + + for (const auto& person : batch) { + encoder.Encode(person); + process(encoder.GetWriter().ToRow()); + } +} +``` + +### 4. 零拷贝读取 + +```cpp +// 指向现有缓冲区(零拷贝) +Row row(schema); +row.PointTo(buffer, offset, size); + +// 直接从缓冲区访问字段 +int32_t id = row.GetInt32(0); +``` + +## 支持类型汇总 + +| C++ 类型 | 行类型 | 固定大小 | +| ------------------------ | ---------------- | -------- | +| `bool` | `boolean()` | 1 字节 | +| `int8_t` | `int8()` | 1 字节 | +| `int16_t` | `int16()` | 2 字节 | +| `int32_t` | `int32()` | 4 字节 | +| `int64_t` | `int64()` | 8 字节 | +| `float` | `float32()` | 4 字节 | +| `double` | `float64()` | 8 字节 | +| `std::string` | `utf8()` | 可变 | +| `std::vector` | `list(T)` | 可变 | +| `std::map` | `map(K,V)` | 可变 | +| `std::optional` | 内部类型 | 可空 | +| 结构体 (FORY_FIELD_INFO) | `struct_({...})` | 可变 | + +## 相关主题 + +- [基础序列化](basic-serialization.md) - 对象图序列化 +- [配置](configuration.md) - 构建器选项 +- [支持的类型](supported-types.md) - 所有支持的类型 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/cpp/schema-evolution.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/cpp/schema-evolution.md new file mode 100644 index 00000000000..5db865f3b40 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/cpp/schema-evolution.md @@ -0,0 +1,404 @@ +--- +title: Schema 演化 +sidebar_position: 3 +id: schema_evolution +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. +--- + +Apache Fory™ 在 **Compatible 模式**下支持 schema 演化,允许序列化和反序列化双方拥有不同的类型定义。 + +## Compatible 模式 + +使用 `compatible(true)` 启用 schema 演化: + +```cpp +#include "fory/serialization/fory.h" + +using namespace fory::serialization; + +// 版本 1:原始 schema +struct PersonV1 { + std::string name; + int32_t age; +}; +FORY_STRUCT(PersonV1, name, age); + +// 版本 2:添加 email 字段 +struct PersonV2 { + std::string name; + int32_t age; + std::string email; // 新字段 +}; +FORY_STRUCT(PersonV2, name, age, email); + +int main() { + // 为每个 schema 版本创建单独的 Fory 实例 + auto fory_v1 = Fory::builder() + .compatible(true) // 启用 schema 演化 + .xlang(true) + .build(); + + auto fory_v2 = Fory::builder() + .compatible(true) + .xlang(true) + .build(); + + // 使用相同的类型 ID 进行 schema 演化 + constexpr uint32_t PERSON_TYPE_ID = 100; + fory_v1.register_struct(PERSON_TYPE_ID); + fory_v2.register_struct(PERSON_TYPE_ID); + + // 使用 V1 序列化 + PersonV1 v1{"Alice", 30}; + auto bytes = fory_v1.serialize(v1).value(); + + // 反序列化为 V2 - email 获得默认值(空字符串) + auto v2 = fory_v2.deserialize(bytes).value(); + assert(v2.name == "Alice"); + assert(v2.age == 30); + assert(v2.email == ""); // 缺失字段的默认值 + + return 0; +} +``` + +## Schema 演化特性 + +Compatible 模式支持以下 schema 变更: + +| 变更类型 | 支持 | 行为 | +| ------------ | ---- | ---------------------------- | +| 添加新字段 | ✅ | 缺失字段使用默认值 | +| 删除字段 | ✅ | 额外字段被跳过 | +| 重排字段顺序 | ✅ | 按名称匹配字段,而非位置 | +| 更改可空性 | ✅ | `T` ↔ `std::optional` | +| 更改字段类型 | ❌ | 类型必须兼容 | +| 重命名字段 | ❌ | 字段名必须匹配(区分大小写) | + +## 添加字段(向后兼容) + +当使用具有额外字段的新 schema 反序列化旧数据时: + +```cpp +// 旧 schema (V1) +struct ProductV1 { + std::string name; + double price; +}; +FORY_STRUCT(ProductV1, name, price); + +// 新 schema (V2) 带有额外字段 +struct ProductV2 { + std::string name; + double price; + std::vector tags; // 新字段 + std::map attributes; // 新字段 +}; +FORY_STRUCT(ProductV2, name, price, tags, attributes); + +// 序列化 V1 +ProductV1 v1{"Laptop", 999.99}; +auto bytes = fory_v1.serialize(v1).value(); + +// 反序列化为 V2 +auto v2 = fory_v2.deserialize(bytes).value(); +assert(v2.name == "Laptop"); +assert(v2.price == 999.99); +assert(v2.tags.empty()); // 默认:空 vector +assert(v2.attributes.empty()); // 默认:空 map +``` + +## 删除字段(向前兼容) + +当使用具有较少字段的旧 schema 反序列化新数据时: + +```cpp +// 完整 schema +struct UserFull { + int64_t id; + std::string username; + std::string email; + std::string password_hash; + int32_t login_count; +}; +FORY_STRUCT(UserFull, id, username, email, password_hash, login_count); + +// 精简 schema(删除了 3 个字段) +struct UserMinimal { + int64_t id; + std::string username; +}; +FORY_STRUCT(UserMinimal, id, username); + +// 序列化完整版本 +UserFull full{12345, "johndoe", "john@example.com", "hash123", 42}; +auto bytes = fory_full.serialize(full).value(); + +// 反序列化为精简版本 - 额外字段被跳过 +auto minimal = fory_minimal.deserialize(bytes).value(); +assert(minimal.id == 12345); +assert(minimal.username == "johndoe"); +// email、password_hash、login_count 被跳过 +``` + +## 字段重排 + +在 compatible 模式下,字段按名称匹配,而非位置: + +```cpp +// 原始字段顺序 +struct ConfigOriginal { + std::string host; + int32_t port; + bool enable_ssl; + std::string protocol; +}; +FORY_STRUCT(ConfigOriginal, host, port, enable_ssl, protocol); + +// 重排后的字段 +struct ConfigReordered { + bool enable_ssl; // 移到第一位 + std::string protocol; // 移到第二位 + std::string host; // 移到第三位 + int32_t port; // 移到最后 +}; +FORY_STRUCT(ConfigReordered, enable_ssl, protocol, host, port); + +// 使用原始顺序序列化 +ConfigOriginal orig{"localhost", 8080, true, "https"}; +auto bytes = fory_orig.serialize(orig).value(); + +// 使用不同字段顺序反序列化 - 正常工作 +auto reordered = fory_reord.deserialize(bytes).value(); +assert(reordered.host == "localhost"); +assert(reordered.port == 8080); +assert(reordered.enable_ssl == true); +assert(reordered.protocol == "https"); +``` + +## 嵌套结构体演化 + +Schema 演化递归支持嵌套结构体: + +```cpp +// V1 Address +struct AddressV1 { + std::string street; + std::string city; +}; +FORY_STRUCT(AddressV1, street, city); + +// V2 Address 带有新字段 +struct AddressV2 { + std::string street; + std::string city; + std::string country; // 新字段 + std::string zipcode; // 新字段 +}; +FORY_STRUCT(AddressV2, street, city, country, zipcode); + +// V1 Employee 使用 V1 Address +struct EmployeeV1 { + std::string name; + AddressV1 home_address; +}; +FORY_STRUCT(EmployeeV1, name, home_address); + +// V2 Employee 使用 V2 Address 和新字段 +struct EmployeeV2 { + std::string name; + AddressV2 home_address; // 嵌套结构体已演化 + std::string employee_id; // 新字段 +}; +FORY_STRUCT(EmployeeV2, name, home_address, employee_id); + +// 使用相同 ID 注册类型 +constexpr uint32_t ADDRESS_TYPE_ID = 100; +constexpr uint32_t EMPLOYEE_TYPE_ID = 101; + +fory_v1.register_struct(ADDRESS_TYPE_ID); +fory_v1.register_struct(EMPLOYEE_TYPE_ID); +fory_v2.register_struct(ADDRESS_TYPE_ID); +fory_v2.register_struct(EMPLOYEE_TYPE_ID); + +// 序列化 V1 +EmployeeV1 emp_v1{"Jane Doe", {"123 Main St", "NYC"}}; +auto bytes = fory_v1.serialize(emp_v1).value(); + +// 反序列化为 V2 +auto emp_v2 = fory_v2.deserialize(bytes).value(); +assert(emp_v2.name == "Jane Doe"); +assert(emp_v2.home_address.street == "123 Main St"); +assert(emp_v2.home_address.city == "NYC"); +assert(emp_v2.home_address.country == ""); // 默认值 +assert(emp_v2.home_address.zipcode == ""); // 默认值 +assert(emp_v2.employee_id == ""); // 默认值 +``` + +## 双向演化 + +Schema 演化双向工作: + +```cpp +// V2 -> V1(降级) +PersonV2 v2{"Charlie", 35, "charlie@example.com"}; +auto bytes = fory_v2.serialize(v2).value(); + +auto v1 = fory_v1.deserialize(bytes).value(); +assert(v1.name == "Charlie"); +assert(v1.age == 35); +// email 字段在反序列化时被丢弃 +``` + +## 默认值 + +当字段缺失时,使用 C++ 默认初始化: + +| 类型 | 默认值 | +| ---------------------- | -------------- | +| `int8_t`、`int16_t`... | `0` | +| `float`、`double` | `0.0` | +| `bool` | `false` | +| `std::string` | `""` | +| `std::vector` | 空 vector | +| `std::map` | 空 map | +| `std::set` | 空 set | +| `std::optional` | `std::nullopt` | +| 结构体类型 | 默认构造 | + +## SchemaConsistent 模式(默认) + +不使用 compatible 模式时,schema 必须完全匹配: + +```cpp +// 严格模式(默认) +auto fory = Fory::builder() + .compatible(false) // 默认:schema 必须匹配 + .xlang(true) + .build(); + +// 序列化/反序列化要求相同的 schema +// Schema 不匹配可能导致错误或未定义行为 +``` + +**何时使用 SchemaConsistent 模式:** + +- Schema 保证匹配(相同的二进制版本) +- 需要最高性能(更少的元数据开销) +- 您同时控制序列化和反序列化 + +**何时使用 Compatible 模式:** + +- Schema 可能独立演化 +- 需要跨版本兼容性 +- 不同服务可能有不同的 schema 版本 + +## 类型 ID 要求 + +要使 schema 演化正常工作: + +1. **相同类型 ID**:同一结构体的不同版本必须使用相同的类型 ID +2. **一致的 ID**:类型 ID 必须在所有 Fory 实例中保持一致 +3. **注册所有版本**:每个 Fory 实例注册自己的结构体版本 + +```cpp +constexpr uint32_t PERSON_TYPE_ID = 100; + +// 实例 1 使用 PersonV1 +fory_v1.register_struct(PERSON_TYPE_ID); + +// 实例 2 使用 PersonV2 +fory_v2.register_struct(PERSON_TYPE_ID); + +// 相同的类型 ID 启用 schema 演化 +``` + +## 最佳实践 + +### 1. 为演化做规划 + +设计 schema 时考虑未来的变更: + +```cpp +// 好的做法:对可能删除的字段使用 optional +struct Config { + std::string host; + int32_t port; + std::optional deprecated_field; // 以后可以删除 +}; +``` + +### 2. 使用有意义的默认值 + +考虑新字段使用什么默认值才有意义: + +```cpp +struct Settings { + int32_t timeout_ms; // 默认:0(可能需要一个合理的默认值) + bool enabled; // 默认:false + std::string mode; // 默认:""(可能需要 "default") +}; +``` + +### 3. 记录 Schema 版本 + +跟踪 schema 变更以便调试: + +```cpp +// V1:初始 schema(2024-01-01) +// V2:添加 email 字段(2024-02-01) +// V3:添加 phone、address 字段(2024-03-01) +``` + +### 4. 测试演化路径 + +测试升级和降级场景: + +```cpp +// 测试 V1 -> V2 +// 测试 V2 -> V1 +// 测试 V1 -> V3 +// 测试 V3 -> V1 +``` + +## 跨语言 Schema 演化 + +使用 xlang 模式时,schema 演化跨语言工作: + +```cpp +// 使用 compatible 模式的 C++ +auto fory = Fory::builder() + .compatible(true) + .xlang(true) + .build(); +``` + +```java +// 使用 compatible 模式的 Java +Fory fory = Fory.builder() + .withCompatibleMode(CompatibleMode.COMPATIBLE) + .withLanguage(Language.XLANG) + .build(); +``` + +即使 schema 版本不同,两个实例也可以交换数据。 + +## 相关主题 + +- [配置](configuration.md) - 启用 compatible 模式 +- [类型注册](type-registration.md) - 类型 ID 管理 +- [跨语言](xlang-serialization.md) - 跨语言注意事项 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/cpp/schema-metadata.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/cpp/schema-metadata.md new file mode 100644 index 00000000000..6f61f4e658d --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/cpp/schema-metadata.md @@ -0,0 +1,191 @@ +--- +title: Schema 元数据 +sidebar_position: 5 +id: schema_metadata +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. +--- + +字段配置直接嵌入在 `FORY_STRUCT` 中。字段条目可以是裸字段,也可以是包含成员名和 `fory::F(...)` 构建器的元组: + +```cpp +#include "fory/serialization/fory.h" + +struct DataV2 { + uint32_t id; + uint64_t timestamp; + std::optional version; +}; + +FORY_STRUCT(DataV2, id, (timestamp, fory::F().tagged()), version); +``` + +该配置是编译期元数据。它不会分配编解码器对象,也不会在序列化路径上增加虚分派。 + +## 字段身份 {#field-identity} + +`fory::F()` 使用名称模式的字段身份。裸字段也使用名称模式: + +```cpp +FORY_STRUCT(DataV2, id, (timestamp, fory::F().tagged()), version); +``` + +`fory::F(id)` 使用显式的基于 ID 的字段身份。ID 必须是非负数: + +```cpp +FORY_STRUCT(DataV2, (id, fory::F(0)), (timestamp, fory::F(1).tagged()), + (version, fory::F(2))); +``` + +没有显式 ID 的字段仍使用其 snake_case 字段名。在同一协议字段组内,显式 ID 会排在基于名称的字段之前,因此单个 `FORY_STRUCT` 可以混用 `fory::F(id)`、`fory::F()` 和裸字段。 + +## 标量编码 {#scalar-encoding} + +整数编码可以配置在字段上,也可以配置在嵌套 value-node spec 上: + +```cpp +struct Counters { + uint32_t fixed_id; + uint64_t tagged_time; + int64_t signed_score; +}; + +FORY_STRUCT(Counters, (fixed_id, fory::F().fixed()), + (tagged_time, fory::F().tagged()), + (signed_score, fory::F().varint())); +``` + +支持的标量编码方法如下: + +| Method | 含义 | +| ---------- | ---------------------------- | +| `fixed()` | 在合法类型上使用定长整数编码 | +| `varint()` | 在合法类型上使用变长整数编码 | +| `tagged()` | 在合法类型上使用带 tag 的整数编码 | + +非法的标量/类型组合会在编译期失败。 + +## 嵌套 Spec {#nested-specs} + +对容器和包装承载类型内部的 value-node spec,请使用 `fory::T` 命名空间。无类型 spec 会推断该节点处的实际 C++ 类型: + +```cpp +namespace T = fory::T; + +struct Foo { + std::vector values; + std::map> nested; +}; + +FORY_STRUCT(Foo, + (values, fory::F().list(T::fixed())), + (nested, fory::F().map(T::varint(), + T::list(T::tagged())))); +``` + +带类型的 spec 是可选的校验器,也能显式表达预期的节点类型: + +```cpp +FORY_STRUCT(Foo, (nested, fory::F().map(T::uint32().varint(), + T::list(T::int64().tagged())))); +``` + +支持的递归组合方法如下: + +| Method | 适用于 | +| ------------------- | ------------------------------------------- | +| `list(elem)` | `std::vector` 和类 list 字段 | +| `set(elem)` | `std::set` 和类 set 字段 | +| `map(key, value)` | `std::map` 和类 map 字段 | +| `map().key(spec)` | 仅覆盖 map key | +| `map().value(spec)` | 仅覆盖 map value | +| `inner(child)` | 透明的单子节点承载类型 | + +当只有一侧需要非默认编码时,局部 map 覆盖很有用: + +```cpp +FORY_STRUCT(Foo, + (nested, fory::F().map().key(T::varint())), + (other, fory::F().map().value(T::list(T::tagged())))); +``` + +## 承载类型内部 Spec {#carrier-inner-specs} + +对包装器式承载类型使用 `.inner(...)`。承载类型的种类仍来自实际 C++ 类型,并控制可空性和引用行为: + +```cpp +struct WrapperFields { + std::optional> maybe_values; + std::shared_ptr> shared_values; +}; + +FORY_STRUCT(WrapperFields, + (maybe_values, fory::F().inner(T::list(T::varint()))), + (shared_values, + fory::F().nullable().ref().inner(T::list(T::tagged())))); +``` + +`.inner(...)` 是 `std::optional`、`std::shared_ptr`、`std::unique_ptr` 和 `fory::serialization::SharedWeak` 唯一公开的组合器。 + +## 可空性、引用跟踪和动态字段 {#nullability-reference-tracking-and-dynamic-fields} + +`std::optional` 默认可空。智能指针可以在字段 spec 中标记为可空或启用引用跟踪: + +```cpp +struct Node { + std::string name; + std::shared_ptr next; +}; + +FORY_STRUCT(Node, name, (next, fory::F().nullable().ref())); +``` + +对于多态指针字段,使用 `.dynamic(true)` 始终写入运行时类型信息,使用 `.dynamic(false)` 直接使用声明类型,或省略它让 Fory 根据 C++ 类型推断行为: + +```cpp +struct Zoo { + std::shared_ptr star; + std::shared_ptr mascot; +}; + +FORY_STRUCT(Zoo, (star, fory::F().nullable().dynamic(true)), + (mascot, fory::F().nullable().dynamic(false))); +``` + +## Union {#unions} + +`FORY_UNION` case 必须使用显式 ID。名称模式的 `fory::F()` 对 union 元数据无效: + +```cpp +struct Choice { + std::variant value; + + static Choice text(std::string value); + static Choice code(uint32_t value); +}; + +FORY_UNION(Choice, (text, std::string, fory::F(1)), + (code, uint32_t, fory::F(2).fixed())); +``` + +当生成的 C++ 能从非重载的单参数工厂函数推断载荷类型时,可以省略显式 case 类型: + +```cpp +FORY_UNION(GeneratedChoice, (text, fory::F(1)), + (code, fory::F(2).fixed())); +``` + +三元素形式是手写代码稳定的公开形式。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/cpp/supported-types.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/cpp/supported-types.md new file mode 100644 index 00000000000..8a34a004675 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/cpp/supported-types.md @@ -0,0 +1,277 @@ +--- +title: 支持的类型 +sidebar_position: 5 +id: supported_types +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. +--- + +本页记录 Fory C++ 序列化支持的所有类型。 + +## 基本类型 + +所有 C++ 基本类型都支持高效的二进制编码: + +| 类型 | 大小 | Fory TypeId | 说明 | +| ---------- | ------ | ----------- | ------------------ | +| `bool` | 1 字节 | BOOL | True/false | +| `int8_t` | 1 字节 | INT8 | 有符号字节 | +| `uint8_t` | 1 字节 | INT8 | 无符号字节 | +| `int16_t` | 2 字节 | INT16 | 有符号短整型 | +| `uint16_t` | 2 字节 | INT16 | 无符号短整型 | +| `int32_t` | 4 字节 | INT32 | 有符号整型 | +| `uint32_t` | 4 字节 | INT32 | 无符号整型 | +| `int64_t` | 8 字节 | INT64 | 有符号长整型 | +| `uint64_t` | 8 字节 | INT64 | 无符号长整型 | +| `float` | 4 字节 | FLOAT32 | IEEE 754 单精度 | +| `double` | 8 字节 | FLOAT64 | IEEE 754 双精度 | +| `char` | 1 字节 | INT8 | 字符(作为有符号) | +| `char16_t` | 2 字节 | INT16 | 16 位字符 | +| `char32_t` | 4 字节 | INT32 | 32 位字符 | + +```cpp +int32_t value = 42; +auto bytes = fory.serialize(value).value(); +auto decoded = fory.deserialize(bytes).value(); +assert(value == decoded); +``` + +## 字符串类型 + +| 类型 | Fory TypeId | 说明 | +| ------------------ | ----------- | ------------------ | +| `std::string` | STRING | UTF-8 编码 | +| `std::string_view` | STRING | 零拷贝视图(读取) | +| `std::u16string` | STRING | UTF-16(转换) | +| `binary` | BINARY | 无长度的原始字节 | + +```cpp +std::string text = "Hello, World!"; +auto bytes = fory.serialize(text).value(); +auto decoded = fory.deserialize(bytes).value(); +assert(text == decoded); +``` + +## 集合类型 + +### Vector / List + +`std::vector` 支持任何可序列化的元素类型: + +```cpp +std::vector numbers{1, 2, 3, 4, 5}; +auto bytes = fory.serialize(numbers).value(); +auto decoded = fory.deserialize>(bytes).value(); + +// 嵌套 vector +std::vector> nested{ + {"a", "b"}, + {"c", "d", "e"} +}; +``` + +### Set + +`std::set` 和 `std::unordered_set`: + +```cpp +std::set tags{"cpp", "serialization", "fory"}; +auto bytes = fory.serialize(tags).value(); +auto decoded = fory.deserialize>(bytes).value(); + +std::unordered_set ids{1, 2, 3}; +``` + +### Map + +`std::map` 和 `std::unordered_map`: + +```cpp +std::map scores{ + {"Alice", 100}, + {"Bob", 95} +}; +auto bytes = fory.serialize(scores).value(); +auto decoded = fory.deserialize>(bytes).value(); + +// 无序 map +std::unordered_map lookup{ + {1, "one"}, + {2, "two"} +}; +``` + +## 智能指针 + +### std::optional + +任何类型的可空包装: + +```cpp +std::optional maybe_value = 42; +std::optional empty_value = std::nullopt; + +auto bytes = fory.serialize(maybe_value).value(); +auto decoded = fory.deserialize>(bytes).value(); +assert(decoded.has_value() && *decoded == 42); +``` + +### std::shared_ptr + +带引用跟踪的共享所有权: + +```cpp +auto shared = std::make_shared("Alice", 30); + +auto bytes = fory.serialize(shared).value(); +auto decoded = fory.deserialize>(bytes).value(); +``` + +**启用引用跟踪(`track_ref(true)`)时:** + +- 共享对象只序列化一次 +- 对同一对象的引用被保留 +- 循环引用自动处理 + +### std::unique_ptr + +独占所有权: + +```cpp +auto unique = std::make_unique("Bob", 25); + +auto bytes = fory.serialize(unique).value(); +auto decoded = fory.deserialize>(bytes).value(); +``` + +## Variant 类型 + +`std::variant` 用于类型安全的联合: + +```cpp +using MyVariant = std::variant; + +MyVariant v1 = 42; +MyVariant v2 = std::string("hello"); +MyVariant v3 = 3.14; + +auto bytes = fory.serialize(v1).value(); +auto decoded = fory.deserialize(bytes).value(); +assert(std::get(decoded) == 42); +``` + +### std::monostate + +空 variant 替代: + +```cpp +using OptionalInt = std::variant; + +OptionalInt empty = std::monostate{}; +OptionalInt value = 42; +``` + +## 时间类型 + +### Duration + +`std::chrono::nanoseconds`: + +```cpp +using Duration = std::chrono::nanoseconds; + +Duration d = std::chrono::seconds(30); +auto bytes = fory.serialize(d).value(); +auto decoded = fory.deserialize(bytes).value(); +``` + +### Timestamp + +自 Unix 纪元以来的时间点: + +```cpp +using Timestamp = std::chrono::time_point; + +Timestamp now = std::chrono::system_clock::now(); +auto bytes = fory.serialize(now).value(); +auto decoded = fory.deserialize(bytes).value(); +``` + +### LocalDate + +自 Unix 纪元以来的天数: + +```cpp +LocalDate date{18628}; // 自 1970-01-01 以来的天数 + +auto bytes = fory.serialize(date).value(); +auto decoded = fory.deserialize(bytes).value(); +``` + +## 用户定义结构体 + +任何结构体都可以使用 `FORY_STRUCT` 进行序列化: + +```cpp +struct Point { + double x; + double y; + double z; +}; +FORY_STRUCT(Point, x, y, z); + +struct Line { + Point start; + Point end; + std::string label; +}; +FORY_STRUCT(Line, start, end, label); +``` + +## 枚举类型 + +使用 `FORY_ENUM` 支持作用域和非作用域枚举: + +```cpp +// 作用域枚举(C++11 enum class) +enum class Color { RED = 0, GREEN = 1, BLUE = 2 }; + +// 非连续值的非作用域枚举 +enum Priority : int32_t { LOW = -10, NORMAL = 0, HIGH = 10 }; +FORY_ENUM(Priority, LOW, NORMAL, HIGH); + +// 使用 +Color c = Color::GREEN; +auto bytes = fory.serialize(c).value(); +auto decoded = fory.deserialize(bytes).value(); +``` + +## 不支持的类型 + +当前不支持: + +- 原始指针(`T*`)- 使用智能指针代替 +- `std::tuple` - 使用结构体代替 +- `std::array` - 使用 `std::vector` 代替 +- 函数指针 +- 引用(`T&`、`const T&`)- 仅支持按值传递 + +## 相关主题 + +- [基础序列化](basic-serialization.md) - 使用这些类型 +- [类型注册](type-registration.md) - 注册类型 +- [跨语言](xlang-serialization.md) - 跨语言兼容性 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/cpp/type-registration.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/cpp/type-registration.md new file mode 100644 index 00000000000..9d0a7f7244a --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/cpp/type-registration.md @@ -0,0 +1,225 @@ +--- +title: 类型注册 +sidebar_position: 4 +id: type_registration +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. +--- + +本页介绍如何注册类型以进行序列化。 + +## 概述 + +Apache Fory™ 要求显式注册结构体类型。这种设计使得: + +- **跨语言兼容性**:注册的类型 ID 跨语言边界使用 +- **类型安全**:在反序列化时检测类型不匹配 +- **多态序列化**:通过智能指针启用多态对象的序列化 + +## 注册结构体 + +使用 `register_struct(type_id)` 注册结构体类型: + +```cpp +#include "fory/serialization/fory.h" + +using namespace fory::serialization; + +struct Person { + std::string name; + int32_t age; +}; +FORY_STRUCT(Person, name, age); + +int main() { + auto fory = Fory::builder().xlang(true).build(); + + // 使用唯一的类型 ID 注册 + fory.register_struct(100); + + Person person{"Alice", 30}; + auto bytes = fory.serialize(person).value(); + auto decoded = fory.deserialize(bytes).value(); +} +``` + +## 类型 ID 指南 + +类型 ID 必须: + +1. **唯一**:每个类型在 Fory 实例中必须有唯一的 ID +2. **一致**:必须在所有语言和版本中使用相同的 ID + +用户注册的类型 ID 与内置类型 ID 在不同的命名空间中,因此可以从 0 开始: + +```cpp +// 用户类型 ID 可以从 0 开始 +fory.register_struct
(0); +fory.register_struct(1); +fory.register_struct(2); +``` + +## 注册枚举 + +使用 `register_enum(type_id)` 注册枚举类型。对于从 0 开始连续值的简单枚举,不需要宏: + +```cpp +// 简单连续枚举 - 不需要 FORY_ENUM +enum class Color { RED, GREEN, BLUE }; // 值:0、1、2 + +// 使用 register_enum 注册 +fory.register_enum(0); +``` + +对于具有非连续值的枚举,使用 `FORY_ENUM` 宏将值映射到序号: + +```cpp +// 非连续枚举值 - 需要 FORY_ENUM +enum class Priority { LOW = 10, MEDIUM = 50, HIGH = 100 }; +FORY_ENUM(Priority, LOW, MEDIUM, HIGH); + +// 全局命名空间枚举(前缀 ::) +enum LegacyStatus { UNKNOWN = -1, OK = 0, ERROR = 1 }; +FORY_ENUM(::LegacyStatus, UNKNOWN, OK, ERROR); + +// FORY_ENUM 后注册 +fory.register_enum(1); +fory.register_enum(2); +``` + +**何时使用 `FORY_ENUM`:** + +- 枚举值不从 0 开始 +- 枚举值不连续(例如 10、50、100) +- 需要在编译时进行名称到值的映射 + +## 线程安全注册 + +对于 `ThreadSafeFory`,在生成线程之前注册类型: + +```cpp +auto fory = Fory::builder().xlang(true).build_thread_safe(); + +// 首先注册所有类型 +fory.register_struct(100); +fory.register_struct(101); + +// 现在可以从多个线程安全使用 +std::thread t1([&]() { + auto result = fory.serialize(obj_a); +}); +std::thread t2([&]() { + auto result = fory.serialize(obj_b); +}); +``` + +## 跨语言注册 + +为了跨语言兼容性,确保: + +1. **相同类型 ID**:在所有语言中使用相同的 ID +2. **兼容类型**:跨语言使用等效类型 + +### Java + +```java +Fory fory = Fory.builder().build(); +fory.register(Person.class, 100); +fory.register(Address.class, 101); +``` + +### Python + +```python +import pyfory + +fory = pyfory.Fory() +fory.register(Person, 100) +fory.register(Address, 101) +``` + +### C++ + +```cpp +auto fory = Fory::builder().xlang(true).build(); +fory.register_struct(100); +fory.register_struct
(101); +``` + +## 内置类型 ID + +内置类型有预分配的类型 ID,不需要注册: + +| 类型 ID | 类型 | +| ------- | -------------------- | +| 0 | NONE | +| 1 | BOOL | +| 2 | INT8 | +| 3 | INT16 | +| 4 | INT32 | +| 5 | VAR_INT32 | +| 6 | INT64 | +| 7 | VAR_INT64 | +| 8 | SLI_INT64 | +| 9 | FLOAT16 | +| 10 | FLOAT32 | +| 11 | FLOAT64 | +| 12 | STRING | +| 13 | LIST | +| 14 | MAP | +| 15 | SET | +| 16 | TIMESTAMP | +| 17 | DURATION | +| 18 | LOCAL_DATE | +| 19 | DECIMAL | +| 20 | BINARY | +| 21 | ARRAY | +| 22 | BOOL_ARRAY | +| 23-28 | INT_ARRAY 变体 | +| 29-31 | FLOAT_ARRAY 变体 | +| 32 | STRUCT | +| 33 | COMPATIBLE_STRUCT | +| 34 | NAMED_STRUCT | +| 35 | NAMED*COMPATIBLE*... | +| 36 | EXT | +| 37 | NAMED_EXT | +| 63 | UNKNOWN | + +## 错误处理 + +注册错误在序列化/反序列化时检查: + +```cpp +// 尝试序列化未注册的类型 +auto result = fory.serialize(unregistered_obj); +if (!result.ok()) { + // 错误:"Type not registered: ..." + std::cerr << result.error().to_string() << std::endl; +} + +// 反序列化时类型 ID 不匹配 +auto result = fory.deserialize(bytes); +if (!result.ok()) { + // 错误:"Type mismatch: expected X, got Y" + std::cerr << result.error().to_string() << std::endl; +} +``` + +## 相关主题 + +- [基础序列化](basic-serialization.md) - 使用已注册的类型 +- [跨语言](xlang-serialization.md) - 跨语言注意事项 +- [支持的类型](supported-types.md) - 所有支持的类型 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/cpp/xlang-serialization.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/cpp/xlang-serialization.md new file mode 100644 index 00000000000..35f33eaa5fe --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/cpp/xlang-serialization.md @@ -0,0 +1,271 @@ +--- +title: Xlang 序列化 +sidebar_position: 2 +id: xlang_serialization +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. +--- + +本页介绍如何使用 Fory 在 C++ 和其他语言之间进行xlang 序列化。 + +## 概述 + +Apache Fory™ 支持在 C++、Java、Python、Go、Rust 和 JavaScript 之间无缝交换数据。xlang(跨语言)模式确保所有支持语言之间的二进制兼容性。 + +## 启用xlang 模式 + +```cpp +#include "fory/serialization/fory.h" + +using namespace fory::serialization; + +auto fory = Fory::builder() + .xlang(true) // 启用xlang 模式 + .build(); +``` + +## 跨语言示例 + +### C++ 生产者 + +```cpp +#include "fory/serialization/fory.h" +#include + +using namespace fory::serialization; + +struct Message { + std::string topic; + int64_t timestamp; + std::map headers; + std::vector payload; + + bool operator==(const Message &other) const { + return topic == other.topic && timestamp == other.timestamp && + headers == other.headers && payload == other.payload; + } +}; +FORY_STRUCT(Message, topic, timestamp, headers, payload); + +int main() { + auto fory = Fory::builder().xlang(true).build(); + fory.register_struct(100); + + Message msg{ + "events.user", + 1699999999000, + {{"content-type", "application/json"}}, + {'h', 'e', 'l', 'l', 'o'} + }; + + auto result = fory.serialize(msg); + if (result.ok()) { + auto bytes = std::move(result).value(); + // 写入文件、通过网络发送等 + std::ofstream file("message.bin", std::ios::binary); + file.write(reinterpret_cast(bytes.data()), bytes.size()); + } + return 0; +} +``` + +### Java 消费者 + +```java +import org.apache.fory.Fory; +import org.apache.fory.config.Language; + +public class Message { + public String topic; + public long timestamp; + public Map headers; + public byte[] payload; +} + +public class Consumer { + public static void main(String[] args) throws Exception { + Fory fory = Fory.builder() + .withLanguage(Language.XLANG) + .build(); + fory.register(Message.class, 100); // 与 C++ 相同的 ID + + byte[] bytes = Files.readAllBytes(Path.of("message.bin")); + Message msg = (Message) fory.deserialize(bytes); + + System.out.println("Topic: " + msg.topic); + System.out.println("Timestamp: " + msg.timestamp); + } +} +``` + +### Python 消费者 + +```python +import pyfory + +class Message: + topic: str + timestamp: int + headers: dict[str, str] + payload: bytes + +fory = pyfory.Fory() +fory.register(Message, type_id=100) # 与 C++ 相同的 ID + +with open("message.bin", "rb") as f: + data = f.read() + +msg = fory.deserialize(data) +print(f"Topic: {msg.topic}") +print(f"Timestamp: {msg.timestamp}") +``` + +## 类型映射 + +### 基本类型 + +| C++ 类型 | Java 类型 | Python 类型 | Go 类型 | Rust 类型 | +| --------- | --------- | ----------- | --------- | --------- | +| `bool` | `boolean` | `bool` | `bool` | `bool` | +| `int8_t` | `byte` | `int` | `int8` | `i8` | +| `int16_t` | `short` | `int` | `int16` | `i16` | +| `int32_t` | `int` | `int` | `int32` | `i32` | +| `int64_t` | `long` | `int` | `int64` | `i64` | +| `float` | `float` | `float` | `float32` | `f32` | +| `double` | `double` | `float` | `float64` | `f64` | + +### 字符串类型 + +| C++ 类型 | Java 类型 | Python 类型 | Go 类型 | Rust 类型 | +| ------------- | --------- | ----------- | -------- | --------- | +| `std::string` | `String` | `str` | `string` | `String` | + +### 集合类型 + +| C++ 类型 | Java 类型 | Python 类型 | Go 类型 | +| ---------------- | ---------- | ----------- | ---------------- | +| `std::vector` | `List` | `list` | `[]T` | +| `std::set` | `Set` | `set` | `map[T]struct{}` | +| `std::map` | `Map` | `dict` | `map[K]V` | + +### 时间类型 + +| C++ 类型 | Java 类型 | Python 类型 | Go 类型 | +| ----------- | ----------- | --------------- | --------------- | +| `Timestamp` | `Instant` | `datetime` | `time.Time` | +| `Duration` | `Duration` | `timedelta` | `time.Duration` | +| `LocalDate` | `LocalDate` | `datetime.date` | `time.Time` | + +## 字段顺序要求 + +**关键:** 字段将按照其 snake_cased 字段名排序,转换后的名称必须在各语言之间保持一致 + +### C++ + +```cpp +struct Person { + std::string name; // 字段 0 + int32_t age; // 字段 1 + std::string email; // 字段 2 +}; +FORY_STRUCT(Person, name, age, email); // 顺序很重要! +``` + +### Java + +```java +public class Person { + public String name; // 字段 0 + public int age; // 字段 1 + public String email; // 字段 2 +} +``` + +### Python + +```python +class Person: + name: str # 字段 0 + age: int # 字段 1 + email: str # 字段 2 +``` + +## 类型 ID 一致性 + +所有语言必须使用相同的类型 ID: + +```cpp +// C++ +fory.register_struct(100); +fory.register_struct
(101); +fory.register_struct(102); +``` + +```java +// Java +fory.register(Person.class, 100); +fory.register(Address.class, 101); +fory.register(Order.class, 102); +``` + +```python +# Python +fory.register(Person, type_id=100) +fory.register(Address, type_id=101) +fory.register(Order, type_id=102) +``` + +## Compatible 模式 + +用于跨语言边界的 schema 演化: + +```cpp +// 使用 compatible 模式的 C++ +auto fory = Fory::builder() + .xlang(true) + .compatible(true) // 启用 schema 演化 + .build(); +``` + +Compatible 模式允许: + +- 添加新字段(带默认值) +- 删除未使用的字段 +- 重排字段顺序 + +## 故障排查 + +### 类型不匹配错误 + +``` +Error: Type mismatch: expected 100, got 101 +``` + +**解决方案:** 确保类型 ID 在所有语言中匹配。 + +### 编码错误 + +``` +Error: Invalid UTF-8 sequence +``` + +**解决方案:** 确保所有语言中的字符串都是有效的 UTF-8。 + +## 相关主题 + +- [配置](configuration.md) - 构建器选项 +- [类型注册](type-registration.md) - 注册类型 +- [支持的类型](supported-types.md) - 类型兼容性 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/csharp/_category_.json b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/csharp/_category_.json new file mode 100644 index 00000000000..c5e03ee3c59 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/csharp/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "C#", + "position": 7, + "collapsible": true, + "collapsed": true +} diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/csharp/basic-serialization.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/csharp/basic-serialization.md new file mode 100644 index 00000000000..ce8cd2e5941 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/csharp/basic-serialization.md @@ -0,0 +1,131 @@ +--- +title: 基础序列化 +sidebar_position: 1 +id: basic_serialization +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. +--- + +本页介绍 Apache Fory™ C# 的强类型序列化 API。 + +## 对象图序列化 + +在类或结构体上使用 `[ForyObject]`,并在使用前完成注册。 + +```csharp +using Apache.Fory; + +[ForyObject] +public sealed class Address +{ + public string Street { get; set; } = string.Empty; + public int Zip { get; set; } +} + +[ForyObject] +public sealed class Person +{ + public long Id { get; set; } + public string Name { get; set; } = string.Empty; + public string? Nickname { get; set; } + public List Scores { get; set; } = []; + public List
Addresses { get; set; } = []; +} + +Fory fory = Fory.Builder().Build(); +fory.Register
(100); +fory.Register(101); + +Person person = new() +{ + Id = 42, + Name = "Alice", + Nickname = null, + Scores = [10, 20, 30], + Addresses = [new Address { Street = "Main", Zip = 94107 }], +}; + +byte[] payload = fory.Serialize(person); +Person decoded = fory.Deserialize(payload); +``` + +## 强类型 API + +### 使用字节数组进行 Serialize / Deserialize + +```csharp +byte[] payload = fory.Serialize(value); +MyType decoded = fory.Deserialize(payload); +``` + +### 从 `ReadOnlySpan` 反序列化 + +```csharp +ReadOnlySpan span = payload; +MyType decoded = fory.Deserialize(span); +``` + +### 以流式方式消费帧 + +```csharp +using System.Buffers; + +ReadOnlySequence sequence = GetFramedSequence(); +MyType first = fory.Deserialize(ref sequence); +MyType second = fory.Deserialize(ref sequence); +``` + +## 通过泛型对象 API 处理动态载荷 + +当编译期类型未知或载荷包含异构对象时,可以配合 `object?` 使用泛型 API。 + +```csharp +Dictionary value = new() +{ + ["k1"] = 7, + [2] = "v2", + [true] = null, +}; + +byte[] payload = fory.Serialize(value); +object? decoded = fory.Deserialize(payload); +``` + +## Buffer Writer API + +直接序列化到 `IBufferWriter` 目标。 + +```csharp +using System.Buffers; + +ArrayBufferWriter writer = new(); +fory.Serialize(writer, value); + +ArrayBufferWriter dynamicWriter = new(); +fory.Serialize(dynamicWriter, value); +``` + +## 说明 + +- 复用同一个 `Fory` 或 `ThreadSafeFory` 实例可以获得更好的性能。 +- 基础类型和集合类型不需要用户手动注册。 +- 用户自定义的 `[ForyObject]` 类型和自定义序列化器类型应显式注册。 + +## 相关主题 + +- [类型注册](type-registration.md) +- [支持的类型](supported-types.md) +- [引用](references.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/csharp/configuration.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/csharp/configuration.md new file mode 100644 index 00000000000..b4a21f22696 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/csharp/configuration.md @@ -0,0 +1,178 @@ +--- +title: 配置 +sidebar_position: 2 +id: configuration +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. +--- + +本页介绍 Apache Fory™ C# 的 `ForyBuilder` 选项和默认配置值。 +`Config` 是由 `ForyBuilder` 创建的不可变运行时快照。 + +## 构建运行时 + +```csharp +using Apache.Fory; + +Fory fory = Fory.Builder().Build(); +ThreadSafeFory threadSafe = Fory.Builder().BuildThreadSafe(); +``` + +## 默认配置 + +`Fory.Builder().Build()` 使用以下默认值: + +| 选项 | 默认值 | 说明 | +| --------------------------------- | ------- | --------------------------------- | +| `TrackRef` | `false` | 默认关闭引用跟踪 | +| `Compatible` | `false` | Schema 一致模式,不写入演进元数据 | +| `CheckStructVersion` | `false` | 默认关闭结构体 schema hash 校验 | +| `MaxDepth` | `20` | 动态对象图的最大嵌套深度 | +| `MaxTypeFields` | `512` | 一个收到的 struct metadata body 最大字段数 | +| `MaxTypeMetaBytes` | `4096` | 一个收到的 metadata body 最大编码字节数 | +| `MaxSchemaVersionsPerType` | `10` | 一个逻辑类型最大远端 metadata 版本数 | +| `MaxAverageSchemaVersionsPerType` | `3` | 所有远端类型的平均 metadata 版本数 | + +## 构建器选项 + +C# 始终使用与 xlang 兼容的帧头,因此 `ForyBuilder` 不提供单独的 `Xlang(...)` 开关。 + +### `TrackRef(bool enabled = false)` + +为共享对象图和循环对象图启用引用跟踪。 + +```csharp +Fory fory = Fory.Builder() + .TrackRef(true) + .Build(); +``` + +### `Compatible(bool enabled = false)` + +启用 Schema 演进模式。 + +```csharp +Fory fory = Fory.Builder() + .Compatible(true) + .Build(); +``` + +### `CheckStructVersion(bool enabled = false)` + +为生成的结构体序列化器启用严格的 schema hash 校验。 + +```csharp +Fory fory = Fory.Builder() + .CheckStructVersion(true) + .Build(); +``` + +### `MaxDepth(int value)` + +设置动态对象图允许的最大嵌套深度。 + +```csharp +Fory fory = Fory.Builder() + .MaxDepth(32) + .Build(); +``` + +`value` 必须大于 `0`。 + +### `MaxTypeFields(int value)` + +设置一个收到的远端 struct metadata body 中可接受的最大字段数。 + +```csharp +Fory fory = Fory.Builder() + .MaxTypeFields(512) + .Build(); +``` + +### `MaxTypeMetaBytes(int value)` + +设置一个收到的 TypeMeta body 可接受的最大编码 body 字节数,不包含 8 字节 header 和扩展 size varint。 + +```csharp +Fory fory = Fory.Builder() + .MaxTypeMetaBytes(4096) + .Build(); +``` + +### `MaxSchemaVersionsPerType(int value)` + +设置一个逻辑类型可接受的最大远端 metadata 版本数。 + +```csharp +Fory fory = Fory.Builder() + .MaxSchemaVersionsPerType(10) + .Build(); +``` + +### `MaxAverageSchemaVersionsPerType(int value)` + +设置所有已接受远端类型的平均 metadata 版本数限制。有效全局下限为 `8192` 个 schema。 + +```csharp +Fory fory = Fory.Builder() + .MaxAverageSchemaVersionsPerType(3) + .Build(); +``` + +## 常见配置 + +### 追求速度的 Schema 一致服务 + +```csharp +Fory fory = Fory.Builder() + .TrackRef(false) + .Compatible(false) + .Build(); +``` + +### 兼容的跨语言服务 + +```csharp +Fory fory = Fory.Builder() + .Compatible(true) + .TrackRef(true) + .Build(); +``` + +### 线程安全的服务实例 + +```csharp +ThreadSafeFory fory = Fory.Builder() + .Compatible(true) + .TrackRef(true) + .BuildThreadSafe(); +``` + +## 安全 + +安全相关配置: + +- 在反序列化不可信 payload 前,只注册预期的类型。 +- 对 intentional same-schema payload,将 `CheckStructVersion(true)` 与 `Compatible(false)` 配合使用。 +- 设置 `MaxDepth(...)` 以拒绝异常深的动态对象图。 +- 除非数据不是恶意输入,且可信 peer 会发送更大的 metadata 或大量 schema 版本,否则保持远端 schema metadata 限制的默认值。 +- 对不可信输入,优先使用生成或已注册的具体 model,避免宽泛的动态字段。 + +## 相关主题 + +- [基础序列化](basic_serialization) +- [Schema 演进](schema_evolution) +- [线程安全](thread_safety) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/csharp/custom-serializers.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/csharp/custom-serializers.md new file mode 100644 index 00000000000..62264114250 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/csharp/custom-serializers.md @@ -0,0 +1,84 @@ +--- +title: 自定义序列化器 +sidebar_position: 4 +id: custom_serializers +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. +--- + +当某个类型不是通过 `[ForyObject]` 生成,或者需要专门的编码方式时,可以使用自定义序列化器。 + +## 实现 `Serializer` + +```csharp +using Apache.Fory; + +public sealed class Point +{ + public int X { get; set; } + public int Y { get; set; } +} + +public sealed class PointSerializer : Serializer +{ + public override Point DefaultValue => new(); + + public override void WriteData(WriteContext context, in Point value, bool hasGenerics) + { + context.Writer.WriteVarInt32(value.X); + context.Writer.WriteVarInt32(value.Y); + } + + public override Point ReadData(ReadContext context) + { + return new Point + { + X = context.Reader.ReadVarInt32(), + Y = context.Reader.ReadVarInt32(), + }; + } +} +``` + +## 注册序列化器 + +```csharp +Fory fory = Fory.Builder().Build(); +fory.Register(200); + +Point value = new() { X = 10, Y = 20 }; +byte[] payload = fory.Serialize(value); +Point decoded = fory.Deserialize(payload); +``` + +## 序列化器行为说明 + +- `WriteData` / `ReadData` 只负责处理载荷内容。 +- 除非显式重写,否则引用标志和类型信息由基类 `Serializer.Write` / `Read` 处理。 +- `DefaultValue` 用于空值或默认值回退路径。 + +## 最佳实践 + +1. 保持序列化器逻辑确定且读写对称。 +2. 对整数密集型载荷要有意识地选择 varint、fixed 或 tagged 编码。 +3. 在所有读写端都注册自定义序列化器。 +4. 对常规领域模型优先使用 `[ForyObject]` 生成的序列化器。 + +## 相关主题 + +- [类型注册](type-registration.md) +- [字段配置](schema-metadata.md) +- [故障排查](troubleshooting.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/csharp/grpc-support.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/csharp/grpc-support.md new file mode 100644 index 00000000000..d68d388e6e1 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/csharp/grpc-support.md @@ -0,0 +1,130 @@ +--- +title: gRPC 支持 +sidebar_position: 10 +id: grpc_support +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. +--- + +Fory 可以为包含 service 定义的 schema 生成 C# gRPC service companion。生成代码使用标准 .NET +gRPC API,而 request/response 对象使用 Fory payload 编码。 + +当 RPC 两端都由同一份 Fory IDL、protobuf IDL 或 FlatBuffers IDL 生成,并且你希望使用 +gRPC 传输语义与 Fory payload 编码时,可以使用这种模式。如果 API 必须被通用 protobuf client +消费,请使用标准 protobuf gRPC 代码生成。 + +## 添加依赖 + +Server project: + +```xml + + + + +``` + +Client project: + +```xml + + + + + +``` + +根据应用使用的 .NET gRPC hosting/client package 调整依赖。`Apache.Fory` 不会把 gRPC 作为硬依赖。 + +## 定义 Service + +```protobuf +package demo.greeter; + +message HelloRequest { + string name = 1; +} + +message HelloReply { + string reply = 1; +} + +service Greeter { + rpc SayHello (HelloRequest) returns (HelloReply); +} +``` + +生成 C# model 和 gRPC companion: + +```bash +foryc service.fdl --csharp_out=./generated/csharp --grpc +``` + +生成 companion 会包含 service base、client、method descriptor,以及 Fory-backed request/response +marshaller。 + +## 实现 Server + +```csharp +using Grpc.Core; + +public sealed class GreeterService : Greeter.GreeterBase +{ + public override Task SayHello( + HelloRequest request, + ServerCallContext context) + { + return Task.FromResult(new HelloReply + { + Reply = $"Hello, {request.Name}" + }); + } +} +``` + +在 ASP.NET Core gRPC host 中注册生成 service,与普通 .NET gRPC service 一样管理 TLS、认证、 +interceptor、deadline 和 cancellation。 + +## 创建 Client + +```csharp +using Grpc.Net.Client; + +using var channel = GrpcChannel.ForAddress("https://localhost:50051"); +var client = new Greeter.GreeterClient(channel); +var reply = await client.SayHelloAsync(new HelloRequest { Name = "Fory" }); +Console.WriteLine(reply.Reply); +``` + +生成 client 使用 Fory marshaller 编码 request/response。Call option、metadata、deadline 和 +credential 仍遵循 .NET gRPC 行为。 + +## Streaming RPC + +Fory service 支持 unary、server-streaming、client-streaming 和 bidirectional streaming。生成 C# +代码使用 .NET gRPC 的 `AsyncUnaryCall`、`AsyncServerStreamingCall`、`AsyncClientStreamingCall` +和 `AsyncDuplexStreamingCall` 形态。 + +## 故障排查 + +### 缺少 `Grpc.*` 类型 + +添加应用所需的 .NET gRPC package,例如 `Grpc.AspNetCore` 或 `Grpc.Net.Client`。 + +### Protobuf Client 无法读取响应 + +Fory gRPC 使用 Fory 二进制协议 payload,不是 protobuf wire-format message。请在两端使用同一份 +schema 生成的 Fory gRPC companion。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/csharp/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/csharp/index.md new file mode 100644 index 00000000000..5e7137c8454 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/csharp/index.md @@ -0,0 +1,104 @@ +--- +title: C# 序列化指南 +sidebar_position: 0 +id: serialization_index +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. +--- + +Apache Fory™ C# 是面向 .NET 的高性能跨语言序列化运行时。它提供对象图序列化、Schema 演进、泛型对象载荷支持,以及面向并发负载的线程安全封装。 + +## 为什么选择 Fory C#? + +- 面向 .NET 8+ 的高性能二进制序列化 +- 与 Java、Python、C++、Go、Rust 和 JavaScript 中的 Fory 实现保持跨语言兼容 +- 基于 source generator 的 `[ForyObject]` 类型序列化器 +- 可选引用跟踪,支持共享对象图和循环对象图 +- 面向 Schema 演进的兼容模式 +- 面向多线程服务的线程安全运行时 `ThreadSafeFory` + +## 快速开始 + +### 要求 + +- .NET SDK 8.0+ +- C# 语言版本 12+ + +### 从 NuGet 安装 + +引用单个 `Apache.Fory` 包即可。它同时包含运行时和 `[ForyObject]` 类型所需的 source generator。 + +```xml + + + +``` + +### 基础示例 + +```csharp +using Apache.Fory; + +[ForyObject] +public sealed class User +{ + public long Id { get; set; } + public string Name { get; set; } = string.Empty; + public string? Email { get; set; } +} + +Fory fory = Fory.Builder().Build(); +fory.Register(1); + +User user = new() +{ + Id = 1, + Name = "Alice", + Email = "alice@example.com", +}; + +byte[] payload = fory.Serialize(user); +User decoded = fory.Deserialize(payload); +``` + +## 核心 API + +- `Serialize(in T value)` / `Deserialize(...)` +- `Serialize(...)` / `Deserialize(...)`,用于动态载荷 +- `Register(uint typeId)` 以及基于命名空间和名称的注册 API +- `Register(...)`,用于自定义序列化器 + +## 文档 + +| 主题 | 说明 | +| --------------------------------------------- | ---------------------------------- | +| [配置](configuration.md) | 构建器选项与运行时模式 | +| [基础序列化](basic-serialization.md) | 强类型和动态序列化 API | +| [类型注册](type-registration.md) | 注册用户类型和自定义序列化器 | +| [自定义序列化器](custom-serializers.md) | 实现 `Serializer` | +| [字段配置](schema-metadata.md) | `[Field]` 特性与整数编码选项 | +| [引用](references.md) | 共享引用与循环引用处理 | +| [Schema 演进](schema-evolution.md) | 兼容模式行为 | +| [跨语言](xlang-serialization.md) | 互操作性指导 | +| [支持的类型](supported-types.md) | 内置类型与生成类型支持 | +| [线程安全](thread-safety.md) | `Fory` 与 `ThreadSafeFory` 的用法 | +| [故障排查](troubleshooting.md) | 常见错误与调试步骤 | + +## 相关资源 + +- [跨语言序列化规范](../../specification/xlang_serialization_spec.md) +- [跨语言指南](../xlang/index.md) +- [C# 源码目录](https://github.com/apache/fory/tree/main/csharp) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/csharp/references.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/csharp/references.md new file mode 100644 index 00000000000..bdbf56d28f4 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/csharp/references.md @@ -0,0 +1,72 @@ +--- +title: 引用 +sidebar_position: 6 +id: references +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. +--- + +当启用 `TrackRef(true)` 时,Apache Fory™ C# 可以保留共享引用和循环引用。 + +## 启用引用跟踪 + +```csharp +Fory fory = Fory.Builder() + .TrackRef(true) + .Build(); +``` + +启用后: + +- 共享对象身份会被保留。 +- 循环对象图可以安全地序列化和反序列化。 + +## 循环引用示例 + +```csharp +using Apache.Fory; + +[ForyObject] +public sealed class Node +{ + public int Value { get; set; } + public Node? Next { get; set; } +} + +Fory fory = Fory.Builder() + .TrackRef(true) + .Build(); +fory.Register(200); + +Node node = new() { Value = 7 }; +node.Next = node; + +byte[] payload = fory.Serialize(node); +Node decoded = fory.Deserialize(payload); + +// The cycle is preserved. +System.Diagnostics.Debug.Assert(object.ReferenceEquals(decoded, decoded.Next)); +``` + +## 何时使用 `TrackRef(false)` + +对于树状、无环且不关心引用身份的数据,`TrackRef(false)` 往往更快。 + +## 相关主题 + +- [配置](configuration.md) +- [基础序列化](basic-serialization.md) +- [线程安全](thread-safety.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/csharp/schema-evolution.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/csharp/schema-evolution.md new file mode 100644 index 00000000000..6531eb54067 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/csharp/schema-evolution.md @@ -0,0 +1,90 @@ +--- +title: Schema 演进 +sidebar_position: 7 +id: schema_evolution +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. +--- + +Apache Fory™ C# 在 `Compatible(true)` 模式下支持 Schema 演进。 + +## 兼容模式 + +```csharp +Fory fory = Fory.Builder() + .Compatible(true) + .Build(); +``` + +兼容模式会写入类型元信息,使结构体定义不同的读写端也能互操作。 + +## 示例:新增一个字段 + +```csharp +using Apache.Fory; + +[ForyObject] +public sealed class OneStringField +{ + public string? F1 { get; set; } +} + +[ForyObject] +public sealed class TwoStringField +{ + public string F1 { get; set; } = string.Empty; + public string F2 { get; set; } = string.Empty; +} + +Fory fory1 = Fory.Builder().Compatible(true).Build(); +fory1.Register(200); + +Fory fory2 = Fory.Builder().Compatible(true).Build(); +fory2.Register(200); + +byte[] payload = fory1.Serialize(new OneStringField { F1 = "hello" }); +TwoStringField evolved = fory2.Deserialize(payload); + +// F2 falls back to default value on reader side. +System.Diagnostics.Debug.Assert(evolved.F1 == "hello"); +System.Diagnostics.Debug.Assert(evolved.F2 == string.Empty); +``` + +## 带版本校验的 Schema 一致模式 + +如果你需要严格的 Schema 身份校验,而不是演进行为: + +```csharp +Fory strict = Fory.Builder() + .Compatible(false) + .CheckStructVersion(true) + .Build(); +``` + +这种模式会在 schema hash 不匹配时抛出异常。 + +## 最佳实践 + +1. 对独立部署的服务启用 `Compatible(true)`。 +2. 在不同版本之间保持稳定的 type ID。 +3. 新增字段时提供安全的默认值。 +4. 如果要求严格匹配,使用 `CheckStructVersion(true)`。 + +## 相关主题 + +- [配置](configuration.md) +- [类型注册](type-registration.md) +- [故障排查](troubleshooting.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/csharp/schema-metadata.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/csharp/schema-metadata.md new file mode 100644 index 00000000000..d54cdef75d5 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/csharp/schema-metadata.md @@ -0,0 +1,93 @@ +--- +title: Schema 元数据 +sidebar_position: 4 +id: schema_metadata +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. +--- + +本页介绍 C# 生成序列化器的字段级序列化器配置。 + +## `[ForyObject]` 和 `[ForyField]` {#foryobject-and-foryfield} + +使用 `[ForyObject]` 启用源码生成的序列化器。使用 `[ForyField]` 分配一个可选、稳定、非负的字段 ID,或覆盖字段使用的 Fory schema 类型。 + +```csharp +using Apache.Fory; +using S = Apache.Fory.Schema.Types; + +[ForyObject] +public sealed class Metrics +{ + [ForyField(Type = typeof(S.UInt32))] + public uint Count { get; set; } + + [ForyField(Type = typeof(S.Tagged))] + public ulong TraceId { get; set; } + + public long LatencyMicros { get; set; } +} +``` + +`Id` 是可选的。省略时,兼容模式仍会按名称匹配字段。 + +```csharp +using Apache.Fory; +using S = Apache.Fory.Schema.Types; + +[ForyObject] +public sealed class NestedMetrics +{ + [ForyField(Type = typeof(S.Map, S.List>>))] + public Dictionary?> Values { get; set; } = []; + + [ForyField(3, Type = typeof(S.UInt64))] + public ulong StableCount { get; set; } +} +``` + +## Schema 描述符类型 {#schema-descriptor-types} + +Schema 描述符位于 `Apache.Fory.Schema.Types` 下,并且只作为元数据使用。它们不会取代普通的 C# 承载类型。 + +常见标量描述符包括: + +- `S.Int32`, `S.UInt32` +- `S.Int64`, `S.UInt64` +- `S.Float16`, `S.BFloat16`, `S.Float32`, `S.Float64` + +容器描述符可以组合: + +- `S.Fixed` 和 `S.Tagged`,用于标量整数编码 +- `S.List` +- `S.Set` +- `S.Map` +- `S.Array` + +密集数组字段使用 `S.Array`,例如 `S.Array` 或 `S.Array`。 + +可空性来自 C# 承载类型。列表元素可空时使用 `List`,map 需要可空键时使用 `NullableKeyDictionary`。 + +## 可空性和引用跟踪 {#nullability-and-reference-tracking} + +- 字段可空性来自 C# 类型可空性(`string?`、可空值类型等)。 +- 引用跟踪由运行时的 `ForyBuilder.TrackRef(...)` 控制。 + +## 相关主题 {#related-topics} + +- [配置](configuration.md) +- [Schema 演进](schema-evolution.md) +- [支持的类型](supported-types.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/csharp/supported-types.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/csharp/supported-types.md new file mode 100644 index 00000000000..14d59a1fb64 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/csharp/supported-types.md @@ -0,0 +1,97 @@ +--- +title: 支持的类型 +sidebar_position: 9 +id: supported_types +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. +--- + +本页汇总 Apache Fory™ C# 的内置类型支持和生成类型支持。 + +## 基础类型 + +| C# 类型 | 说明 | +| ------- | ---- | +| `bool` | 支持 | +| `sbyte`, `short`, `int`, `long` | 支持 | +| `byte`, `ushort`, `uint`, `ulong` | 支持 | +| `float`, `double` | 支持 | +| `string` | 支持 | +| `byte[]` | 支持 | +| 可空基础类型,例如 `int?` | 支持 | + +## 数组 + +- 基础数值数组,例如 `bool[]`、`int[]`、`ulong[]` +- `byte[]` +- 通过集合序列化器支持的一般数组 `T[]` + +## 集合 + +### 类 List + +- `List` +- `LinkedList` +- `Queue` +- `Stack` + +### 类 Set + +- `HashSet` +- `SortedSet` +- `ImmutableHashSet` + +### 类 Map + +- `Dictionary` +- `SortedDictionary` +- `SortedList` +- `ConcurrentDictionary` +- `NullableKeyDictionary` + +## 时间类型 + +| C# 类型 | 编码类型 | +| ------- | -------- | +| `DateOnly` | `Date` | +| `DateTime` | `Timestamp` | +| `DateTimeOffset` | `Timestamp` | +| `TimeSpan` | `Duration` | + +## 用户类型 + +- 通过 source generator 生成序列化器的 `[ForyObject]` 类、结构体、枚举 +- 通过 `Register(...)` 注册的自定义序列化器类型 +- `Union` / `Union2<...>` 强类型联合支持 + +## 动态类型 + +通过 `Serialize` / `Deserialize` 处理动态对象载荷时,支持: + +- 基础值和对象值 +- 动态列表、集合、映射 +- 嵌套的动态结构 + +## 说明 + +- 用户自定义类型应显式注册。 +- 跨语言使用时,请遵循 [xlang 指南](../xlang/index.md)。 + +## 相关主题 + +- [基础序列化](basic-serialization.md) +- [类型注册](type-registration.md) +- [跨语言](xlang-serialization.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/csharp/thread-safety.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/csharp/thread-safety.md new file mode 100644 index 00000000000..5c2a33917ba --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/csharp/thread-safety.md @@ -0,0 +1,73 @@ +--- +title: 线程安全 +sidebar_position: 10 +id: thread_safety +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. +--- + +Apache Fory™ C# 提供两种运行时形态,它们的线程保证不同。 + +## `Fory`(单线程运行时) + +`Fory` 针对单线程复用做了优化,不能被多个线程并发使用。 + +```csharp +Fory fory = Fory.Builder().Build(); +``` + +如果你显式管理线程亲和性,可以为每个线程分配一个 `Fory` 实例。 + +## `ThreadSafeFory`(并发封装) + +`ThreadSafeFory` 为每个线程封装一个 `Fory` 实例,并对外提供线程安全 API。 + +```csharp +using Apache.Fory; + +using ThreadSafeFory fory = Fory.Builder() + .Compatible(true) + .TrackRef(true) + .BuildThreadSafe(); + +fory.Register(100); + +Parallel.For(0, 64, i => +{ + byte[] payload = fory.Serialize(i); + int decoded = fory.Deserialize(payload); +}); +``` + +## 注册行为 + +- `ThreadSafeFory.Register(...)` 会集中保存注册信息。 +- 已存在的线程内运行时会被更新。 +- 新线程会自动收到此前所有注册信息。 + +## 释放资源 + +`ThreadSafeFory` 实现了 `IDisposable`,在不再使用时应及时释放。 + +```csharp +using ThreadSafeFory fory = Fory.Builder().BuildThreadSafe(); +``` + +## 相关主题 + +- [配置](configuration.md) +- [类型注册](type-registration.md) +- [引用](references.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/csharp/troubleshooting.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/csharp/troubleshooting.md new file mode 100644 index 00000000000..0a9212b3d52 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/csharp/troubleshooting.md @@ -0,0 +1,92 @@ +--- +title: 故障排查 +sidebar_position: 11 +id: troubleshooting +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. +--- + +本页介绍常见的 C# 运行时问题及其解决方法。 + +## `TypeNotRegisteredException` + +**现象**:`Type not registered: ...` + +**原因**:用户类型在没有注册的情况下被序列化或反序列化。 + +**修复方式**: + +```csharp +Fory fory = Fory.Builder().Build(); +fory.Register(100); +``` + +请确保读写两端使用相同的 type-ID 或名称映射。 + +## `InvalidDataException: xlang bitmap mismatch` + +**原因**:载荷不是 xlang Fory 帧,或者它来自不输出 C# 所要求 xlang 头的对端或运行时模式。 + +**修复方式**:确保载荷由与 xlang 兼容的 Fory 运行时生成。C# 始终要求 xlang 头,并且不提供单独的 `Xlang(...)` 构建器选项。 + +```csharp +Fory writer = Fory.Builder().Compatible(true).Build(); +Fory reader = Fory.Builder().Compatible(true).Build(); +``` + +## 严格模式下的 Schema 版本不匹配 + +**现象**:反序列化生成的结构体类型时抛出 `InvalidDataException`。 + +**原因**:`Compatible(false)` 配合 `CheckStructVersion(true)` 时会要求 schema hash 完全一致。 + +**可选修复方式**: + +- 启用 `Compatible(true)` 以支持 Schema 演进。 +- 保持写端和读端的模型定义同步。 + +## 循环引用失败 + +**现象**:类似栈溢出的递归问题,或者对象图重建异常。 + +**原因**:循环对象图在 `TrackRef(false)` 下运行。 + +**修复方式**: + +```csharp +Fory fory = Fory.Builder().TrackRef(true).Build(); +``` + +## 并发问题 + +**原因**:在多个线程之间共享同一个 `Fory` 实例。 + +**修复方式**:改用 `BuildThreadSafe()`。 + +## 验证命令 + +从仓库根目录运行 C# 测试: + +```bash +cd csharp +dotnet test Fory.sln -c Release +``` + +## 相关主题 + +- [配置](configuration) +- [Schema 演进](schema_evolution) +- [线程安全](thread_safety) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/csharp/type-registration.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/csharp/type-registration.md new file mode 100644 index 00000000000..cd8af14949f --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/csharp/type-registration.md @@ -0,0 +1,82 @@ +--- +title: 类型注册 +sidebar_position: 3 +id: type_registration +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. +--- + +本页介绍如何在 Apache Fory™ C# 中注册用户类型。 + +## 按数字 type ID 注册 + +显式 ID 可以提供紧凑且稳定的跨服务映射。 + +```csharp +Fory fory = Fory.Builder().Build(); +fory.Register(100); +fory.Register(101); +``` + +## 按类型名注册 + +如果你更倾向于使用符号化映射,可以按命名空间和类型名注册。 + +```csharp +Fory fory = Fory.Builder().Build(); +fory.Register("com.example", "User"); +``` + +也可以使用简写重载: + +```csharp +fory.Register("User"); +``` + +## 注册自定义序列化器 + +```csharp +Fory fory = Fory.Builder().Build(); +fory.Register(200); +``` + +同样支持基于命名空间的自定义序列化器注册: + +```csharp +fory.Register("com.example", "MyType"); +``` + +## 线程安全注册 + +`ThreadSafeFory` 提供相同的注册 API。注册信息会传播到各个线程内运行时。 + +```csharp +using ThreadSafeFory fory = Fory.Builder().BuildThreadSafe(); +fory.Register(100); +fory.Register(101); +``` + +## 注册规则 + +- 在写端和读端都要注册用户定义类型。 +- 在服务和语言之间保持 ID 或名称映射一致。 +- 在高频序列化负载开始前完成注册,避免运行时缺失。 + +## 相关主题 + +- [基础序列化](basic-serialization.md) +- [自定义序列化器](custom-serializers.md) +- [跨语言](xlang-serialization.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/csharp/xlang-serialization.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/csharp/xlang-serialization.md new file mode 100644 index 00000000000..7ad3133cc96 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/csharp/xlang-serialization.md @@ -0,0 +1,105 @@ +--- +title: Xlang 序列化 +sidebar_position: 2 +id: xlang_serialization +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. +--- + +Apache Fory™ C# 支持与其他 Fory 运行时进行xlang 序列化。 + +## 跨语言运行时 + +C# 始终会读写 xlang 帧头。它没有单独的 `Xlang(...)` 构建器选项,因此互操作代码只需要配置兼容模式、引用跟踪等其余运行时行为。 + +```csharp +Fory fory = Fory.Builder() + .Compatible(true) + .Build(); +``` + +## 使用稳定 ID 注册 + +```csharp +[ForyObject] +public sealed class Person +{ + public string Name { get; set; } = string.Empty; + public int Age { get; set; } +} + +Fory fory = Fory.Builder() + .Compatible(true) + .Build(); + +fory.Register(100); +``` + +请在所有语言中保持相同的 ID 映射。 + +## 按命名空间和类型名注册 + +```csharp +fory.Register("com.example", "Person"); +``` + +## 跨语言示例 + +### C#(序列化端) + +```csharp +Person person = new() { Name = "Alice", Age = 30 }; +byte[] payload = fory.Serialize(person); +``` + +### Java(反序列化端) + +```java +Fory fory = Fory.builder() + .withLanguage(Language.XLANG) + .withRefTracking(true) + .build(); + +fory.register(Person.class, 100); +Person value = (Person) fory.deserialize(payloadFromCSharp); +``` + +### Python(反序列化端) + +```python +import pyfory + +fory = pyfory.Fory(xlang=True, ref=True) +fory.register_type(Person, type_id=100) +value = fory.deserialize(payload_from_csharp) +``` + +## 类型映射参考 + +完整映射请参见 [xlang 指南](../xlang/)。 + +## 最佳实践 + +1. 保持 type ID 稳定并做好文档记录。 +2. 在滚动升级时启用 `Compatible(true)`。 +3. 在读写两端都注册所有用户类型。 +4. 用真实载荷做端到端回环验证。 + +## 相关主题 + +- [类型注册](type_registration) +- [Schema 演进](schema_evolution) +- [支持的类型](supported_types) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/dart/_category_.json b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/dart/_category_.json new file mode 100644 index 00000000000..b2d06cfcd25 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/dart/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "Dart", + "position": 9, + "collapsible": true, + "collapsed": true +} diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/dart/basic-serialization.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/dart/basic-serialization.md new file mode 100644 index 00000000000..b5ce9821236 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/dart/basic-serialization.md @@ -0,0 +1,144 @@ +--- +title: 基础序列化 +sidebar_position: 2 +id: dart_basic_serialization +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. +--- + +本页介绍如何使用 Apache Fory™ Dart 对值进行序列化和反序列化。 + +## 创建 `Fory` 实例 + +创建一个实例并复用它。每次调用都新建 `Fory` 只会浪费资源。 + +```dart +import 'package:fory/fory.dart'; + +final fory = Fory(); +``` + +## 序列化和反序列化带注解的类型 + +```dart +import 'package:fory/fory.dart'; + +part 'person.fory.dart'; + +@ForyStruct() +class Person { + Person(); + + String name = ''; + Int32 age = Int32(0); +} + +void main() { + final fory = Fory(); + PersonFory.register( + fory, + Person, + namespace: 'example', + typeName: 'Person', + ); + + final person = Person() + ..name = 'Ada' + ..age = Int32(36); + + final bytes = fory.serialize(person); + final roundTrip = fory.deserialize(bytes); + print(roundTrip.name); +} +``` + +`deserialize` 会返回并转换为 `T` 的解码结果。如果载荷描述的类型与 `T` 不一致,就会抛出异常。 + +## Null 值 + +支持直接序列化 `null`: + +```dart +final fory = Fory(); +final bytes = fory.serialize(null); +final value = fory.deserialize(bytes); +``` + +## 序列化集合和动态载荷 + +你可以直接序列化集合值: + +```dart +final fory = Fory(); +final bytes = fory.serialize([ + 'hello', + Int32(42), + true, +]); +final value = fory.deserialize>(bytes); +``` + +对于异构集合,请反序列化为 `Object?`、`List` 或 `Map`。 + +## 引用跟踪 + +默认情况下,Fory 不会跟踪对象标识。如果同一个对象在列表中出现两次,它会被序列化两次。当数据包含共享引用或循环结构时,请启用引用跟踪。 + +对于顶层集合: + +```dart +final fory = Fory(); +final shared = String.fromCharCodes('shared'.codeUnits); +final bytes = fory.serialize([shared, shared], trackRef: true); +final roundTrip = fory.deserialize>(bytes); +print(identical(roundTrip[0], roundTrip[1])); // true +``` + +对于生成结构体中的字段,请改用该字段上的 `@ForyField(ref: true)`。 + +## 复用缓冲区 + +如果你想避免每次调用都分配新的 `Uint8List`,可以配合显式 `Buffer` 使用 `serializeTo` 和 `deserializeFrom`: + +```dart +final fory = Fory(); +final buffer = Buffer(); + +fory.serializeTo(Int32(42), buffer); +final value = fory.deserializeFrom(buffer); +``` + +这是性能优化手段。对大多数应用来说,默认的 `serialize` / `deserialize` 就足够了。 + +## 在序列化前注册类型 + +在序列化自定义类或枚举之前,必须先把它注册到 `Fory` 中。生成代码会让这件事变得很简单: + +```dart +PersonFory.register( + fory, + Person, + id: 100, +); +``` + +如果跳过注册,运行时会得到 `Type ... is not registered` 错误。参见 [类型注册](type-registration.md) 和 [代码生成](code-generation.md)。 + +## 相关主题 + +- [配置](configuration.md) +- [类型注册](type-registration.md) +- [字段配置](schema-metadata.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/dart/code-generation.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/dart/code-generation.md new file mode 100644 index 00000000000..b41147393c3 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/dart/code-generation.md @@ -0,0 +1,113 @@ +--- +title: 代码生成 +sidebar_position: 3 +id: dart_code_generation +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. +--- + +Fory 会在构建阶段为你的 Dart 类生成高性能序列化代码。你只需要给模型加注解、运行 `build_runner`,剩下的由 Fory 处理。 + +## 第一步:给模型加注解 + +为每个需要序列化的类添加 `@ForyStruct()`。同时在文件顶部加入生成的 `part` 指令。 + +```dart +import 'package:fory/fory.dart'; + +part 'models.fory.dart'; + +@ForyStruct() +class Address { + Address(); + + String city = ''; + String street = ''; +} + +@ForyStruct() +class User { + User(); + + String name = ''; + Int32 age = Int32(0); + Address address = Address(); +} +``` + +定义在同一文件中的枚举会自动包含到生成的注册代码里。 + +## 第二步:运行生成器 + +在包含 `pubspec.yaml` 的目录下运行: + +```bash +dart run build_runner build --delete-conflicting-outputs +``` + +这会在源文件旁边生成一个 `.fory.dart` 文件。每当你新增或重命名带注解的类型时,都需要重新运行这个命令。 + +## 第三步:注册并使用 + +生成器会创建一个以源文件命名的命名空间,并提供 `register` 函数。请在序列化前调用它: + +```dart +final fory = Fory(); +ModelsFory.register(fory, Address, id: 1); +ModelsFory.register(fory, User, id: 2); +``` + +也可以用稳定名称代替数字 ID,这在跨语言场景中更有用: + +```dart +ModelsFory.register( + fory, + User, + namespace: 'example', + typeName: 'User', +); +``` + +关于如何在 ID 和名称之间做选择,见 [类型注册](type-registration.md)。 + +## Schema 演进:`evolving` + +`@ForyStruct()` 默认使用 `evolving: true`,这对大多数应用都是正确选择。 + +- `evolving: true`:Fory 会保存足够的元信息,因此当你之后新增或删除字段时,旧代码与新代码仍然可以交换消息。只要你的应用或服务可能存在多个版本同时运行,就应该启用它。 +- `evolving: false`:不写入额外元信息,载荷会略小一些。只有在写端和读端总是一起升级时才安全。 + +```dart +// evolving: true 是默认值,可以省略 +@ForyStruct(evolving: true) +class Event { + Event(); + + String name = ''; +} +``` + +使用 evolving 结构体时,也要在首次对外发送载荷之前通过 `@ForyField(id: ...)` 为字段分配稳定 ID,因为 Fory 会依赖这些 ID 在 Schema 变化后匹配字段。 + +## 什么时候不要使用代码生成 + +如果你无法给某个类型加注解,例如它来自一个你无法修改的第三方包,那就改用 [自定义序列化器](custom-serializers.md)。 + +## 相关主题 + +- [类型注册](type-registration.md) +- [字段配置](schema-metadata.md) +- [Schema 演进](schema-evolution.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/dart/configuration.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/dart/configuration.md new file mode 100644 index 00000000000..93d5a07eeba --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/dart/configuration.md @@ -0,0 +1,148 @@ +--- +title: 配置 +sidebar_position: 1 +id: dart_configuration +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. +--- + +本页介绍 `Fory` 构造函数的可选项。 + +## 创建 `Fory` 实例 + +直接把选项传给构造函数: + +```dart +import 'package:fory/fory.dart'; + +// 默认配置,适合大多数单服务场景 +final fory = Fory(); + +// 需要 Schema 演进的跨语言服务 +final fory = Fory( + compatible: true, + maxDepth: 512, + maxTypeFields: 512, + maxTypeMetaBytes: 4096, + maxSchemaVersionsPerType: 10, + maxAverageSchemaVersionsPerType: 3, +); +``` + +每个应用创建一个实例并复用即可。按请求新建 `Fory` 没有任何收益。 + +## 选项 + +### `compatible` + +当你的服务需要处理来自另一份模型版本代码的载荷时,请设置为 `true`。例如各服务独立发布,无法保证通信双方同时升级。 + +```dart +final fory = Fory(compatible: true); +``` + +当 `compatible: true` 时: + +- 一侧新增或删除字段不会破坏另一侧。 +- 各端仍然必须使用相同的 `namespace` + `typeName`,或者相同的数字 `id` 来标识类型。 + +当 `compatible: false`(默认)时: + +- 双方必须拥有完全相同的 Schema。这样会略快一些,适合仅有 Dart 服务或始终一起升级的场景。 + +### `checkStructVersion` + +仅在 `compatible: false` 时相关。当它为 `true` 时,Fory 会校验载荷中的 Schema 版本是否与接收端已知版本一致,从而在运行时尽早发现误用的 Schema。 + +```dart +final fory = Fory( + compatible: false, + checkStructVersion: true, // default +); +``` + +当 `compatible: true` 时,这个选项不起作用。 + +### `maxDepth` + +限制对象图的最大嵌套深度。如果你的数据确实有很深的树形结构,可以增大它;如果你想快速拒绝异常深的载荷,可以减小它。 + +```dart +final fory = Fory(maxDepth: 128); +``` + +### 远端 Schema Metadata 限制 + +兼容模式可能接收用于 Schema 演进的远端 metadata。以下限制用于约束 metadata 大小和可接受的 schema 版本数: + +```dart +final fory = Fory( + maxTypeFields: 512, + maxTypeMetaBytes: 4096, + maxSchemaVersionsPerType: 10, + maxAverageSchemaVersionsPerType: 3, +); +``` + +- `maxTypeFields` 限制一个收到的 struct metadata body 中的字段数。 +- `maxTypeMetaBytes` 限制一个收到的 TypeMeta body 的编码 body 字节数,不包含 8 字节 header 和扩展 size varint。 +- `maxSchemaVersionsPerType` 限制一个逻辑类型可接受的远端 metadata 版本数。 +- `maxAverageSchemaVersionsPerType` 限制所有已接受远端类型的平均版本数;有效全局下限为 `8192` 个 schema。 + +### `maxCollectionSize` + +任意单个 list、set 或 map 字段可接受的最大元素数。用于防止畸形消息触发失控的内存分配。 + +```dart +final fory = Fory(maxCollectionSize: 100000); +``` + +### `maxBinarySize` + +任意单个二进制 blob 字段允许接受的最大字节数。 + +```dart +final fory = Fory(maxBinarySize: 8 * 1024 * 1024); +``` + +## 默认值 + +| 选项 | 默认值 | +| --------------------------------- | --------- | +| `compatible` | `false` | +| `checkStructVersion` | `true` | +| `maxDepth` | 256 | +| `maxTypeFields` | 512 | +| `maxTypeMetaBytes` | 4096 | +| `maxSchemaVersionsPerType` | 10 | +| `maxAverageSchemaVersionsPerType` | 3 | +| `maxCollectionSize` | 1 048 576 | +| `maxBinarySize` | 64 MiB | + +## 跨语言说明 + +当 Fory 用于不同语言实现的服务之间通信时: + +- 如果任意一端需要 Schema 演进,则**所有**端都应设置 `compatible: true`。 +- 每一端都要使用相同的数字 ID,或者相同的 `namespace + typeName` 组合。 +- 写端和读端的 `compatible` 设置必须一致,模式不匹配会直接失败。 +- 除非数据不是恶意输入,且可信 peer 会发送更大的 metadata 或大量 schema 版本,否则保持远端 schema metadata 限制的默认值。 + +## 相关主题 + +- [基础序列化](basic-serialization.md) +- [Schema 演进](schema-evolution.md) +- [跨语言](xlang-serialization.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/dart/custom-serializers.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/dart/custom-serializers.md new file mode 100644 index 00000000000..d0eb5fd943b --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/dart/custom-serializers.md @@ -0,0 +1,139 @@ +--- +title: 自定义序列化器 +sidebar_position: 5 +id: dart_custom_serializers +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. +--- + +自定义序列化器让你可以完全控制某个类型如何编码和解码。通常只有在以下情况才需要使用它: + +- 类型来自你无法修改的第三方包,无法添加 `@ForyStruct()` +- 你需要完全自定义的二进制布局 +- 你要实现 union / discriminated type + +对于你自己的模型,`@ForyStruct()` 配合代码生成几乎总是更好的选择。 + +## 实现 `Serializer` + +继承 `Serializer` 并实现 `write` 和 `read`。通过 `context.buffer` 直接读写原始字节: + +```dart +import 'package:fory/fory.dart'; + +final class Person { + Person(this.name, this.age); + + final String name; + final int age; +} + +final class PersonSerializer extends Serializer { + const PersonSerializer(); + + @override + void write(WriteContext context, Person value) { + final buffer = context.buffer; + buffer.writeUtf8(value.name); + buffer.writeInt64(value.age); + } + + @override + Person read(ReadContext context) { + final buffer = context.buffer; + return Person(buffer.readUtf8(), buffer.readInt64()); + } +} +``` + +在使用前先注册这个序列化器: + +```dart +final fory = Fory(); +fory.registerSerializer( + Person, + const PersonSerializer(), + namespace: 'example', + typeName: 'Person', +); +``` + +## 写入嵌套对象 + +如果自定义序列化器中的某个字段本身也是由 Fory 管理的类型,请使用 `context.writeRef` 和 `context.readRef`,而不是递归调用 `fory.serialize`。这样才能保持引用跟踪正确,也避免在嵌套载荷里再写入完整根帧。 + +```dart +@override +void write(WriteContext context, Wrapper value) { + context.writeRef(value.child); +} + +@override +Wrapper read(ReadContext context) { + return Wrapper(context.readRef() as Child); +} +``` + +如果你明确知道某个嵌套值永远不会在对象图中重复出现,也不需要保持引用标识,可以用 `writeNonRef`: + +```dart +context.writeNonRef(value.child); +``` + +## Union + +对于带判别标签的 union,请继承 `UnionSerializer` 而不是 `Serializer`。先写入判别值,再写入当前激活的变体;读取时先解析判别值,再分派到正确分支。 + +```dart +final class ShapeSerializer extends UnionSerializer { + const ShapeSerializer(); + + @override + void write(WriteContext context, Shape value) { + // write active variant + } + + @override + Shape read(ReadContext context) { + // read discriminant, return correct variant + throw UnimplementedError(); + } +} +``` + +## 自定义序列化器中的循环引用 + +如果你的序列化器会遇到循环对象图,那么在读取嵌套字段之前,必须先把对象绑定到引用跟踪器中: + +```dart +final value = Node.empty(); +context.reference(value); // register the object first +value.next = context.readRef() as Node?; // now nested reads can refer back to it +return value; +``` + +跳过这一步会导致指向该对象的回溯引用解析成 `null`。 + +## 提示 + +- 在热点路径中,优先使用 `context.buffer` 直接读写字节。 +- 在所有端上,都用相同的身份注册该序列化器,即相同的 `id` 或相同的 `namespace + typeName`。 + +## 相关主题 + +- [类型注册](type-registration.md) +- [跨语言](xlang-serialization.md) +- [故障排查](troubleshooting.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/dart/grpc-support.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/dart/grpc-support.md new file mode 100644 index 00000000000..333a9b69b29 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/dart/grpc-support.md @@ -0,0 +1,284 @@ +--- +title: gRPC 支持 +sidebar_position: 12 +id: grpc_support +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. +--- + +Fory 可以为包含 service 定义的 schema 生成 Dart gRPC service companion。 +生成代码使用标准 `package:grpc` client、service base、method descriptor、 +call option、deadline、取消和 status code;request 和 response 对象则使用 +Fory 序列化,而不是 protobuf。 + +当 RPC 两端都由同一份 Fory IDL、protobuf IDL 或 FlatBuffers IDL 生成,并且 +两端都期望 Fory 编码的 message body 时,可以使用这种模式。如果 API 必须被通用 +protobuf client、reflection 工具或期望 protobuf message bytes 的组件消费,请使用 +标准 protobuf gRPC 代码生成。 + +## 添加依赖 + +`fory` package 不会加入 gRPC 依赖。编译或运行生成 service companion 的应用需要 +添加 `grpc`,同时添加用于生成 Fory serializer 代码的 `build_runner` dev dependency: + +```yaml +dependencies: + fory: ^1.3.0 + grpc: ^4.0.0 + +dev_dependencies: + build_runner: ^2.4.0 +``` + +client 和 server 应用使用同一组依赖。 + +## 定义 Service + +Service 定义可以来自 Fory IDL、protobuf IDL 或 FlatBuffers `rpc_service` 定义。 +Fory IDL service 示例: + +```protobuf +package demo.greeter; + +message HelloRequest { + string name = 1; +} + +message HelloReply { + string reply = 1; +} + +service Greeter { + rpc SayHello (HelloRequest) returns (HelloReply); +} +``` + +使用 `--grpc` 生成 Dart model 和 gRPC companion 代码: + +```bash +foryc service.fdl --dart_out=./lib/generated --grpc +``` + +然后运行一次 `build_runner`,为生成的 model 输出 Fory serializer part 文件。代码运行前 +必须执行这一步: + +```bash +dart run build_runner build --delete-conflicting-outputs +``` + +对这个 schema,Dart generator 会输出如下文件(model 文件和 module 名称来自 package +最后一段 `greeter`): + +| 文件 | 用途 | +| ------------------------------------------- | ----------------------------------------- | +| `demo/greeter/greeter.dart` | Fory model type 和 schema module | +| `demo/greeter/greeter.fory.dart` | Serializer 和注册逻辑,由 build_runner 生成 | +| `demo/greeter/greeter_grpc.dart` | gRPC client、service base 和 method descriptor | +| `GreeterForyModule` in `greeter.dart` | 生成类型的 Fory 注册 module | +| `GreeterServiceBase` in `greeter_grpc.dart` | server 实现使用的 base class | +| `GreeterClient` in `greeter_grpc.dart` | gRPC 调用使用的 client stub | + +生成的 client 和 service base 会自动获取可用的 `Fory`,并在首次使用时注册该 +schema 的类型,因此不需要手动注册。若需要共享自定义 `Fory`(例如已经配置了额外 +module 的实例),可在第一次 RPC 前调用一次 `GreeterForyModule.install(yourFory)`; +这是可选操作。 + +## 实现 Server + +继承生成的 `GreeterServiceBase`,并用 grpc-dart 的 `Server` 承载: + +```dart +import 'dart:io'; + +import 'package:grpc/grpc.dart'; +import 'demo/greeter/greeter.dart'; +import 'demo/greeter/greeter_grpc.dart'; + +class GreeterService extends GreeterServiceBase { + @override + Future sayHello(ServiceCall call, HelloRequest request) async { + final reply = HelloReply()..reply = 'Hello, ${request.name}'; + return reply; + } +} + +Future main() async { + final server = Server.create(services: [GreeterService()]); + await server.serve(address: InternetAddress.loopbackIPv4, port: 50051); +} +``` + +## 创建 Client + +通过 `ClientChannel` 使用生成的 client: + +```dart +import 'package:grpc/grpc.dart'; +import 'demo/greeter/greeter.dart'; +import 'demo/greeter/greeter_grpc.dart'; + +Future main() async { + final channel = ClientChannel( + 'localhost', + port: 50051, + options: const ChannelOptions( + credentials: ChannelCredentials.insecure(), + ), + ); + final client = GreeterClient(channel); + + final reply = await client.sayHello(HelloRequest()..name = 'Fory'); + print(reply.reply); + + await channel.shutdown(); +} +``` + +## Streaming RPC + +Fory service 定义可以使用相同的 gRPC streaming 形态: + +```protobuf +service Greeter { + rpc SayHello (HelloRequest) returns (HelloReply); + rpc LotsOfReplies (HelloRequest) returns (stream HelloReply); + rpc LotsOfGreetings (stream HelloRequest) returns (HelloReply); + rpc Chat (stream HelloRequest) returns (stream HelloReply); +} +``` + +生成的 Dart 方法遵循 grpc-dart 约定。单响应方法返回 `ResponseFuture` +(client-streaming 会用 `.single` 适配调用);streaming 响应方法返回 +`ResponseStream`。Server 端,单请求以 message type 传入,streaming 请求以 +`Stream` 传入;方法对单响应返回 `Future`,对 streaming 响应返回 `Stream`: + +| IDL shape | Client 方法 | Server 方法(override) | +| ----------------------------------------- | --------------------------------------------------- | ----------------------------------------------------- | +| `rpc A (Req) returns (Res)` | `ResponseFuture a(Req request, {CallOptions?})` | `Future a(ServiceCall call, Req request)` | +| `rpc A (Req) returns (stream Res)` | `ResponseStream a(Req request, {CallOptions?})` | `Stream a(ServiceCall call, Req request)` | +| `rpc A (stream Req) returns (Res)` | `ResponseFuture a(Stream request, {...})` | `Future a(ServiceCall call, Stream request)` | +| `rpc A (stream Req) returns (stream Res)` | `ResponseStream a(Stream request, {...})` | `Stream a(ServiceCall call, Stream request)` | + +Server 实现直接使用生成的 streaming 方法形态: + +```dart +class GreeterService extends GreeterServiceBase { + @override + Stream lotsOfReplies( + ServiceCall call, + HelloRequest request, + ) async* { + for (final greeting in ['Hello, ${request.name}', 'Welcome, ${request.name}']) { + yield HelloReply()..reply = greeting; + } + } + + @override + Future lotsOfGreetings( + ServiceCall call, + Stream request, + ) async { + final names = []; + await for (final message in request) { + names.add(message.name); + } + return HelloReply()..reply = names.join(', '); + } + + @override + Stream chat( + ServiceCall call, + Stream request, + ) async* { + await for (final message in request) { + yield HelloReply()..reply = 'Hello, ${message.name}'; + } + } +} +``` + +生成的 client 返回标准 grpc-dart 调用对象: + +```dart +// Server streaming. +await for (final reply in client.lotsOfReplies(HelloRequest()..name = 'Fory')) { + print(reply.reply); +} + +// Client streaming. +final summary = await client.lotsOfGreetings( + Stream.fromIterable([ + HelloRequest()..name = 'Ada', + HelloRequest()..name = 'Grace', + ]), +); +print(summary.reply); + +// Bidirectional streaming. +await for (final reply in client.chat( + Stream.fromIterable([HelloRequest()..name = 'Fory']), +)) { + print(reply.reply); +} +``` + +生成的 descriptor 会为 gRPC path 保留 IDL 中精确的 service 和 method 名称; +Dart 方法使用 camelCase 名称。 + +## 生成的 Module 名称 + +Dart model 文件和 schema module 按 package 的最后一段命名,而不是按 gRPC service +命名。(当 schema 没有 package 时,使用源文件 stem。) + +| Schema 输入(package) | Model 文件 | Schema module | +| ----------------------------- | ------------------- | ----------------------- | +| `service.fdl` (`demo.greeter`) | `greeter.dart` | `GreeterForyModule` | +| `api.fdl` (`demo.order_events`) | `order_events.dart` | `OrderEventsForyModule` | +| `greeter.fdl` (`demo.greeter`) | `greeter.dart` | `GreeterForyModule` | + +名为 `Greeter` 的 gRPC service 仍会生成 `_grpc.dart` companion,其中包含 +`GreeterClient` 和 `GreeterServiceBase`;它不会改变 schema module 名称。如果多个 +schema 文件使用同一个 package leaf,请将它们放到不同输出目录,或选择能生成不同 Dart +model 文件的 package/file 名称。 + +## gRPC 运行时行为 + +生成的 service 代码只替换 request 和 response 序列化。所有常规 gRPC 运行行为仍由你的 +gRPC stack 负责: + +- Deadline 和取消 +- TLS 和认证 +- 名称解析与负载均衡 +- Client 和 server interceptor +- Status code 和 metadata +- Channel 生命周期管理 + +## 故障排查 + +### 缺少 `package:grpc` 类型 + +将 `grpc` 加到应用依赖。生成的 Fory service 文件会 import grpc-dart API,但 `fory` +有意不依赖 gRPC。 + +### 生成代码引用缺失的 `.fory.dart` Part + +生成或重新生成 Dart source 后,运行 `dart run build_runner build --delete-conflicting-outputs`。 +Serializer part 文件由 `build_runner` 生成,不由 `foryc` 直接生成。 + +### Protobuf Client 无法解码 Service + +Fory gRPC companion 不使用 protobuf 编码格式。请为 Fory-generated service 使用 +Fory-generated client,或为通用 protobuf client 暴露单独的 protobuf service endpoint。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/dart/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/dart/index.md new file mode 100644 index 00000000000..1d36db92336 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/dart/index.md @@ -0,0 +1,141 @@ +--- +title: Dart 序列化指南 +sidebar_position: 0 +id: dart_serialization_index +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. +--- + +Apache Fory™ Dart 可以把 Dart 对象序列化为字节,再从字节反序列化回来,并且支持与 Java、Go、C#、Python 以及其他 Fory 支持语言编写的服务进行互通。 + +## 为什么选择 Fory Dart? + +- **跨语言**:在 Dart 中序列化,在 Java、Go、C# 等语言中反序列化,无需额外胶水代码 +- **高性能**:生成的序列化代码会替代运行时反射 +- **Schema 演进**:可以新增或删除字段,而不破坏已有消息 +- **循环引用**:可选的引用跟踪可处理共享或递归对象图 +- **逃生口**:对任何无法加注解的类型,你都可以手写序列化器 + +## 快速开始 + +### 要求 + +- Dart SDK 3.6 或更高版本 +- `build_runner`,用于生成序列化代码 + +### 安装 + +把依赖加入你的 `pubspec.yaml`: + +```yaml +dependencies: + fory: ^1.3.0 + +dev_dependencies: + build_runner: ^2.4.0 +``` + +### 基础示例 + +定义模型,先运行一次生成器,然后进行序列化: + +```dart +import 'package:fory/fory.dart'; + +part 'person.fory.dart'; + +enum Color { + red, + blue, +} + +@ForyStruct() +class Person { + Person(); + + String name = ''; + Int32 age = Int32(0); + Color favoriteColor = Color.red; + List tags = []; +} + +void main() { + final fory = Fory(); + PersonFory.register( + fory, + Color, + namespace: 'example', + typeName: 'Color', + ); + PersonFory.register( + fory, + Person, + namespace: 'example', + typeName: 'Person', + ); + + final person = Person() + ..name = 'Ada' + ..age = Int32(36) + ..favoriteColor = Color.blue + ..tags = ['engineer', 'mathematician']; + + final bytes = fory.serialize(person); + final roundTrip = fory.deserialize(bytes); + print(roundTrip.name); +} +``` + +在运行程序之前,先生成配套文件: + +```bash +dart run build_runner build --delete-conflicting-outputs +``` + +`PersonFory` 由 `build_runner` 生成。`namespace` 和 `typeName` 是其他语言中的对端识别同一类型的方式,一旦服务进入生产环境,就应保持稳定。 + +## API 概览 + +- `Fory(...)`:创建序列化实例;创建一次并复用 +- `fory.serialize(value)`:返回 `Uint8List` 字节 +- `fory.deserialize(bytes)`:返回一个 `T` +- `@ForyStruct()`:标记需要生成代码的类 +- `@ForyField(...)`:字段级选项,例如跳过、ID、可空性、引用 +- 整数包装类型:`Int8`、`Int16`、`Int32`、`UInt8`、`UInt16`、`UInt32` +- 浮点包装类型:`Float16`、`Float32` +- 时间包装类型:`LocalDate`、`Timestamp` + +## 文档 + +| 主题 | 说明 | +| --------------------------------------------- | ------------------------------------ | +| [配置](configuration.md) | 运行时选项、兼容模式和安全限制 | +| [基础序列化](basic-serialization.md) | `serialize`、`deserialize`、生成注册、根对象图 | +| [代码生成](code-generation.md) | `@ForyStruct`、build runner 和生成命名空间 | +| [类型注册](type-registration.md) | 基于 ID 与基于名称的注册,以及注册规则 | +| [自定义序列化器](custom-serializers.md) | 手写 `Serializer` 实现与 union | +| [字段配置](schema-metadata.md) | `@ForyField`、字段 ID、可空性、引用、多态 | +| [支持的类型](supported-types.md) | 内置 xlang 值、包装类型、集合和结构体 | +| [Schema 演进](schema-evolution.md) | 兼容结构体与可演进 Schema | +| [跨语言](xlang-serialization.md) | 互操作规则与字段对齐 | +| [故障排查](troubleshooting.md) | 常见错误、诊断方法和验证步骤 | + +## 相关资源 + +- [Xlang 序列化规范](../../specification/xlang_serialization_spec.md) +- [Xlang 实现指南](../../specification/xlang_implementation_guide.md) +- [跨语言指南](../xlang/index.md) +- [Dart 运行时源码目录](https://github.com/apache/fory/tree/main/dart) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/dart/schema-evolution.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/dart/schema-evolution.md new file mode 100644 index 00000000000..0a446e353e6 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/dart/schema-evolution.md @@ -0,0 +1,92 @@ +--- +title: Schema 演进 +sidebar_position: 8 +id: dart_schema_evolution +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. +--- + +Schema 演进让应用的不同版本之间也能安全交换消息。例如,v2 写出的消息仍然可以被 v1 读取,反过来也一样。 + +## 两种模式 + +### 兼容模式(推荐给会演进的服务) + +当服务可能同时运行不同版本时启用它,例如滚动发布期间,或者客户端无法立即升级时。 + +```dart +final fory = Fory(compatible: true); +``` + +在兼容模式下,Fory 会在每条消息中写入足够的字段元信息,使读端能够跳过未知字段,并为缺失字段使用默认值。请用稳定字段 ID 来锚定跨版本 Schema。 + +### Schema 一致模式(默认) + +通信双方必须拥有同一个模型。Fory 会校验双方 Schema 是否一致,并拒绝来自其他 Schema 版本的消息。适用于所有服务总是一起升级,并且你希望尽早把 Schema 不匹配直接报错的场景。 + +```dart +final fory = Fory(); // compatible: false by default +``` + +## 为演进做好准备 + +为了安全地使用兼容模式,请给结构体添加 `@ForyStruct(evolving: true)`(默认值),并在第一次对外发送载荷之前,为每个字段都分配稳定的 `@ForyField(id: ...)`: + +```dart +@ForyStruct(evolving: true) +class UserProfile { + UserProfile(); + + @ForyField(id: 1) + String name = ''; + + @ForyField(id: 2, nullable: true) + String? nickname; +} +``` + +如果载荷已经在生产环境中存在之后你才补加字段 ID,那么旧消息里不会包含这些 ID,Schema 演进也就无法正确工作。 + +## 哪些变更是安全的 + +**安全变更**(双方兼容): + +- 新增一个带新字段 ID 的可选字段 +- 重命名字段,只要 `@ForyField(id: ...)` 保持不变 +- 删除字段,对端会忽略缺失值并使用 Dart 默认值 + +**危险变更**(可能破坏已有消息): + +- 把一个已有字段 ID 复用给另一个不同字段 +- 将字段类型改为不兼容类型,例如 `Int32` 改成 `String` +- 在消息进入生产环境后修改某个类型的注册身份,即 `id`、`namespace` 或 `typeName` +- 不改字段 ID,却改变字段的逻辑含义 + +## 跨语言说明 + +只有在**所有**交换消息的运行时都一致满足以下条件时,Schema 演进才会生效: + +1. 使用相同的 `compatible` 设置。 +2. 使用相同的类型注册身份,即相同的数字 ID,或者相同的 `namespace + typeName`。 +3. 对字段 ID 的逻辑含义有相同理解。 + +部署前请用真实 round trip 覆盖滚动升级场景。 + +## 相关主题 + +- [配置](configuration.md) +- [字段配置](schema-metadata.md) +- [跨语言](xlang-serialization.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/dart/schema-metadata.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/dart/schema-metadata.md new file mode 100644 index 00000000000..eb200a1a37a --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/dart/schema-metadata.md @@ -0,0 +1,138 @@ +--- +title: Schema 元信息 +sidebar_position: 5 +id: schema_metadata +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. +--- + +在 `@ForyStruct()` 类中的字段上添加 `@ForyField(...)`,即可改变该字段的序列化方式。 + +## 快速参考 + +```dart +@ForyField( + skip: false, // 包含该字段;设为 true 可排除它 + id: 10, // 用于 Schema 演进的稳定字段 ID + nullable: true, // 覆盖可空性检测 + ref: true, // 为该字段启用引用跟踪 + dynamic: false, // 控制是否写入运行时类型 +) +``` + +## `skip` + +完全排除某个字段,不参与序列化。适用于缓存值、计算值或仅 UI 使用且不应落入持久化消息或传输消息的值。 + +```dart +@ForyField(skip: true) +String cachedDisplayName = ''; +``` + +## `id` + +为字段分配稳定身份,使 Fory 能在 schema 变更(字段重命名或重排序)后按 ID 匹配字段。**如果你计划将来新增、删除或重命名字段,请现在就为所有字段分配 ID**,也就是在交付第一个载荷之前完成。 + +```dart +@ForyField(id: 1) +String name = ''; +``` + +一旦载荷已在服务之间共享,就不要把同一个 `id` 复用于另一个字段。 + +## `nullable` + +显式将字段标记为可空或不可空,覆盖 Fory 从 Dart 类型推断出的结果。当 Dart 类型不可空,但你希望 Fory 接受编码格式中的 `null` 时使用它,例如读取来自较旧生产者且可能省略字段的消息。 + +```dart +@ForyField(nullable: true) +String nickname = ''; +``` + +在跨语言场景中,确保可空性契约也符合对端运行时的预期。 + +## `ref` + +为特定字段启用引用跟踪。当图中的多个对象可能指向同一个实例,或者字段类型可能形成循环时使用它。如果没有 `ref: true`,同一个对象值出现在两个字段中时,Fory 会序列化两次。 + +```dart +@ForyField(ref: true) +List sharedNodes = []; +``` + +注意:即使设置了 `ref: true`,`int`、`double` 和 `bool` 等标量类型也不会从引用跟踪中受益。 + +## `dynamic` + +控制 Fory 是否把字段值的具体运行时类型写入载荷。 + +- `null`(默认)- Fory 根据声明类型自动决定。 +- `false` - 始终使用声明的字段类型;更紧凑,但反序列化器必须知道精确类型。 +- `true` - 始终写入实际运行时类型;当字段声明为 `Object?` 或基类,但运行时可以保存不同具体类型时需要它(多态)。 + +```dart +@ForyField(dynamic: true) +Object? payload; // 运行时可以保存任何已注册类型 +``` + +## 数值字段类型 + +Dart `int` 在运行时是 64 位值。与 Java、Go 或 C# 交换消息时,接收端可能期望更窄的整数。使用 `@ForyField(type: ...)` 固定精确的编码格式: + +```dart +@ForyStruct() +class Sample { + Sample(); + + @ForyField(type: Int32Type(encoding: Encoding.fixed)) + int fixedWidthInt = 0; + + @ForyField(type: Int64Type(encoding: Encoding.tagged)) + Int64 compactLong = Int64(0); + + @ForyField(type: Uint32Type()) + int smallUnsigned = 0; +} +``` + +可用的标量类型节点包括 `Int8Type`、`Int16Type`、`Int32Type`、`Int64Type`、`Uint8Type`、`Uint16Type`、`Uint32Type`、`Uint64Type`、`Float16Type`、`Bfloat16Type` 和 `Float32Type`。 + +对于嵌套容器,使用 `ListField`、`SetField`、`MapField`,或完整的 `ForyField(type: ...)` 树: + +```dart +@MapField( + value: ListType( + element: Int32Type(encoding: Encoding.fixed), + ), +) +Map> metrics = >{}; +``` + +即使指定了原始元素规格,泛型 `List` 仍使用 `list` 编码格式类型。打包的 `*_array` 编码格式种类来自专用承载类型,例如 `Int32List`、`Uint32List`、`Int64List` 和 `Uint64List`。如果你为泛型 `List` 标注非空定长原始元素规格,代码生成会拒绝它,并提示你使用匹配的类型化 list 承载类型。 + +## 跨语言对齐字段 + +当同一个模型在多种语言中定义时: + +- 为每个可能随时间变化的字段分配稳定的 `id` 值。 +- 对真正多态的字段使用 `dynamic: true`。 +- 保持每个字段的逻辑含义在各语言中一致。Fory 会按名称或 ID 匹配字段,但无法协调语义差异。 + +## 相关主题 + +- [代码生成](code-generation.md) +- [Schema 演进](schema-evolution.md) +- [Xlang 序列化](xlang-serialization.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/dart/supported-types.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/dart/supported-types.md new file mode 100644 index 00000000000..7a92c1a3c04 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/dart/supported-types.md @@ -0,0 +1,102 @@ +--- +title: 支持的类型 +sidebar_position: 7 +id: dart_supported_types +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. +--- + +本页列出可在 Fory 消息中使用的 Dart 类型,并标出哪些地方在跨语言兼容性上需要特别小心。 + +## 内置原始类型 + +以下 Dart 类型可以直接序列化,不需要特殊处理: + +| Dart 类型 | 跨语言说明 | +| -------------------- | ------------------------------------------------------------------------------------------------------------ | +| `bool` | 直接映射 | +| `int` | 默认按 64 位序列化。如果对端期望更窄的整数,请使用包装类型或 `@Int32Type` 等注解 | +| `double` | 映射为 64 位浮点数。如果对端期望 32 位浮点,请使用 `Float32` 包装类型 | +| `String` | 直接映射 | +| `Uint8List` | 二进制 blob | +| `List`, `Set`, `Map` | 支持,但元素类型也必须是受支持类型 | +| `DateTime` | 如需明确语义,请使用 `Timestamp` 或 `LocalDate` 包装类型 | + +## 整数包装类型 + +Dart `int` 在运行时是 64 位值。如果对端语言期望 32 位整数,例如 Java `int`、Go `int32`、C# `int`,而你发送的是 Dart `int`,反序列化可能失败,或者静默截断。 + +使用整数包装类型可以固定精确的编码宽度: + +```dart +final Int8 tiny = Int8(-1); // 8-bit signed +final Int16 shortValue = Int16(7); // 16-bit signed +final Int32 age = Int32(36); // 32-bit signed — matches Java int, C# int, Go int32 +final UInt8 flags = UInt8(255); // 8-bit unsigned +final UInt16 port = UInt16(65535); // 16-bit unsigned +final UInt32 count = UInt32(4000000000); // 32-bit unsigned +``` + +每个包装类型都会把存储值限制在目标位宽内。 + +## 浮点包装类型 + +Dart `double` 对应 64 位浮点。如果对端使用 32 位浮点,请改用包装类型: + +- `Float32`:32 位浮点,对应 Java `float`、C# `float`、Go `float32` +- `Float16`:半精度浮点,适用于专门的数值载荷 + +## 时间和日期类型 + +不要直接把原始 `DateTime` 跨语言发送。时区处理和 epoch 差异在不同语言间并不完全一致。请改用下面这些显式包装类型: + +- `Timestamp`:带纳秒精度的 UTC 时间点,即秒数加纳秒数 +- `LocalDate`:不带时间和时区的日历日期 + +```dart +final now = Timestamp.fromDateTime(DateTime.now().toUtc()); +final birthday = LocalDate(1990, 12, 1); +``` + +## Struct 和 Enum + +给类添加 `@ForyStruct()`,然后运行 `build_runner`,它们就能序列化。定义在同一文件中的枚举会自动包含进去。 + +```dart +@ForyStruct() +class User { + User(); + + String name = ''; + Int32 age = Int32(0); // use Int32 when peers expect a 32-bit integer +} +``` + +参见 [代码生成](code-generation.md)。 + +## 集合 + +Fory 支持 `List`、`Set` 和 `Map`。元素类型和键类型本身也必须可序列化。请避免使用可变对象作为 map 键。 + +## 兼容性提示 + +一旦不确定某个 Dart 类型是否与对端预期一致,就优先使用显式包装类型。数字宽度选错,是跨语言场景里最常见的 bug 之一。 + +## 相关主题 + +- [字段配置](schema-metadata.md) +- [跨语言](xlang-serialization.md) +- [Schema 演进](schema-evolution.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/dart/troubleshooting.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/dart/troubleshooting.md new file mode 100644 index 00000000000..5d67c1a77da --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/dart/troubleshooting.md @@ -0,0 +1,103 @@ +--- +title: 故障排查 +sidebar_position: 10 +id: dart_troubleshooting +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. +--- + +本页汇总 Dart 运行时中常见的问题及其修复方式。 + +## `Only xlang payloads are supported by the Dart runtime.` + +写端发送的是 native-mode(非 xlang)载荷。请确保每个服务都走跨语言兼容路径: + +- **Java**:在 Fory builder 上添加 `.withLanguage(Language.XLANG)`。 +- **Go**:在 Fory 选项中使用 `WithXlang(true)`。 +- **其他运行时**:查看各自文档,确认如何启用跨语言模式。 + +## `Type ... is not registered.` + +Fory 不知道如何序列化或反序列化这个类型。可按以下方式修复: + +1. 如果还没生成代码,先运行:`dart run build_runner build --delete-conflicting-outputs` +2. 在调用 `serialize` 或 `deserialize` **之前**,先调用生成的 `register` 函数,或者 `registerSerializer` +3. 注册消息中可能出现的**所有**类型,而不仅仅是根类型。例如,如果 `Order` 包含 `Address`,那两者都要注册 + +## 生成的 part 文件缺失或已过期 + +重新生成代码: + +```bash +cd dart/packages/fory +dart run build_runner build --delete-conflicting-outputs +``` + +如果你移动了文件或重命名了类型,请在重新执行分析或测试前先重新构建。 + +## `Deserialized value has type ..., expected ...` + +载荷描述的类型与 `deserialize` 中的 `T` 不一致。常见原因包括: + +- 写端注册该类型时使用的 ID 或名称,与读端不一致 +- 载荷来自另一条代码路径,根对象类型不同 +- 你正在反序列化异构容器。应先按 `Object?` 或 `List` 解码,再做类型转换 + +## 反序列化后对象不再是同一个实例 + +默认情况下,Fory 不会跟踪对象标识,因此两个字段如果指向同一个对象,round trip 后会变成两个独立副本。 + +如果需要保留对象标识: + +- 对 `@ForyStruct` 内部字段,在对应字段上加 `@ForyField(ref: true)` +- 对顶层集合,调用 `fory.serialize(...)` 时传入 `trackRef: true` +- 在自定义序列化器中,使用 `context.writeRef` / `context.readRef`,并在读取嵌套字段之前先调用 `context.reference(obj)` + +## 跨语言字段不匹配(数据缺失或值错误) + +典型症状:往返另一种语言后,字段变成默认值,或者类型错误。 + +检查清单: + +1. 双方使用相同的注册身份,即相同数字 ID,**或**相同的 `namespace + typeName` +2. 在第一份载荷发送前,就已经分配了稳定 `@ForyField(id: ...)` +3. 数字宽度兼容。当对端字段是 Java `int`、Go `int32` 或 C# `int` 时,在 Dart 端使用 `Int32` +4. 日期时间字段使用 `Timestamp` / `LocalDate`,而不是原始 `DateTime` +5. 如果用到 Schema 演进,则**双方**都要开启 `compatible: true` + +## 本地运行测试 + +主 Dart 包: + +```bash +dart run build_runner build --delete-conflicting-outputs +dart analyze +dart test +``` + +集成测试包: + +```bash +cd dart/packages/fory-test +dart run build_runner build --delete-conflicting-outputs +dart test +``` + +## 相关主题 + +- [跨语言](xlang-serialization.md) +- [代码生成](code-generation.md) +- [自定义序列化器](custom-serializers.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/dart/type-registration.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/dart/type-registration.md new file mode 100644 index 00000000000..3d9bf025661 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/dart/type-registration.md @@ -0,0 +1,98 @@ +--- +title: 类型注册 +sidebar_position: 4 +id: dart_type_registration +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. +--- + +Fory 需要知道序列化消息中的某个类型对应哪个类。你要做的,就是在序列化或反序列化之前注册每个类。 + +## 选择注册策略 + +Fory 提供两种策略。选定一种后,就要在所有读写该类型的语言里保持一致。 + +### 策略一:数字 ID + +更紧凑,也更快。适合小团队在服务间统一协调 ID。 + +```dart +ModelsFory.register(fory, User, id: 100); +``` + +其他语言里也必须使用相同的数字: + +```java +// Java side +fory.register(User.class, 100); +``` + +### 策略二:Namespace + Type Name + +自描述性更强。适合多个团队或多个包独立定义类型,而协调数字 ID 不现实的场景。 + +```dart +ModelsFory.register( + fory, + User, + namespace: 'example', + typeName: 'User', +); +``` + +每个读写该类型的运行时都必须使用相同的 `namespace` 和 `typeName`。 + +> **不要对同一个类型混用策略。** 如果一侧使用数字 ID,另一侧使用名称,反序列化会失败。 + +## 注册生成类型 + +调用 `.fory.dart` 文件中生成的 `register` 函数,它会为你安装好所需的全部序列化元信息: + +```dart +UserModelsFory.register(fory, User, id: 100); +``` + +## 注册自定义序列化器 + +对于无法添加 `@ForyStruct()` 的类型,可以直接传入序列化器实例: + +```dart +fory.registerSerializer( + ExternalType, + const ExternalTypeSerializer(), + namespace: 'example', + typeName: 'ExternalType', +); +``` + +关于如何实现序列化器,见 [自定义序列化器](custom-serializers.md)。 + +## 必须遵守的规则 + +- 在第一次调用 `serialize` 或 `deserialize` **之前**完成注册 +- 注册消息中可能出现的**每一个**类,而不仅是根类型 +- 一旦载荷已经持久化,或已经在服务间交换,就必须保持 ID 或名称**稳定** +- 对同一个类型,不要一侧用数字 ID,另一侧用名称 + +## 跨语言要求 + +所有读写该类型的运行时都必须使用相同的数字 ID,或者相同的 `namespace + typeName` 组合。示例见 [跨语言](xlang-serialization.md)。 + +## 相关主题 + +- [代码生成](code-generation.md) +- [跨语言](xlang-serialization.md) +- [自定义序列化器](custom-serializers.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/dart/web-platform-support.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/dart/web-platform-support.md new file mode 100644 index 00000000000..36ba40e5481 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/dart/web-platform-support.md @@ -0,0 +1,189 @@ +--- +title: Web 平台支持 +sidebar_position: 10 +id: web_platform_support +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. +--- + +Fory Dart 通过生成的序列化器和平台专用运行时实现,支持 Dart VM/AOT、Flutter、浏览器以及 Flutter web 构建。这些平台上的公开 API 和注册流程相同,但 web 构建有更严格的整数精度规则,因为 Dart `int` 由 JavaScript number 表示。 + +## 支持的目标 + +Dart 运行时支持: + +- Dart VM/JIT 应用。 +- Dart AOT/native 应用。 +- Flutter 移动端和桌面端应用。 +- 编译为浏览器 JavaScript 的 Dart 应用。 +- Flutter web 应用。 +- 在所有受支持目标上使用生成的 `@ForyStruct` 序列化器和手动注册的序列化器。 + +## 必须使用代码生成 + +Fory Dart 使用显式注册,而不是运行时反射。对于带注解的 struct,请先运行代码生成并注册生成的序列化器,然后再序列化或反序列化值: + +```dart +import 'package:fory/fory.dart'; + +part 'account.fory.dart'; + +@ForyStruct() +class Account { + Account(); + + String name = ''; + Int64 sequence = Int64(0); +} + +void main() { + final fory = Fory(); + AccountFory.register( + fory, + Account, + namespace: 'example', + typeName: 'Account', + ); + + final bytes = fory.serialize(Account()..name = 'web'); + final account = fory.deserialize(bytes); + print(account.name); +} +``` + +在构建或测试前生成配套文件: + +```bash +cd dart/packages/fory +dart run build_runner build --delete-conflicting-outputs +``` + +注册调用在 VM/AOT、Flutter 和 web 上相同。手写序列化器使用 `registerSerializer(...)`;生成的 struct 使用生成的 `register` 包装器。 + +## 64 位整数规则 + +Dart VM 的 `int` 值是有符号 64 位值。Dart web 的 `int` 值由 JavaScript number 支撑,只在 JavaScript 安全整数范围内精确: + +```text +-9007199254740991 <= value <= 9007199254740991 +``` + +选择字段类型时使用以下规则: + +| 逻辑值 | web 上推荐的 Dart 字段类型 | 说明 | +| -------------------------------------- | ----------------------------------- | ------------------------------------------------------------------------------------ | +| JS 安全范围内的有符号 64 位值 | `int` | 可用于默认 `int64` 映射以及 `@ForyField(type: Int64Type(...))` 编码。 | +| 完整的有符号 64 位范围 | `Int64` | 保留 JS 安全范围之外的值。 | +| 无符号 64 位值 | `Uint64` | 对无法放入有符号或 JS 安全 Dart `int` 的值是必需的。 | +| 8/16/32 位整数 | `int` + `@ForyField(type: ...)` | 使用显式字段元信息与对端运行时精确匹配。 | + +`@ForyField(type: Int64Type(...))` 控制 Dart `int` 字段的编码格式: + +```dart +@ForyStruct() +class SafeCounter { + SafeCounter(); + + @ForyField(type: Int64Type(encoding: Encoding.tagged)) + int count = 0; // 将 web 值保持在 JS 安全范围内 +} +``` + +它不会让 Dart `int` 在 web 上能够存储所有 64 位值。对于完整范围的有符号值,请使用 `Int64`: + +```dart +@ForyStruct() +class FullRangeCounter { + FullRangeCounter(); + + Int64 count = Int64(0); +} +``` + +对于无符号值,请使用 `Uint64`: + +```dart +@ForyStruct() +class StorageExtent { + StorageExtent(); + + Uint64 byteOffset = Uint64(0); +} +``` + +## 自定义序列化器 + +自定义序列化器可以在 VM/AOT、Flutter 和 web 上使用相同的 `Buffer`、`WriteContext` 与 `ReadContext` API。对于 64 位值: + +- 对完整范围的有符号 64 位值,使用 `buffer.writeInt64(Int64(...))` 和 `buffer.readInt64()`。 +- 对完整范围的无符号 64 位值,使用 `buffer.writeUint64(Uint64(...))` 和 `buffer.readUint64()`。 +- 仅当值本来就要作为 Dart `int` 使用,因此在 web 上必须保持 JS 安全时,才使用 `writeInt64FromInt`、`writeVarInt64FromInt` 以及匹配的 `AsInt` 读取方法。 + +示例: + +```dart +final class OffsetSerializer extends Serializer { + const OffsetSerializer(); + + @override + void write(WriteContext context, StorageExtent value) { + context.buffer.writeUint64(value.byteOffset); + } + + @override + StorageExtent read(ReadContext context) { + return StorageExtent()..byteOffset = context.buffer.readUint64(); + } +} +``` + +## 集合和类型化数组 + +`List`、`Set`、`Map`、`Uint8List`、数值类型化数组、`Int64List` 和 `Uint64List` 在 web 上都受支持。`Int64List` 和 `Uint64List` 的实现会保留 64 位值,不依赖 JavaScript 整数精度。当 schema 是 `array` 或 `array` 时,请使用 Fory 包装 list 类型。 + +## 测试浏览器构建 + +修改必须在 web 上工作的代码时,请同时在 VM 和 Chrome 中运行包测试: + +```bash +cd dart/packages/fory +dart run build_runner build --delete-conflicting-outputs +dart test +dart test -p chrome +``` + +如果 Chrome 测试因生成文件过期或缺少 part 文件而失败,请重新运行 `build_runner`,然后从 `dart/packages/fory` 目录重试测试命令。 + +## 常见 Web 失败 + +### `Dart int value ... is outside the JS-safe signed int64 range` + +序列化器正尝试在 web 上把 Dart `int` 写成有符号 64 位值,但该值超出了 JavaScript number 可以精确表示的范围。请将字段类型改为 `Int64`,或把值保持在 JS 安全范围内。 + +### `Int64 value ... is not a JS-safe int` + +反序列化器读取了完整范围的 `Int64`,但目标字段或自定义序列化器要求 Dart `int`。请将字段类型改为 `Int64`,或使用 `readInt64()` 解码,而不是使用 `AsInt` 辅助方法。 + +### `Uint64 value ... is not a JS-safe int` + +代码正在 web 上把 `Uint64` 转换为 Dart `int`。除非应用已经验证该值位于 JS 安全的非负范围内,否则请保持为 `Uint64`。 + +## 相关主题 + +- [支持的类型](supported-types.md) +- [Schema 元信息](schema-metadata.md) +- [代码生成](code-generation.md) +- [故障排查](troubleshooting.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/dart/xlang-serialization.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/dart/xlang-serialization.md new file mode 100644 index 00000000000..f43190184c6 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/dart/xlang-serialization.md @@ -0,0 +1,193 @@ +--- +title: Xlang 序列化 +sidebar_position: 2 +id: xlang_serialization +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. +--- + +Apache Fory™ Dart 生成的二进制格式与 Java、Go、C#、Python、Rust 和 Swift 的 Fory 运行时保持一致。你可以在 Dart 中写消息,在 Java 中读取,或者反过来,整个过程都不需要额外的转换层。 + +## 设置 + +像平常一样创建 `Fory` 实例即可。Dart 中不需要单独开启“xlang 模式”: + +```dart +final fory = Fory(); // 或者在需要 Schema 演进时使用 Fory(compatible: true) +``` + +关键要求是:通信两端必须用同一身份注册同一个类型。 + +## 注册身份 + +最重要的规则是:**每一端都要使用相同的类型身份**。你有两种选择: + +### 数字 ID + +更适合小团队、强协同的场景: + +```dart +// Dart +ModelsFory.register(fory, Person, id: 100); +``` + +### Namespace + Type Name + +更适合多个团队分别定义类型的场景: + +```dart +// Dart +ModelsFory.register( + fory, + Person, + namespace: 'example', + typeName: 'Person', +); +``` + +不要在不同运行时上对同一个类型混用这两种策略。 + +## Dart 到 Java 示例 + +### Dart + +```dart +import 'package:fory/fory.dart'; + +part 'person.fory.dart'; + +@ForyStruct() +class Person { + Person(); + + String name = ''; + Int32 age = Int32(0); +} + +final fory = Fory(); +PersonFory.register(fory, Person, id: 100); +final bytes = fory.serialize(Person() + ..name = 'Alice' + ..age = Int32(30)); +``` + +### Java + +```java +Fory fory = Fory.builder() + .withLanguage(Language.XLANG) + .build(); + +fory.register(Person.class, 100); +Person value = (Person) fory.deserialize(bytesFromDart); +``` + +## Dart 到 C# 示例 + +### Dart + +```dart +final fory = Fory(compatible: true); +PersonFory.register(fory, Person, id: 100); +final bytes = fory.serialize(Person() + ..name = 'Alice' + ..age = Int32(30)); +``` + +### CSharp + +```csharp +[ForyObject] +public sealed class Person +{ + public string Name { get; set; } = string.Empty; + public int Age { get; set; } +} + +Fory fory = Fory.Builder() + .Compatible(true) + .Build(); + +fory.Register(100); +Person person = fory.Deserialize(payloadFromDart); +``` + +## Dart 到 Go 示例 + +### Dart + +```dart +final fory = Fory(); +PersonFory.register(fory, Person, id: 100); +final bytes = fory.serialize(Person() + ..name = 'Alice' + ..age = Int32(30)); +``` + +### Go + +```go +type Person struct { + Name string + Age int32 +} + +f := fory.New(fory.WithXlang(true)) +_ = f.RegisterStruct(Person{}, 100) + +var person Person +_ = f.Deserialize(bytesFromDart, &person) +``` + +## 字段匹配规则 + +Fory 会按字段名或稳定字段 ID 匹配字段。为了获得稳健的跨语言互操作性: + +1. 各端对同一类型使用相同的类型身份,即相同的数字 ID 或相同的 `namespace + typeName`。 +2. 在首次对外发送载荷之前,为所有字段分配稳定的 `@ForyField(id: ...)`。 +3. 保持字段名一致,或者依赖字段 ID,因为 Dart 通常使用 `lowerCamelCase`,Go 为导出字段使用 `PascalCase`,C# 也常用 `PascalCase` 属性。 +4. 使用兼容的数字类型:当对端为 Java `int`、Go `int32` 或 C# `int` 时,在 Dart 中使用 `Int32`;Dart 的 `double` 对应 64 位浮点;32 位浮点请使用 `Float32`。 +5. 日期时间字段优先使用 `Timestamp` 和 `LocalDate`,不要直接用原始 `DateTime`。 +6. 发布前一定要在所有目标语言间完成真实 round trip 验证。 + +## Dart 的类型映射说明 + +由于 Dart `int` 本身并不承诺精确的跨语言编码宽度,所以一旦跨语言解释需要精确定义,就应优先使用包装类型或数字字段注解: + +- `Int32` 对应 xlang `int32` +- `UInt32` 对应 xlang `uint32` +- `Float16` 和 `Float32` 对应用于较小宽度的浮点数 +- `Timestamp` 和 `LocalDate` 用于显式的时间语义 + +参见 [支持的类型](supported-types.md) 和 [xlang type mapping](../../specification/xlang_type_mapping.md)。 + +## 验证 + +在生产环境依赖跨语言契约之前,请让每一种支持的运行时都完成端到端验证。 + +运行 Dart 端: + +```bash +dart run build_runner build --delete-conflicting-outputs +dart analyze +dart test +``` + +## 相关主题 + +- [类型注册](type-registration.md) +- [Schema 演进](schema-evolution.md) +- [跨语言指南](../xlang/index.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/go/_category_.json b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/go/_category_.json new file mode 100644 index 00000000000..e4d176a7628 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/go/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "Go", + "position": 5, + "collapsible": true, + "collapsed": true +} diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/go/basic-serialization.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/go/basic-serialization.md new file mode 100644 index 00000000000..c667c203408 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/go/basic-serialization.md @@ -0,0 +1,404 @@ +--- +title: 基本序列化 +sidebar_position: 20 +id: basic_serialization +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. +--- + +本指南介绍 Fory Go 的核心序列化 API。 + +## 创建 Fory 实例 + +在序列化前先创建 Fory 实例并注册类型: + +```go +import "github.com/apache/fory/go/fory" + +f := fory.New() + +// Register struct with a type ID +f.RegisterStruct(User{}, 1) +f.RegisterStruct(Order{}, 2) + +// Or register with a name (more flexible, less prone to ID conflicts, but higher serialization cost) +f.RegisterNamedStruct(User{}, "example.User") + +// Register enum types +f.RegisterEnum(Color(0), 3) +``` + +**重要**:应在多次序列化调用间复用同一个 Fory 实例。创建新实例会分配内部缓冲区、类型缓存和解析器,开销较高。默认实例不是线程安全的;并发场景请使用线程安全封装(见 [线程安全](thread-safety.md))。 + +更多内容请参考 [类型注册](type-registration.md)。 + +## 核心 API + +### Serialize 与 Deserialize + +最主要的序列化接口: + +```go +// Serialize any value +data, err := f.Serialize(value) +if err != nil { + // Handle error +} + +// Deserialize into target +var result MyType +err = f.Deserialize(data, &result) +if err != nil { + // Handle error +} +``` + +### Marshal 与 Unmarshal + +`Serialize` 和 `Deserialize` 的别名(对 Go 开发者更熟悉): + +```go +data, err := f.Marshal(value) +err = f.Unmarshal(data, &result) +``` + +## 序列化基础类型 + +```go +// Integers +data, _ := f.Serialize(int64(42)) +var i int64 +f.Deserialize(data, &i) // i = 42 + +// Floats +data, _ = f.Serialize(float64(3.14)) +var fl float64 +f.Deserialize(data, &fl) // fl = 3.14 + +// Strings +data, _ = f.Serialize("hello") +var s string +f.Deserialize(data, &s) // s = "hello" + +// Booleans +data, _ = f.Serialize(true) +var b bool +f.Deserialize(data, &b) // b = true +``` + +## 序列化集合类型 + +### Slice + +```go +// String slice +strs := []string{"a", "b", "c"} +data, _ := f.Serialize(strs) + +var result []string +f.Deserialize(data, &result) +// result = ["a", "b", "c"] + +// Integer slice +nums := []int64{1, 2, 3} +data, _ = f.Serialize(nums) + +var intResult []int64 +f.Deserialize(data, &intResult) +// intResult = [1, 2, 3] +``` + +### Map + +```go +// String to string map +m := map[string]string{"key": "value"} +data, _ := f.Serialize(m) + +var result map[string]string +f.Deserialize(data, &result) +// result = {"key": "value"} + +// String to int map +m2 := map[string]int64{"count": 42} +data, _ = f.Serialize(m2) + +var result2 map[string]int64 +f.Deserialize(data, &result2) +// result2 = {"count": 42} +``` + +## 序列化结构体 + +### 基础结构体序列化 + +只有**导出字段**(首字母大写)会被序列化: + +```go +type User struct { + ID int64 // Serialized + Name string // Serialized + password string // NOT serialized (unexported) +} + +f.RegisterStruct(User{}, 1) + +user := &User{ID: 1, Name: "Alice", password: "secret"} +data, _ := f.Serialize(user) + +var result User +f.Deserialize(data, &result) +// result.ID = 1, result.Name = "Alice", result.password = "" +``` + +### 嵌套结构体 + +```go +type Address struct { + City string + Country string +} + +type Person struct { + Name string + Address Address +} + +f.RegisterStruct(Address{}, 1) +f.RegisterStruct(Person{}, 2) + +person := &Person{ + Name: "Alice", + Address: Address{City: "NYC", Country: "USA"}, +} + +data, _ := f.Serialize(person) + +var result Person +f.Deserialize(data, &result) +// result.Address.City = "NYC" +``` + +### 指针字段 + +```go +type Node struct { + Value int32 + Child *Node +} + +// Use WithTrackRef for pointer fields +f := fory.New(fory.WithTrackRef(true)) +f.RegisterStruct(Node{}, 1) + +root := &Node{ + Value: 1, + Child: &Node{Value: 2, Child: nil}, +} + +data, _ := f.Serialize(root) + +var result Node +f.Deserialize(data, &result) +// result.Child.Value = 2 +``` + +## 流式 API + +当你希望自行管理缓冲区时可使用流式接口。 + +### SerializeTo + +序列化到现有缓冲区: + +```go +buf := fory.NewByteBuffer(nil) + +// Serialize multiple values to same buffer +f.SerializeTo(buf, value1) +f.SerializeTo(buf, value2) + +// Get all serialized data +data := buf.GetByteSlice(0, buf.WriterIndex()) +``` + +### DeserializeFrom + +从现有缓冲区反序列化: + +```go +buf := fory.NewByteBuffer(data) + +var result1, result2 MyType +f.DeserializeFrom(buf, &result1) +f.DeserializeFrom(buf, &result2) +``` + +## 泛型 API(类型安全) + +Fory Go 提供了泛型函数用于类型安全的序列化: + +```go +import "github.com/apache/fory/go/fory" + +type User struct { + ID int64 + Name string +} + +// Type-safe serialization +user := &User{ID: 1, Name: "Alice"} +data, err := fory.Serialize(f, user) + +// Type-safe deserialization +var result User +err = fory.Deserialize(f, data, &result) +``` + +泛型 API 的优势: + +- 在编译期推断类型 +- 提供更好的类型安全 +- 在某些场景下可获得性能收益 + +## 错误处理 + +务必检查序列化/反序列化返回的错误: + +```go +data, err := f.Serialize(value) +if err != nil { + switch e := err.(type) { + case fory.Error: + fmt.Printf("Fory error: %s (kind: %d)\n", e.Error(), e.Kind()) + default: + fmt.Printf("Unknown error: %v\n", err) + } + return +} + +err = f.Deserialize(data, &result) +if err != nil { + // Handle deserialization error +} +``` + +常见错误类型: + +- `ErrKindBufferOutOfBound`:读写越界 +- `ErrKindTypeMismatch`:反序列化时类型 ID 不匹配 +- `ErrKindUnknownType`:遇到未知类型 +- `ErrKindMaxDepthExceeded`:超过递归深度限制 +- `ErrKindHashMismatch`:结构体哈希不一致(schema 已变更) + +排查建议见 [故障排查](troubleshooting.md)。 + +## Nil 处理 + +### Nil 指针 + +```go +var ptr *User = nil +data, _ := f.Serialize(ptr) + +var result *User +f.Deserialize(data, &result) +// result = nil +``` + +### 空集合 + +```go +// Nil slice +var slice []string = nil +data, _ := f.Serialize(slice) + +var result []string +f.Deserialize(data, &result) +// result = nil + +// Empty slice (different from nil) +empty := []string{} +data, _ = f.Serialize(empty) + +f.Deserialize(data, &result) +// result = [] (empty, not nil) +``` + +## 完整示例 + +```go +package main + +import ( + "fmt" + "github.com/apache/fory/go/fory" +) + +type Order struct { + ID int64 + Customer string + Items []Item + Total float64 +} + +type Item struct { + Name string + Quantity int32 + Price float64 +} + +func main() { + f := fory.New() + f.RegisterStruct(Order{}, 1) + f.RegisterStruct(Item{}, 2) + + order := &Order{ + ID: 12345, + Customer: "Alice", + Items: []Item{ + {Name: "Widget", Quantity: 2, Price: 9.99}, + {Name: "Gadget", Quantity: 1, Price: 24.99}, + }, + Total: 44.97, + } + + // Serialize + data, err := f.Serialize(order) + if err != nil { + panic(err) + } + fmt.Printf("Serialized %d bytes\n", len(data)) + + // Deserialize + var result Order + if err := f.Deserialize(data, &result); err != nil { + panic(err) + } + + fmt.Printf("Order ID: %d\n", result.ID) + fmt.Printf("Customer: %s\n", result.Customer) + fmt.Printf("Items: %d\n", len(result.Items)) + fmt.Printf("Total: %.2f\n", result.Total) +} +``` + +## 相关主题 + +- [配置](configuration.md) +- [类型注册](type-registration.md) +- [支持类型](supported-types.md) +- [引用](references.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/go/codegen.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/go/codegen.md new file mode 100644 index 00000000000..622bdcd9fc8 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/go/codegen.md @@ -0,0 +1,430 @@ +--- +title: 代码生成 +sidebar_position: 90 +id: codegen +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. +--- + +:::warning Experimental Feature +代码生成是 Fory Go 中的**实验性**特性。其 API 和行为可能会在后续版本中发生变化。标准运行时路径仍是大多数场景下稳定且推荐的做法。 +::: + +Fory Go 为性能关键路径提供可选的 AOT 代码生成。它会提前生成专用序列化器,并增加编译期 shape 校验。 + +## 为什么使用代码生成? + +| 方面 | 标准路径 | 代码生成 | +| -------- | ---------------- | ---------------------- | +| 接入成本 | 零配置 | 需要 `go generate` | +| 性能 | 已经很优秀 | 热路径上更快 | +| 类型安全 | 运行时校验 | 编译期校验 | +| 维护成本 | 自动完成 | 需要重新生成 | + +**适合使用代码生成的场景:** + +- 需要极致性能 +- 很看重编译期类型安全 +- 热路径对性能非常敏感 + +**适合继续使用标准路径的场景:** + +- 更偏好简单接入 +- 类型经常变化 +- 需要动态类型 +- 不希望引入代码生成复杂度 + +## 安装 + +安装 `fory` 生成器二进制: + +```bash +go install github.com/apache/fory/go/fory/cmd/fory@latest + +GO111MODULE=on go get -u github.com/apache/fory/go/fory/cmd/fory +``` + +确保 `$GOBIN` 或 `$GOPATH/bin` 已加入 `PATH`。 + +## 基本用法 + +### 步骤 1:标注结构体 + +在结构体上方添加 `//fory:generate` 注释: + +```go +package models + +//fory:generate +type User struct { + ID int64 `json:"id"` + Name string `json:"name"` +} + +//fory:generate +type Order struct { + ID int64 + Customer string + Total float64 +} +``` + +### 步骤 2:添加 Go Generate 指令 + +添加 `go:generate` 指令(每个文件或每个包一次即可): + +```go +//go:generate fory -file models.go +``` + +或者针对整个包: + +```go +//go:generate fory -pkg . +``` + +### 步骤 3:执行代码生成 + +```bash +go generate ./... +``` + +这会生成包含序列化器代码的 `models_fory_gen.go` 文件。 + +## 生成代码的结构 + +生成器会输出以下内容: + +### 类型快照 + +用于检测结构体变化的编译期检查: + +```go +// Snapshot of User's underlying type at generation time +type _User_expected struct { + ID int64 + Name string +} + +// Compile-time check: fails if User no longer matches +var _ = func(x User) { _ = _User_expected(x) } +``` + +### 序列化器实现 + +强类型的序列化方法: + +```go +type User_ForyGenSerializer struct{} + +func NewSerializerFor_User() fory.Serializer { + return &User_ForyGenSerializer{} +} + +func (User_ForyGenSerializer) WriteTyped(ctx *fory.WriteContext, v *User) error { + buf := ctx.Buffer() + buf.WriteInt64(v.ID) + ctx.WriteString(v.Name) + return nil +} + +func (User_ForyGenSerializer) ReadTyped(ctx *fory.ReadContext, v *User) error { + err := ctx.Err() + buf := ctx.Buffer() + v.ID = buf.ReadInt64(err) + v.Name = ctx.ReadString() + if ctx.HasError() { + return ctx.TakeError() + } + return nil +} +``` + +### 自动注册 + +序列化器会在 `init()` 中注册: + +```go +func init() { + fory.RegisterSerializerFactory((*User)(nil), NewSerializerFor_User) +} +``` + +## 命令行选项 + +### 按文件生成 + +为某个文件生成: + +```bash +fory -file models.go +``` + +### 按包生成 + +为整个包生成: + +```bash +fory -pkg ./models +``` + +### 显式指定类型(旧用法) + +显式指定类型: + +```bash +fory -pkg ./models -type "User,Order" +``` + +### 强制重新生成 + +即使看起来已经是最新,也强制重新生成: + +```bash +fory --force -file models.go +``` + +## 何时需要重新生成 + +出现以下任意变化时,都应重新生成: + +- 字段新增、删除或重命名 +- 字段类型变化 +- 结构体 tag 变化 +- 新增带 `//fory:generate` 的结构体 + +### 自动检测 + +Fory 内置了编译期守卫: + +```go +// If struct changed, this fails to compile +var _ = func(x User) { _ = _User_expected(x) } +``` + +如果忘记重新生成,构建会失败,并给出明确提示。 + +### 自动重试 + +当通过 `go generate` 调用时,生成器会检测陈旧代码并自动重试: + +1. 发现守卫触发的编译错误 +2. 删除旧的生成文件 +3. 重新生成最新代码 + +## 支持的类型 + +代码生成支持: + +- 所有原始类型(`bool`、`int*`、`uint*`、`float*`、`string`) +- 原始类型和结构体的 slice +- 键和值类型受支持的 map +- 嵌套结构体(这些结构体也必须生成) +- 指向结构体的指针 + +### 嵌套结构体 + +所有嵌套结构体也必须带有 `//fory:generate`: + +```go +//fory:generate +type Address struct { + City string + Country string +} + +//fory:generate +type Person struct { + Name string + Address Address // Address must also be generated +} +``` + +## CI/CD 集成 + +### 提交生成代码 + +**推荐用于库项目:** + +```bash +go generate ./... +git add *_fory_gen.go +git commit -m "Regenerate Fory serializers" +``` + +**优点**:使用者无需安装生成器即可构建;构建更可复现 +**缺点**:diff 更大;需要记得重新生成 + +### 在流水线中生成 + +**推荐用于应用项目:** + +```yaml +steps: + - run: go install github.com/apache/fory/go/fory/cmd/fory@latest + - run: go generate ./... + - run: go build ./... +``` + +## 与生成代码配合使用 + +生成代码可以透明接入: + +```go +f := fory.New() + +// Fory automatically uses generated serializer if available +user := &User{ID: 1, Name: "Alice"} +data, _ := f.Serialize(user) + +var result User +f.Deserialize(data, &result) +``` + +无需修改业务代码,注册会在 `init()` 中自动完成。 + +## 混用生成与非生成方式 + +你可以混合使用两种方式: + +```go +//fory:generate +type HotPathStruct struct { + // Performance-critical, use codegen +} + +type ColdPathStruct struct { + // Not annotated, uses the standard runtime serializer +} +``` + +## 限制 + +### 实验性状态 + +- API 可能变化 +- 尚未覆盖所有边界场景 +- 可能仍有未发现的缺陷 + +### 暂不支持 + +- 接口字段(动态类型) +- 无指针的递归类型 +- 私有(未导出)字段 +- 自定义序列化器 + +### 标准路径回退 + +如果生成的序列化器不可用,Fory 会自动回退到标准序列化路径: + +```go +// If User_ForyGenSerializer is not linked in, Fory uses the standard path +f.Serialize(&User{}) +``` + +## 故障排查 + +### `"fory: command not found"` + +确保二进制已经加入 `PATH`: + +```bash +export PATH=$PATH:$(go env GOPATH)/bin +``` + +### 结构体变更后出现编译错误 + +重新生成: + +```bash +go generate ./... +``` + +或强制重新生成: + +```bash +fory --force -file yourfile.go +``` + +### 生成代码不同步 + +编译期守卫会捕获这种情况: + +``` +cannot use x (variable of type User) as type _User_expected in argument +``` + +运行 `go generate` 即可修复。 + +## 示例项目结构 + +``` +myproject/ +├── models/ +│ ├── models.go # Struct definitions +│ ├── models_fory_gen.go # Generated code +│ └── generate.go # go:generate directive +├── main.go +└── go.mod +``` + +**models/generate.go**: + +```go +package models + +//go:generate fory -pkg . +``` + +**models/models.go**: + +```go +package models + +//fory:generate +type User struct { + ID int64 + Name string +} +``` + +## FAQ + +### 代码生成是必须的吗? + +不是。标准序列化路径在不使用代码生成时也能正常工作。 + +### 生成代码能跨 Go 版本使用吗? + +可以。生成代码就是普通 Go 代码,不依赖某个特定版本的语言特性。 + +### 可以混用生成和非生成类型吗? + +可以。只要生成的序列化器可用,Fory 会自动优先使用它。 + +### 如何更新生成代码? + +结构体变更后运行 `go generate ./...`。 + +### 应该提交生成文件吗? + +对于库项目:建议提交。对于应用项目:两种做法都可以。 + +## 相关主题 + +- [基础序列化](basic-serialization.md) +- [配置](configuration.md) +- [故障排查](troubleshooting.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/go/configuration.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/go/configuration.md new file mode 100644 index 00000000000..87301e7ad86 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/go/configuration.md @@ -0,0 +1,380 @@ +--- +title: 配置 +sidebar_position: 10 +id: configuration +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. +--- + +Fory Go 使用函数式选项模式进行配置。你可以在保持合理默认值的前提下,自定义序列化行为。 + +## 创建 Fory 实例 + +### 默认配置 + +```go +import "github.com/apache/fory/go/fory" + +f := fory.New() +``` + +默认设置: + +| Option | Default | Description | +| ------------------------------- | ------- | -------------------------------------- | +| TrackRef | false | 关闭引用跟踪 | +| MaxDepth | 20 | 最大嵌套深度 | +| IsXlang | false | 关闭跨语言模式 | +| Compatible | false | 关闭 Schema 演进兼容模式 | +| MaxTypeFields | 512 | 一个收到的 struct metadata body 最大字段数 | +| MaxTypeMetaBytes | 4096 | 一个收到的 metadata body 最大编码字节数 | +| MaxSchemaVersionsPerType | 10 | 一个逻辑类型最大远端 metadata 版本数 | +| MaxAverageSchemaVersionsPerType | 3 | 所有远端类型的平均 metadata 版本数 | + +### 通过选项配置 + +```go +f := fory.New( + fory.WithTrackRef(true), + fory.WithCompatible(true), + fory.WithMaxDepth(10), + fory.WithMaxTypeFields(512), + fory.WithMaxTypeMetaBytes(4096), + fory.WithMaxSchemaVersionsPerType(10), + fory.WithMaxAverageSchemaVersionsPerType(3), +) +``` + +## 配置项 + +### WithTrackRef + +启用引用跟踪,以支持循环引用和共享对象: + +```go +f := fory.New(fory.WithTrackRef(true)) +``` + +**开启后:** + +- 多次出现的同一对象只会序列化一次 +- 可正确处理循环引用 +- 字段级 `fory:"ref"` 标签生效 +- 会带来对象身份跟踪开销 + +**关闭(默认)时:** + +- 每次对象出现都独立序列化 +- 循环引用会导致栈溢出或超出最大深度错误 +- 字段级 `fory:"ref"` 标签被忽略 +- 对简单数据结构性能更好 + +**建议启用场景:** + +- 数据中存在循环引用 +- 同一对象被多处引用 +- 需要序列化图结构(如带父指针的树、带环链表) + +详见 [引用](references.md)。 + +### WithCompatible + +启用 schema 演进兼容模式: + +```go +f := fory.New(fory.WithCompatible(true)) +``` + +**开启后:** + +- 会向序列化数据写入类型元信息 +- 支持版本间新增/删除字段 +- 按字段名或字段 ID 匹配(与顺序无关) +- 因元信息导致输出更大 + +**关闭(默认)时:** + +- 不写字段元信息,序列化更紧凑 +- 序列化更快、输出更小 +- 字段按排序顺序匹配 +- 要求各服务间结构体定义保持一致 + +详见 [Schema 演进](schema-evolution.md)。 + +### WithMaxDepth + +设置最大嵌套深度,防止栈溢出: + +```go +f := fory.New(fory.WithMaxDepth(30)) +``` + +- 默认值:20 +- 防护深层递归结构或恶意数据 +- 超过限制会返回错误 + +### WithMaxTypeFields + +设置一个收到的远端 struct metadata body 中可接受的最大字段数: + +```go +f := fory.New(fory.WithMaxTypeFields(512)) +``` + +### WithMaxTypeMetaBytes + +设置一个收到的 TypeDef body 可接受的最大编码 body 字节数,不包含 8 字节 header 和扩展 size varint: + +```go +f := fory.New(fory.WithMaxTypeMetaBytes(4096)) +``` + +### WithMaxSchemaVersionsPerType + +设置一个逻辑类型可接受的最大远端 metadata 版本数: + +```go +f := fory.New(fory.WithMaxSchemaVersionsPerType(10)) +``` + +### WithMaxAverageSchemaVersionsPerType + +设置所有已接受远端类型的平均 metadata 版本数限制。有效全局下限为 `8192` 个 schema: + +```go +f := fory.New(fory.WithMaxAverageSchemaVersionsPerType(3)) +``` + +### WithXlang + +启用跨语言序列化模式: + +```go +f := fory.New(fory.WithXlang(true)) +``` + +**开启后:** + +- 使用跨语言类型系统 +- 与 Java、Python、C++、Rust、JavaScript 兼容 +- 类型 ID 遵循 xlang 规范 + +**关闭(默认)时:** + +- 使用 Go 原生序列化模式 +- 支持更多 Go 原生类型 +- 与其他语言实现不兼容 + +## 线程安全 + +默认 `Fory` 实例**不是线程安全的**。并发场景请使用线程安全封装: + +```go +import "github.com/apache/fory/go/fory/threadsafe" + +// Create thread-safe Fory with same options +f := threadsafe.New( + fory.WithTrackRef(true), + fory.WithCompatible(true), +) + +// Safe for concurrent use from multiple goroutines +go func() { + data, _ := f.Serialize(value1) + // data is already copied, safe to use after return +}() +go func() { + data, _ := f.Serialize(value2) +}() +``` + +线程安全封装具备: + +- 内部使用 `sync.Pool` 高效复用实例 +- 返回前自动复制序列化数据 +- 与 `fory.New()` 相同的配置选项 + +### 全局线程安全实例 + +为了便捷,`threadsafe` 包提供全局函数: + +```go +import "github.com/apache/fory/go/fory/threadsafe" + +// Uses a global thread-safe instance with default configuration +data, err := threadsafe.Marshal(&myValue) +err = threadsafe.Unmarshal(data, &result) +``` + +详见 [线程安全](thread-safety.md)。 + +## 缓冲区管理 + +### 零拷贝行为 + +默认 `Fory` 实例会复用内部缓冲区: + +```go +f := fory.New() + +data1, _ := f.Serialize(value1) +// WARNING: data1 becomes invalid after next Serialize call! +data2, _ := f.Serialize(value2) +// data1 now points to invalid memory + +// To keep the data, copy it: +safeCopy := make([]byte, len(data1)) +copy(safeCopy, data1) +``` + +线程安全封装会自动拷贝数据,因此不存在该问题: + +```go +f := threadsafe.New() +data1, _ := f.Serialize(value1) +data2, _ := f.Serialize(value2) +// Both data1 and data2 are valid +``` + +### 手动控制缓冲区 + +高吞吐场景可手动管理缓冲区: + +```go +f := fory.New() +buf := fory.NewByteBuffer(nil) + +// Serialize to existing buffer +err := f.SerializeTo(buf, value) + +// Get serialized data +data := buf.GetByteSlice(0, buf.WriterIndex()) + +// Process data... + +// Reset for next use +buf.Reset() +``` + +## 配置示例 + +### 简单数据(默认配置) + +适用于不含循环引用的简单结构体: + +```go +f := fory.New() + +type Config struct { + Host string + Port int32 +} + +f.RegisterStruct(Config{}, 1) +data, _ := f.Serialize(&Config{Host: "localhost", Port: 8080}) +``` + +### 图结构数据 + +适用于存在循环引用的数据: + +```go +f := fory.New(fory.WithTrackRef(true)) + +type Node struct { + Value int32 + Next *Node `fory:"ref"` +} + +f.RegisterStruct(Node{}, 1) +n1 := &Node{Value: 1} +n2 := &Node{Value: 2} +n1.Next = n2 +n2.Next = n1 // Circular reference + +data, _ := f.Serialize(n1) +``` + +### Schema 演进 + +适用于结构可能随时间变化的数据: + +```go +// V1: original struct +type UserV1 struct { + ID int64 + Name string +} + +// V2: added Email field +type UserV2 struct { + ID int64 + Name string + Email string // New field +} + +// Serialize with V1 +f1 := fory.New(fory.WithCompatible(true)) +f1.RegisterStruct(UserV1{}, 1) +data, _ := f1.Serialize(&UserV1{ID: 1, Name: "Alice"}) + +// Deserialize into V2 - Email will have zero value +f2 := fory.New(fory.WithCompatible(true)) +f2.RegisterStruct(UserV2{}, 1) +var user UserV2 +f2.Deserialize(data, &user) +``` + +### 高性能并发场景 + +适用于高吞吐并发处理: + +```go +type Request struct { + ID int64 + Payload string +} + +f := threadsafe.New( + fory.WithMaxDepth(30), +) +f.RegisterStruct(Request{}, 1) + +// Process requests concurrently +for req := range requests { + go func(r Request) { + data, _ := f.Serialize(&r) + sendResponse(data) + }(req) +} +``` + +## 最佳实践 + +1. **复用 Fory 实例**:创建实例有初始化开销,建议创建一次后复用。 +2. **并发时使用线程安全封装**:不要在多个 goroutine 间共享默认实例。 +3. **按需启用引用跟踪**:引用跟踪会增加开销。 +4. **需要长期保存数据时请复制**:默认实例返回的字节切片会在后续调用后失效。 +5. **合理设置最大深度**:深层结构可适当调大,但需关注内存占用。 +6. **Schema 可能演进时启用兼容模式**:服务版本存在结构差异时建议开启。 +7. **保留远端 metadata 限制默认值**:除非输入可信且 peer 确实会发送更大的 metadata 或大量 schema 版本。 + +## 相关主题 + +- [基本序列化](basic-serialization.md) +- [引用](references.md) +- [Schema 演进](schema-evolution.md) +- [线程安全](thread-safety.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/go/custom-serializers.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/go/custom-serializers.md new file mode 100644 index 00000000000..3c3e3152428 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/go/custom-serializers.md @@ -0,0 +1,286 @@ +--- +title: 自定义序列化器 +sidebar_position: 10 +id: custom_serializers +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. +--- + +自定义序列化器允许你精确定义某个类型如何序列化和反序列化。这适用于需要特殊处理、优化或跨语言兼容性的类型。 + +## 何时使用自定义序列化器 + +- **特殊编码**:需要特定二进制格式的类型 +- **第三方类型**:来自外部库、Fory 无法自动处理的类型 +- **优化**:当你能比默认的基于反射的方法更高效地序列化时 +- **跨语言兼容性**:当互操作需要精确控制二进制格式时 + +## ExtensionSerializer 接口 + +自定义序列化器实现 `ExtensionSerializer` 接口: + +```go +type ExtensionSerializer interface { + // WriteData 将值序列化到缓冲区。 + // 只写入数据,Fory 会处理类型信息和引用。 + // 使用 ctx.Buffer() 访问 ByteBuffer。 + // 使用 ctx.SetError() 上报错误。 + WriteData(ctx *WriteContext, value reflect.Value) + + // ReadData 从缓冲区反序列化值,并写入提供的 value。 + // 只读取数据,Fory 会处理类型信息和引用。 + // 使用 ctx.Buffer() 访问 ByteBuffer。 + // 使用 ctx.SetError() 上报错误。 + ReadData(ctx *ReadContext, value reflect.Value) +} +``` + +## 基础示例 + +下面是一个带整数号字段的类型所使用的简单自定义序列化器: + +```go +import ( + "reflect" + "github.com/apache/fory/go/fory" +) + +type MyExt struct { + Id int32 +} + +type MyExtSerializer struct{} + +func (s *MyExtSerializer) WriteData(ctx *fory.WriteContext, value reflect.Value) { + myExt := value.Interface().(MyExt) + ctx.Buffer().WriteVarint32(myExt.Id) +} + +func (s *MyExtSerializer) ReadData(ctx *fory.ReadContext, value reflect.Value) { + id := ctx.Buffer().ReadVarint32(ctx.Err()) + value.Set(reflect.ValueOf(MyExt{Id: id})) +} + +// 注册自定义序列化器 +f := fory.New(fory.WithXlang(true)) +err := f.RegisterExtension(MyExt{}, 100, &MyExtSerializer{}) +``` + +## Context 方法 + +`WriteContext` 和 `ReadContext` 提供对序列化资源的访问: + +| 方法 | 说明 | +| ---------------- | ---------------------------------------------- | +| `Buffer()` | 返回用于读写的 `*ByteBuffer` | +| `Err()` | 返回 `*Error`,用于延迟错误检查 | +| `SetError(err)` | 在 context 上设置错误 | +| `HasError()` | 如果已设置错误,则返回 true | +| `TypeResolver()` | 返回用于嵌套类型的类型解析器 | +| `RefResolver()` | 返回用于引用支持的引用解析器 | + +## ByteBuffer 方法 + +`ByteBuffer` 提供读写基本类型的方法: + +### 写入方法 + +| 方法 | 说明 | +| -------------------------- | ------------------------ | +| `WriteBool(v bool)` | 写入布尔值 | +| `WriteInt8(v int8)` | 写入有符号 8 位整数 | +| `WriteInt16(v int16)` | 写入有符号 16 位整数 | +| `WriteInt32(v int32)` | 写入有符号 32 位整数 | +| `WriteInt64(v int64)` | 写入有符号 64 位整数 | +| `WriteFloat32(v float32)` | 写入 32 位浮点数 | +| `WriteFloat64(v float64)` | 写入 64 位浮点数 | +| `WriteVarint32(v int32)` | 写入变长有符号 32 位整数 | +| `WriteVarint64(v int64)` | 写入变长有符号 64 位整数 | +| `WriteBinary(data []byte)` | 写入原始字节 | + +### 读取方法 + +所有读取方法都接受一个 `*Error` 参数,用于延迟错误检查: + +| 方法 | 说明 | +| ------------------------------------------- | ---------------------------- | +| `ReadBool(err *Error) bool` | 读取布尔值 | +| `ReadInt8(err *Error) int8` | 读取有符号 8 位整数 | +| `ReadInt16(err *Error) int16` | 读取有符号 16 位整数 | +| `ReadInt32(err *Error) int32` | 读取有符号 32 位整数 | +| `ReadInt64(err *Error) int64` | 读取有符号 64 位整数 | +| `ReadFloat32(err *Error) float32` | 读取 32 位浮点数 | +| `ReadFloat64(err *Error) float64` | 读取 64 位浮点数 | +| `ReadVarint32(err *Error) int32` | 读取变长有符号 32 位整数 | +| `ReadVarint64(err *Error) int64` | 读取变长有符号 64 位整数 | +| `ReadBinary(length int, err *Error) []byte` | 读取指定长度的原始字节 | + +## 复杂类型示例 + +下面是一个包含多个字段的类型的自定义序列化器: + +```go +type Point3D struct { + X, Y, Z float64 + Label string +} + +type Point3DSerializer struct{} + +func (s *Point3DSerializer) WriteData(ctx *fory.WriteContext, value reflect.Value) { + p := value.Interface().(Point3D) + buf := ctx.Buffer() + buf.WriteFloat64(p.X) + buf.WriteFloat64(p.Y) + buf.WriteFloat64(p.Z) + // 将字符串写为长度 + 字节 + labelBytes := []byte(p.Label) + buf.WriteVarint32(int32(len(labelBytes))) + buf.WriteBinary(labelBytes) +} + +func (s *Point3DSerializer) ReadData(ctx *fory.ReadContext, value reflect.Value) { + buf := ctx.Buffer() + err := ctx.Err() + x := buf.ReadFloat64(err) + y := buf.ReadFloat64(err) + z := buf.ReadFloat64(err) + labelLen := buf.ReadVarint32(err) + labelBytes := buf.ReadBinary(int(labelLen), err) + value.Set(reflect.ValueOf(Point3D{ + X: x, + Y: y, + Z: z, + Label: string(labelBytes), + })) +} + +f := fory.New(fory.WithXlang(true)) +f.RegisterExtension(Point3D{}, 101, &Point3DSerializer{}) +``` + +## 处理指针 + +当类型包含指针时,需要显式处理 nil 值: + +```go +type OptionalValue struct { + Value *int64 +} + +type OptionalValueSerializer struct{} + +func (s *OptionalValueSerializer) WriteData(ctx *fory.WriteContext, value reflect.Value) { + ov := value.Interface().(OptionalValue) + buf := ctx.Buffer() + if ov.Value == nil { + buf.WriteBool(false) // nil 标志 + } else { + buf.WriteBool(true) // 非 nil + buf.WriteInt64(*ov.Value) + } +} + +func (s *OptionalValueSerializer) ReadData(ctx *fory.ReadContext, value reflect.Value) { + buf := ctx.Buffer() + err := ctx.Err() + hasValue := buf.ReadBool(err) + if !hasValue { + value.Set(reflect.ValueOf(OptionalValue{Value: nil})) + return + } + v := buf.ReadInt64(err) + value.Set(reflect.ValueOf(OptionalValue{Value: &v})) +} +``` + +## 错误处理 + +使用 `ctx.SetError()` 上报错误: + +```go +func (s *MySerializer) ReadData(ctx *fory.ReadContext, value reflect.Value) { + buf := ctx.Buffer() + version := buf.ReadInt8(ctx.Err()) + if ctx.HasError() { + return + } + + if version != 1 { + ctx.SetError(fory.DeserializationErrorf("unsupported version: %d", version)) + return + } + // 继续读取... + value.Set(reflect.ValueOf(result)) +} +``` + +## 注册选项 + +### 按 ID 注册 + +序列化更紧凑,但要求跨语言协调 ID: + +```go +f.RegisterExtension(MyType{}, 100, &MySerializer{}) +``` + +### 按名称注册 + +更灵活,但序列化成本更高,因为序列化数据会包含类型名: + +```go +f.RegisterExtensionByName(MyType{}, "myapp.MyType", &MySerializer{}) +``` + +## 最佳实践 + +1. **保持简单**:只序列化真正需要的内容 +2. **使用变长整数**:对经常较小的整数使用 `WriteVarint32`/`WriteVarint64` +3. **显式处理 nil**:检查 nil 指针和 slice +4. **为格式设置版本**:考虑添加版本字节,为未来兼容性预留空间 +5. **测试往返**:始终验证 `Read(Write(value)) == value` +6. **匹配读写顺序**:读取字段的顺序必须与写入顺序完全一致 +7. **检查错误**:读取后使用 `ctx.HasError()` 优雅地处理错误 +8. **先部署再使用**:发送由注册序列化器生成的数据前,务必先把该序列化器部署到所有服务。如果服务收到未注册序列化器对应的数据,反序列化会失败 + +## 测试自定义序列化器 + +```go +func TestMySerializer(t *testing.T) { + f := fory.New(fory.WithXlang(true)) + f.RegisterExtension(MyType{}, 100, &MySerializer{}) + + original := MyType{Field: "test"} + + // 序列化 + data, err := f.Serialize(original) + require.NoError(t, err) + + // 反序列化 + var result MyType + err = f.Deserialize(data, &result) + require.NoError(t, err) + + assert.Equal(t, original, result) +} +``` + +## 相关主题 + +- [类型注册](type-registration.md) +- [支持的类型](supported-types.md) +- [Xlang 序列化](xlang-serialization.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/go/grpc-support.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/go/grpc-support.md new file mode 100644 index 00000000000..21f5552dc98 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/go/grpc-support.md @@ -0,0 +1,195 @@ +--- +title: gRPC 支持 +sidebar_position: 13 +id: grpc_support +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. +--- + +Fory 可以为包含 service 定义的 schema 生成 Go gRPC service companion。生成代码使用 +grpc-go 负责传输,并使用 Fory-backed `CodecV2` 编码 request 和 response payload。 + +当 RPC 两端都由同一份 Fory IDL、protobuf IDL 或 FlatBuffers IDL 生成,并且你希望使用 +gRPC 传输语义与 Fory payload 编码时,可以使用这种模式。如果客户端或工具必须直接消费 +protobuf message bytes,请使用标准 protobuf gRPC 代码生成。 + +## 添加依赖 + +向 Go module 添加 grpc-go。Fory Go package 不会把 gRPC 作为硬依赖。 + +```bash +go get google.golang.org/grpc +``` + +生成代码也会导入 Fory Go module: + +```bash +go get github.com/apache/fory/go/fory +``` + +## 定义 Service + +Service 定义可以来自 Fory IDL、protobuf IDL 或 FlatBuffers `rpc_service`: + +```protobuf +package demo.greeter; + +message HelloRequest { + string name = 1; +} + +message HelloReply { + string reply = 1; +} + +service Greeter { + rpc SayHello (HelloRequest) returns (HelloReply); +} +``` + +使用 `--grpc` 生成 Go model 和 gRPC companion: + +```bash +foryc service.fdl --go_out=./generated/go --grpc +``` + +输出包含: + +| 文件 | 用途 | +| ------------------------------ | ----------------------------------------- | +| `greeter/demo_greeter.go` | Fory model 类型和注册辅助逻辑 | +| `greeter/demo_greeter_grpc.go` | grpc-go client、server interface 和 codec | + +生成的 Go 方法使用导出的 PascalCase 名称,例如 `SayHello`。底层 gRPC method path 保留 +schema 中的原始方法名,因此 `sayHello` 或 `say_hello` 等名称仍按 schema 拼写路由。 + +## 实现 Server + +实现生成的 `GreeterServer` interface,使用生成的 Fory codec 创建 grpc-go server,并注册 service: + +```go +package main + +import ( + "context" + "log" + "net" + + "google.golang.org/grpc" + + "example.com/app/generated/go/greeter" +) + +type greeterService struct { + greeter.UnimplementedGreeterServer +} + +func (greeterService) SayHello( + ctx context.Context, + request *greeter.HelloRequest, +) (*greeter.HelloReply, error) { + return &greeter.HelloReply{Reply: "Hello, " + request.Name}, nil +} + +func main() { + listener, err := net.Listen("tcp", ":50051") + if err != nil { + log.Fatal(err) + } + + server := grpc.NewServer( + grpc.ForceServerCodecV2(greeter.CodecV2{}), + ) + greeter.RegisterGreeterServer(server, greeterService{}) + + if err := server.Serve(listener); err != nil { + log.Fatal(err) + } +} +``` + +`grpc.ForceServerCodecV2(...)` 是必需的,它让 server 使用生成的 Fory codec 解码 frame,而不是默认 protobuf codec。 + +## 创建 Client + +生成的 client constructor 接收 grpc-go connection。生成的 client 方法会为每次调用强制使用匹配的 Fory codec。 + +```go +package main + +import ( + "context" + "fmt" + "log" + "time" + + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + + "example.com/app/generated/go/greeter" +) + +func main() { + conn, err := grpc.NewClient( + "localhost:50051", + grpc.WithTransportCredentials(insecure.NewCredentials()), + ) + if err != nil { + log.Fatal(err) + } + defer conn.Close() + + client := greeter.NewGreeterClient(conn) + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + reply, err := client.SayHello(ctx, &greeter.HelloRequest{Name: "Fory"}) + if err != nil { + log.Fatal(err) + } + fmt.Println(reply.Reply) +} +``` + +## Streaming RPC + +Fory service 支持 unary、server-streaming、client-streaming 和 bidirectional streaming。生成 Go 代码遵循 grpc-go 约定: + +- Unary 方法接收 `context.Context` 和 request pointer,返回 response pointer 与 `error`。 +- Server-streaming client 方法返回生成的 stream client。 +- Client-streaming server 方法接收生成的 stream server。 +- Bidirectional streaming 使用生成的 stream client/server interface。 +- 每个 message frame 都使用生成 codec。 + +## gRPC 运行时行为 + +生成的 service companion 只提供 Fory 序列化。deadline、取消、TLS、credential、unary/stream +interceptor、status code、metadata、名称解析、负载均衡、连接生命周期和 backoff 等 Service 行为都遵循标准 grpc-go。 + +## 故障排查 + +### 缺少 `google.golang.org/grpc` + +向 module 添加 grpc-go: + +```bash +go get google.golang.org/grpc +``` + +### `grpc: error while marshaling` + +确认 client 和 server 都使用生成的 `CodecV2{}`,并且生成的 model 文件与 gRPC companion 编译在同一个 package 中。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/go/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/go/index.md new file mode 100644 index 00000000000..0a92e3f143a --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/go/index.md @@ -0,0 +1,155 @@ +--- +title: Go 序列化指南 +sidebar_position: 0 +id: index +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. +--- + +Apache Fory Go 是一个面向 Go 的高性能跨语言序列化库。它支持自动对象图序列化,并具备循环引用、多态和跨语言兼容能力。 + +## 为什么选择 Fory Go? + +- **高性能**:序列化速度快,二进制协议经过优化 +- **跨语言**:可与 Java、Python、C++、Rust、JavaScript 无缝交换数据 +- **自动序列化**:无需 IDL 定义或 schema 编译 +- **引用跟踪**:内置循环引用和共享对象支持 +- **类型安全**:具备基于 schema 感知序列化器的强类型能力 +- **Schema 演进**:兼容模式支持前向/后向兼容 +- **线程安全选项**:提供基于池化的线程安全封装,适用于并发场景 + +## 快速开始 + +### 安装 + +**要求**:Go 1.24 或更高版本 + +```bash +go get github.com/apache/fory/go/fory +``` + +### 基本用法 + +```go +package main + +import ( + "fmt" + "github.com/apache/fory/go/fory" +) + +type User struct { + ID int64 + Name string + Age int32 +} + +func main() { + // Create a Fory instance + f := fory.New() + + // Register struct with a type ID + if err := f.RegisterStruct(User{}, 1); err != nil { + panic(err) + } + + // Serialize + user := &User{ID: 1, Name: "Alice", Age: 30} + data, err := f.Serialize(user) + if err != nil { + panic(err) + } + + // Deserialize + var result User + if err := f.Deserialize(data, &result); err != nil { + panic(err) + } + + fmt.Printf("Deserialized: %+v\n", result) + // Output: Deserialized: {ID:1 Name:Alice Age:30} +} +``` + +## 默认序列化路径 + +Fory Go 可直接作用于普通 Go 结构体。标准运行时路径会缓存类型元数据,并对热点序列化路径做优化,因此大多数应用都可以直接使用,而不需要额外的构建步骤: + +```go +f := fory.New() +data, _ := f.Serialize(myStruct) +``` + +## 配置 + +Fory Go 使用函数式选项模式进行配置: + +```go +f := fory.New( + fory.WithTrackRef(true), // Enable reference tracking + fory.WithCompatible(true), // Enable schema evolution + fory.WithMaxDepth(20), // Set max nesting depth +) +``` + +全部可选项请参考 [配置](configuration.md)。 + +## 支持类型 + +Fory Go 支持广泛的数据类型: + +- **基础类型**:`bool`、`int8`-`int64`、`uint8`-`uint64`、`float32`、`float64`、`string` +- **集合类型**:slice、map、set +- **时间类型**:`time.Time`、`time.Duration` +- **指针类型**:自动处理 nil 的各类指针 +- **结构体**:任意包含导出字段的结构体 + +完整映射请见 [支持类型](supported-types.md)。 + +## 跨语言序列化 + +Fory Go 与其他 Fory 实现完全兼容。在 Go 中序列化的数据可在 Java、Python、C++、Rust 或 JavaScript 中反序列化: + +```go +// Go serialization +f := fory.New() +f.RegisterStruct(User{}, 1) +data, _ := f.Serialize(&User{ID: 1, Name: "Alice"}) +// 'data' can be deserialized by Java, Python, etc. +``` + +类型映射与兼容性细节请参考 [跨语言序列化](xlang-serialization.md)。 + +## 文档导航 + +| 主题 | 说明 | +| ------------------------------------ | --------------------- | +| [配置](configuration.md) | 配置项与运行参数 | +| [基本序列化](basic-serialization.md) | 核心 API 与使用模式 | +| [类型注册](type-registration.md) | 序列化前的类型注册 | +| [支持类型](supported-types.md) | 完整类型支持说明 | +| [引用](references.md) | 循环引用与共享对象 | +| [Struct 标签](schema-metadata.md) | 字段级配置 | +| [Schema 演进](schema-evolution.md) | 前向/后向兼容策略 | +| [跨语言](xlang-serialization.md) | 多语言序列化 | +| [线程安全](thread-safety.md) | 并发使用模式 | +| [故障排查](troubleshooting.md) | 常见问题与解决方案 | + +## 相关资源 + +- [Xlang 序列化规范](https://fory.apache.org/docs/specification/fory_xlang_serialization_spec) +- [跨语言类型映射](https://fory.apache.org/docs/specification/xlang_type_mapping) +- [GitHub 仓库](https://github.com/apache/fory) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/go/native-serialization.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/go/native-serialization.md new file mode 100644 index 00000000000..26adfdd0ff5 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/go/native-serialization.md @@ -0,0 +1,203 @@ +--- +title: Native 序列化 +sidebar_position: 3 +id: native_serialization +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. +--- + +Go native 序列化是通过 `fory.WithXlang(false)` 选择的 Go 专用编码模式。当所有写入方和读取方都是 Go 服务,并且载荷应遵循 Go 类型系统而不是可移植的 xlang 类型系统时使用它。 + +当字节需要由 Java、Python、C++、Rust、JavaScript 或其他非 Go Fory 运行时读取时,请使用 Go 默认模式 +[Xlang 序列化](xlang-serialization.md)。 + +## 何时使用 Native 序列化 + +在以下场景使用 native 序列化: + +- 载荷只由 Go 应用生成和消费。 +- 数据模型使用 Go 特有行为,例如原生 `int`/`uint`、nil slice、nil map、指针、interface,或仅 Go 使用的动态值。 +- 你需要 schema 一致的 Go 载荷,并希望同 schema 场景下的元信息面尽可能小。 +- 你希望为仅 Go 的滚动部署使用兼容的 Schema 演进,但不承诺跨语言类型映射。 +- 你正在为永远不会离开 Go 的 Go struct 使用反射或代码生成序列化器。 + +## 创建 Native 运行时 + +```go +package main + +import "github.com/apache/fory/go/fory" + +type Order struct { + ID int64 + Amount float64 +} + +func main() { + f := fory.New(fory.WithXlang(false)) + if err := f.RegisterStruct(Order{}, 100); err != nil { + panic(err) + } + + data, err := f.Serialize(&Order{ID: 1, Amount: 42.5}) + if err != nil { + panic(err) + } + + var decoded Order + if err := f.Deserialize(data, &decoded); err != nil { + panic(err) + } +} +``` + +复用已配置的 `Fory` 实例。默认实例持有可复用缓冲区,并且不是线程安全的;并发 goroutine 场景请使用线程安全封装。 + +```go +import ( + "github.com/apache/fory/go/fory" + "github.com/apache/fory/go/fory/threadsafe" +) + +f := threadsafe.New(fory.WithXlang(false), fory.WithTrackRef(true)) +_ = f.RegisterStruct(Order{}, 100) +``` + +## Schema 演进 + +Native 序列化默认使用 schema 一致模式。如果未设置 `WithCompatible(true)`,写入方和读取方的 struct 应保持一致。 + +当仅 Go 服务独立滚动升级时,启用兼容模式: + +```go +writer := fory.New(fory.WithXlang(false), fory.WithCompatible(true)) +reader := fory.New(fory.WithXlang(false), fory.WithCompatible(true)) +``` + +兼容模式会写入 schema 元信息;当字段名或显式字段 ID 保持兼容时,读取方可以容忍字段新增、删除或重排。参见 +[Schema 演进](schema-evolution.md)。 + +## 注册 + +在序列化 struct 前先注册它们。对于长期存在的载荷,优先使用显式数字 ID: + +```go +_ = f.RegisterStruct(Order{}, 100) +_ = f.RegisterStruct(LineItem{}, 101) +``` + +当 ID 协调较困难时,基于名称的注册很有用: + +```go +_ = f.RegisterStructByName(Order{}, "example.Order") +``` + +如果注册时没有稳定 ID,每个写入方和读取方都必须做出相同的注册选择。 + +## Go 对象范围 + +Native 序列化让 Go 数据保持在 Go 运行时路径上: + +- 基础数字类型,包括 Go 原生 `int` 和 `uint`。 +- 带导出字段的 struct。 +- Slice、array、map 和 Fory set。 +- 指针和 nil 值,包括 nil slice 和 map。 +- 当已注册序列化器能够解析具体类型时支持 interface 和动态值。 +- `time.Time` 和 `time.Duration` 等时间值。 +- 基于反射和代码生成的序列化器。 + +完整类型范围和 xlang 映射细节请参见[支持的类型](supported-types.md)。 + +## 引用与指针 + +为共享对象身份或循环启用引用跟踪: + +```go +f := fory.New(fory.WithXlang(false), fory.WithTrackRef(true)) + +type Node struct { + Value int32 + Next *Node `fory:"ref"` +} +``` + +对于值形态的数据,可以关闭引用跟踪。这样更快、体积更小,但重复指针会反序列化为独立值,且不支持循环图。 + +## 缓冲区所有权 + +默认 `Fory` 实例会复用内部缓冲区。如果序列化后的字节需要在下一次序列化调用后继续存在,请复制它们: + +```go +data, _ := f.Serialize(value) +stable := append([]byte(nil), data...) +``` + +线程安全封装会在返回前复制字节。对于高吞吐单线程代码,可以序列化到调用方持有的 `ByteBuffer`: + +```go +buf := fory.NewByteBuffer(nil) +err := f.SerializeTo(buf, value) +data := buf.GetByteSlice(0, buf.WriterIndex()) +_ = err +_ = data +``` + +## 性能建议 + +- 复用 `Fory` 或线程安全封装,不要为每个请求构造运行时。 +- 对同步发布的 Go 服务保持 schema 一致模式;只有在需要 Schema 演进时才启用兼容模式。 +- 使用显式数字 ID 注册 struct。 +- 除非对象图需要身份或循环,否则关闭引用跟踪。 +- 当反射开销重要时,为热点 Go struct 使用代码生成。 +- 只有当数据必须在下一次序列化调用后继续存在时,才复制返回的字节。 + +## Native 与 Xlang 对比 + +| 需求 | 使用 native 序列化 | 使用 xlang 序列化 | +| ---------------------------------------- | ------------------ | ----------------- | +| 仅 Go 载荷 | 是 | 可选 | +| 非 Go 读取方或写入方 | 否 | 是 | +| Go 原生 `int`、`uint`、nil slice/map | 是 | 有限 | +| 同语言 schema 一致载荷 | 是 | 否 | +| 默认兼容的 Schema 演进 | 否 | 是 | +| 跨运行时的可移植类型映射 | 否 | 是 | + +## 故障排查 + +### 非 Go 运行时无法读取载荷 + +写入方正在使用 native 序列化。使用 `fory.WithXlang(true)` 重新构建它,并与每个对端运行时对齐类型注册。 + +### 字段变更后滚动部署失败 + +Native 序列化默认使用 schema 一致模式。当 struct 定义可能不同时,在写入方和读取方都使用 `fory.WithCompatible(true)`。 + +### nil slice 或 map 的形态发生变化 + +对于必须保留 Go nil slice/map 语义的仅 Go 载荷,请使用 native 序列化。跨语言 schema 应显式建模可空性。 + +### 另一次序列化后返回的字节发生变化 + +默认运行时会复用缓冲区。复制该字节 slice,或使用 `threadsafe.New(...)`。 + +## 相关主题 + +- [Xlang 序列化](xlang-serialization.md) - 跨运行时 Go 载荷 +- [配置](configuration.md) - Go 运行时选项 +- [类型注册](type-registration.md) - Struct 和 enum 注册 +- [引用](references.md) - 共享引用和循环引用 +- [Schema 演进](schema-evolution.md) - 兼容模式 +- [代码生成](codegen.md) - 生成的序列化器 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/go/references.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/go/references.md new file mode 100644 index 00000000000..2becedd121b --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/go/references.md @@ -0,0 +1,356 @@ +--- +title: 引用 +sidebar_position: 8 +id: references +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. +--- + +Fory Go 支持引用跟踪,用于处理循环引用和共享对象。这对序列化图、带父指针的树、带环链表等复杂数据结构至关重要。 + +## 启用引用跟踪 + +引用跟踪**默认禁用**。创建 Fory 实例时启用它: + +```go +f := fory.New(fory.WithXlang(true), fory.WithTrackRef(true)) +``` + +**重要**:必须启用全局引用跟踪,任何引用跟踪才会生效。当 `WithTrackRef(false)`(默认值)时,所有逐字段引用 tag 都会被忽略。 + +## 引用跟踪的工作方式 + +### 不使用引用跟踪(默认) + +禁用时,每个对象都会独立序列化: + +```go +f := fory.New(fory.WithXlang(true)) // TrackRef 默认禁用 + +shared := &Data{Value: 42} +container := &Container{A: shared, B: shared} + +data, _ := f.Serialize(container) +// 'shared' 会被序列化两次(不去重) +``` + +### 使用引用跟踪 + +启用后,对象会按身份进行跟踪: + +```go +f := fory.New(fory.WithXlang(true), fory.WithTrackRef(true)) + +shared := &Data{Value: 42} +container := &Container{A: shared, B: shared} + +data, _ := f.Serialize(container) +// 'shared' 只序列化一次,第二次出现会写为引用 +``` + +## 引用标志 + +Fory 在序列化期间使用标志表示引用状态: + +| 标志 | 值 | 含义 | +| ------------------ | ----- | ---------------------------- | +| `NullFlag` | -3 | Nil/null 值 | +| `RefFlag` | -2 | 指向先前已序列化对象的引用 | +| `NotNullValueFlag` | -1 | 非 null 值(后续为数据) | +| `RefValueFlag` | 0 | 引用值标志 | + +## 可引用类型 + +只有特定类型支持引用跟踪。在 xlang 模式中,以下类型可以跟踪引用: + +| 类型 | 是否跟踪引用 | 说明 | +| ----------------------------- | ------------ | ---------------------------- | +| `*struct`(指向 struct 的指针) | 是 | 使用 `fory:"ref"` tag 启用 | +| `any`(interface) | 是 | 自动跟踪 | +| `[]T`(slice) | 是 | 使用 `fory:"ref"` tag 启用 | +| `map[K]V` | 是 | 使用 `fory:"ref"` tag 启用 | +| `*int`、`*string` 等 | 否 | 排除指向基本类型的指针 | +| 基本类型 | 否 | 值类型 | +| `time.Time`、`time.Duration` | 否 | 值类型 | +| 数组(`[N]T`) | 否 | 值类型 | + +## 逐字段引用控制 + +默认情况下,即使设置了全局 `WithTrackRef(true)`,单个字段的引用跟踪也**禁用**。可以使用 `ref` struct tag 为特定字段启用引用跟踪: + +```go +type Container struct { + // 为此字段启用引用跟踪 + SharedData *Data `fory:"ref"` + + // 显式禁用引用跟踪(与默认值相同) + SimpleData *Data `fory:"ref=false"` +} +``` + +**重要说明**: + +- 逐字段 tag 只有在设置全局 `WithTrackRef(true)` 时才会生效 +- 当全局 `WithTrackRef(false)`(默认)时,所有字段 ref tag 都会被忽略 +- 适用于 slice、map 和指向 struct 的指针字段 +- 指向基本类型的指针(例如 `*int`、`*string`)不能使用此 tag +- 默认值为 `ref=false`(字段级别不启用引用跟踪) + +更多细节请参阅 [Struct Tags](schema-metadata.md)。 + +## 循环引用 + +循环数据结构需要启用引用跟踪: + +### 循环链表 + +```go +type Node struct { + Value int32 + Next *Node `fory:"ref"` +} + +f := fory.New(fory.WithXlang(true), fory.WithTrackRef(true)) +f.RegisterStruct(Node{}, 1) + +// 创建循环链表 +n1 := &Node{Value: 1} +n2 := &Node{Value: 2} +n3 := &Node{Value: 3} +n1.Next = n2 +n2.Next = n3 +n3.Next = n1 // 循环引用回 n1 + +data, _ := f.Serialize(n1) + +var result Node +f.Deserialize(data, &result) +// 循环结构会被保留 +// result.Next.Next.Next == &result +``` + +### 父子树 + +```go +type TreeNode struct { + Value string + Parent *TreeNode `fory:"ref"` + Children []*TreeNode `fory:"ref"` +} + +f := fory.New(fory.WithXlang(true), fory.WithTrackRef(true)) +f.RegisterStruct(TreeNode{}, 1) + +root := &TreeNode{Value: "root"} +child1 := &TreeNode{Value: "child1", Parent: root} +child2 := &TreeNode{Value: "child2", Parent: root} +root.Children = []*TreeNode{child1, child2} + +data, _ := f.Serialize(root) + +var result TreeNode +f.Deserialize(data, &result) +// result.Children[0].Parent == &result +``` + +### 图结构 + +```go +type GraphNode struct { + ID int32 + Neighbors []*GraphNode `fory:"ref"` +} + +f := fory.New(fory.WithXlang(true), fory.WithTrackRef(true)) +f.RegisterStruct(GraphNode{}, 1) + +// 创建图 +a := &GraphNode{ID: 1} +b := &GraphNode{ID: 2} +c := &GraphNode{ID: 3} + +// 双向连接 +a.Neighbors = []*GraphNode{b, c} +b.Neighbors = []*GraphNode{a, c} +c.Neighbors = []*GraphNode{a, b} + +data, _ := f.Serialize(a) + +var result GraphNode +f.Deserialize(data, &result) +``` + +## 共享对象去重 + +引用跟踪还会对共享对象去重: + +```go +type Config struct { + Setting string +} + +type Application struct { + MainConfig *Config `fory:"ref"` + BackupConfig *Config `fory:"ref"` + FallbackConfig *Config `fory:"ref"` +} + +f := fory.New(fory.WithXlang(true), fory.WithTrackRef(true)) +f.RegisterStruct(Config{}, 1) +f.RegisterStruct(Application{}, 2) + +// 共享配置 +config := &Config{Setting: "value"} + +// 对同一对象的多个引用 +app := &Application{ + MainConfig: config, + BackupConfig: config, + FallbackConfig: config, +} + +data, _ := f.Serialize(app) +// 'config' 序列化一次,其余位置为引用 + +var result Application +f.Deserialize(data, &result) +// result.MainConfig == result.BackupConfig == result.FallbackConfig +``` + +## 性能注意事项 + +### 开销 + +引用跟踪会增加开销: + +- 用于跟踪已见对象的内存(哈希表) +- 序列化期间的哈希查找 +- 引用标志和 ID 产生的额外字节 + +### 何时启用 + +**在以下情况下启用引用跟踪**: + +- 数据存在循环引用 +- 同一个对象被引用多次 +- 正在序列化图结构 +- 必须保留对象身份 + +**在以下情况下禁用引用跟踪**: + +- 数据是树状结构(无环) +- 每个对象只出现一次 +- 需要最高性能 +- 对象身份不重要 + +### 内存使用 + +引用跟踪会维护正在序列化对象的映射: + +```go +// 内部引用跟踪结构 +type RefResolver struct { + writtenObjects map[refKey]int32 // 指针 -> 引用 ID + readObjects []reflect.Value // 引用 ID -> 对象 +} +``` + +对于大型对象图,这可能增加内存使用。 + +## 错误处理 + +### 不使用引用跟踪 + +未启用跟踪的循环引用会导致栈溢出或最大深度错误: + +```go +f := fory.New(fory.WithXlang(true)) // 不启用引用跟踪 + +n1 := &Node{Value: 1} +n1.Next = n1 // 自引用 + +data, err := f.Serialize(n1) +// 错误:max depth exceeded(或栈溢出) +``` + +### 无效引用 ID + +反序列化期间,无效引用 ID 会产生错误: + +```go +// 错误类型:ErrKindInvalidRefId +``` + +当序列化数据包含指向先前未序列化对象的引用时,就会发生这种情况。 + +## 完整示例 + +```go +package main + +import ( + "fmt" + "github.com/apache/fory/go/fory" +) + +type Person struct { + Name string + Friends []*Person `fory:"ref"` + BestFriend *Person `fory:"ref"` +} + +func main() { + f := fory.New(fory.WithXlang(true), fory.WithTrackRef(true)) + f.RegisterStruct(Person{}, 1) + + // 创建带相互好友关系的人 + alice := &Person{Name: "Alice"} + bob := &Person{Name: "Bob"} + charlie := &Person{Name: "Charlie"} + + alice.Friends = []*Person{bob, charlie} + alice.BestFriend = bob + + bob.Friends = []*Person{alice, charlie} + bob.BestFriend = alice // 互为最好的朋友 + + charlie.Friends = []*Person{alice, bob} + + // 序列化 + data, err := f.Serialize(alice) + if err != nil { + panic(err) + } + fmt.Printf("Serialized %d bytes\n", len(data)) + + // 反序列化 + var result Person + if err := f.Deserialize(data, &result); err != nil { + panic(err) + } + + // 验证循环引用被保留 + fmt.Printf("Alice's best friend: %s\n", result.BestFriend.Name) + fmt.Printf("Bob's best friend: %s\n", result.BestFriend.BestFriend.Name) + // 输出:Alice(循环引用被保留) +} +``` + +## 相关主题 + +- [配置](configuration.md) +- [Struct Tags](schema-metadata.md) +- [Xlang 序列化](xlang-serialization.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/go/schema-evolution.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/go/schema-evolution.md new file mode 100644 index 00000000000..03a60a9b10c --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/go/schema-evolution.md @@ -0,0 +1,363 @@ +--- +title: Schema 演进 +sidebar_position: 9 +id: schema_evolution +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. +--- + +Schema 演进允许数据结构随时间变化,同时保持与先前序列化数据的兼容性。Fory Go 通过兼容模式支持这一点。Xlang 模式默认使用兼容的 Schema 演进;native 模式默认使用 schema-consistent 载荷,并显式启用兼容模式。 + +## 兼容模式默认值 + +对于跨语言和默认 Go 载荷,请使用默认运行时: + +```go +f := fory.New(fory.WithXlang(true)) +``` + +对于需要 Schema 演进的仅 Go native-mode 载荷,请显式启用兼容模式: + +```go +f := fory.New(fory.WithXlang(false), fory.WithCompatible(true)) +``` + +## 工作方式 + +### Schema-Consistent Native 模式 + +- 紧凑序列化,不写入元数据 +- 反序列化期间会检查 struct 哈希 +- 任何 schema 变更都会导致 `ErrKindHashMismatch` + +### 使用兼容模式 + +- 类型元数据会写入序列化数据 +- 支持添加、移除和重排字段 +- 启用向前和向后兼容 + +### 为稳定 Struct 禁用演进 + +如果 struct schema 稳定且不会变化,可以为该 struct 禁用演进,以避免兼容元数据开销。实现 `ForyEvolving` 接口并返回 `false`: + +```go +type StableMessage struct { + ID int64 +} + +func (StableMessage) ForyEvolving() bool { + return false +} +``` + +## 支持的 Schema 变更 + +### 添加字段 + +可以添加新字段;反序列化旧数据时,这些字段会获得零值: + +```go +// 版本 1 +type UserV1 struct { + ID int64 + Name string +} + +// 版本 2(添加 Email) +type UserV2 struct { + ID int64 + Name string + Email string // 新字段 +} + +f := fory.New(fory.WithXlang(true)) +f.RegisterStruct(UserV1{}, 1) + +// 使用 V1 序列化 +userV1 := &UserV1{ID: 1, Name: "Alice"} +data, _ := f.Serialize(userV1) + +// 使用 V2 反序列化 +f2 := fory.New(fory.WithXlang(true)) +f2.RegisterStruct(UserV2{}, 1) + +var userV2 UserV2 +f2.Deserialize(data, &userV2) +// userV2.Email = ""(零值) +``` + +### 移除字段 + +反序列化时会跳过被移除的字段: + +```go +// 版本 1 +type ConfigV1 struct { + Host string + Port int32 + Timeout int64 + Debug bool // 将被移除 +} + +// 版本 2(移除 Debug) +type ConfigV2 struct { + Host string + Port int32 + Timeout int64 + // Debug 字段已移除 +} + +f := fory.New(fory.WithXlang(true)) +f.RegisterStruct(ConfigV1{}, 1) + +// 使用 V1 序列化 +config := &ConfigV1{Host: "localhost", Port: 8080, Timeout: 30, Debug: true} +data, _ := f.Serialize(config) + +// 使用 V2 反序列化 +f2 := fory.New(fory.WithXlang(true)) +f2.RegisterStruct(ConfigV2{}, 1) + +var configV2 ConfigV2 +f2.Deserialize(data, &configV2) +// Debug 字段数据会被跳过 +``` + +### 重排字段 + +字段顺序可以在版本之间变化: + +```go +// 版本 1 +type PersonV1 struct { + FirstName string + LastName string + Age int32 +} + +// 版本 2(重新排序) +type PersonV2 struct { + Age int32 // 上移 + LastName string + FirstName string // 下移 +} +``` + +兼容模式会通过按名称匹配字段自动处理这种变化。 + +## 不兼容变更 + +即使在兼容模式中,也不支持某些变更: + +### 类型变更 + +```go +// 不支持 +type V1 struct { + Value int32 // int32 +} + +type V2 struct { + Value string // 改为 string,不兼容 +} +``` + +### 重命名字段 + +```go +// 不支持(会被视为移除 + 添加) +type V1 struct { + UserName string +} + +type V2 struct { + Username string // 名称不同,不是重命名 +} +``` + +这会被视为移除 `UserName` 并添加 `Username`,从而导致数据丢失。 + +## 最佳实践 + +### 1. 对持久化数据使用兼容模式 + +```go +// 默认 xlang 载荷已经使用兼容模式。 +f := fory.New(fory.WithXlang(true)) +``` + +对于存储在数据库、文件或缓存中的仅 Go native-mode 数据,请启用兼容模式: + +```go +f := fory.New(fory.WithXlang(false), fory.WithCompatible(true)) +``` + +### 2. 提供默认值 + +```go +type ConfigV2 struct { + Host string + Port int32 + Timeout int64 + Retries int32 // 新字段 +} + +func NewConfigV2() *ConfigV2 { + return &ConfigV2{ + Retries: 3, // 默认值 + } +} + +// 反序列化后,应用默认值 +if config.Retries == 0 { + config.Retries = 3 +} +``` + +## Xlang Schema 演进 + +Schema 演进可跨语言工作: + +### Go(生产者) + +```go +type MessageV1 struct { + ID int64 + Content string +} + +f := fory.New(fory.WithXlang(true)) +f.RegisterStruct(MessageV1{}, 1) +data, _ := f.Serialize(&MessageV1{ID: 1, Content: "Hello"}) +``` + +### Java(使用较新 schema 的消费者) + +```java +public class Message { + long id; + String content; + String author; // Java 中的新字段 +} + +Fory fory = Fory.builder().withXlang(true).build(); +fory.register(Message.class, 1); +Message msg = fory.deserialize(data, Message.class); +// msg.author 将为 null +``` + +## 性能注意事项 + +兼容模式主要影响序列化尺寸: + +| 方面 | Schema Consistent | 兼容模式 | +| --------------- | ----------------- | --------------------------------------------- | +| 序列化尺寸 | 更小 | 更大(包含元数据,尤其是没有字段 ID 时) | +| 速度 | 快 | 类似(元数据只是 memcpy) | +| Schema 灵活性 | 无 | 完整 | + +**说明**:在兼容模式中使用字段 ID(`fory:"id=N"`)可以减少元数据尺寸。 + +**建议**:以下场景使用兼容模式: + +- 持久化存储 +- 跨服务通信 +- 长期缓存 + +以下场景使用 native schema-consistent 模式: + +- 内存内操作 +- 同版本通信 +- 最小序列化尺寸 + +## 错误处理 + +### 哈希不匹配(Native Schema-Consistent 模式) + +```go +f := fory.New(fory.WithXlang(false)) // 兼容模式禁用 + +// 未启用兼容模式时 schema 发生变化 +err := f.Deserialize(oldData, &newStruct) +// 错误:ErrKindHashMismatch +``` + +### 未知字段 + +在兼容模式中,未知字段会被静默跳过。要检测它们: + +```go +// 目前,Fory 会自动跳过未知字段 +// 没有用于检测未知字段的显式 API +``` + +## 完整示例 + +```go +package main + +import ( + "fmt" + "github.com/apache/fory/go/fory" +) + +// V1:初始 schema +type ProductV1 struct { + ID int64 + Name string + Price float64 +} + +// V2:添加字段 +type ProductV2 struct { + ID int64 + Name string + Price float64 + Description string // 新增 + InStock bool // 新增 +} + +func main() { + // 使用 V1 序列化 + f1 := fory.New(fory.WithXlang(true)) + f1.RegisterStruct(ProductV1{}, 1) + + product := &ProductV1{ID: 1, Name: "Widget", Price: 9.99} + data, _ := f1.Serialize(product) + fmt.Printf("V1 serialized: %d bytes\n", len(data)) + + // 使用 V2 反序列化 + f2 := fory.New(fory.WithXlang(true)) + f2.RegisterStruct(ProductV2{}, 1) + + var productV2 ProductV2 + if err := f2.Deserialize(data, &productV2); err != nil { + panic(err) + } + + fmt.Printf("ID: %d\n", productV2.ID) + fmt.Printf("Name: %s\n", productV2.Name) + fmt.Printf("Price: %.2f\n", productV2.Price) + fmt.Printf("Description: %q (zero value)\n", productV2.Description) + fmt.Printf("InStock: %v (zero value)\n", productV2.InStock) +} +``` + +## 相关主题 + +- [配置](configuration.md) +- [Xlang 序列化](xlang-serialization.md) +- [故障排查](troubleshooting.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/go/schema-metadata.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/go/schema-metadata.md new file mode 100644 index 00000000000..c008547d3ba --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/go/schema-metadata.md @@ -0,0 +1,391 @@ +--- +title: Schema 元数据 +sidebar_position: 5 +id: schema_metadata +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. +--- + +Fory Go 使用 struct tag 自定义字段级序列化行为。这样可以细粒度控制各个字段的序列化方式。 + +## Tag 语法 + +Fory struct tag 的通用语法如下: + +```go +type MyStruct struct { + Field Type `fory:"option1,option2=value"` +} +``` + +多个选项用逗号(`,`)分隔。 + +## 可用 Tag + +### 字段 ID + +使用 `id=N` 为字段分配数字 ID,以便进行紧凑编码: + +```go +type User struct { + ID int64 `fory:"id=0"` + Name string `fory:"id=1"` + Age int32 `fory:"id=2"` +} +``` + +**优势**: + +- 序列化尺寸更小(数字 ID 相比字段名更紧凑) +- 序列化/反序列化更快 +- 这是获得最佳跨语言兼容性的必要条件 + +**说明**: + +- ID 在同一个 struct 内必须唯一 +- ID 必须大于等于 0 +- 如果未指定,将使用字段名(载荷更大) + +### 忽略字段 + +使用 `-` 将字段排除在序列化之外: + +```go +type User struct { + ID int64 + Name string + Password string `fory:"-"` // 不序列化 +} +``` + +`Password` 字段不会包含在序列化输出中,反序列化后会保持零值。 + +### 可空 + +使用 `nullable` 控制是否为指针、slice、map 或 interface 字段写入空值标志: + +```go +type Record struct { + // 为此字段写入空值标志(允许 nil 值) + OptionalData *Data `fory:"nullable"` + + // 跳过空值标志(字段不能为 nil) + RequiredData *Data `fory:"nullable=false"` +} +``` + +**说明**: + +- 仅适用于指针、slice、map 和 interface 字段 +- 当 `nullable=false` 时,序列化 nil 值会导致错误 +- Xlang 模式下,顶层 struct 字段默认只会让指针和 `optional` carrier 字段可空。Native 模式下,指针、slice、map 和 interface 字段默认可空。 + +### 引用跟踪 + +控制 slice、map 或指向 struct 的指针字段的逐字段引用跟踪: + +```go +type Container struct { + // 为此字段启用引用跟踪 + SharedData *Data `fory:"ref"` + + // 为此字段禁用引用跟踪 + SimpleData *Data `fory:"ref=false"` +} +``` + +**说明**: + +- 适用于 slice、map 和指向 struct 的指针字段 +- 指向基本类型的指针(例如 `*int`、`*string`)不能使用此 tag +- 默认值为 `ref=false`(不启用引用跟踪) +- 设置全局 `WithTrackRef(false)` 时,字段 ref tag 会被忽略 +- 设置全局 `WithTrackRef(true)` 时,可用 `ref=false` 为特定字段禁用引用跟踪 + +**使用场景**: + +- 可能存在循环引用或共享引用的字段应启用 +- 始终唯一的字段可禁用(优化) + +### 编码 + +使用 `encoding` 控制数字字段的编码方式: + +```go +type Metrics struct { + // 变长编码(默认,小数值更小) + Count int64 `fory:"encoding=varint"` + + // 定长编码(尺寸固定) + Timestamp int64 `fory:"encoding=fixed"` + + // 带 tag 编码(包含类型 tag) + Value int64 `fory:"encoding=tagged"` +} +``` + +**支持的编码**: + +| 类型 | 选项 | 默认值 | +| -------- | --------------------------- | -------- | +| `int32` | `varint`, `fixed` | `varint` | +| `uint32` | `varint`, `fixed` | `varint` | +| `int64` | `varint`, `fixed`, `tagged` | `varint` | +| `uint64` | `varint`, `fixed`, `tagged` | `varint` | + +**何时使用**: + +- `varint`:最适合经常较小的值(默认) +- `fixed`:最适合使用完整取值范围的值(例如时间戳、哈希) +- `tagged`:需要保留类型信息时使用 + +### 类型覆盖 + +使用 `type=` 覆盖推断得到的 carrier 语义或嵌套值编码: + +```go +type Foo struct { + // 强制使用通用 list 协议。 + Values []int32 `fory:"type=list"` + + // 为通用 list 覆盖内部整数编码 + FixedValues []int32 `fory:"type=list(element=int32(encoding=fixed))"` + + // 覆盖嵌套 map/list 的整数编码 + Nested map[string][]*uint64 `fory:"type=map(value=list(element=uint64(encoding=tagged)))"` + + // 显式声明密集数字数组 schema。 + Dense []int32 `fory:"type=array(element=int32)"` + + // 在 map 值中使用 array schema。 + Packed map[string][]int32 `fory:"type=map(value=array(element=int32))"` +} +``` + +**说明**: + +- `list(...)`、`array(...)`、`set(...)` 和 `map(...)` 是显式容器覆盖 +- `list(...)` 始终使用 list schema,绝不会折叠为密集 array schema +- `array(element=...)` 要求元素域为 bool 或数字类型,并拒绝可空元素和标量编码修饰符 + +## 组合 Tag + +多个 tag 可以用逗号分隔符组合: + +```go +type Document struct { + ID int64 `fory:"id=0,encoding=fixed"` + Content string `fory:"id=1"` + Author *User `fory:"id=2,nullable=false,ref"` +} +``` + +## 与其他 Tag 集成 + +Fory tag 可以与其他 struct tag 共存: + +```go +type User struct { + ID int64 `json:"id" fory:"id=0"` + Name string `json:"name,omitempty" fory:"id=1"` + Password string `json:"-" fory:"-"` +} +``` + +每个 tag 命名空间都是独立的。 + +## 字段可见性 + +只会考虑**导出字段**(以大写字母开头): + +```go +type User struct { + ID int64 // 会序列化 + Name string // 会序列化 + password string // 不会序列化(未导出,无需 tag) +} +``` + +无论是否带 tag,未导出字段都会被忽略。 + +## 字段排序 + +字段按以下规则以一致顺序序列化: + +1. 字段名(按 snake_case 字母序) +2. 字段类型 + +这能在字段顺序重要的场景中保证跨语言兼容性。 + +## Struct 哈希 + +Fory 会计算 struct 字段的哈希,用于版本检查: + +- 哈希包含字段名和类型 +- 哈希会写入序列化数据 +- 不匹配会触发 `ErrKindHashMismatch` + +Struct 字段变更会影响哈希: + +```go +// 这些会产生不同的哈希 +type V1 struct { + UserID int64 +} + +type V2 struct { + UserId int64 // 字段名不同 = 哈希不同 +} +``` + +## 示例 + +### API 响应 Struct + +```go +type APIResponse struct { + Status int32 `json:"status" fory:"id=0"` + Message string `json:"message" fory:"id=1"` + Data any `json:"data" fory:"id=2"` + Internal string `json:"-" fory:"-"` // 在 JSON 和 Fory 中都忽略 +} +``` + +### 使用共享引用进行缓存 + +```go +type CacheEntry struct { + Key string + Value *CachedData `fory:"ref"` // 可能被共享 + Metadata *Metadata `fory:"ref=false"` // 始终唯一 + ExpiresAt int64 +} +``` + +### 带循环引用的 Document + +```go +type Document struct { + ID int64 + Title string + Parent *Document `fory:"ref"` // 可能引用自身或兄弟节点 + Children []*Document `fory:"ref"` +} +``` + +## Tag 解析错误 + +无效 tag 会在注册期间产生错误: + +```go +type BadStruct struct { + Field int `fory:"invalid=option=format"` +} + +f := fory.New(fory.WithXlang(true)) +err := f.RegisterStruct(BadStruct{}, 1) +// 错误:ErrKindInvalidTag +``` + +## Native 模式与 Xlang 模式 + +字段配置会因序列化模式而异: + +**Native 模式**: + +- **可空**:指针、slice、map 和 interface 类型默认可空 +- **引用跟踪**:默认禁用(未设置 `ref` tag) + +**Xlang 模式**: + +- **可空**:指针和 `optional.Optional[T]` 字段默认可空(slice、map 和 interface 除非带 tag,否则不可空) +- **引用跟踪**:默认禁用(未设置 `ref` tag) + +在以下场景中,你**需要配置字段**: + +- 字段可能为 nil(使用 `*string`、`*int32` 等指针类型) +- 字段需要为共享/循环对象启用引用跟踪(使用 `fory:"ref"`) +- 想减少元数据尺寸(使用 `fory:"id=N"` 字段 ID) + +```go +// Xlang 模式:需要显式配置 +type User struct { + ID int64 `fory:"id=0"` + Name string `fory:"id=1"` + Email *string `fory:"id=2"` // 用指针类型表示可空 + Friend *User `fory:"id=3,ref"` // 共享对象必须声明 ref +} +``` + +### 默认值摘要 + +| 选项 | 默认值 | 启用方式 | +| ---------- | ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------ | +| `nullable` | Xlang 模式中的指针和 `optional.Optional[T]` 字段;native 模式中的指针、slice、map 和 interface 字段 | 使用 `fory:"nullable"` 或 `fory:"nullable=false"` | +| `ref` | `false` | 添加 `fory:"ref"` tag | +| `id` | 省略 | 添加 `fory:"id=N"` tag | + +## 最佳实践 + +1. **对敏感数据使用 `-`**:密码、token、内部状态 +2. **为共享对象启用引用跟踪**:当同一个指针出现多次时 +3. **为简单字段禁用引用跟踪**:当你知道字段唯一时可作为优化 +4. **保持名称一致**:跨语言名称应保持一致 +5. **记录 tag 用法**:尤其是非显而易见的配置 + +## 常见模式 + +### 忽略计算字段 + +```go +type Rectangle struct { + Width float64 + Height float64 + Area float64 `fory:"-"` // 计算值,不序列化 +} + +func (r *Rectangle) ComputeArea() { + r.Area = r.Width * r.Height +} +``` + +### 带 Parent 的循环结构 + +```go +type TreeNode struct { + Value string + Parent *TreeNode `fory:"ref"` // 循环反向引用 + Children []*TreeNode `fory:"ref"` +} +``` + +### 混合序列化需求 + +```go +type Session struct { + ID string + UserID int64 + Token string `fory:"-"` // 安全:不要序列化 + User *User `fory:"ref"` // 可能跨 session 共享 + CreatedAt int64 +} +``` + +## 相关主题 + +- [引用](references.md) +- [基础序列化](basic-serialization.md) +- [Schema 演进](schema-evolution.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/go/supported-types.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/go/supported-types.md new file mode 100644 index 00000000000..9b36cc7c97f --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/go/supported-types.md @@ -0,0 +1,371 @@ +--- +title: 支持的类型 +sidebar_position: 7 +id: supported_types +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. +--- + +Fory Go 支持序列化多种 Go 类型。本指南覆盖所有支持的类型及其跨语言映射。 + +## 基本类型 + +| Go 类型 | Fory TypeId | 编码 | 说明 | +| ---------------- | ------------ | ---------------------- | ---------------------------------- | +| `bool` | BOOL (1) | 1 字节 | | +| `int8` | INT8 (2) | 1 字节,有符号 | | +| `int16` | INT16 (3) | 2 字节,有符号 | 小端序 | +| `int32` | INT32 (4) | Varint | 变长编码 | +| `int64` | INT64 (6) | Varint | 变长编码 | +| `int` | INT32/INT64 | Varint | 依平台而定(32 位或 64 位) | +| `uint8` / `byte` | UINT8 (9) | 1 字节,无符号 | | +| `uint16` | UINT16 (10) | 2 字节,无符号 | 小端序 | +| `uint32` | UINT32 (11) | Varuint | 变长编码 | +| `uint64` | UINT64 (13) | Varuint | 变长编码 | +| `float32` | FLOAT32 (17) | 4 字节 | IEEE 754 | +| `float64` | FLOAT64 (18) | 8 字节 | IEEE 754 | +| `string` | STRING (19) | 长度前缀 UTF-8 | | + +### 整数编码 + +Fory 使用变长整数编码(varint)来获得更好的压缩效果: + +- 小数值使用更少字节 +- 负数使用 ZigZag 编码 +- 平台 `int` 在 32 位系统上映射为 `int32`,在 64 位系统上映射为 `int64` + +```go +f := fory.New(fory.WithXlang(true)) + +// 支持所有整数类型 +var i8 int8 = 127 +var i16 int16 = 32767 +var i32 int32 = 2147483647 +var i64 int64 = 9223372036854775807 + +data, _ := f.Serialize(i64) // 使用 varint 编码 +``` + +## 集合类型 + +### 根值和动态 Slice + +当 slice 作为根值或动态值序列化时,基本类型 slice 会使用密集 array 编码 tag,以便紧凑传输。在 struct 字段中,未注解的 Go slice 是逻辑 `list` 字段;当 struct 字段是密集数字 `array` 数据时,请使用 `fory:"type=array(element=...)"`。 + +| Go 类型 | Fory TypeId | 说明 | +| --------------- | ------------- | -------------------- | +| `[]bool` | BOOL_ARRAY | 优化编码 | +| `[]int8` | INT8_ARRAY | 优化编码 | +| `[]int16` | INT16_ARRAY | 优化编码 | +| `[]int32` | INT32_ARRAY | 优化编码 | +| `[]int64` | INT64_ARRAY | 优化编码 | +| `[]float32` | FLOAT32_ARRAY | 优化编码 | +| `[]float64` | FLOAT64_ARRAY | 优化编码 | +| `[]string` | LIST | 通用 list 编码 | +| `[]T`(任意) | LIST (20) | 任意可序列化类型 | +| `[]I`(any/any) | LIST | 任意 interface 类型 | + +```go +f := fory.New(fory.WithXlang(true)) + +// 基本类型根 slice(优化的密集 array 载荷) +ints := []int32{1, 2, 3, 4, 5} +data, _ := f.Serialize(ints) + +// 字符串 slice +strs := []string{"a", "b", "c"} +data, _ = f.Serialize(strs) + +// 结构体 slice +users := []User{{ID: 1}, {ID: 2}} +data, _ = f.Serialize(users) + +// 动态 slice +dynamic := []any{1, "hello", true} +data, _ = f.Serialize(dynamic) +``` + +### Map + +| Go 类型 | Fory TypeId | 说明 | +| -------------------- | ----------- | ----------------- | +| `map[string]string` | MAP (22) | 优化 | +| `map[string]int64` | MAP | 优化 | +| `map[string]int32` | MAP | 优化 | +| `map[string]int` | MAP | 优化 | +| `map[string]float64` | MAP | 优化 | +| `map[string]bool` | MAP | 优化 | +| `map[int32]int32` | MAP | 优化 | +| `map[int64]int64` | MAP | 优化 | +| `map[int]int` | MAP | 优化 | +| `map[string]any` | MAP | 动态值 | +| `map[any]any` | MAP | 动态键和值 | + +```go +f := fory.New(fory.WithXlang(true)) + +// 字符串键 map +m1 := map[string]string{"key": "value"} +m2 := map[string]int64{"count": 42} + +// 整数键 map +m3 := map[int32]int32{1: 100, 2: 200} + +// 动态 map +m4 := map[string]any{ + "name": "Alice", + "age": int64(30), +} +``` + +### Set + +Fory 提供泛型 `Set[T]` 类型(使用 `map[T]struct{}`,零内存开销): + +```go +// 创建字符串 set +s := fory.NewSet[string]() +s.Add("a", "b", "c") + +// 检查成员关系 +if s.Contains("a") { + fmt.Println("found") +} + +// 序列化 +data, _ := f.Serialize(s) +``` + +## 时间类型 + +| Go 类型 | Fory TypeId | 说明 | +| --------------- | -------------- | ----------- | +| `time.Time` | TIMESTAMP (34) | 纳秒精度 | +| `time.Duration` | DURATION (33) | 纳秒精度 | + +```go +import "time" + +f := fory.New(fory.WithXlang(true)) + +// 时间戳 +t := time.Now() +data, _ := f.Serialize(t) + +// 持续时间 +d := 5 * time.Second +data, _ = f.Serialize(d) +``` + +## Struct 类型 + +| 类别 | Fory TypeId | 说明 | +| ----------------------- | ---------------------------- | ----------------------- | +| Struct | STRUCT (25) | 按 ID 注册,无演进 | +| 兼容 Struct | COMPATIBLE_STRUCT (26) | 支持 Schema 演进 | +| 命名 Struct | NAMED_STRUCT (27) | 按名称注册,无演进 | +| 命名兼容 Struct | NAMED_COMPATIBLE_STRUCT (28) | 按名称注册并支持演进 | + +### Struct 要求 + +1. **仅导出字段**:以大写字母开头的字段会被序列化 +2. **支持的字段类型**:本文档中列出的所有类型 +3. **注册**:用于跨语言时应注册 struct + +```go +type User struct { + ID int64 // 会序列化 + Name string // 会序列化 + Age int32 // 会序列化 + password string // 不会序列化(未导出) +} + +f := fory.New(fory.WithXlang(true)) +f.RegisterStruct(User{}, 1) + +user := &User{ID: 1, Name: "Alice", Age: 30, password: "secret"} +data, _ := f.Serialize(user) +``` + +### 嵌套 Struct + +```go +type Address struct { + Street string + City string + Country string +} + +type Company struct { + Name string + Address Address + Founded int32 +} + +f := fory.New(fory.WithXlang(true)) +f.RegisterStruct(Address{}, 1) +f.RegisterStruct(Company{}, 2) +``` + +## 指针类型 + +| Go 类型 | 行为 | +| ------- | ------------------------------ | +| `*T` | 可为 nil,可引用跟踪(若启用) | +| `**T` | 支持嵌套指针 | + +```go +f := fory.New(fory.WithXlang(true), fory.WithTrackRef(true)) + +type Node struct { + Value int32 + Left *Node + Right *Node +} + +f.RegisterStruct(Node{}, 1) + +root := &Node{ + Value: 1, + Left: &Node{Value: 2}, + Right: &Node{Value: 3}, +} + +data, _ := f.Serialize(root) +``` + +### Nil 处理 + +```go +var ptr *User = nil +data, _ := f.Serialize(ptr) + +var result *User +f.Deserialize(data, &result) +// result == nil +``` + +## Interface 类型 + +| Go 类型 | Fory TypeId | 说明 | +| ------- | ----------- | ---------- | +| `any` | UNION (31) | 多态值 | + +```go +f := fory.New(fory.WithXlang(true)) + +// 序列化 any +var value any = "hello" +data, _ := f.Serialize(value) + +var result any +f.Deserialize(data, &result) +// result = "hello"(string) +``` + +对于 struct interface,需要注册所有可能的具体类型: + +```go +type Shape interface { + Area() float64 +} + +type Circle struct { + Radius float64 +} + +func (c Circle) Area() float64 { + return 3.14159 * c.Radius * c.Radius +} + +f := fory.New(fory.WithXlang(true)) +f.RegisterStruct(Circle{}, 1) + +var shape Shape = Circle{Radius: 5.0} +data, _ := f.Serialize(shape) +``` + +## 二进制数据 + +| Go 类型 | Fory TypeId | 说明 | +| -------- | ----------- | ---------- | +| `[]byte` | BINARY (37) | 变长字节 | + +```go +f := fory.New(fory.WithXlang(true)) + +data := []byte{0x01, 0x02, 0x03, 0x04} +serialized, _ := f.Serialize(data) + +var result []byte +f.Deserialize(serialized, &result) +``` + +## Enum 类型 + +Go 使用整数类型表示 enum: + +```go +type Status int32 + +const ( + StatusPending Status = 0 + StatusActive Status = 1 + StatusComplete Status = 2 +) + +f := fory.New(fory.WithXlang(true)) +f.RegisterEnum(Status(0), 1) + +status := StatusActive +data, _ := f.Serialize(status) +``` + +## Xlang 类型映射 + +| Go 类型 | Java | Python | C++ | Rust | +| --------------- | ---------- | --------- | ------------------ | -------------- | +| `bool` | boolean | bool | bool | bool | +| `int8` | byte | int | int8_t | i8 | +| `int16` | short | int | int16_t | i16 | +| `int32` | int | int | int32_t | i32 | +| `int64` | long | int | int64_t | i64 | +| `float32` | float | float | float | f32 | +| `float64` | double | float | double | f64 | +| `string` | String | str | std::string | String | +| `[]T` | `List` | list | `std::vector` | `Vec` | +| `map[K]V` | `Map` | dict | std::unordered_map | `HashMap` | +| `time.Time` | Instant | datetime | - | - | +| `time.Duration` | Duration | timedelta | - | - | + +详细映射请参阅 [Xlang 序列化](xlang-serialization.md)。 + +## 不支持的类型 + +以下 Go 类型**不支持**: + +- Channel(`chan T`) +- 函数(`func()`) +- 复数(`complex64`、`complex128`) +- Unsafe 指针(`unsafe.Pointer`) + +尝试序列化这些类型会产生错误。 + +## 相关主题 + +- [类型注册](type-registration.md) +- [Xlang 序列化](xlang-serialization.md) +- [引用](references.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/go/thread-safety.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/go/thread-safety.md new file mode 100644 index 00000000000..8c4c2516aa5 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/go/thread-safety.md @@ -0,0 +1,347 @@ +--- +title: 线程安全 +sidebar_position: 12 +id: thread_safety +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. +--- + +本指南介绍 Fory Go 的并发使用模式,包括线程安全包装器以及多 goroutine 环境中的最佳实践。 + +## 默认 Fory 实例 + +默认 `Fory` 实例**不是线程安全的**: + +```go +f := fory.New(fory.WithXlang(true)) + +// 不安全:多个 goroutine 并发访问 +go func() { + f.Serialize(value1) // 竞态条件! +}() +go func() { + f.Serialize(value2) // 竞态条件! +}() +``` + +### 为什么不是线程安全的? + +出于性能考虑,Fory 会复用内部状态: + +- Buffer 会在调用之间清空并复用 +- 引用解析器会被重置 +- Context 对象会被回收 + +这避免了内存分配,但要求独占访问。 + +## 线程安全包装器 + +并发使用时,请使用 `threadsafe` 包: + +```go +import "github.com/apache/fory/go/fory/threadsafe" + +// 创建线程安全的 Fory +f := threadsafe.New() + +// 可安全并发使用 +go func() { + data, _ := f.Serialize(value1) +}() +go func() { + data, _ := f.Serialize(value2) +}() +``` + +### 工作方式 + +线程安全包装器使用 `sync.Pool`: + +1. **获取**:从池中获取一个 Fory 实例 +2. **使用**:执行序列化/反序列化 +3. **复制**:复制结果数据(buffer 将被复用) +4. **释放**:将实例返回到池中 + +```go +// 简化实现 +func (f *Fory) Serialize(v any) ([]byte, error) { + fory := f.pool.Get().(*fory.Fory) + defer f.pool.Put(fory) + + data, err := fory.Serialize(v) + if err != nil { + return nil, err + } + + // 复制,因为底层 buffer 将被复用 + result := make([]byte, len(data)) + copy(result, data) + return result, nil +} +``` + +### API + +```go +// 创建线程安全实例 +f := threadsafe.New() + +// 实例方法 +data, err := f.Serialize(value) +err = f.Deserialize(data, &target) + +// 泛型函数 +data, err := threadsafe.Serialize(f, &value) +err = threadsafe.Deserialize(f, data, &target) + +// 全局便捷函数 +data, err := threadsafe.Marshal(&value) +err = threadsafe.Unmarshal(data, &target) +``` + +## 类型注册 + +类型注册应在并发使用前完成: + +```go +f := threadsafe.New() + +// 并发访问前注册类型 +f.RegisterStruct(User{}, 1) +f.RegisterStruct(Order{}, 2) + +// 现在可以安全并发使用 +go func() { + f.Serialize(&User{ID: 1}) +}() +``` + +### 线程安全注册 + +线程安全包装器会安全地处理注册: + +```go +// 安全:注册过程会同步 +f := threadsafe.New() +f.RegisterStruct(User{}, 1) // 线程安全 +``` + +不过,为获得最佳性能,建议在启动时、并发使用前注册所有类型。 + +## 零拷贝注意事项 + +### 非线程安全实例 + +使用默认 Fory 时,返回的字节 slice 是内部 buffer 的视图: + +```go +f := fory.New(fory.WithXlang(true)) + +data1, _ := f.Serialize(value1) +// data1 目前有效 + +data2, _ := f.Serialize(value2) +// data1 现在已失效(buffer 被复用) +``` + +### 线程安全实例 + +线程安全包装器会自动复制数据: + +```go +f := threadsafe.New() + +data1, _ := f.Serialize(value1) +data2, _ := f.Serialize(value2) +// data1 和 data2 都有效(独立副本) +``` + +这更安全,但会带来分配开销。 + +## 性能对比 + +| 场景 | 非线程安全 | 线程安全 | +| -------------- | ----------------- | ------------------------ | +| 单 goroutine | 最快 | 较慢(池开销) | +| 多 goroutine | 不安全 | 安全,扩展性好 | +| 内存分配 | 最少 | 每次调用复制 | +| Buffer 复用 | 是 | 每个池内实例各自复用 | + +### 基准测试 + +```go +func BenchmarkNonThreadSafe(b *testing.B) { + f := fory.New(fory.WithXlang(true)) + f.RegisterStruct(User{}, 1) + user := &User{ID: 1, Name: "Alice"} + + for i := 0; i < b.N; i++ { + data, _ := f.Serialize(user) + _ = data + } +} + +func BenchmarkThreadSafe(b *testing.B) { + f := threadsafe.New() + f.RegisterStruct(User{}, 1) + user := &User{ID: 1, Name: "Alice"} + + for i := 0; i < b.N; i++ { + data, _ := f.Serialize(user) + _ = data + } +} +``` + +## 使用模式 + +### 每个 Goroutine 一个实例 + +当 goroutine 数量已知并追求最高性能时: + +```go +func worker(id int) { + // 每个 worker 都有自己的 Fory 实例 + f := fory.New(fory.WithXlang(true)) + f.RegisterStruct(User{}, 1) + + for task := range tasks { + data, _ := f.Serialize(task) + process(data) + } +} + +// 启动 worker +for i := 0; i < numWorkers; i++ { + go worker(i) +} +``` + +### 共享线程安全实例 + +当 goroutine 数量动态变化或希望保持简单时: + +```go +// 单个共享实例 +var f = threadsafe.New() + +func init() { + f.RegisterStruct(User{}, 1) +} + +func handleRequest(user *User) []byte { + // 可从任何 goroutine 安全调用 + data, _ := f.Serialize(user) + return data +} +``` + +### HTTP Handler 示例 + +```go +var fory = threadsafe.New() + +func init() { + fory.RegisterStruct(Response{}, 1) +} + +func handler(w http.ResponseWriter, r *http.Request) { + response := &Response{ + Status: "ok", + Data: getData(), + } + + // 安全:threadsafe.Fory 会处理并发 + data, err := fory.Serialize(response) + if err != nil { + http.Error(w, err.Error(), 500) + return + } + + w.Header().Set("Content-Type", "application/octet-stream") + w.Write(data) +} +``` + +## 常见错误 + +### 共享非线程安全实例 + +```go +// 错误:竞态条件 +var f = fory.New(fory.WithXlang(true)) + +func handler1() { + f.Serialize(value1) // 竞态! +} + +func handler2() { + f.Serialize(value2) // 竞态! +} +``` + +**修复**:使用 `threadsafe.New()` 或每个 goroutine 一个实例。 + +### 保留 Buffer 引用 + +```go +// 错误:下一次调用会使 buffer 失效 +f := fory.New(fory.WithXlang(true)) +data, _ := f.Serialize(value1) +savedData := data // 只复制了 slice 头! + +f.Serialize(value2) // 使 data 和 savedData 失效 +``` + +**修复**:克隆数据或使用线程安全包装器。 + +```go +// 正确:克隆数据 +data, _ := f.Serialize(value1) +savedData := make([]byte, len(data)) +copy(savedData, data) + +// 或使用线程安全实例(自动复制) +f := threadsafe.New() +data, _ := f.Serialize(value1) // 已经复制 +``` + +### 并发注册类型 + +```go +// 有风险:并发注册 +go func() { + f.RegisterStruct(TypeA{}, 1) +}() +go func() { + f.Serialize(value) // 可能看不到 TypeA +}() +``` + +**修复**:在并发使用前注册所有类型。 + +## 最佳实践 + +1. **启动时注册类型**:在任何并发操作之前完成 +2. **保留引用时克隆数据**:使用非线程安全实例时 +3. **热路径使用每个 worker 一个实例**:消除池竞争 +4. **优化前先做性能分析**:线程安全开销可能可以忽略 + +## 相关主题 + +- [配置](configuration.md) +- [基础序列化](basic-serialization.md) +- [故障排查](troubleshooting.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/go/troubleshooting.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/go/troubleshooting.md new file mode 100644 index 00000000000..77ab310476e --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/go/troubleshooting.md @@ -0,0 +1,449 @@ +--- +title: 故障排查 +sidebar_position: 13 +id: troubleshooting +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. +--- + +本指南介绍使用 Fory Go 时的常见问题和解决方案。 + +## 错误类型 + +Fory Go 使用带有具体错误种类的类型化错误: + +```go +type Error struct { + kind ErrorKind + message string + // 额外上下文字段 +} + +func (e Error) Kind() ErrorKind { return e.kind } +func (e Error) Error() string { return e.message } +``` + +### 错误种类 + +| 种类 | 值 | 说明 | +| ------------------------------ | ----- | ---------------------------- | +| `ErrKindOK` | 0 | 无错误 | +| `ErrKindBufferOutOfBound` | 1 | 读/写超出 buffer 边界 | +| `ErrKindTypeMismatch` | 2 | 类型 ID 不匹配 | +| `ErrKindUnknownType` | 3 | 遇到未知类型 | +| `ErrKindSerializationFailed` | 4 | 一般序列化失败 | +| `ErrKindDeserializationFailed` | 5 | 一般反序列化失败 | +| `ErrKindMaxDepthExceeded` | 6 | 超过递归深度限制 | +| `ErrKindNilPointer` | 7 | 意外的 nil 指针 | +| `ErrKindInvalidRefId` | 8 | 无效引用 ID | +| `ErrKindHashMismatch` | 9 | Struct 哈希不匹配 | +| `ErrKindInvalidTag` | 10 | 无效的 fory struct tag | + +## 常见错误和解决方案 + +### ErrKindUnknownType + +**错误**:`unknown type encountered` + +**原因**:序列化/反序列化前未注册类型。 + +**解决方案**: + +```go +f := fory.New() + +// 使用前注册类型 +f.RegisterStruct(User{}, 1) + +// 现在可以序列化 +data, _ := f.Serialize(&User{ID: 1}) +``` + +### ErrKindTypeMismatch + +**错误**:`type mismatch: expected X, got Y` + +**原因**:序列化数据的类型与预期类型不同。 + +**解决方案**: + +1. **使用正确的目标类型**: + +```go +// 错误:将 User 反序列化为 Order +var order Order +f.Deserialize(userData, &order) // 错误! + +// 正确 +var user User +f.Deserialize(userData, &user) +``` + +2. **确保注册一致**: + +```go +// 序列化端 +f1 := fory.New() +f1.RegisterStruct(User{}, 1) + +// 反序列化端必须使用相同 ID +f2 := fory.New() +f2.RegisterStruct(User{}, 1) // 相同 ID! +``` + +### ErrKindHashMismatch + +**错误**:`hash X is not consistent with Y for type Z` + +**原因**:序列化和反序列化之间 struct 定义发生变化。 + +**解决方案**: + +1. **启用兼容模式**: + +```go +// 在每个对端的同一组选项中添加 WithCompatible(true)。 +f := fory.New(/* 现有选项 */, fory.WithCompatible(true)) +``` + +2. **确保 struct 定义匹配**: + +```go +// 序列化端和反序列化端必须使用相同 struct +type User struct { + ID int64 + Name string +} +``` + +3. **重新生成代码生成产物**(如果使用): + +```bash +go generate ./... +``` + +### ErrKindMaxDepthExceeded + +**错误**:`max depth exceeded` + +**原因**:数据嵌套超过最大深度限制。 + +**可能原因**: + +- 深层嵌套的数据结构超过默认限制(20) +- 未启用引用跟踪时出现非预期的循环引用 +- **恶意数据**:攻击者可能构造深层嵌套载荷来耗尽资源 + +**解决方案**: + +1. **增加最大深度**(默认值为 20): + +```go +f := fory.New(fory.WithMaxDepth(50)) +``` + +2. **启用引用跟踪**(用于循环数据): + +```go +f := fory.New(fory.WithTrackRef(true)) +``` + +3. **检查数据中是否存在非预期的循环引用**。 + +4. **验证不可信数据**:从不可信来源反序列化数据时,不要盲目提高最大深度。应考虑在反序列化前验证输入尺寸和结构。 + +### ErrKindBufferOutOfBound + +**错误**:`buffer out of bound: offset=X, need=Y, size=Z` + +**原因**:读取超出可用数据范围。 + +**解决方案**: + +1. **确保完整传输数据**: + +```go +// 错误:截断数据 +data := fullData[:100] +f.Deserialize(data, &target) // 如果原始数据更大则会报错 + +// 正确:使用完整数据 +f.Deserialize(fullData, &target) +``` + +2. **检查数据损坏**:验证传输过程中的数据完整性。 + +### ErrKindInvalidRefId + +**错误**:`invalid reference ID` + +**原因**:序列化数据中引用了不存在或未知的对象。 + +**解决方案**: + +1. **确保引用跟踪设置一致**: + +```go +// 序列化端和反序列化端必须使用相同设置 +f1 := fory.New(fory.WithTrackRef(true)) +f2 := fory.New(fory.WithTrackRef(true)) // 必须匹配! +``` + +2. **检查数据损坏**。 + +### ErrKindInvalidTag + +**错误**:`invalid fory struct tag` + +**原因**:struct tag 配置无效。 + +**常见原因**: + +1. **无效 tag ID**:ID 必须为非负数 + +```go +// 错误:负 ID +type Bad struct { + Field int `fory:"id=-5"` +} + +// 正确 +type Good struct { + Field int `fory:"id=0"` +} +``` + +2. **重复 tag ID**:同一个 struct 内每个字段都必须有唯一 ID + +```go +// 错误:重复 ID +type Bad struct { + Field1 int `fory:"id=0"` + Field2 int `fory:"id=0"` // 重复! +} + +// 正确 +type Good struct { + Field1 int `fory:"id=0"` + Field2 int `fory:"id=1"` +} +``` + +## Xlang 问题 + +### 字段顺序不匹配 + +**症状**:数据可以反序列化,但字段值错误。 + +**原因**:不同语言之间字段排序不同。在 schema-consistent 模式中,字段按其 snake_case 名称排序。CamelCase 字段名(例如 `FirstName`)会转换为 snake_case(例如 `first_name`)再参与排序。 + +**解决方案**: + +1. **确保转换后的 snake_case 名称一致**:跨语言字段名必须产生相同的 snake_case 排序: + +```go +type User struct { + FirstName string // Go: FirstName -> first_name + LastName string // Go: LastName -> last_name + // 按 snake_case 字母序排序:first_name, last_name +} +``` + +2. **使用字段 ID 获得一致排序**:字段 ID(非负整数)作为字段名别名,在反序列化期间同时用于排序和字段匹配: + +```go +type User struct { + FirstName string `fory:"id=0"` + LastName string `fory:"id=1"` +} +``` + +确保所有语言中对应字段使用相同的字段 ID。 + +### 名称注册不匹配 + +**症状**:其他语言中出现 `unknown type`。 + +**解决方案**:使用完全相同的名称: + +```go +// Go +f.RegisterStructByName(User{}, "example.User") + +// Java - 必须完全匹配 +fory.register(User.class, "example.User"); + +// Python +fory.register(User, typename="example.User") +``` + +## 性能问题 + +### 序列化慢 + +**可能原因**: + +1. **大型对象图**:减少数据尺寸或增量序列化。 + +2. **过度引用跟踪**:不需要时禁用: + +```go +f := fory.New(fory.WithTrackRef(false)) +``` + +3. **深层嵌套**:尽可能扁平化数据结构。 + +### 内存使用高 + +**可能原因**: + +1. **大型序列化数据**:分块处理。 + +2. **引用跟踪开销**:不需要时禁用。 + +3. **Buffer 未释放**:复用 buffer: + +```go +buf := fory.NewByteBuffer(nil) +f.SerializeTo(buf, value) +// 处理数据 +buf.Reset() // 复用于下一次序列化 +``` + +### 线程竞争 + +**症状**:并发负载下变慢。 + +**解决方案**: + +1. **热路径使用每 goroutine 一个实例**: + +```go +func worker() { + f := fory.New() // 每个 worker 都有自己的实例 + for task := range tasks { + f.Serialize(task) + } +} +``` + +2. **分析线程安全包装器的池使用情况**。 + +## 调试技巧 + +### 启用调试输出 + +设置环境变量: + +```bash +ENABLE_FORY_DEBUG_OUTPUT=1 go test ./... +``` + +### 检查序列化数据 + +```go +data, _ := f.Serialize(value) +fmt.Printf("Serialized %d bytes\n", len(data)) +fmt.Printf("Header: %x\n", data[:4]) // Magic + 标志 +``` + +### 检查类型注册 + +```go +// 验证类型已注册 +f := fory.New() +err := f.RegisterStruct(User{}, 1) +if err != nil { + fmt.Printf("Registration failed: %v\n", err) +} +``` + +### 比较 Struct 哈希 + +如果遇到哈希不匹配,请比较 struct 定义: + +```go +// 打印 struct 信息用于调试 +t := reflect.TypeOf(User{}) +for i := 0; i < t.NumField(); i++ { + f := t.Field(i) + fmt.Printf("Field: %s, Type: %s\n", f.Name, f.Type) +} +``` + +## 测试提示 + +### 测试往返 + +```go +func TestRoundTrip(t *testing.T) { + f := fory.New() + f.RegisterStruct(User{}, 1) + + original := &User{ID: 1, Name: "Alice"} + + data, err := f.Serialize(original) + require.NoError(t, err) + + var result User + err = f.Deserialize(data, &result) + require.NoError(t, err) + + assert.Equal(t, original.ID, result.ID) + assert.Equal(t, original.Name, result.Name) +} +``` + +### 测试 Xlang + +```bash +cd java/fory-core +FORY_GO_JAVA_CI=1 mvn test -Dtest=org.apache.fory.xlang.GoXlangTest +``` + +### 测试 Schema 演进 + +```go +func TestSchemaEvolution(t *testing.T) { + f1 := fory.New() + f1.RegisterStruct(UserV1{}, 1) + + data, _ := f1.Serialize(&UserV1{ID: 1, Name: "Alice"}) + + f2 := fory.New() + f2.RegisterStruct(UserV2{}, 1) + + var result UserV2 + err := f2.Deserialize(data, &result) + require.NoError(t, err) +} +``` + +## 获取帮助 + +如果遇到本文未覆盖的问题: + +1. **查看 GitHub Issues**:[github.com/apache/fory/issues](https://github.com/apache/fory/issues) +2. **启用调试输出**:`ENABLE_FORY_DEBUG_OUTPUT=1` +3. **创建最小复现**:隔离问题 +4. **报告问题**:包含 Go 版本、Fory 版本和最小代码 + +## 相关主题 + +- [配置](configuration.md) +- [Xlang 序列化](xlang-serialization.md) +- [Schema 演进](schema-evolution.md) +- [线程安全](thread-safety.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/go/type-registration.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/go/type-registration.md new file mode 100644 index 00000000000..566cf13e0ea --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/go/type-registration.md @@ -0,0 +1,260 @@ +--- +title: 类型注册 +sidebar_position: 30 +id: type_registration +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. +--- + +类型注册用于告知 Fory 如何识别并序列化你的自定义类型。对于结构体、枚举和扩展类型,注册是必需的。 + +## 为什么要注册类型? + +1. **类型识别**:反序列化时,Fory 需要知道实际类型 +2. **多态支持**:反序列化接口类型时,Fory 需要知道要创建哪个具体类型 +3. **跨语言兼容**:其他语言实现需要识别并反序列化你的类型 + +## 结构体注册 + +### 通过 ID 注册 + +使用数值类型 ID 注册结构体,可获得更紧凑的序列化结果: + +```go +type User struct { + ID int64 + Name string +} + +f := fory.New() +err := f.RegisterStruct(User{}, 1) +if err != nil { + panic(err) +} +``` + +**ID 使用建议**: + +- ID 在应用内必须唯一 +- 若用于跨语言,所有语言中的 ID 必须一致 +- 序列化端和反序列化端必须为同一类型使用同一 ID + +### 通过名称注册 + +使用类型名字符串注册结构体。该方式更灵活,但序列化开销更高: + +```go +f := fory.New() +err := f.RegisterNamedStruct(User{}, "example.User") +if err != nil { + panic(err) +} +``` + +**名称使用建议**: + +- 使用 `namespace.TypeName` 约定的全限定名 +- 名称在所有语言中必须唯一且一致 +- 名称区分大小写 + +## 枚举注册 + +Go 没有原生枚举类型,但可以把整数类型按枚举注册。 + +### 通过 ID 注册 + +```go +type Status int32 + +const ( + StatusPending Status = 0 + StatusActive Status = 1 + StatusComplete Status = 2 +) + +f := fory.New() +err := f.RegisterEnum(Status(0), 1) +``` + +### 通过名称注册 + +```go +err := f.RegisterNamedEnum(Status(0), "example.Status") +``` + +## 扩展类型 + +对于需要自定义序列化逻辑的类型,可将其注册为扩展类型,并提供自定义序列化器: + +```go +f := fory.New() + +// Register by ID +err := f.RegisterExtension(CustomType{}, 1, &CustomSerializer{}) + +// Or register by name +err = f.RegisterNamedExtension(CustomType{}, "example.Custom", &CustomSerializer{}) +``` + +关于 `ExtensionSerializer` 接口实现,请参考 [自定义序列化器](custom-serializers.md)。 + +## 注册作用域 + +类型注册是 **Fory 实例级别** 的: + +```go +f1 := fory.New() +f2 := fory.New() + +// Types registered on f1 are NOT available on f2 +f1.RegisterStruct(User{}, 1) + +// f2 cannot deserialize User unless also registered +f2.RegisterStruct(User{}, 1) +``` + +## 注册时机 + +应在创建 Fory 实例后、首次序列化/反序列化之前完成类型注册: + +```go +f := fory.New() + +// Register before use +f.RegisterStruct(User{}, 1) +f.RegisterStruct(Order{}, 2) + +// Now serialize/deserialize +data, _ := f.Serialize(&User{ID: 1, Name: "Alice"}) +``` + +## 嵌套类型注册 + +对象图中的所有结构体类型(包括嵌套类型)都应注册: + +```go +type Address struct { + City string + Country string +} + +type Person struct { + Name string + Address Address +} + +f := fory.New() + +// Register ALL struct types used in the object graph +f.RegisterStruct(Address{}, 1) +f.RegisterStruct(Person{}, 2) +``` + +## 跨语言注册 + +进行跨语言序列化时,必须在所有语言中保持一致注册。 + +### 使用 ID + +所有语言使用同一个数值 ID: + +**Go**: + +```go +f.RegisterStruct(User{}, 1) +``` + +**Java**: + +```java +fory.register(User.class, 1); +``` + +**Python**: + +```python +fory.register(User, type_id=1) +``` + +### 使用名称 + +所有语言使用同一个类型名: + +**Go**: + +```go +f.RegisterNamedStruct(User{}, "example.User") +``` + +**Java**: + +```java +fory.register(User.class, "example.User"); +``` + +**Python**: + +```python +fory.register(User, typename="example.User") +``` + +**Rust**: + +```rust +#[derive(Fory)] +struct User { + id: i64, + name: String, +} + +let mut fory = Fory::default(); +fory.register_by_name::("example.User")?; +``` + +## 最佳实践 + +1. **尽早注册**:应用启动后、任何序列化操作前完成全部类型注册 +2. **保持一致**:所有语言与实例中使用一致的 ID 或名称 +3. **注册完整**:不仅注册顶层类型,也要注册嵌套结构体 +4. **性能优先时用 ID**:数值 ID 比名称开销更低 +5. **灵活性优先时用名称**:名称更易维护,也更不易冲突 + +## 常见错误 + +### 类型未注册 + +``` +error: unknown type encountered +``` + +**解决方式**:在序列化/反序列化前先注册类型。 + +### ID/名称不匹配 + +用某个 ID/名称序列化的数据,若在反序列化端使用不同 ID/名称注册,将无法正确反序列化。 + +**解决方式**:确保序列化端与反序列化端使用一致的 ID 或名称。 + +### 重复注册 + +两个类型使用同一 ID 会发生冲突。 + +**解决方式**:确保每个类型使用唯一 ID。 + +## 相关主题 + +- [基本序列化](basic-serialization.md) +- [跨语言序列化](xlang-serialization.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/go/xlang-serialization.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/go/xlang-serialization.md new file mode 100644 index 00000000000..85fa4399304 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/go/xlang-serialization.md @@ -0,0 +1,280 @@ +--- +title: Xlang 序列化 +sidebar_position: 2 +id: xlang_serialization +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. +--- + +Fory Go 支持与 Java、Python、C++、Rust、JavaScript 无缝交换数据。本指南介绍跨语言兼容与类型映射要点。 + +## 启用xlang 模式 + +需要显式开启跨语言(xlang)模式: + +```go +f := fory.New(fory.WithXlang(true)) +``` + +## 跨语言类型注册 + +在所有语言中使用一致的类型 ID: + +### Go + +```go +type User struct { + ID int64 + Name string +} + +f := fory.New(fory.WithXlang(true)) +f.RegisterStruct(User{}, 1) +data, _ := f.Serialize(&User{ID: 1, Name: "Alice"}) +``` + +### Java + +```java +public class User { + public long id; + public String name; +} +Fory fory = Fory.builder().withXlang(true).build(); +fory.register(User.class, 1); +User user = fory.deserialize(data, User.class); +``` + +### Python + +```python +from dataclasses import dataclass +import pyfory + +@dataclass +class User: + id: pyfory.Int64Type + name: str + +fory = pyfory.Fory() +fory.register(User, type_id=1) +user = fory.deserialize(data) +``` + +## 类型映射 + +不同语言间完整类型映射请参考 [类型映射规范](https://fory.apache.org/docs/specification/xlang_type_mapping)。 + +## 字段顺序 + +xlang 序列化要求字段顺序一致。Fory 会将字段名转为 snake_case 后按字母序排序。 + +Go 字段名会先转 snake_case: + +```go +type Example struct { + UserID int64 // -> user_id + FirstName string // -> first_name + Age int32 // -> age +} + +// Sorted order: age, first_name, user_id +``` + +请确保其他语言使用能产生相同 snake_case 顺序的字段名;或通过字段 ID 显式控制: + +```go +type Example struct { + UserID int64 `fory:"id=0"` + FirstName string `fory:"id=1"` + Age int32 `fory:"id=2"` +} +``` + +## 示例 + +### Go 到 Java + +**Go(序列化端)**: + +```go +type Order struct { + ID int64 + Customer string + Total float64 + Items []string +} + +f := fory.New(fory.WithXlang(true)) +f.RegisterStruct(Order{}, 1) + +order := &Order{ + ID: 12345, + Customer: "Alice", + Total: 99.99, + Items: []string{"Widget", "Gadget"}, +} +data, _ := f.Serialize(order) +// Send 'data' to Java service +``` + +**Java(反序列化端)**: + +```java +public class Order { + public long id; + public String customer; + public double total; + public List items; +} + +Fory fory = Fory.builder().withXlang(true).build(); +fory.register(Order.class, 1); + +Order order = fory.deserialize(data, Order.class); +``` + +### Python 到 Go + +**Python(序列化端)**: + +```python +from dataclasses import dataclass +import pyfory + +@dataclass +class Message: + id: pyfory.Int64Type + content: str + timestamp: pyfory.Int64Type + +fory = pyfory.Fory() +fory.register(Message, type_id=1) + +msg = Message(id=1, content="Hello from Python", timestamp=1234567890) +data = fory.serialize(msg) +``` + +**Go(反序列化端)**: + +```go +type Message struct { + ID int64 + Content string + Timestamp int64 +} + +f := fory.New(fory.WithXlang(true)) +f.RegisterStruct(Message{}, 1) + +var msg Message +f.Deserialize(data, &msg) +fmt.Println(msg.Content) // "Hello from Python" +``` + +### 嵌套结构 + +跨语言嵌套结构要求相关类型全部注册: + +**Go**: + +```go +type Address struct { + Street string + City string + Country string +} + +type Company struct { + Name string + Address Address +} + +f := fory.New(fory.WithXlang(true)) +f.RegisterStruct(Address{}, 1) +f.RegisterStruct(Company{}, 2) +``` + +**Java**: + +```java +public class Address { + public String street; + public String city; + public String country; +} + +public class Company { + public String name; + public Address address; +} + +fory.register(Address.class, 1); +fory.register(Company.class, 2); +``` + +## 常见问题 + +### 字段名不匹配 + +Go 常用 PascalCase,其他语言可能是 camelCase 或 snake_case。Fory 按 snake_case 转换结果做字段匹配: + +```go +// Go +type User struct { + FirstName string // -> first_name +} + +// Java - field name converted to snake_case must match +public class User { + public String firstName; // -> first_name (matches) +} +``` + +### 类型语义差异 + +Go 的无符号类型在 Java 中会映射到相同比特位的有符号类型: + +```go +var value uint64 = 18446744073709551615 // Max uint64 +``` + +Java `long` 持有相同比特位,但按有符号解释为 `-1`。若需无符号语义,可在 Java 中使用 `Long.toUnsignedString()`。 + +### Nil 与 Null + +Go 的 nil slice/map 在不同配置下序列化方式不同: + +```go +var slice []string = nil +// In xlang mode: serializes based on nullable configuration +``` + +请确保其他语言端能正确处理 null。 + +## 最佳实践 + +1. **统一 type ID**:同一类型在所有语言中使用相同数值 ID +2. **完整注册类型**:包括嵌套结构体类型 +3. **统一字段顺序**:使用一致 snake_case,或使用显式字段 ID +4. **尽早做跨语言集成测试**:持续验证兼容性 +5. **注意类型语义差异**:特别是有符号/无符号解释差异 + +## 相关主题 + +- [类型注册](type-registration.md) +- [支持类型](supported-types.md) +- [Schema 演进](schema-evolution.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/_category_.json b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/_category_.json new file mode 100644 index 00000000000..3508cf90083 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "Java", + "position": 1, + "collapsible": true, + "collapsed": true +} diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/advanced-features.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/advanced-features.md new file mode 100644 index 00000000000..eae18ac70d1 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/advanced-features.md @@ -0,0 +1,208 @@ +--- +title: 高级特性 +sidebar_position: 10 +id: advanced_features +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. +--- + +本页介绍高级特性,包括零拷贝序列化、深拷贝、内存管理和日志记录。 + +## 零拷贝序列化 + +Fory 支持零拷贝序列化,以高效处理大型二进制数据: + +```java +import org.apache.fory.*; +import org.apache.fory.config.*; +import org.apache.fory.serializer.BufferObject; +import org.apache.fory.memory.MemoryBuffer; + +import java.util.*; +import java.util.stream.Collectors; + +public class ZeroCopyExample { + // 注意 fory 实例应该复用,而不是每次都创建。 + static Fory fory = Fory.builder() + .withLanguage(Language.JAVA) + .build(); + + public static void main(String[] args) { + List list = Arrays.asList("str", new byte[1000], new int[100], new double[100]); + Collection bufferObjects = new ArrayList<>(); + byte[] bytes = fory.serialize(list, e -> !bufferObjects.add(e)); + List buffers = bufferObjects.stream() + .map(BufferObject::toBuffer).collect(Collectors.toList()); + System.out.println(fory.deserialize(bytes, buffers)); + } +} +``` + +## 对象深拷贝 + +Fory 提供高效的深拷贝功能: + +### 启用引用跟踪 + +```java +Fory fory = Fory.builder().withRefCopy(true).build(); +SomeClass a = xxx; +SomeClass copied = fory.copy(a); +``` + +### 禁用引用跟踪(更好的性能) + +禁用时,深拷贝将忽略循环引用和共享引用。对象图的相同引用将在一次 `Fory#copy` 中被复制为不同的对象: + +```java +Fory fory = Fory.builder().withRefCopy(false).build(); +SomeClass a = xxx; +SomeClass copied = fory.copy(a); +``` + +## 内存分配自定义 + +Fory 提供了 `MemoryAllocator` 接口,允许你自定义在序列化操作期间如何分配和增长内存缓冲区。这对于性能优化、内存池化或调试内存使用很有用。 + +### MemoryAllocator 接口 + +`MemoryAllocator` 接口定义了两个关键方法: + +```java +public interface MemoryAllocator { + /** + * 分配具有指定初始容量的新 MemoryBuffer。 + */ + MemoryBuffer allocate(int initialCapacity); + + /** + * 增长现有缓冲区以容纳新容量。 + * 实现必须通过修改现有缓冲区实例来原地增长缓冲区。 + */ + MemoryBuffer grow(MemoryBuffer buffer, int newCapacity); +} +``` + +### 使用自定义内存分配器 + +你可以设置一个全局内存分配器,所有 `MemoryBuffer` 实例都将使用它: + +```java +// 创建自定义分配器 +MemoryAllocator customAllocator = new MemoryAllocator() { + @Override + public MemoryBuffer allocate(int initialCapacity) { + // 为调试或池化添加额外容量 + return MemoryBuffer.fromByteArray(new byte[initialCapacity + 100]); + } + + @Override + public MemoryBuffer grow(MemoryBuffer buffer, int newCapacity) { + if (newCapacity <= buffer.size()) { + return buffer; + } + + // 自定义增长策略 - 添加 100% 额外容量 + int newSize = (int) (newCapacity * 2); + byte[] data = new byte[newSize]; + buffer.copyToUnsafe(0, data, Platform.BYTE_ARRAY_OFFSET, buffer.size()); + buffer.initHeapBuffer(data, 0, data.length); + return buffer; + } +}; + +// 全局设置自定义分配器 +MemoryBuffer.setGlobalAllocator(customAllocator); + +// 所有后续的 MemoryBuffer 分配都将使用你的自定义分配器 +Fory fory = Fory.builder().withLanguage(Language.JAVA).build(); +byte[] bytes = fory.serialize(someObject); // 使用自定义分配器 +``` + +### 默认内存分配器行为 + +默认分配器使用以下增长策略: + +- 对于小于 `BUFFER_GROW_STEP_THRESHOLD` (100MB) 的缓冲区:将容量乘以 2 +- 对于较大的缓冲区:将容量乘以 1.5(上限为 `Integer.MAX_VALUE - 8`) + +这在避免频繁重新分配和防止过度内存使用之间提供了平衡。 + +### 使用场景 + +自定义内存分配器适用于: + +- **内存池化**:重用分配的缓冲区以减少 GC 压力 +- **性能调优**:根据你的工作负载使用不同的增长策略 +- **调试**:添加日志记录或跟踪以监控内存使用 +- **堆外内存**:与堆外内存管理系统集成 + +## 日志记录 + +### ForyLogger + +默认情况下,Fory 使用自定义日志记录器 `ForyLogger` 来满足内部需求。它将生成的日志数据构建为单个字符串,并直接发送到 `System.out`。结果行布局类似于(Log4j 表示法): + +``` +%d{yyyy-MM-dd hh:mm:ss} %p %C:%L [%t] - %m%n +``` + +布局无法更改。 + +示例输出: + +``` +2025-11-07 08:49:59 INFO CompileUnit:55 [main] - Generate code for org.apache.fory.builder.SerializedLambdaForyCodec_0 took 35 ms. +2025-11-07 08:50:00 INFO JaninoUtils:121 [main] - Compile [SerializedLambdaForyCodec_0] take 144 ms +``` + +### Slf4jLogger + +如果需要更复杂的日志记录器,可以通过 `LoggerFactory.useSlf4jLogging()` 配置 Fory 使用 Slf4j。例如,在创建 Fory 之前启用 Slf4j: + +```java +public static final ThreadSafeFory FORY; + +static { + LoggerFactory.useSlf4jLogging(true); + FORY = Fory.builder() + .buildThreadSafeFory(); +} +``` + +**注意**:当应用程序在 GraalVM native image 中运行时,通过 `useSlf4jLogging` 启用 Slf4j 将被忽略。 + +### 抑制 Fory 日志 + +`ForyLogger` 和 `Slf4jLogger` 都允许控制日志输出级别或完全抑制日志。通过 `LoggerFactory.setLogLevel()` 配置日志级别: + +```java +static { + // 只记录 WARN 及更高级别 + LoggerFactory.setLogLevel(LogLevel.WARN_LEVEL); + + // 完全禁用日志记录 + LoggerFactory.disableLogging(); +} +``` + +**注意**:所选的日志级别在 Slf4j 实现的日志级别之前应用。因此,如果你设置 `WARN_LEVEL`(如上例),即使在 Logback 中启用了 INFO,你也不会看到来自 Fory 的 INFO 消息。 + +## 相关主题 + +- [压缩](compression.md) - 数据压缩选项 +- [配置选项](configuration.md) - 所有 ForyBuilder 选项 +- [跨语言序列化](xlang-serialization.md) - XLANG 模式 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/android-support.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/android-support.md new file mode 100644 index 00000000000..eb9286a90f1 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/android-support.md @@ -0,0 +1,119 @@ +--- +title: Android 支持 +sidebar_position: 15 +id: android_support +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. +--- + +## Android 运行时 + +Fory Java 通过常规的 `fory-core` 构件支持 Android 8.0+(API level 26+)。核心对象序列化不需要单独的 Android 构件。 + +在 Android 上使用核心对象序列化: + +- `Fory#serialize(Object)` 和 `Fory#deserialize(byte[])`。 +- `BaseFory#deserialize(ByteBuffer)`,用于堆、直接和只读 `ByteBuffer` 输入。 +- Stream、channel 和 out-of-band buffer API,可通过 byte-array、heap-buffer 或 `ByteBuffer` 复制路径使用。 +- Java collections/maps 和 xlang collections/maps。 + +`java/fory-format` row-format API 仅适用于 JVM,不支持 Android。 + +## 运行时代码生成 + +Android 上会禁用运行时序列化器代码生成。如果设置了 `withCodegen(true)`,Fory 会让 Android 序列化保持在非代码生成路径,并记录一条警告日志。 + +需要生成序列化器的 Android 应用应改用构建时静态生成序列化器。 + +## 静态生成序列化器 + +Android 应用类应使用 `@ForyStruct` 静态生成序列化器。它们由 javac 在应用构建期间生成,无需运行时字节码生成即可工作。 + +### 安装注解处理器 + +将 `fory-annotation-processor` 添加到编译 Android 模型类的模块的注解处理器路径中: + +```xml + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.apache.fory + fory-annotation-processor + ${fory.version} + + + + + + +``` + +然后使用 `@ForyStruct` 标注 Android 模型类。 + +当被序列化类使用 Fory type-use 注解时,Android 上必须使用静态生成序列化器,例如: + +```java +import java.util.List; +import org.apache.fory.annotation.ForyStruct; +import org.apache.fory.annotation.UInt8Type; + +@ForyStruct +public class ImageBlock { + public List<@UInt8Type Integer> pixels; +} +``` + +如果没有生成的静态描述符,Android 反射可能无法暴露 `@Ref`、`@Int8Type`、`@UInt8Type`、`@Float16Type` 或 `@BFloat16Type` 等注解所需的嵌套 type-use 元信息。这些类的序列化将无法获得 Fory 所需的 Schema 信息。 + +设置说明见[静态生成序列化器](static-generated-serializers.md)。 + +## 对象模型要求 + +Android 序列化器使用公开的 Android 运行时能力。对于应用类,优先使用: + +- 可访问的无参构造函数,或带受支持构造函数的 records。 +- public、protected 或 package-private 的序列化字段。 +- 用于 private 序列化字段的非 private getter 和 setter。 +- Android 模型类的 `@ForyStruct` 静态生成序列化器。 + +普通类中的 final 字段不适合生成的 read/copy 方法。基于构造函数的不可变值应使用 records。 + +## 不支持的功能 + +Android 不支持以下 JVM 功能: + +- 运行时序列化器代码生成和异步编译。 +- Lambda 和 `SerializedLambda` 序列化。 +- 原生地址序列化 API 和原生地址 `MemoryBuffer` 包装。 +- 原始 unsafe 内存复制 API。 +- `java/fory-format` row-format API。 + +## ByteBuffer + +`BaseFory#deserialize(ByteBuffer)` 通过将剩余字节复制到 Fory 拥有的堆缓冲区,在 Android 上支持堆、直接和只读缓冲区。调用方缓冲区的 position 和 limit 不会改变。 + +原始 direct-buffer 地址包装是仅限 JVM 的快速路径,Android 上不会使用。 + +## Collections、Maps 和 Proxies + +Android 支持常见的 JDK collection 和 map 实现。在 xlang 模式下,collection 和 map 序列化使用 xlang 协议,不编码 Java wrapper/view 内部结构。 + +`java.lang.reflect.Proxy` 序列化支持普通代理用法。代理仍在反序列化时,不要调用、记录或将其作为 map/set key 使用;此时 invocation handler 可能尚未准备好。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/basic-serialization.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/basic-serialization.md new file mode 100644 index 00000000000..5e9e8b287a1 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/basic-serialization.md @@ -0,0 +1,137 @@ +--- +title: 基础序列化 +sidebar_position: 2 +id: basic_serialization +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. +--- + +本页介绍基本的序列化模式和 Fory 实例创建。 + +## 创建 Fory 实例 + +### 单线程 Fory + +对于单线程应用程序: + +```java +Fory fory = Fory.builder() + .withLanguage(Language.JAVA) + // 启用引用跟踪以支持共享/循环引用。 + // 如果没有重复引用,禁用它会有更好的性能。 + .withRefTracking(false) + .withCompatibleMode(CompatibleMode.SCHEMA_CONSISTENT) + // 启用类型前向/后向兼容性 + // 禁用它以获得更小的大小和更好的性能。 + // .withCompatibleMode(CompatibleMode.COMPATIBLE) + // 启用异步多线程编译。 + .withAsyncCompilation(true) + .build(); +byte[] bytes = fory.serialize(object); +System.out.println(fory.deserialize(bytes)); +``` + +### 线程安全 Fory + +对于多线程应用程序: + +```java +ThreadSafeFory fory = Fory.builder() + .withLanguage(Language.JAVA) + // 启用引用跟踪以支持共享/循环引用。 + // 如果没有重复引用,禁用它会有更好的性能。 + .withRefTracking(false) + // 压缩 int 以获得更小的大小 + // .withIntCompressed(true) + // 压缩 long 以获得更小的大小 + // .withLongCompressed(true) + .withCompatibleMode(CompatibleMode.SCHEMA_CONSISTENT) + // 启用类型前向/后向兼容性 + // 禁用它以获得更小的大小和更好的性能。 + // .withCompatibleMode(CompatibleMode.COMPATIBLE) + // 启用异步多线程编译。 + .withAsyncCompilation(true) + .buildThreadSafeFory(); +byte[] bytes = fory.serialize(object); +System.out.println(fory.deserialize(bytes)); +``` + +## 对象深拷贝 + +Fory 提供高效的深拷贝功能: + +### 启用引用跟踪 + +```java +Fory fory = Fory.builder().withRefCopy(true).build(); +SomeClass a = xxx; +SomeClass copied = fory.copy(a); +``` + +### 禁用引用跟踪(更好的性能) + +禁用时,深拷贝将忽略循环引用和共享引用。对象图的相同引用将在一次 `Fory#copy` 中被复制为不同的对象: + +```java +Fory fory = Fory.builder().withRefCopy(false).build(); +SomeClass a = xxx; +SomeClass copied = fory.copy(a); +``` + +## 序列化 API + +### 基本序列化/反序列化 + +```java +// 将对象序列化为字节数组 +byte[] bytes = fory.serialize(object); + +// 将字节数组反序列化为对象 +Object obj = fory.deserialize(bytes); +``` + +### 带类型的序列化/反序列化 + +```java +// 使用显式类型序列化 +byte[] bytes = fory.serializeJavaObject(object); + +// 使用预期类型反序列化 +MyClass obj = fory.deserializeJavaObject(bytes, MyClass.class); +``` + +### 带类型信息的序列化/反序列化 + +```java +// 带类型信息序列化 +byte[] bytes = fory.serializeJavaObjectAndClass(object); + +// 使用嵌入的类型信息反序列化 +Object obj = fory.deserializeJavaObjectAndClass(bytes); +``` + +## 最佳实践 + +1. **复用 Fory 实例**:创建 Fory 成本很高,始终复用实例 +2. **使用适当的线程安全性**:根据需要在单线程和线程安全之间选择 +3. **注册类**:注册常用类以获得更好的性能 +4. **配置引用跟踪**:如果没有循环/共享引用,请禁用它 + +## 相关主题 + +- [配置选项](configuration.md) - 所有 ForyBuilder 选项 +- [类型注册](type-registration.md) - 类注册 +- [故障排除](troubleshooting.md) - 常见 API 使用问题 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/compression.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/compression.md new file mode 100644 index 00000000000..35d44152f85 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/compression.md @@ -0,0 +1,136 @@ +--- +title: 压缩 +sidebar_position: 7 +id: compression +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. +--- + +本页介绍用于减小序列化数据体积的压缩选项。 + +## 整数压缩 + +可使用 `ForyBuilder#withIntCompressed` / `ForyBuilder#withLongCompressed` 对 int / long 进行压缩以减小体积。通常压缩 int 就足够了。 + +这两个压缩选项默认都已启用。如果序列化体积并不重要,例如你之前使用的是不会做压缩的 FlatBuffers,那么应考虑关闭压缩。如果你的数据几乎全是数字,压缩可能带来 80% 的性能回退。 + +### Int 压缩 + +对于 int 压缩,Fory 使用 1 到 5 个字节进行编码。每个字节的第一位表示后面是否还有下一个字节。如果第一位被置位,就继续读取下一个字节,直到某个字节的第一位未置位为止。 + +### Long 压缩 + +对于 long 压缩,Fory 支持两种编码方式: + +#### SLI(Small Long as Int)编码,默认 + +- 如果 long 落在 `[-1073741824, 1073741823]` 范围内,则按 4 字节 int 编码:`| little-endian: ((int) value) << 1 |` +- 否则写成 9 字节:`| 0b1 | little-endian 8bytes long |` + +#### PVL(Progressive Variable-length Long)编码 + +- 每个字节的第一位表示后面是否还有下一个字节。如果第一位被置位,就继续读取下一个字节,直到某个字节的第一位未置位为止。 +- 负数会通过 `(v << 1) ^ (v >> 63)` 转换为正数,以降低小负数的编码成本。 + +如果一个数字虽然是 `long` 类型,但多数情况下并不能用更小字节数表示,那么压缩效果就不会理想,不值得承担对应的性能成本。如果你发现它没有带来明显的空间收益,可以考虑关闭 long 压缩。 + +## 数组压缩 + +当原始数组(`int[]` 和 `long[]`)中的值可以放入更小的数据类型时,Fory 支持使用 SIMD 加速的数组压缩。该特性要求 Java 16+,并借助 Vector API 获得最佳性能。 + +### 数组压缩如何工作 + +数组压缩会分析数组,判断这些值是否可以用更少字节存储: + +- **`int[]` → `byte[]`**:当所有值都落在 [-128, 127] 范围内时,体积减少 75% +- **`int[]` → `short[]`**:当所有值都落在 [-32768, 32767] 范围内时,体积减少 50% +- **`long[]` → `int[]`**:当所有值都可落入 int 范围时,体积减少 50% + +### 配置与注册 + +要启用数组压缩,你必须显式注册对应的序列化器: + +```java +Fory fory = Fory.builder() + .withLanguage(Language.JAVA) + // 启用 int 数组压缩 + .withIntArrayCompressed(true) + // 启用 long 数组压缩 + .withLongArrayCompressed(true) + .build(); + +// 你必须显式注册压缩数组序列化器 +CompressedArraySerializers.registerSerializers(fory); +``` + +**注意**:要让压缩数组序列化器可用,依赖中必须包含 `fory-simd` 模块。 + +### Maven 依赖 + +```xml + + org.apache.fory + fory-simd + 1.3.0 + +``` + +## 字符串压缩 + +字符串压缩可通过 `ForyBuilder#withStringCompressed(true)` 启用。该选项默认关闭。 + +## 配置摘要 + +| 选项 | 描述 | 默认值 | +| ------------------- | ----------------------------------- | ------- | +| `compressInt` | 启用 int 压缩 | `true` | +| `compressLong` | 启用 long 压缩 | `true` | +| `compressIntArray` | 启用 SIMD int 数组压缩(Java 16+) | `false` | +| `compressLongArray` | 启用 SIMD long 数组压缩(Java 16+) | `false` | +| `compressString` | 启用字符串压缩 | `false` | + +## 性能注意事项 + +1. **数字密集型数据建议关闭压缩**:如果你的数据大多是数字,压缩开销可能并不划算。 +2. **数组压缩要求 Java 16+**:它依赖 Vector API 实现 SIMD 加速。 +3. **Long 压缩未必适合大数值**:如果多数 long 无法放进更小表示,请关闭它。 +4. **字符串压缩存在开销**:仅在字符串高度可压缩时启用。 + +## 示例配置 + +```java +// 主要是数字的数据:关闭压缩 +Fory fory = Fory.builder() + .withLanguage(Language.JAVA) + .withIntCompressed(false) + .withLongCompressed(false) + .build(); + +// 包含数组的混合数据:启用数组压缩 +Fory fory = Fory.builder() + .withLanguage(Language.JAVA) + .withIntCompressed(true) + .withLongCompressed(true) + .withIntArrayCompressed(true) + .withLongArrayCompressed(true) + .build(); +CompressedArraySerializers.registerSerializers(fory); +``` + +## 相关主题 + +- [配置](configuration.md) - 所有 ForyBuilder 选项 +- [高级特性](advanced-features.md) - 内存管理 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/configuration.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/configuration.md new file mode 100644 index 00000000000..afdf2f5ef97 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/configuration.md @@ -0,0 +1,94 @@ +--- +title: 配置选项 +sidebar_position: 1 +id: configuration +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. +--- + +本页记录通过 `ForyBuilder` 提供的全部配置选项。 + +## ForyBuilder 选项 + +| 选项名称 | 描述 | 默认值 | +| ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | +| `timeRefIgnored` | 当启用引用跟踪时,是否忽略 `TimeSerializers` 中已注册的所有时间类型及其子类的引用跟踪。如果忽略,也可以通过调用 `Fory#registerSerializer(Class, Serializer)` 为某个时间类型重新启用引用跟踪。例如:`fory.registerSerializer(Date.class, new DateSerializer(fory.getConfig(), true))`。注意,对包含时间字段的类型而言,启用引用跟踪必须发生在相应序列化器代码生成之前,否则这些字段仍会跳过引用跟踪。 | `true` | +| `compressInt` | 启用或禁用 int 压缩以减小体积。 | `true` | +| `compressLong` | 启用或禁用 long 压缩以减小体积。 | `true` | +| `compressIntArray` | 当值可以装入更小数据类型时,启用或禁用对 int 数组的 SIMD 加速压缩。需要 Java 16+。 | `false` | +| `compressLongArray` | 当值可以装入更小数据类型时,启用或禁用对 long 数组的 SIMD 加速压缩。需要 Java 16+。 | `false` | +| `compressString` | 启用或禁用字符串压缩以减小体积。 | `false` | +| `classLoader` | 每个 `Fory` 实例的类加载器在首次解析类之后就固定下来,因为 Fory 会缓存类元数据。若要使用不同的类加载器,请创建一个配置了该类加载器的新 `Fory` 或 `ThreadSafeFory`,或者在首次类解析前依赖线程上下文类加载器。 | `Thread.currentThread().getContextClassLoader()` | +| `compatibleMode` | 类型前向/后向兼容性配置,也与 `checkClassVersion` 相关。`SCHEMA_CONSISTENT` 表示序列化端和反序列化端的类 schema 必须一致;`COMPATIBLE` 表示两端的类 schema 可以不同,可独立新增或删除字段。[查看更多](schema-evolution.md)。 | `CompatibleMode.SCHEMA_CONSISTENT` | +| `checkClassVersion` | 是否检查类 schema 的一致性。启用后,Fory 会基于 `classVersionHash` 写入并校验一致性。当启用 `CompatibleMode#COMPATIBLE` 时,该选项会自动关闭。除非你能确保类不会演进,否则不建议关闭。 | `false` | +| `checkJdkClassSerializable` | 启用或禁用对 `java.*` 下类型的 `Serializable` 接口检查。如果某个 `java.*` 类型未实现 `Serializable`,Fory 会抛出 `UnsupportedOperationException`。 | `true` | +| `registerGuavaTypes` | 是否预注册 Guava 类型,例如 `RegularImmutableMap` / `RegularImmutableList`。这些类型不是公共 API,但看起来相当稳定。 | `true` | +| `requireClassRegistration` | 关闭后可能允许未知类被反序列化,从而带来安全风险。 | `true` | +| `maxDepth` | 设置反序列化的最大深度,超过时会抛出异常。可用于阻止反序列化 DDOS 攻击。 | `50` | +| `maxTypeFields` | 一个收到的远端 struct metadata body 中可接受的最大字段数。 | `512` | +| `maxTypeMetaBytes` | 一个收到的 TypeDef 或 TypeMeta body 可接受的最大编码 body 字节数,不包含 8 字节 header 和扩展 size varint。 | `4096` | +| `maxSchemaVersionsPerType` | 一个逻辑类型可接受的最大远端 metadata 版本数。 | `10` | +| `maxAverageSchemaVersionsPerType` | 所有已接受远端类型的平均 metadata 版本数限制;有效全局下限为 `8192` 个 metadata entry。 | `3` | +| `suppressClassRegistrationWarnings` | 是否抑制类注册警告。这些警告可用于安全审计,但可能较为烦人,因此默认启用抑制。 | `true` | +| `metaShareEnabled` | 启用或禁用元数据共享模式。 | 如果设置了 `CompatibleMode.COMPATIBLE` 则为 `true`,否则为 false。 | +| `scopedMetaShareEnabled` | 作用域元数据共享只关注单次序列化过程。在该过程中创建或识别的元数据只归属于这次序列化,不会与其他序列化共享。 | 如果设置了 `CompatibleMode.COMPATIBLE` 则为 `true`,否则为 false。 | +| `metaCompressor` | 设置元数据压缩器。注意传入的 `MetaCompressor` 必须是线程安全的。默认使用基于 `Deflater` 的 `DeflaterMetaCompressor`。用户也可以传入 `zstd` 等其他压缩器以获得更好的压缩率。 | `DeflaterMetaCompressor` | +| `deserializeUnknownClass` | 启用或禁用对不存在或未知类数据的反序列化/跳过。 | 如果设置了 `CompatibleMode.COMPATIBLE` 则为 `true`,否则为 false。 | +| `codeGenEnabled` | 关闭后可能让首次序列化更快,但后续序列化会变慢。 | `true` | +| `asyncCompilationEnabled` | 如果启用,序列化会先使用解释执行模式,在某个类的异步序列化器 JIT 完成后切换到 JIT 序列化。 | `false` | +| `scalaOptimizationEnabled` | 启用或禁用 Scala 专用序列化优化。 | `false` | +| `copyRef` | 关闭后,拷贝性能会更好;但 Fory 深拷贝将忽略循环引用和共享引用。对象图中的同一个引用会在一次 `Fory#copy` 中被复制成不同对象。 | `false` | +| `serializeEnumByName` | 启用后,Fory 会序列化枚举名称,而不是数值枚举 tag。未启用时,Fory 默认写入声明顺序对应的 ordinal,或者在枚举配置了 `@ForyEnumId` 时写入显式稳定 ID。 | `false` | + +## 示例配置 + +```java +Fory fory = Fory.builder() + .withLanguage(Language.JAVA) + // 启用引用跟踪以支持共享/循环引用。 + // 如果没有重复引用,关闭它通常会有更好的性能。 + .withRefTracking(false) + // 压缩 int 以减小体积 + .withIntCompressed(true) + // 压缩 long 以减小体积 + .withLongCompressed(true) + .withCompatibleMode(CompatibleMode.SCHEMA_CONSISTENT) + // 启用类型前向/后向兼容性 + // 关闭它通常能得到更小体积和更好性能。 + // .withCompatibleMode(CompatibleMode.COMPATIBLE) + // 启用异步多线程编译 + .withAsyncCompilation(true) + .build(); +``` + +## 安全 + +安全相关选项: + +- `requireClassRegistration(true)` 将反序列化限制为已注册类。 +- `withMaxDepth(...)` 拒绝异常深的对象图。 +- `withMaxTypeFields(...)` 和 `withMaxTypeMetaBytes(...)` 约束一个收到的远端 metadata body 的字段数和编码 body 大小。 +- `withMaxSchemaVersionsPerType(...)` 和 `withMaxAverageSchemaVersionsPerType(...)` 约束可接受的远端 metadata 版本数,但不改变注册、动态加载或 Schema 演进语义。 +- `withDeserializeUnknownClass(false)` 避免从 metadata 物化 unknown class。 +- `checkJdkClassSerializable(true)` 保持对 `java.*` class 的 JDK serializability 检查。 + +## 相关主题 + +- [字段配置](schema-metadata.md) - `@ForyField`、`@Ignore` 与整数编码注解 +- [枚举配置](schema-metadata.md) - `serializeEnumByName` 与 `@ForyEnumId` +- [Schema 演进](schema-evolution.md) - 兼容模式与元数据共享 +- [压缩](compression.md) - Int、long 和数组压缩详情 +- [类型注册](type-registration.md) - 类注册选项 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/custom-serializers.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/custom-serializers.md new file mode 100644 index 00000000000..e18e0ac20b9 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/custom-serializers.md @@ -0,0 +1,220 @@ +--- +title: 自定义序列化器 +sidebar_position: 4 +id: custom_serializers +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. +--- + +本页介绍当前 Java 自定义序列化器 API。 + +## 构造函数输入 + +自定义序列化器不应该持有 `Fory`。 + +- 当序列化器只依赖不可变配置、并且可共享时,使用 `Config`。 +- 当序列化器需要类型元信息、泛型或嵌套动态分派时,使用 `TypeResolver`。 +- 如果序列化器持有 `TypeResolver`,它通常就不可共享,也不应实现 `Shareable`。 + +## 基础序列化器 + +运行时状态应通过 `WriteContext` 和 `ReadContext` 传递。只有在确实要做多次读写时,才把 buffer 提取到局部变量中。 + +```java +import org.apache.fory.config.Config; +import org.apache.fory.context.ReadContext; +import org.apache.fory.context.WriteContext; +import org.apache.fory.memory.MemoryBuffer; +import org.apache.fory.serializer.Serializer; +import org.apache.fory.serializer.Shareable; + +public final class FooSerializer extends Serializer implements Shareable { + public FooSerializer(Config config) { + super(config, Foo.class); + } + + @Override + public void write(WriteContext writeContext, Foo value) { + writeContext.getBuffer().writeInt64(value.f1); + writeContext.writeString(value.f2); + } + + @Override + public Foo read(ReadContext readContext) { + MemoryBuffer buffer = readContext.getBuffer(); + Foo foo = new Foo(); + foo.f1 = buffer.readInt64(); + foo.f2 = readContext.readString(buffer); + return foo; + } +} +``` + +如果序列化器可共享,就使用基于 `Config` 的构造方式注册: + +```java +Fory fory = Fory.builder().build(); +fory.registerSerializer(Foo.class, new FooSerializer(fory.getConfig())); +``` + +## 嵌套对象 + +如果序列化器需要写入或读取嵌套对象,请使用 context helper,而不要持有 `Fory`: + +```java +import org.apache.fory.config.Config; +import org.apache.fory.context.ReadContext; +import org.apache.fory.context.WriteContext; +import org.apache.fory.serializer.Serializer; + +public final class EnvelopeSerializer extends Serializer { + public EnvelopeSerializer(Config config) { + super(config, Envelope.class); + } + + @Override + public void write(WriteContext writeContext, Envelope value) { + writeContext.writeRef(value.header); + writeContext.writeRef(value.payload); + } + + @Override + public Envelope read(ReadContext readContext) { + Envelope envelope = new Envelope(); + envelope.header = (Header) readContext.readRef(); + envelope.payload = readContext.readRef(); + return envelope; + } +} +``` + +由于它不保留任何运行时局部可变状态,因此该序列化器可以实现 `Shareable`。 + +## 集合序列化器 + +对于 Java 集合,请继承 `CollectionSerializer` 或 `CollectionLikeSerializer`。 + +- `CollectionSerializer` 用于真正的 `Collection` 实现。 +- `CollectionLikeSerializer` 用于形态像集合、但并未实现 `Collection` 的类型。 +- 当集合可以使用标准元素代码生成路径时,保持 `supportCodegenHook == true`。 +- 只有在你需要完全控制元素 IO 时,才把 `supportCodegenHook` 设为 `false`。 + +示例: + +```java +import java.util.ArrayList; +import java.util.Collection; +import org.apache.fory.context.ReadContext; +import org.apache.fory.context.WriteContext; +import org.apache.fory.memory.MemoryBuffer; +import org.apache.fory.resolver.TypeResolver; +import org.apache.fory.serializer.collection.CollectionSerializer; + +public final class CustomCollectionSerializer> + extends CollectionSerializer { + public CustomCollectionSerializer(TypeResolver typeResolver, Class type) { + super(typeResolver, type, true); + } + + @Override + public Collection onCollectionWrite(WriteContext writeContext, T value) { + writeContext.getBuffer().writeVarUint32Small7(value.size()); + return value; + } + + @Override + public T onCollectionRead(Collection collection) { + return (T) collection; + } + + @Override + public Collection newCollection(ReadContext readContext) { + MemoryBuffer buffer = readContext.getBuffer(); + int numElements = buffer.readVarUint32Small7(); + setNumElements(numElements); + return new ArrayList(numElements); + } +} +``` + +## Map 序列化器 + +对于 Java map,请继承 `MapSerializer` 或 `MapLikeSerializer`。 + +```java +import java.util.LinkedHashMap; +import java.util.Map; +import org.apache.fory.memory.MemoryBuffer; +import org.apache.fory.context.ReadContext; +import org.apache.fory.context.WriteContext; +import org.apache.fory.resolver.TypeResolver; +import org.apache.fory.serializer.collection.MapSerializer; + +public final class CustomMapSerializer> extends MapSerializer { + public CustomMapSerializer(TypeResolver typeResolver, Class type) { + super(typeResolver, type, true); + } + + @Override + public Map onMapWrite(WriteContext writeContext, T value) { + writeContext.getBuffer().writeVarUint32Small7(value.size()); + return value; + } + + @Override + public T onMapRead(Map map) { + return (T) map; + } + + @Override + public Map newMap(ReadContext readContext) { + MemoryBuffer buffer = readContext.getBuffer(); + int numElements = buffer.readVarUint32Small7(); + setNumElements(numElements); + return new LinkedHashMap(numElements); + } +} +``` + +## 注册 + +```java +Fory fory = Fory.builder().build(); + +fory.registerSerializer(Foo.class, new FooSerializer(fory.getConfig())); +fory.registerSerializer( + CustomMap.class, new CustomMapSerializer<>(fory.getTypeResolver(), CustomMap.class)); +fory.registerSerializer( + CustomCollection.class, + new CustomCollectionSerializer<>(fory.getTypeResolver(), CustomCollection.class)); +``` + +如果希望 Fory 按需懒加载地构造序列化器,可以注册工厂: + +```java +fory.registerSerializer( + CustomMap.class, resolver -> new CustomMapSerializer<>(resolver, CustomMap.class)); +``` + +## 可共享性 + +当某个序列化器能够在等价运行时之间以及并发操作中安全复用时,就应实现 `Shareable` 标记接口。可共享序列化器不能保留操作状态、运行时局部可变状态,也不能在多次调用之间复用可变 scratch buffer。使用者可以通过 `serializer instanceof Shareable` 来判断其是否可共享。 + +在实践中: + +- 只依赖 `Config` 的序列化器通常是可共享的。 +- 基于 `TypeResolver` 的序列化器通常不可共享。 +- 操作状态应放在 `WriteContext`、`ReadContext` 和 `CopyContext` 中,而不是放在序列化器字段里。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/graalvm-support.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/graalvm-support.md new file mode 100644 index 00000000000..09376d36b02 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/graalvm-support.md @@ -0,0 +1,321 @@ +--- +title: GraalVM 支持 +sidebar_position: 13 +id: graalvm_support +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. +--- + +## GraalVM Native Image + +GraalVM `native image` 提前将 Java 代码编译为本地可执行文件,从而实现更快的启动速度和更低的内存使用。但是,本地镜像不支持运行时 JIT 编译或反射,除非进行显式配置。 + +Apache Fory™ 通过**使用代码生成而非反射**,可以完美地与 GraalVM native image 配合使用。所有序列化器代码都在构建时生成,在大多数情况下无需反射配置文件。 + +## 工作原理 + +当您执行以下操作时,Fory 会在 GraalVM 构建时生成序列化代码: + +1. 将 Fory 创建为**静态**字段 +2. 在静态初始化器中**注册**所有类 +3. 调用 `fory.ensureSerializersCompiled()` 来编译序列化器 +4. 通过 `native-image.properties` 配置该类在构建时初始化 + +**主要优势**:对于大多数可序列化的类,您无需配置[反射 json](https://www.graalvm.org/latest/reference-manual/native-image/metadata/#specifying-reflection-metadata-in-json) 或[序列化 json](https://www.graalvm.org/latest/reference-manual/native-image/metadata/#serialization)。 + +注意:Fory 的 `asyncCompilationEnabled` 选项在 GraalVM native image 中会自动禁用,因为不支持运行时 JIT。 + +## 基础用法 + +### 步骤 0:添加 GraalVM 支持依赖 + +在构建 native image 时,请将 `fory-graalvm-feature` 添加到应用依赖中: + +```xml + + org.apache.fory + fory-graalvm-feature + ${fory.version} + +``` + +该依赖已经在 `META-INF/native-image` 中携带 GraalVM feature 元数据,因此只要将它加入依赖,就会在 `native-image` 构建期间自动启用 `org.apache.fory.graalvm.feature.ForyGraalVMFeature`。 + +### 步骤 1:创建 Fory 并注册类 + +```java +import org.apache.fory.Fory; + +public class Example { + // 必须是静态字段 + static Fory fory; + + static { + fory = Fory.builder().build(); + fory.register(MyClass.class); + fory.register(AnotherClass.class); + // 在构建时编译所有序列化器 + fory.ensureSerializersCompiled(); + } + + public static void main(String[] args) { + byte[] bytes = fory.serialize(new MyClass()); + MyClass obj = (MyClass) fory.deserialize(bytes); + } +} +``` + +### 步骤 2:配置构建时初始化 + +创建 `resources/META-INF/native-image/your-group/your-artifact/native-image.properties`: + +```properties +Args = --initialize-at-build-time=com.example.Example +``` + +## `fory-graalvm-feature` 会处理什么 + +加入 `fory-graalvm-feature` 依赖后,Fory 会自动注册这些高级场景所需的额外 GraalVM 元数据: + +- **私有构造函数**(没有可访问的无参构造函数的类) +- **私有内部类/记录** +- **动态代理序列化** + +这意味着在大多数应用中都不再需要手工维护 `reflect-config.json`。您自己的 `native-image.properties` 仍只需要配置在构建时初始化的引导类,例如: + +```properties +Args = --initialize-at-build-time=com.example.Example +``` + +| 场景 | 不使用 Feature | 使用 Feature | +| ------------------------ | --------------------------- | ------------ | +| 具有无参构造函数的公共类 | ✅ 可工作 | ✅ 可工作 | +| 私有构造函数 | ❌ 需要 reflect-config.json | ✅ 自动注册 | +| 私有内部记录 | ❌ 需要 reflect-config.json | ✅ 自动注册 | +| 动态代理 | ❌ 需要手动配置 | ✅ 自动注册 | + +### 私有记录示例 + +```java +public class Example { + // 私有内部记录 - 需要 ForyGraalVMFeature + private record PrivateRecord(int id, String name) {} + + static Fory fory; + + static { + fory = Fory.builder().build(); + fory.register(PrivateRecord.class); + fory.ensureSerializersCompiled(); + } +} +``` + +### 动态代理示例 + +```java +import org.apache.fory.util.GraalvmSupport; + +public class ProxyExample { + public interface MyService { + String execute(); + } + + public interface Audited { + String traceId(); + } + + static Fory fory; + + static { + fory = Fory.builder().build(); + // 按照 Proxy.newProxyInstance(...) 的接口顺序精确注册 + GraalvmSupport.registerProxySupport(MyService.class, Audited.class); + fory.ensureSerializersCompiled(); + } +} +``` + +对于只实现单个接口的代理,可以使用 `registerProxySupport(MyService.class)`。如果代理实现了多个接口,则应按创建代理时使用的相同顺序传入完整接口列表。只要 classpath 中包含 `fory-graalvm-feature`,这就可以替代这些代理形状对应的手工 `proxy-config.json` 配置。 + +## 线程安全的 Fory + +对于多线程应用程序,使用 `ThreadLocalFory`: + +```java +import org.apache.fory.Fory; +import org.apache.fory.ThreadLocalFory; +import org.apache.fory.ThreadSafeFory; + +public class ThreadSafeExample { + public record Foo(int f1, String f2, List f3) {} + + static ThreadSafeFory fory; + + static { + fory = new ThreadLocalFory(builder -> { + Fory f = builder.build(); + f.register(Foo.class); + f.ensureSerializersCompiled(); + return f; + }); + } + + public static void main(String[] args) { + Foo foo = new Foo(10, "abc", List.of("str1", "str2")); + byte[] bytes = fory.serialize(foo); + Foo result = (Foo) fory.deserialize(bytes); + } +} +``` + +## 故障排除 + +### "Type is instantiated reflectively but was never registered" + +如果您看到此错误: + +``` +Type com.example.MyClass is instantiated reflectively but was never registered +``` + +**解决方案**:使用 Fory 注册该类(不要添加到 reflect-config.json): + +```java +fory.register(MyClass.class); +fory.ensureSerializersCompiled(); +``` + +如果该类具有私有构造函数,可以: + +1. 确保 `fory-graalvm-feature` 已经位于 native-image classpath 中,或 +2. 为该特定类创建 `reflect-config.json` + +## 框架集成 + +对于集成 Fory 的框架开发者: + +1. 为用户提供配置文件以列出可序列化的类 +2. 加载这些类并为每个类调用 `fory.register(Class)` +3. 完成所有注册后调用 `fory.ensureSerializersCompiled()` +4. 配置您的集成类以在构建时初始化 + +## 基准测试 + +Fory 与 GraalVM JDK 序列化的性能比较: + +| 类型 | 压缩 | 速度 | 大小 | +| ------ | ---- | --------- | ---- | +| Struct | 关闭 | 46 倍更快 | 43% | +| Struct | 开启 | 24 倍更快 | 31% | +| Pojo | 关闭 | 12 倍更快 | 56% | +| Pojo | 开启 | 12 倍更快 | 48% | + +查看 [Benchmark.java](https://github.com/apache/fory/blob/main/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/Benchmark.java) 获取基准测试代码。 + +### Struct 基准测试 + +#### 类字段 + +```java +public class Struct implements Serializable { + public int f1; + public long f2; + public float f3; + public double f4; + public int f5; + public long f6; + public float f7; + public double f8; + public int f9; + public long f10; + public float f11; + public double f12; +} +``` + +#### 基准测试结果 + +无压缩: + +``` +Benchmark repeat number: 400000 +Object type: class org.apache.fory.graalvm.Struct +Compress number: false +Fory size: 76.0 +JDK size: 178.0 +Fory serialization took mills: 49 +JDK serialization took mills: 2254 +Compare speed: Fory is 45.70x speed of JDK +Compare size: Fory is 0.43x size of JDK +``` + +数字压缩: + +``` +Benchmark repeat number: 400000 +Object type: class org.apache.fory.graalvm.Struct +Compress number: true +Fory size: 55.0 +JDK size: 178.0 +Fory serialization took mills: 130 +JDK serialization took mills: 3161 +Compare speed: Fory is 24.16x speed of JDK +Compare size: Fory is 0.31x size of JDK +``` + +### Pojo 基准测试 + +#### 类字段 + +```java +public class Foo implements Serializable { + int f1; + String f2; + List f3; + Map f4; +} +``` + +#### 基准测试结果 + +无压缩: + +``` +Benchmark repeat number: 400000 +Object type: class org.apache.fory.graalvm.Foo +Compress number: false +Fory size: 541.0 +JDK size: 964.0 +Fory serialization took mills: 1663 +JDK serialization took mills: 16266 +Compare speed: Fory is 12.19x speed of JDK +Compare size: Fory is 0.56x size of JDK +``` + +数字压缩: + +``` +Benchmark repeat number: 400000 +Object type: class org.apache.fory.graalvm.Foo +Compress number: true +Fory size: 459.0 +JDK size: 964.0 +Fory serialization took mills: 1289 +JDK serialization took mills: 15069 +Compare speed: Fory is 12.11x speed of JDK +Compare size: Fory is 0.48x size of JDK +``` diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/grpc-support.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/grpc-support.md new file mode 100644 index 00000000000..15c26ea22f3 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/grpc-support.md @@ -0,0 +1,383 @@ +--- +title: gRPC 支持 +sidebar_position: 10 +id: grpc_support +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. +--- + +Fory 可以为包含 service 定义的 schema 生成 Java gRPC service companion。生成的 service code +使用普通 grpc-java channel、server、deadline、status code、interceptor 和 transport security, +但 request/response 对象使用 Fory 序列化,而不是 protobuf。 + +当两端都由同一份 Fory IDL、protobuf IDL 或 FlatBuffers IDL 生成,并且你希望使用 gRPC +传输语义与 Fory payload 编码时,可以使用这种模式。如果 API 必须被通用 protobuf client、 +reflection 工具或期望 protobuf message bytes 的组件消费,请使用标准 protobuf gRPC 代码生成。 + +Scala 生成的 grpc-java companion 见 [Scala gRPC 支持](../scala/grpc-support.md)。Kotlin +coroutine stub 和 service base 见 [Kotlin gRPC 支持](../kotlin/grpc-support.md)。 + +## 添加依赖 + +生成的 Java service 文件编译时需要 grpc-java。Fory Java artifact 不会把 gRPC 作为硬依赖, +因此请在应用中添加 grpc-java 依赖: + +```xml + + org.apache.fory + fory-core + ${fory.version} + + + io.grpc + grpc-api + ${grpc.version} + + + io.grpc + grpc-stub + ${grpc.version} + + + io.grpc + grpc-netty-shaded + ${grpc.version} + +``` + +Gradle 示例: + +```kotlin +dependencies { + implementation("org.apache.fory:fory-core:$foryVersion") + implementation("io.grpc:grpc-api:$grpcVersion") + implementation("io.grpc:grpc-stub:$grpcVersion") + implementation("io.grpc:grpc-netty-shaded:$grpcVersion") +} +``` + +## 定义 Service + +```protobuf +package demo.greeter; + +message HelloRequest { + string name = 1; +} + +message HelloReply { + string reply = 1; +} + +service Greeter { + rpc SayHello (HelloRequest) returns (HelloReply); +} +``` + +使用 `--grpc` 生成 Java model 和 gRPC companion: + +```bash +foryc service.fdl --java_out=./generated/java --grpc +``` + +该 schema 会生成: + +| 文件 | 用途 | +| ------------------------ | ------------------------------------- | +| `HelloRequest.java` | request 的 Fory model 类型 | +| `HelloReply.java` | response 的 Fory model 类型 | +| `GreeterForyModule.java` | 生成类型的 Fory 注册 module | +| `GreeterGrpc.java` | grpc-java service base、stub 和 codec | + +生成的 method descriptor 使用 Fory-backed `MethodDescriptor.Marshaller`,因此不会调用 protobuf +parser。 + +## 实现 Server + +实现生成的 service base,并注册到标准 grpc-java `Server`: + +```java +package demo.greeter; + +import io.grpc.Server; +import io.grpc.ServerBuilder; +import io.grpc.stub.StreamObserver; + +final class GreeterService extends GreeterGrpc.GreeterImplBase { + @Override + public void sayHello( + HelloRequest request, StreamObserver responseObserver) { + HelloReply reply = new HelloReply(); + reply.setReply("Hello, " + request.getName()); + responseObserver.onNext(reply); + responseObserver.onCompleted(); + } +} + +public final class GreeterServer { + public static void main(String[] args) throws Exception { + Server server = + ServerBuilder.forPort(50051) + .addService(new GreeterService()) + .build() + .start(); + server.awaitTermination(); + } +} +``` + +生成代码负责注册和序列化 request/response 类型,service 实现不需要手动创建 Fory 实例。 + +## 创建 Client + +使用普通 grpc-java channel 和生成 stub: + +```java +package demo.greeter; + +import io.grpc.ManagedChannel; +import io.grpc.ManagedChannelBuilder; + +public final class GreeterClient { + public static void main(String[] args) { + ManagedChannel channel = + ManagedChannelBuilder.forAddress("localhost", 50051) + .usePlaintext() + .build(); + try { + GreeterGrpc.GreeterBlockingStub stub = + GreeterGrpc.newBlockingStub(channel); + + HelloRequest request = new HelloRequest(); + request.setName("Fory"); + HelloReply reply = stub.sayHello(request); + System.out.println(reply.getReply()); + } finally { + channel.shutdownNow(); + } + } +} +``` + +Channel lifecycle、deadline、credential、metadata、load balancing、retry 和 interceptor 都保持 +grpc-java 行为。 + +## Streaming RPC + +Fory service 可以使用 gRPC 的所有 streaming shape: + +```protobuf +service Greeter { + rpc SayHello (HelloRequest) returns (HelloReply); + rpc LotsOfReplies (HelloRequest) returns (stream HelloReply); + rpc LotsOfGreetings (stream HelloRequest) returns (HelloReply); + rpc Chat (stream HelloRequest) returns (stream HelloReply); +} +``` + +生成 Java service 方法遵循 grpc-java 约定: + +| IDL shape | Server 方法形态 | Client 方法形态 | +| ----------------------------------------- | ------------------------------------------------------ | ----------------------------------- | +| `rpc A (Req) returns (Res)` | `void a(Req request, StreamObserver responses)` | blocking、async、future unary stub | +| `rpc A (Req) returns (stream Res)` | `void a(Req request, StreamObserver responses)` | blocking iterator 或 async observer | +| `rpc A (stream Req) returns (Res)` | `StreamObserver a(StreamObserver responses)` | async request observer | +| `rpc A (stream Req) returns (stream Res)` | `StreamObserver a(StreamObserver responses)` | async request observer | + +Server 可以直接实现生成的 streaming 方法: + +```java +package demo.greeter; + +import io.grpc.stub.StreamObserver; +import java.util.ArrayList; +import java.util.List; + +final class GreeterService extends GreeterGrpc.GreeterImplBase { + @Override + public void lotsOfReplies( + HelloRequest request, StreamObserver responseObserver) { + HelloReply first = new HelloReply(); + first.setReply("Hello, " + request.getName()); + responseObserver.onNext(first); + + HelloReply second = new HelloReply(); + second.setReply("Welcome, " + request.getName()); + responseObserver.onNext(second); + responseObserver.onCompleted(); + } + + @Override + public StreamObserver lotsOfGreetings( + StreamObserver responseObserver) { + List names = new ArrayList<>(); + return new StreamObserver<>() { + @Override + public void onNext(HelloRequest request) { + names.add(request.getName()); + } + + @Override + public void onError(Throwable error) { + responseObserver.onError(error); + } + + @Override + public void onCompleted() { + HelloReply reply = new HelloReply(); + reply.setReply(String.join(", ", names)); + responseObserver.onNext(reply); + responseObserver.onCompleted(); + } + }; + } + + @Override + public StreamObserver chat( + StreamObserver responseObserver) { + return new StreamObserver<>() { + @Override + public void onNext(HelloRequest request) { + HelloReply reply = new HelloReply(); + reply.setReply("Hello, " + request.getName()); + responseObserver.onNext(reply); + } + + @Override + public void onError(Throwable error) { + responseObserver.onError(error); + } + + @Override + public void onCompleted() { + responseObserver.onCompleted(); + } + }; + } +} +``` + +生成的 client 返回标准 grpc-java 调用形态: + +```java +package demo.greeter; + +import io.grpc.stub.StreamObserver; +import java.util.Iterator; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +final class StreamingClient { + private final GreeterGrpc.GreeterBlockingStub blockingStub; + private final GreeterGrpc.GreeterStub asyncStub; + + StreamingClient( + GreeterGrpc.GreeterBlockingStub blockingStub, + GreeterGrpc.GreeterStub asyncStub) { + this.blockingStub = blockingStub; + this.asyncStub = asyncStub; + } + + void run() throws InterruptedException { + Iterator replies = + blockingStub.lotsOfReplies(newRequest("Fory")); + while (replies.hasNext()) { + System.out.println(replies.next().getReply()); + } + + CountDownLatch greetingsDone = new CountDownLatch(1); + StreamObserver greetings = + asyncStub.lotsOfGreetings(new StreamObserver<>() { + @Override + public void onNext(HelloReply reply) { + System.out.println(reply.getReply()); + } + + @Override + public void onError(Throwable error) { + greetingsDone.countDown(); + } + + @Override + public void onCompleted() { + greetingsDone.countDown(); + } + }); + greetings.onNext(newRequest("Ada")); + greetings.onNext(newRequest("Grace")); + greetings.onCompleted(); + greetingsDone.await(5, TimeUnit.SECONDS); + + CountDownLatch chatDone = new CountDownLatch(1); + StreamObserver chat = + asyncStub.chat(new StreamObserver<>() { + @Override + public void onNext(HelloReply reply) { + System.out.println(reply.getReply()); + } + + @Override + public void onError(Throwable error) { + chatDone.countDown(); + } + + @Override + public void onCompleted() { + chatDone.countDown(); + } + }); + chat.onNext(newRequest("Fory")); + chat.onCompleted(); + chatDone.await(5, TimeUnit.SECONDS); + } + + private static HelloRequest newRequest(String name) { + HelloRequest request = new HelloRequest(); + request.setName(name); + return request; + } +} +``` + +生成 descriptor 会保留 IDL 中的 service 和 method 名称作为 gRPC path。 + +## gRPC 运行时行为 + +生成的 service code 只替换 request/response 序列化。常规 gRPC service 行为仍由 grpc-java 提供: + +- Deadline 和取消 +- TLS 和认证 +- 名称解析与负载均衡 +- Client/server interceptor +- Status code 和 metadata +- Channel 池化与生命周期管理 + +## 故障排查 + +### 缺少 `io.grpc` 或 Guava 类 + +添加上面的 grpc-java 依赖。生成的 Fory service 文件导入 grpc-java API,但 Fory Java artifact +不会自动依赖 gRPC。 + +### `UNIMPLEMENTED` + +确认生成的 service 实现已通过 `ServerBuilder.addService(...)` 注册,并且 client 与 server 来自相同 +package、service 和 method 名称。 + +### Protobuf Client 无法解码 + +Fory gRPC companion 不使用 protobuf wire encoding。请使用 Fory 生成的 client 调用 Fory 生成的 +service,或提供单独的 protobuf endpoint。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/index.md new file mode 100644 index 00000000000..162119e38fa --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/index.md @@ -0,0 +1,205 @@ +--- +title: Java 序列化指南 +sidebar_position: 0 +id: serialization_index +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. +--- + +Apache Fory™ 提供极速的 Java 对象序列化,基于 JIT 编译和零拷贝技术。当只需要 Java 对象序列化时,这种模式相比跨语言对象图序列化提供更好的性能。 + +## 特性 + +### 高性能 + +- **JIT 代码生成**:高度可扩展的 JIT 框架在运行时使用异步多线程编译生成序列化器代码,通过以下方式提供 20-170 倍的加速: + - 内联变量以减少内存访问 + - 内联方法调用以消除虚拟分派开销 + - 最小化条件分支 + - 消除哈希查找 +- **零拷贝**:直接内存访问,无中间缓冲区拷贝;行格式支持随机访问和部分序列化 +- **变长编码**:对整数和长整型进行优化压缩 +- **元数据共享**:缓存的类元数据减少冗余类型信息 +- **SIMD 加速**:支持 Java Vector API 用于数组操作(Java 16+) + +### 即插即用替代 + +- **100% JDK 序列化兼容**:支持 `writeObject`/`readObject`/`writeReplace`/`readResolve`/`readObjectNoData`/`Externalizable` +- **Java 8-24 支持**:适用于所有现代 Java 版本,包括 Java 17+ 的 record +- **GraalVM Native Image**:支持 AOT 编译,无需反射配置 + +### 高级特性 + +- **引用跟踪**:自动处理共享引用和循环引用 +- **Schema 演化**:类 schema 变更的前向/后向兼容性 +- **多态性**:完全支持继承层次结构和接口 +- **深拷贝**:高效深度克隆复杂对象图并保留引用 +- **安全性**:类注册和可配置的反序列化策略 + +## 快速开始 + +注意,Fory 的创建成本不低,**应该在多次序列化之间复用 Fory 实例**,而不是每次都创建。你应该将 Fory 作为静态全局变量,或者某个单例对象或有限对象的实例变量。 + +### 单线程使用 + +```java +import java.util.List; +import java.util.Arrays; + +import org.apache.fory.*; +import org.apache.fory.config.*; + +public class Example { + public static void main(String[] args) { + SomeClass object = new SomeClass(); + // 注意 Fory 实例应该在多次不同对象的序列化之间复用。 + Fory fory = Fory.builder().withLanguage(Language.JAVA) + .requireClassRegistration(true) + .build(); + // 注册类型可以减少类名序列化开销,但不是必须的。 + // 如果启用了类注册,所有自定义类型都必须注册。 + // 如果未指定 id,注册顺序必须一致 + fory.register(SomeClass.class); + byte[] bytes = fory.serialize(object); + System.out.println(fory.deserialize(bytes)); + } +} +``` + +### 多线程使用 + +```java +import org.apache.fory.*; +import org.apache.fory.config.*; + +public class Example { + public static void main(String[] args) { + SomeClass object = new SomeClass(); + ThreadSafeFory fory = Fory.builder() + .withLanguage(Language.JAVA) + .buildThreadSafeFory(); + fory.register(SomeClass.class, 1); + byte[] bytes = fory.serialize(object); + System.out.println(fory.deserialize(bytes)); + } +} +``` + +### Fory 实例复用模式 + +```java +import org.apache.fory.*; +import org.apache.fory.config.*; + +public class Example { + private static final ThreadSafeFory fory = Fory.builder() + .withLanguage(Language.JAVA) + .buildThreadSafeFory(); + + static { + fory.register(SomeClass.class, 1); + } + + public static void main(String[] args) { + SomeClass object = new SomeClass(); + byte[] bytes = fory.serialize(object); + System.out.println(fory.deserialize(bytes)); + } +} +``` + +## 线程安全 + +Fory 提供两种线程安全运行时风格: + +### `buildThreadSafeFory` + +这是默认选择。它会构建一个固定大小的共享 `ThreadPoolFory`,其大小为 `4 * availableProcessors()`,也是虚拟线程工作负载下的首选运行时: + +```java +ThreadSafeFory fory = Fory.builder() + .withLanguage(Language.JAVA) + .withRefTracking(false) + .withCompatibleMode(CompatibleMode.SCHEMA_CONSISTENT) + .withAsyncCompilation(true) + .buildThreadSafeFory(); +``` + +更多细节请参见 [虚拟线程](virtual-threads.md)。 + +### ThreadLocalFory + +仅当你明确希望为每个长期存在的平台线程分配一个 `Fory` 实例,或者无论 JDK 版本如何都要固定采用这种方式时,才使用 `buildThreadLocalFory()`: + +```java +ThreadSafeFory fory = Fory.builder() + .withLanguage(Language.JAVA) + .buildThreadLocalFory(); +fory.register(SomeClass.class, 1); +byte[] bytes = fory.serialize(object); +System.out.println(fory.deserialize(bytes)); +``` + +### `buildThreadSafeForyPool` + +如果你希望显式指定固定共享池大小,请使用 `buildThreadSafeForyPool(poolSize)`。它会预先创建 `poolSize` 个 `Fory` 实例,把它们放在共享固定槽位中,然后让任意调用方通过与线程无关的快速路径借用实例。只有当池中所有实例都在使用时,调用才会阻塞;运行时不会按照线程身份缓存实例: + +```java +ThreadSafeFory fory = Fory.builder() + .withLanguage(Language.JAVA) + .withRefTracking(false) + .withCompatibleMode(CompatibleMode.SCHEMA_CONSISTENT) + .withAsyncCompilation(true) + .buildThreadSafeForyPool(poolSize); +``` + +### Builder 方法 + +```java +// 单线程 Fory +Fory fory = Fory.builder() + .withLanguage(Language.JAVA) + .withRefTracking(false) + .withCompatibleMode(CompatibleMode.SCHEMA_CONSISTENT) + .withAsyncCompilation(true) + .build(); + +// 线程安全 Fory(由 Fory 实例池支撑) +ThreadSafeFory fory = Fory.builder() + .withLanguage(Language.JAVA) + .withRefTracking(false) + .withCompatibleMode(CompatibleMode.SCHEMA_CONSISTENT) + .withAsyncCompilation(true) + .buildThreadSafeFory(); + +// 显式线程本地运行时 +ThreadSafeFory threadLocalFory = Fory.builder() + .withLanguage(Language.JAVA) + .buildThreadLocalFory(); +``` + +## 后续步骤 + +- [配置选项](configuration.md) - 了解 ForyBuilder 选项 +- [字段配置](schema-metadata.md) - `@ForyField`、`@Ignore` 和整数编码注解 +- [枚举配置](schema-metadata.md) - `serializeEnumByName` 与 `@ForyEnumId` +- [基础序列化](basic-serialization.md) - 详细的序列化模式 +- [压缩](compression.md) - 整数、long 和数组压缩选项 +- [虚拟线程](virtual-threads.md) - 虚拟线程使用方式与池大小建议 +- [类型注册](type-registration.md) - 类注册和安全性 +- [自定义序列化器](custom-serializers.md) - 实现自定义序列化器 +- [跨语言序列化](xlang-serialization.md) - 为其他语言序列化数据 +- [GraalVM 支持](graalvm_support) - 面向原生镜像的构建期序列化器编译 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/native-serialization.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/native-serialization.md new file mode 100644 index 00000000000..94de44c7638 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/native-serialization.md @@ -0,0 +1,350 @@ +--- +title: 原生序列化 +sidebar_position: 3 +id: native_serialization +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. +--- + +Java 原生序列化是通过 `withXlang(false)` 选择的 Java 专用编码格式。当所有写入方和读取方都是 Java/JVM 进程,并且载荷应遵循 JVM 类型系统而不是可移植的 xlang 类型系统时,请使用它。对于仅面向 Java/JVM、用于替代 JDK serialization、Kryo、FST、Hessian 或 Java-only Protocol Buffers 载荷的场景,原生序列化是合适的起点。 + +本页中的原生序列化指 Fory 的 `xlang=false` 编码模式。它不同于 GraalVM native image 支持;后者请参阅 [GraalVM 支持](graalvm-support.md)。 + +当字节必须由非 Java Fory 运行时读取时,请使用默认 Java 模式 [Xlang 序列化](xlang-serialization.md)。 + +## 何时使用原生序列化 + +在以下情况下使用原生序列化: + +- 载荷只由 Java/JVM 应用产生和消费。 +- 对象模型使用 Java 特有类型、JDK 集合、包装类型、继承、接口或多态,并且不需要跨语言 Schema。 +- 现有类依赖 JDK 序列化钩子,例如 `writeObject`、`readObject`、`writeReplace`、`readResolve`、`readObjectNoData` 或 `Externalizable`。 +- 你需要通过 `Fory.copy(...)` 进行 Java 对象复制。 +- 大型基本类型数组或二进制载荷应使用原生模式的带外缓冲区。 +- 你正在替换 Java-only 序列化框架,并希望覆盖最广泛的 Java 对象表面。 + +当载荷必须由 Python、C++、Go、Rust、JavaScript/TypeScript、C#、Swift、Dart 或其他非 Java 运行时读取时,请改用 xlang 序列化。 + +## 创建原生运行时 + +```java +import org.apache.fory.Fory; + +Fory fory = Fory.builder() + .withXlang(false) + .requireClassRegistration(true) + .withRefTracking(true) + .build(); + +byte[] bytes = fory.serialize(object); +Object decoded = fory.deserialize(bytes); +``` + +为每种配置创建并复用一个 `Fory` 或 `ThreadSafeFory` 实例。创建 Fory 的成本不低,因为运行时会缓存类元数据、序列化器和生成的代码。 + +```java +import org.apache.fory.Fory; +import org.apache.fory.ThreadSafeFory; + +ThreadSafeFory fory = Fory.builder() + .withXlang(false) + .requireClassRegistration(true) + .withRefTracking(true) + .buildThreadSafeFory(); + +fory.register(Order.class, 100); +``` + +在并发序列化开始前,应在启动阶段注册类和序列化器。当类加载器、注册、安全、Schema 演进或引用跟踪设置不同时,请使用单独的运行时。 + +## Schema 演进 + +原生序列化默认使用 schema-consistent 模式。在 schema-consistent 模式下,写入方和读取方的类应具有相同 Schema。这是原生模式下最直接的路径,也是同步部署的合适默认值。 + +当 Java 类可能在写入方和读取方部署之间独立演进时,请启用兼容模式: + +```java +Fory fory = Fory.builder() + .withXlang(false) + .withCompatible(true) + .build(); +``` + +兼容模式允许读取方在 Schema 元数据仍兼容时容忍字段的新增、删除或重排。它还会默认启用元数据共享。字段 ID、类版本检查、元信息共享以及未知类处理,请参阅 [Schema 演进](schema-evolution.md)。 + +## 注册与安全 + +类注册默认启用。对于服务边界,请保持启用并显式注册应用类: + +```java +Fory fory = Fory.builder() + .withXlang(false) + .requireClassRegistration(true) + .build(); + +fory.register(Order.class, 100); +fory.register(LineItem.class, 101); +``` + +显式数值 ID 可以避免注册顺序漂移。如果使用不带 ID 的 `fory.register(MyClass.class)`,每个写入方和读取方都必须按相同顺序注册类。当类型 ID 协调更困难时,也可以使用基于名称的注册: + +```java +fory.register(Order.class, "com.example", "Order"); +``` + +只有在可信环境中才应禁用类注册。如果需要动态类加载,请安装 `TypeChecker` 或 `AllowListChecker`,使反序列化能够拒绝非预期类: + +```java +import org.apache.fory.Fory; +import org.apache.fory.resolver.AllowListChecker; + +AllowListChecker checker = new AllowListChecker(AllowListChecker.CheckLevel.STRICT); +checker.allowClass("com.example.*"); + +Fory fory = Fory.builder() + .withXlang(false) + .requireClassRegistration(false) + .withTypeChecker(checker) + .withMaxDepth(100) + .build(); +``` + +使用 `withMaxDepth(...)` 限制不可信或外部提供载荷的对象图深度。完整安全配置请参阅 [类型注册](type-registration.md)。 + +## Java 对象表面 + +原生序列化负责 Java 特有的对象表面: + +- POJO、record、枚举、基本类型数组、对象数组以及常见 JDK 集合。 +- 继承、接口、多态字段、共享引用和循环对象图。 +- 不需要映射到可移植 xlang 类型的 Java 包装类型和集合行为。 +- 需要 Java serialization 兼容性的类所使用的 JDK 序列化钩子。 +- 通过 `registerSerializer(...)` 或 `registerSerializerAndType(...)` 注册的自定义序列化器。 + +对于普通应用类,Fory 可以使用生成的序列化器,并避免 JDK `ObjectOutputStream` 语义。需要 JDK 序列化钩子的类可能会使用 Java serialization 兼容路径;当基于钩子的路径成本过高时,应优先为热点类使用 Fory 自定义序列化器。 + +## JDK 序列化钩子 + +Java 原生模式支持许多现有 Java 对象模型中的 JDK 序列化钩子: + +- `writeObject` 和 `readObject` +- `writeReplace` 和 `readResolve` +- `readObjectNoData` +- `Externalizable` + +```java +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; + +public class MyClass implements Serializable { + private void writeObject(ObjectOutputStream out) throws IOException { + // 自定义序列化逻辑。 + } + + private void readObject(ObjectInputStream in) throws IOException { + // 自定义反序列化逻辑。 + } + + private Object writeReplace() { + return this; + } + + private Object readResolve() { + return this; + } +} +``` + +Fory 原生载荷不是 JDK `ObjectOutputStream` 载荷。这些钩子会为了 Java 对象兼容性而被遵循,但新的载荷应由 Fory 写入和读取。 + +## 从 Java 序列化框架迁移 + +替换 JDK serialization、Kryo、FST、Hessian 或 Java-only Protocol Buffers 管线时: + +1. 从 `.withXlang(false)` 开始,因为数据仅面向 Java。 +2. 保持 `requireClassRegistration(true)`,并使用显式 ID 注册应用类。 +3. 如果写入方和读取方部署会独立滚动,请使用 `.withCompatible(true)`。 +4. 只有当身份或循环引用很重要时,才启用 `.withRefTracking(true)`。 +5. 为那些否则会使用昂贵 JDK 序列化钩子的热点类添加自定义序列化器。 +6. 尽可能将旧字节流和新字节流分开。 + +当应用必须读取可能是 JDK `ObjectOutputStream` 字节或 Fory 原生模式字节的数据时,`JavaSerializer.serializedByJDK` 可以在回退到 Fory 前识别 JDK 载荷: + +```java +import java.io.ByteArrayInputStream; +import java.io.ObjectInputStream; +import org.apache.fory.serializer.JavaSerializer; + +if (JavaSerializer.serializedByJDK(bytes)) { + ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(bytes)); + return objectInputStream.readObject(); +} +return fory.deserialize(bytes); +``` + +只在确实接受两种格式的边界使用这个桥接方式。除此之外,原生模式 Fory 载荷应由 Fory 直接写入和读取。 + +## 对象图与引用跟踪 + +启用引用跟踪时,原生模式支持共享引用和循环引用: + +```java +Fory fory = Fory.builder() + .withXlang(false) + .withRefTracking(true) + .build(); +``` + +只有对于身份和循环都不是数据模型组成部分的值形对象图,才应禁用引用跟踪: + +```java +Fory fory = Fory.builder() + .withXlang(false) + .withRefTracking(false) + .build(); +``` + +引用跟踪是一项语义选择。关闭它可以提升性能并减小载荷大小,但重复引用会反序列化为不同对象,并且不支持循环。 + +## 对象复制 + +Fory 可以在不物化字节数组的情况下深拷贝 Java 对象。完整复制语义、自定义复制钩子和故障排查,请参阅 [对象复制](object-copy.md)。 + +```java +Fory fory = Fory.builder() + .withXlang(false) + .withRefCopy(true) + .build(); + +MyClass copy = fory.copy(original); +``` + +`withRefCopy(true)` 控制复制操作中的引用保留。它不同于控制序列化和反序列化的 `withRefTracking(...)`。 + +## 零拷贝序列化 + +原生模式支持将大型二进制值和基本类型数组作为带外 `BufferObject` 载荷: + +```java +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; +import org.apache.fory.Fory; +import org.apache.fory.memory.MemoryBuffer; +import org.apache.fory.serializer.BufferObject; + +Fory fory = Fory.builder() + .withXlang(false) + .build(); + +List value = Arrays.asList("str", new byte[1000], new int[100], new double[100]); +Collection bufferObjects = new ArrayList<>(); +byte[] bytes = fory.serialize(value, bufferObject -> !bufferObjects.add(bufferObject)); +List buffers = bufferObjects.stream() + .map(BufferObject::toBuffer) + .collect(Collectors.toList()); + +Object decoded = fory.deserialize(bytes, buffers); +``` + +对于应以带外方式发送的缓冲区,回调返回 `false`。主字节数组仍包含根对象图,并按回调顺序引用这些缓冲区。 + +当传输层能够分别携带主载荷和缓冲区时,请使用这种方式。如果流会作为一个字节数组存储或发送,请省略回调,让 Fory 将缓冲区内容保留在带内。 + +原生序列化还支持字节数组、`MemoryBuffer`、`ByteBuffer`、`OutputStream`、`ForyInputStream` 和 `ForyReadableChannel` API。选择与你已有边界匹配的 API;当缓冲区或流已经可用时,避免再通过 `byte[]` 复制。 + +## 类加载器 + +```java +ClassLoader loader = Thread.currentThread().getContextClassLoader(); + +Fory fory = Fory.builder() + .withXlang(false) + .withClassLoader(loader) + .build(); +``` + +每个 `Fory` 实例都绑定到一个类加载器,因为类元数据和序列化器会被缓存。对于每个应用、插件或租户类加载器,请构建单独的运行时,而不是在已有运行时上切换加载器。 + +## 性能指南 + +- 复用 `Fory` 或 `ThreadSafeFory` 实例,不要为每个请求重新构建。 +- 使用显式数值 ID 注册类,以获得紧凑的类型元数据和稳定的部署。 +- 对同步部署的 Java 服务保持 schema-consistent 模式;只有在 Schema 演进需要时才启用兼容模式。 +- 对于没有身份或循环的值形对象图,禁用引用跟踪。 +- 当启动延迟可以接受先解释执行再序列化时,在普通 JVM 上使用异步编译: + + ```java + Fory fory = Fory.builder() + .withXlang(false) + .withAsyncCompilation(true) + .build(); + ``` + +- 在普通 JVM 上保持运行时代码生成启用。对于 GraalVM native image 和 Android 流程,使用静态生成的序列化器。 +- 当传输层支持拆分载荷时,对大型基本类型数组或二进制字段使用零拷贝带外缓冲区。 +- 当对象约定允许时,为热点类使用 Fory 自定义序列化器来替换昂贵的 JDK 序列化钩子。 + +## 原生与 Xlang 对比 + +| 需求 | 使用原生序列化 | 使用 xlang 序列化 | +| ---------------------------- | -------------- | ----------------- | +| 仅 Java/JVM 的载荷 | 是 | 可选 | +| 非 Java 读取方或写入方 | 否 | 是 | +| 广泛的 Java 对象表面 | 是 | 仅限 xlang 类型 | +| JDK 序列化钩子 | 是 | 否 | +| Java 对象复制 | 是 | 否 | +| 跨运行时的可移植类型映射 | 否 | 是 | +| 默认兼容 Schema 演进 | 否 | 是 | +| Schema 一致的同语言性能 | 是 | 否 | + +## 故障排查 + +### 非 Java 运行时无法读取载荷 + +写入方正在使用原生序列化。请使用 `.withXlang(true)` 重新构建写入方,并让类型注册与每个对等运行时保持一致。 + +### 反序列化期间类被拒绝 + +保持类注册启用,并在写入方和读取方都注册该类。如果确实需要动态类加载,请只在配合 allow-listing `TypeChecker` 时使用 `requireClassRegistration(false)`。 + +### 滚动部署在字段变更后失败 + +原生序列化默认使用 schema-consistent 模式。当写入方和读取方版本可能不同时,请使用 `.withCompatible(true)`,并为长期存在的 Schema 添加稳定字段元数据。 + +### 对象身份未被保留 + +为序列化和反序列化启用 `.withRefTracking(true)`。对于 `Fory.copy(...)`,启用 `.withRefCopy(true)`。 + +### 迁移后的边界同时收到 JDK 和 Fory 字节 + +只在混合格式边界使用 `JavaSerializer.serializedByJDK(...)`,然后将 JDK 字节路由到 `ObjectInputStream`,将 Fory 原生字节路由到 `fory.deserialize(...)`。 + +## 相关主题 + +- [基础序列化](basic-serialization.md) - 以 Xlang 优先的 Java 快速入门 +- [Xlang 序列化](xlang-serialization.md) - 跨运行时 Java 载荷 +- [配置](configuration.md) - Java 构建器选项 +- [Schema 演进](schema-evolution.md) - 兼容模式和 schema-consistent 模式 +- [类型注册](type-registration.md) - 注册和安全 +- [对象复制](object-copy.md) - 深拷贝语义 +- [自定义序列化器](custom-serializers.md) - 自定义 Java 序列化器 +- [静态生成序列化器](static-generated-serializers.md) - 构建时生成的序列化器 +- [GraalVM 支持](graalvm-support.md) - Native-image 平台支持 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/object-copy.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/object-copy.md new file mode 100644 index 00000000000..4ec9e2247d6 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/object-copy.md @@ -0,0 +1,356 @@ +--- +title: 对象复制 +sidebar_position: 9 +id: object_copy +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. +--- + +本文介绍如何使用 `Fory#copy(Object)` 在内存中复制 Java 对象图。 + +`Fory.copy` 是面向 Java 对象图的深拷贝操作。它不会先序列化为字节,而是使用同一套运行时类型系统和序列化器,在内存中创建复制后的对象图。 + +## 何时使用对象复制 + +当你希望为已有 Java 对象图创建一个分离的内存克隆时,可以使用对象复制。 + +典型使用场景包括: + +- 在修改前克隆请求或响应模型 +- 为乐观更新复制缓存状态 +- 复制包含集合、映射、数组或嵌套 bean 的对象图 +- 在克隆过程中保留共享引用和循环引用 + +当你需要用于传输、存储或跨进程交换的字节时,应改用序列化。 + +| 操作 | `Fory.copy` | `serialize` / `deserialize` | +| ---------------- | ---------------- | --------------------------- | +| 结果 | Java 对象图 | 二进制载荷及重建后的对象 | +| 主要用途 | 内存深拷贝 | 传输、持久化、互操作 | +| 复制引用选项 | `withRefCopy(...)` | `withRefTracking(...)` | +| 跨语言载荷 | 否 | 是,在 xlang 模式下 | +| 中间字节缓冲区 | 否 | 是 | + +## 快速开始 + +对于通用对象图,启用 `withRefCopy(true)`,以便正确处理共享引用和循环: + +```java +import org.apache.fory.Fory; + +public class Example { + public static void main(String[] args) { + Fory fory = Fory.builder() + .withXlang(false) + .withRefCopy(true) + .build(); + + Order original = new Order(); + Order copied = fory.copy(original); + } +} +``` + +`copy(null)` 返回 `null`。 + +## 引用语义 + +最重要的复制选项是 `ForyBuilder#withRefCopy(boolean)`。 + +### `withRefCopy(true)` + +这是通用对象图的安全默认值。共享引用在复制后的对象图中仍保持共享,循环引用也可以被正确复制。 + +```java +import org.apache.fory.Fory; + +public class Example { + static final class Address { + String city; + } + + static final class Pair { + Address left; + Address right; + } + + public static void main(String[] args) { + Fory fory = Fory.builder() + .withXlang(false) + .withRefCopy(true) + .build(); + + Address address = new Address(); + address.city = "Shanghai"; + + Pair pair = new Pair(); + pair.left = address; + pair.right = address; + + Pair copied = fory.copy(pair); + System.out.println(copied.left == copied.right); // true + } +} +``` + +### `withRefCopy(false)` + +只有在你确定对象图是树形结构,并且不依赖共享引用或循环引用时,才应禁用复制引用跟踪。这样可能更快,但重复引用会被复制为不同对象。 + +```java +import org.apache.fory.Fory; + +public class Example { + static final class Address { + String city; + } + + static final class Pair { + Address left; + Address right; + } + + public static void main(String[] args) { + Fory fory = Fory.builder() + .withXlang(false) + .withRefCopy(false) + .build(); + + Address address = new Address(); + Pair pair = new Pair(); + pair.left = address; + pair.right = address; + + Pair copied = fory.copy(pair); + System.out.println(copied.left == copied.right); // false + } +} +``` + +如果禁用 `withRefCopy`,而对象图中包含循环,复制可能会因栈溢出而失败。 + +## `withRefCopy` 与 `withRefTracking` + +这两个选项控制不同的操作: + +- `withRefCopy(true)` 影响 `Fory.copy(...)` +- `withRefTracking(true)` 影响序列化和反序列化 + +启用其中一个不会自动启用另一个。如果应用既会序列化又会复制带有共享引用或循环引用的对象图,请显式配置这两个选项。 + +```java +Fory fory = Fory.builder().withXlang(false) + .withRefTracking(true) + .withRefCopy(true) + .build(); +``` + +## 不可变值与可变值 + +对于不可变值,Fory 可能复用原始实例。对于可变值,它会创建新的对象图。 + +实践中,这意味着: + +- `String`、装箱基本类型、枚举以及许多不可变的 JDK 值类型可能会原样返回 +- 基本类型数组、字符串数组、集合、映射、bean、日期以及其他可变结构会被复制为不同对象 + +不要只根据对象身份判断复制是否成功。应依据待复制值的可变性约定来判断。 + +## 类注册 + +如果要求类注册,请在调用 `copy` 前注册要复制的类。 + +```java +import org.apache.fory.Fory; + +public class Example { + public static void main(String[] args) { + Fory fory = Fory.builder().withXlang(false) + .requireClassRegistration(true) + .withRefCopy(true) + .build(); + + fory.register(Order.class); + Order copied = fory.copy(new Order()); + } +} +``` + +这遵循与运行时其他部分相同的注册规则:如果运行时要求类注册,复制过程中出现的运行时类型必须先完成注册。 + +## 线程安全复制 + +`ThreadSafeFory` 也支持 `copy(...)`。 + +对于通用多线程用法: + +```java +import org.apache.fory.Fory; +import org.apache.fory.ThreadSafeFory; + +public class Example { + public static void main(String[] args) { + ThreadSafeFory fory = Fory.builder() + .withXlang(false) + .withRefCopy(true) + .buildThreadSafeFory(); + + Order copied = fory.copy(new Order()); + } +} +``` + +同一 API 也适用于 `buildThreadLocalFory()` 和 `buildThreadSafeForyPool(poolSize)`。 + +## 内置覆盖范围 + +Fory 已经为许多常见 Java 运行时类型提供复制支持,包括: + +- 基本类型值和装箱基本类型 +- 字符串和基本类型数组 +- 常见 JDK 集合和映射 +- Java time 以及日期/时间值 +- bean、record 和嵌套对象图 + +如果运行时已经知道如何序列化某个可变类型,该序列化器仍可能需要显式的复制实现。对于可变序列化器,默认的 `Serializer.copy(...)` 会抛出 `UnsupportedOperationException`,除非该序列化器重写了它。 + +## 使用 `ForyCopyable` 自定义复制 + +如果某个类型需要自定义复制逻辑,请实现 `ForyCopyable`。 + +当类本身应该控制嵌套字段的复制方式时,这是最简单的方式: + +```java +import java.util.ArrayList; +import java.util.List; +import org.apache.fory.ForyCopyable; +import org.apache.fory.context.CopyContext; + +public final class Node implements ForyCopyable { + private String name; + private final List neighbors = new ArrayList<>(); + + @Override + public Node copy(CopyContext copyContext) { + Node copied = new Node(); + copyContext.reference(this, copied); + copied.name = name; + for (Node neighbor : neighbors) { + copied.neighbors.add(copyContext.copyObject(neighbor)); + } + return copied; + } +} +``` + +指导原则: + +- 如果类型可能参与循环或共享引用对象图,在创建复合可变对象后应立即调用 `copyContext.reference(origin, copy)` +- 使用 `copyContext.copyObject(...)` 复制嵌套值,不要手动重复嵌套复制逻辑 +- 让复制逻辑与该类型的正常运行时语义保持一致 + +## 在序列化器中自定义复制 + +当某个类型已经使用自定义序列化器时,请为可变值重写 `Serializer.copy(...)`。 + +```java +import org.apache.fory.config.Config; +import org.apache.fory.context.CopyContext; +import org.apache.fory.context.ReadContext; +import org.apache.fory.context.WriteContext; +import org.apache.fory.serializer.Serializer; + +public final class EnvelopeSerializer extends Serializer { + public EnvelopeSerializer(Config config) { + super(config, Envelope.class); + } + + @Override + public Envelope copy(CopyContext copyContext, Envelope value) { + Envelope copied = new Envelope(); + copyContext.reference(value, copied); + copied.header = copyContext.copyObject(value.header); + copied.payload = copyContext.copyObject(value.payload); + return copied; + } + + @Override + public void write(WriteContext writeContext, Envelope value) { + throw new UnsupportedOperationException("omitted"); + } + + @Override + public Envelope read(ReadContext readContext) { + throw new UnsupportedOperationException("omitted"); + } +} +``` + +当复制行为应归属于序列化器而不是领域类时,使用这种方式。 + +## 最佳实践 + +- 复用 `Fory` 或 `ThreadSafeFory` 实例,不要为每次复制重新构建 +- 除非你确定对象图无环且不依赖共享引用,否则启用 `withRefCopy(true)` +- 将 `withRefCopy(false)` 视为面向树形数据的性能优化,而不是默认配置 +- 使用共享引用和循环对象图同时测试自定义复制实现 +- 让可变自定义序列化器的复制路径保持显式,不要依赖回退行为 + +## 故障排查 + +### 循环对象图上的栈溢出或复制失败 + +如果复制循环对象图失败,请启用 `withRefCopy(true)`: + +```java +Fory fory = Fory.builder().withXlang(false) + .withRefCopy(true) + .build(); +``` + +禁用复制引用跟踪只对无环对象图安全。 + +### 共享引用未被保留 + +如果同一个源对象被复制成多个不同的目标对象,说明 `withRefCopy` 被禁用了。请启用它: + +```java +Fory fory = Fory.builder().withXlang(false) + .withRefCopy(true) + .build(); +``` + +单独设置 `withRefTracking(true)` 不会改变 `Fory.copy(...)` 的行为。 + +### `Copy for ... is not supported` + +这表示该类型的可变序列化器没有实现 `copy(...)`。 + +可以通过以下方式修复: + +- 在类上实现 `ForyCopyable`,或 +- 在已注册的序列化器中重写 `Serializer.copy(CopyContext, T)` + +### 注册错误 + +如果运行时使用 `requireClassRegistration(true)`,请确保复制过程中出现的运行时类型已在调用 `copy(...)` 前注册。 + +## 相关主题 + +- [基础序列化](basic-serialization.md) - 运行时创建和核心 API +- [配置](configuration.md) - 包括 `withRefCopy` 的构建器选项 +- [自定义序列化器](custom-serializers.md) - 序列化器设计和注册 +- [虚拟线程](virtual-threads.md) - 线程安全运行时指南 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/row-format.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/row-format.md new file mode 100644 index 00000000000..fb7714db317 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/row-format.md @@ -0,0 +1,183 @@ +--- +title: 行格式 +sidebar_position: 9 +id: row_format +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. +--- + +Apache Fory™ 提供了一种随机访问行格式,能够在不完全反序列化的情况下从二进制数据中读取嵌套字段。当处理大对象且只需要部分数据访问时,这大大减少了开销。 + +## 概述 + +行格式是一种缓存友好的二进制随机访问格式,支持: + +- **零拷贝访问**:直接从二进制读取字段,无需分配对象 +- **部分反序列化**:只访问你需要的字段 +- **跳过序列化**:跳过不需要的字段的序列化 +- **跨语言兼容性**:在 Python、Java、C++ 和其他语言之间工作 +- **列格式转换**:可以自动转换为 Apache Arrow 列格式 + +## 基本使用 + +```java +public class Bar { + String f1; + List f2; +} + +public class Foo { + int f1; + List f2; + Map f3; + List f4; +} + +RowEncoder encoder = Encoders.bean(Foo.class); + +// 创建大型数据集 +Foo foo = new Foo(); +foo.f1 = 10; +foo.f2 = IntStream.range(0, 1_000_000).boxed().collect(Collectors.toList()); +foo.f3 = IntStream.range(0, 1_000_000).boxed().collect(Collectors.toMap(i -> "k" + i, i -> i)); +List bars = new ArrayList<>(1_000_000); +for (int i = 0; i < 1_000_000; i++) { + Bar bar = new Bar(); + bar.f1 = "s" + i; + bar.f2 = LongStream.range(0, 10).boxed().collect(Collectors.toList()); + bars.add(bar); +} +foo.f4 = bars; + +// 编码为行格式(跨语言兼容 Python/C++) +BinaryRow binaryRow = encoder.toRow(foo); + +// 零拷贝随机访问,无需完全反序列化 +BinaryArray f2Array = binaryRow.getArray(1); // 访问 f2 列表 +BinaryArray f4Array = binaryRow.getArray(3); // 访问 f4 列表 +BinaryRow bar10 = f4Array.getStruct(10); // 访问第 11 个 Bar +long value = bar10.getArray(1).getInt64(5); // 访问 bar.f2 的第 6 个元素 + +// 部分反序列化 - 只反序列化你需要的 +RowEncoder barEncoder = Encoders.bean(Bar.class); +Bar bar1 = barEncoder.fromRow(f4Array.getStruct(10)); // 只反序列化第 11 个 Bar +Bar bar2 = barEncoder.fromRow(f4Array.getStruct(20)); // 只反序列化第 21 个 Bar + +// 需要时完全反序列化 +Foo newFoo = encoder.fromRow(binaryRow); +``` + +## 主要优势 + +| 特性 | 描述 | +| ------------ | -------------------------------------- | +| 零拷贝访问 | 读取嵌套字段而无需反序列化整个对象 | +| 内存效率 | 直接从磁盘内存映射大数据集 | +| 跨语言 | Java、Python、C++ 之间的二进制格式兼容 | +| 部分反序列化 | 只反序列化你需要的特定元素 | +| 高性能 | 跳过不必要的数据解析用于分析工作负载 | + +## 何时使用行格式 + +行格式适用于: + +- **分析工作负载**:当你只需要访问特定字段时 +- **大数据集**:当完全反序列化成本太高时 +- **内存映射文件**:处理大于 RAM 的数据 +- **数据管道**:无需完整对象重建即可处理数据 +- **跨语言数据共享**:当数据需要从多种语言访问时 + +## 跨语言兼容性 + +行格式在语言之间无缝工作。相同的二进制数据可以从以下语言访问: + +### Python + +```python +import pyfory +import pyarrow as pa +from dataclasses import dataclass +from typing import List, Dict + +@dataclass +class Bar: + f1: str + f2: List[pa.int64] + +@dataclass +class Foo: + f1: pa.int32 + f2: List[pa.int32] + f3: Dict[str, pa.int32] + f4: List[Bar] + +encoder = pyfory.encoder(Foo) +binary: bytes = encoder.to_row(foo).to_bytes() + +# 零拷贝访问 +foo_row = pyfory.RowData(encoder.schema, binary) +print(foo_row.f2[100000]) +print(foo_row.f4[100000].f1) +``` + +### C++ + +```cpp +#include "fory/encoder/row_encoder.h" +#include "fory/row/writer.h" + +struct Bar { + std::string f1; + std::vector f2; +}; + +FORY_FIELD_INFO(Bar, f1, f2); + +struct Foo { + int32_t f1; + std::vector f2; + std::map f3; + std::vector f4; +}; + +FORY_FIELD_INFO(Foo, f1, f2, f3, f4); + +fory::encoder::RowEncoder encoder; +encoder.Encode(foo); +auto row = encoder.GetWriter().ToRow(); + +// 零拷贝随机访问 +auto f2_array = row->GetArray(1); +auto f4_array = row->GetArray(3); +auto bar10 = f4_array->GetStruct(10); +int64_t value = bar10->GetArray(1)->GetInt64(5); +std::string str = bar10->GetString(0); +``` + +## 性能比较 + +| 操作 | 对象格式 | 行格式 | +| ------------ | ------------------ | ------------------ | +| 完全反序列化 | 分配所有对象 | 零分配 | +| 单字段访问 | 需要完全反序列化 | 直接偏移读取 | +| 内存使用 | 完整对象图在内存中 | 仅访问的字段 | +| 适用于 | 小对象,完全访问 | 大对象,选择性访问 | + +## 相关主题 + +- [跨语言序列化](xlang-serialization.md) - XLANG 模式 +- [高级特性](advanced-features.md) - 零拷贝序列化 +- [行格式规范](https://fory.apache.org/docs/specification/row_format_spec) - 协议详情 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/schema-evolution.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/schema-evolution.md new file mode 100644 index 00000000000..1b5bfb8bb05 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/schema-evolution.md @@ -0,0 +1,213 @@ +--- +title: Schema 演进 +sidebar_position: 9 +id: schema_evolution +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. +--- + +本页介绍 schema 演化、元数据共享以及处理不存在的类。 + +## 处理类 Schema 演化 + +在许多系统中,用于序列化的类的 schema 可能会随时间而改变。例如,类中的字段可能会被添加或删除。当序列化和反序列化过程使用不同版本的 jar 时,被反序列化的类的 schema 可能与序列化期间使用的不同。 + +### 默认模式:SCHEMA_CONSISTENT + +默认情况下,Fory 使用 `CompatibleMode.SCHEMA_CONSISTENT` 模式序列化对象。此模式假设反序列化过程使用与序列化过程相同的类 schema,从而最小化有效负载开销。但是,如果存在 schema 不一致,反序列化将失败。 + +### 兼容模式 + +如果预期 schema 会发生变化,为了使反序列化成功(即 schema 前向/后向兼容性),用户必须配置 Fory 使用 `CompatibleMode.COMPATIBLE`。这可以使用 `ForyBuilder#withCompatibleMode(CompatibleMode.COMPATIBLE)` 方法完成。 + +在此兼容模式下,反序列化可以处理 schema 变更,如缺失或额外的字段,允许在序列化和反序列化过程具有不同类 schema 时也能成功。 + +```java +Fory fory = Fory.builder() + .withCompatibleMode(CompatibleMode.COMPATIBLE) + .build(); + +byte[] bytes = fory.serialize(object); +System.out.println(fory.deserialize(bytes)); +``` + +此兼容模式涉及将类元数据序列化到序列化输出中。尽管 Fory 使用复杂的压缩技术来最小化开销,但与类元数据相关联的仍有一些额外的空间成本。 + +## 元数据共享 + +为了进一步降低元数据成本,Fory 引入了类元数据共享机制,允许元数据只发送到反序列化过程一次。 + +Fory 支持在上下文(例如 TCP 连接)中的多次序列化之间共享类型元数据(类名、字段名、最终字段类型信息等)。此信息将在上下文中的第一次序列化期间发送到对等端。基于此元数据,对等端可以重建相同的反序列化器,这避免了为后续序列化传输元数据,并降低网络流量压力,同时自动支持类型前向/后向兼容性。 + +### 使用元数据共享 + +```java +// Fory.builder() +// .withLanguage(Language.JAVA) +// .withRefTracking(false) +// // 在多次序列化之间共享元数据。 +// .withMetaContextShare(true) + +// 非线程安全 fory。 +MetaWriteContext writeContext = xxx; +fory.setMetaWriteContext(writeContext); +byte[] bytes = fory.serialize(o); + +// 非线程安全 fory。 +MetaReadContext readContext = xxx; +fory.setMetaReadContext(readContext); +fory.deserialize(bytes); +``` + +### 线程安全元数据共享 + +```java +// 线程安全 fory +byte[] serialized = fory.execute( + f -> { + f.setMetaWriteContext(writeContext); + return f.serialize(beanA); + } +); + +// 线程安全 fory +Object newObj = fory.execute( + f -> { + f.setMetaReadContext(readContext); + return f.deserialize(serialized); + } +); +``` + +**注意**:`MetaWriteContext` 和 `MetaReadContext` 都不是线程安全的,不能在多个 Fory 实例或多个线程之间重用。在多线程场景下,必须为每个 Fory 实例创建一对独立的 meta context。如果你需要不同的类加载器,请创建一个配置了该类加载器的独立 `Fory` 或 `ThreadSafeFory`,而不是在已有实例上切换类加载器。 + +有关更多详细信息,请参阅 [元数据共享规范](https://fory.apache.org/docs/specification/fory_java_serialization_spec#meta-share)。 + +## 反序列化未知类 + +Fory 支持反序列化不存在或未知的类。此功能可通过 `ForyBuilder#deserializeUnknownClass(true)` 启用。 + +启用后,如果同时启用了元数据共享,Fory 会把这类数据存放在 `Map` 的惰性子类中。通过使用 Fory 实现的惰性 map,可以避免反序列化过程中填充 map 的重平衡成本,从而进一步提升性能。 + +如果这些数据被发送到另一个进程,并且该进程中存在相应类,那么这些数据就会被反序列化为该类型的对象,而不会丢失信息。 + +如果没有启用元数据共享,新类数据会被跳过,并返回 `UnknownSkipClass` 存根对象。 + +## 将对象从一种类型复制/映射到另一种类型 + +Fory 支持将对象从一种类型映射到另一种类型。 + +**注意:** + +1. 此映射将执行深拷贝。所有映射的字段都被序列化为二进制,并从该二进制反序列化以映射到另一种类型。 +2. 所有结构体类型必须使用相同的 ID 注册,否则 Fory 无法映射到正确的结构体类型。使用 `Fory#register(Class)` 时要小心,因为 Fory 会分配一个自动增长的 ID,如果你在 Fory 实例之间以不同的顺序注册类,这可能会不一致。 + +```java +public class StructMappingExample { + static class Struct1 { + int f1; + String f2; + + public Struct1(int f1, String f2) { + this.f1 = f1; + this.f2 = f2; + } + } + + static class Struct2 { + int f1; + String f2; + double f3; + } + + static ThreadSafeFory fory1 = Fory.builder() + .withCompatibleMode(CompatibleMode.COMPATIBLE).buildThreadSafeFory(); + static ThreadSafeFory fory2 = Fory.builder() + .withCompatibleMode(CompatibleMode.COMPATIBLE).buildThreadSafeFory(); + + static { + fory1.register(Struct1.class); + fory2.register(Struct2.class); + } + + public static void main(String[] args) { + Struct1 struct1 = new Struct1(10, "abc"); + Struct2 struct2 = (Struct2) fory2.deserialize(fory1.serialize(struct1)); + Assert.assertEquals(struct2.f1, struct1.f1); + Assert.assertEquals(struct2.f2, struct1.f2); + struct1 = (Struct1) fory1.deserialize(fory2.serialize(struct2)); + Assert.assertEquals(struct1.f1, struct2.f1); + Assert.assertEquals(struct1.f2, struct2.f2); + } +} +``` + +## 将 POJO 反序列化为另一种类型 + +Fory 允许你序列化一个 POJO,并将其反序列化为另一个不同的 POJO。不同的 POJO 意味着 schema 不一致。用户必须将 Fory 配置为 `CompatibleMode.COMPATIBLE`。 + +```java +public class DeserializeIntoType { + static class Struct1 { + int f1; + String f2; + + public Struct1(int f1, String f2) { + this.f1 = f1; + this.f2 = f2; + } + } + + static class Struct2 { + int f1; + String f2; + double f3; + } + + static ThreadSafeFory fory = Fory.builder() + .withCompatibleMode(CompatibleMode.COMPATIBLE).buildThreadSafeFory(); + + public static void main(String[] args) { + Struct1 struct1 = new Struct1(10, "abc"); + byte[] data = fory.serialize(struct1); + Struct2 struct2 = fory.deserialize(data, Struct2.class); + } +} +``` + +## 配置选项 + +| 选项 | 描述 | 默认值 | +| ----------------------------- | ----------------------------------- | ------------------------- | +| `compatibleMode` | `SCHEMA_CONSISTENT` 或 `COMPATIBLE` | `SCHEMA_CONSISTENT` | +| `checkClassVersion` | 检查类 schema 一致性 | `false` | +| `metaShareEnabled` | 启用元数据共享 | 如果是兼容模式则为 `true` | +| `scopedMetaShareEnabled` | 每次序列化的作用域元数据共享 | 如果是兼容模式则为 `true` | +| `deserializeUnknownClass` | 处理不存在或未知的类 | 如果是兼容模式则为 `true` | +| `metaCompressor` | 元数据压缩的压缩器 | `DeflaterMetaCompressor` | + +## 最佳实践 + +1. **对演化的 schema 使用 COMPATIBLE 模式**:当类可能在版本之间更改时 +2. **为网络通信启用元数据共享**:减少重复序列化的带宽 +3. **对结构体映射使用一致的类型 ID**:确保相同的注册顺序或显式 ID +4. **考虑空间开销**:兼容模式会添加元数据,根据需求平衡 + +## 相关主题 + +- [配置选项](configuration.md) - 所有 ForyBuilder 选项 +- [跨语言序列化](xlang-serialization.md) - XLANG 模式 +- [故障排查](troubleshooting.md) - 常见 Schema 问题 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/schema-metadata.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/schema-metadata.md new file mode 100644 index 00000000000..c126155e90d --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/schema-metadata.md @@ -0,0 +1,701 @@ +--- +title: Schema 元数据 +sidebar_position: 7 +id: schema_metadata +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. +--- + +本文说明如何在 Java 中为序列化配置字段级元数据。 + +## 概览 + +Apache Fory™ 通过注解提供字段级配置: + +- **`@ForyField`**:配置字段元数据(id、dynamic) +- **`@Nullable`**:将字段类型或嵌套类型位置标记为可空 +- **`@Ref`**:启用字段或嵌套元素的引用跟踪 +- **`@Ignore`**:从序列化中排除字段 +- **整数类型注解**:控制整数编码(varint、fixed、tagged、unsigned) + +这支持: + +- **Tag ID**:分配紧凑的数值 ID,降低兼容模式下 struct 字段元数据大小开销 +- **可空性**:控制字段是否可以为 null +- **引用跟踪**:为共享对象启用引用跟踪 +- **字段跳过**:从序列化中排除字段 +- **编码控制**:指定整数如何编码 +- **多态控制**:控制 struct 字段的类型信息写入 + +## 基本语法 + +在字段上使用注解: + +```java +import org.apache.fory.annotation.ForyField; +import org.apache.fory.annotation.Nullable; + +public class Person { + @ForyField(id = 0) + private String name; + + @ForyField(id = 1) + private int age; + + @Nullable + @ForyField(id = 2) + private String nickname; +} +``` + +## `@ForyField` 注解 + +使用 `@ForyField` 配置字段级元数据: + +```java +public class User { + @ForyField(id = 0) + private long id; + + @ForyField(id = 1) + private String name; + + @Nullable + @ForyField(id = 2) + private String email; + + @ForyField(id = 3) + private List<@Ref User> friends; + + @ForyField(id = 4, dynamic = ForyField.Dynamic.TRUE) + private Object data; +} +``` + +### 参数 + +| 参数 | 类型 | 默认值 | 说明 | +| --------- | --------- | ------ | ------------------------------------- | +| `id` | `int` | `-1` | 非负字段 tag ID,或无 ID | +| `dynamic` | `Dynamic` | `AUTO` | 控制 struct 字段的多态行为 | + +在字段类型或嵌套类型位置上使用 `@Nullable` 来配置可空 Schema 元数据,使用 `@Ref` 进行引用跟踪。`@ForyField` 本身不携带这两项设置。 + +## 字段 ID (`id`) + +为字段分配数值 ID,以最小化兼容模式下 struct 字段元数据大小开销: + +```java +public class User { + @ForyField(id = 0) + private long id; + + @ForyField(id = 1) + private String name; + + @ForyField(id = 2) + private int age; +} +``` + +**优点**: + +- 序列化大小更小(元数据中使用数值 ID,而不是字段名) +- struct 字段元数据开销更低 +- 允许重命名字段而不破坏二进制兼容性 + +**建议**:建议为兼容模式配置字段 ID,因为它可以降低序列化成本。 + +**说明**: + +- ID 在同一个类内必须唯一 +- 配置 ID 时,ID 必须 >= 0 +- 如果未指定,注解默认值 `-1` 会被忽略,并在元数据中使用字段名(开销更大) + +**没有字段 ID**(元数据中使用字段名): + +```java +public class User { + private long id; + private String name; +} +``` + +## 可空字段 (`@Nullable`) + +对可以为 `null` 的字段使用 `@Nullable`: + +```java +public class Record { + // 可空字符串字段 + @Nullable + @ForyField(id = 0) + private String optionalName; + + // 可空 Integer 字段(装箱类型) + @Nullable + @ForyField(id = 1) + private Integer optionalCount; + + // 非可空字段(默认) + @ForyField(id = 2) + private String requiredName; +} +``` + +**说明**: + +- Xlang 字段默认不可空。 +- 当字段不可空时,Fory 会跳过 null 标记的写入。 +- 可以为 null 的装箱类型(`Integer`、`Long` 等)应使用 `@Nullable`。 + +## 引用跟踪 (`@Ref`) + +为可能共享或循环的字段启用引用跟踪: + +```java +public class RefOuter { + // 两个字段都可能指向同一个 inner 对象 + @Nullable + @ForyField(id = 0) + @Ref + private RefInner inner1; + + @Nullable + @ForyField(id = 1) + @Ref + private RefInner inner2; +} + +public class CircularRef { + @ForyField(id = 0) + private String name; + + // 用于循环引用的自引用字段 + @Nullable + @ForyField(id = 1) + @Ref + private CircularRef selfRef; +} +``` + +**使用场景**: + +- 为可能循环或共享的字段启用 +- 同一对象会被多个字段引用时启用 + +**说明**: + +- 没有 `@Ref` 的字段不会使用字段包装层面的引用跟踪 +- 当值既不共享也不循环时,应避免使用 `@Ref`,这样 Fory 可以跳过引用标记 +- 引用跟踪只有在全局引用跟踪启用时才会生效 + +## Dynamic(多态控制) + +控制跨语言序列化中 struct 字段的多态行为: + +```java +public class Container { + // AUTO:接口/抽象类型是 dynamic,具体类型不是 + @ForyField(id = 0, dynamic = ForyField.Dynamic.AUTO) + private Animal animal; // 接口 - 写入类型信息 + + // FALSE:不写入类型信息,使用声明类型的序列化器 + @ForyField(id = 1, dynamic = ForyField.Dynamic.FALSE) + private Dog dog; // 具体类型 - 不写入类型信息 + + // TRUE:写入类型信息以支持运行时子类型 + @ForyField(id = 2, dynamic = ForyField.Dynamic.TRUE) + private Object data; // 强制多态 +} +``` + +**选项**: + +| 值 | 说明 | +| ------- | ----------------------------------------------- | +| `AUTO` | 自动检测:接口/抽象类型是 dynamic,具体类型不是 | +| `FALSE` | 不写入类型信息,直接使用声明类型的序列化器 | +| `TRUE` | 写入类型信息,以支持运行时子类型 | + +## 跳过字段 + +### 使用 `@Ignore` + +从序列化中排除字段: + +```java +import org.apache.fory.annotation.Ignore; + +public class User { + @ForyField(id = 0) + private long id; + + @ForyField(id = 1) + private String name; + + @Ignore + private String password; // 不序列化 + + @Ignore + private Object internalState; // 不序列化 +} +``` + +### 使用 `transient` + +Java 的 `transient` 关键字也会排除字段: + +```java +public class User { + @ForyField(id = 0) + private long id; + + private transient String password; // 不序列化 + private transient Object cache; // 不序列化 +} +``` + +## 整数类型注解 + +Fory 提供注解来控制整数编码,以实现跨语言兼容性。整数 Schema 注解是 Java 类型使用注解。当字段修饰符存在时,将它们放在字段类型上、字段修饰符之后;如果同时存在 `@ForyField`,则与其配合使用。 + +### 有符号 32 位整数 (`@Int32Type`) + +```java +import org.apache.fory.annotation.Int32Type; +import org.apache.fory.config.Int32Encoding; + +public class MyStruct { + // 变长编码(默认)- 对小值更紧凑 + private @Int32Type(encoding = Int32Encoding.VARINT) int compactId; + + // 固定 4 字节编码 - 大小一致 + private @Int32Type(encoding = Int32Encoding.FIXED) int fixedId; +} +``` + +### 有符号 64 位整数 (`@Int64Type`) + +```java +import org.apache.fory.annotation.Int64Type; +import org.apache.fory.config.Int64Encoding; + +public class MyStruct { + // 变长编码(默认) + private @Int64Type(encoding = Int64Encoding.VARINT) long compactId; + + // 固定 8 字节编码 + private @Int64Type(encoding = Int64Encoding.FIXED) long fixedTimestamp; + + // Tagged 编码(小值占 4 字节,否则占 9 字节) + private @Int64Type(encoding = Int64Encoding.TAGGED) long taggedValue; +} +``` + +### 无符号整数 + +```java +import org.apache.fory.annotation.UInt8Type; +import org.apache.fory.annotation.UInt16Type; +import org.apache.fory.annotation.UInt32Type; +import org.apache.fory.annotation.UInt64Type; +import org.apache.fory.config.Int32Encoding; +import org.apache.fory.config.Int64Encoding; + +public class UnsignedStruct { + // 无符号 8 位 [0, 255] + private @UInt8Type int flags; + + // 无符号 16 位 [0, 65535] + private @UInt16Type int port; + + // 使用 varint 编码的无符号 32 位(默认) + private @UInt32Type(encoding = Int32Encoding.VARINT) long compactCount; + + // 使用 fixed 编码的无符号 32 位 + private @UInt32Type(encoding = Int32Encoding.FIXED) long fixedCount; + + // 使用多种编码的无符号 64 位 + private @UInt64Type(encoding = Int64Encoding.VARINT) long varintU64; + + private @UInt64Type(encoding = Int64Encoding.FIXED) long fixedU64; + + private @UInt64Type(encoding = Int64Encoding.TAGGED) long taggedU64; +} +``` + +### 编码汇总 + +| 注解 | 类型 ID | 编码 | 大小 | +| -------------------------------- | ------- | ------ | ------------ | +| `@Int32Type(encoding = VARINT)` | 5 | varint | 1-5 字节 | +| `@Int32Type(encoding = FIXED)` | 4 | fixed | 4 字节 | +| `@Int64Type(encoding = VARINT)` | 7 | varint | 1-10 字节 | +| `@Int64Type(encoding = FIXED)` | 6 | fixed | 8 字节 | +| `@Int64Type(encoding = TAGGED)` | 8 | tagged | 4 或 9 字节 | +| `@UInt8Type` | 9 | fixed | 1 字节 | +| `@UInt16Type` | 10 | fixed | 2 字节 | +| `@UInt32Type(encoding = VARINT)` | 12 | varint | 1-5 字节 | +| `@UInt32Type(encoding = FIXED)` | 11 | fixed | 4 字节 | +| `@UInt64Type(encoding = VARINT)` | 14 | varint | 1-10 字节 | +| `@UInt64Type(encoding = FIXED)` | 13 | fixed | 8 字节 | +| `@UInt64Type(encoding = TAGGED)` | 15 | tagged | 4 或 9 字节 | + +**何时使用**: + +- `varint`:最适合经常较小的值(默认) +- `fixed`:最适合使用完整范围的值(例如时间戳、哈希) +- `tagged`:在大小和性能之间提供良好平衡 +- 无符号类型:用于与 Rust、Go、C++ 中的无符号数字保持跨语言兼容 + +无符号 Java 标量承载类型是:`@UInt8Type` 和 `@UInt16Type` 使用 `int`/`Integer`,`@UInt32Type` 和 `@UInt64Type` 使用 `long`/`Long`。用 `@UInt8Type` 注解 `byte` 是无效的,因为 Java `byte` 无法表示无符号范围。 + +整数注解也可以应用到嵌套泛型类型参数上: + +```java +import java.util.List; +import java.util.Map; +import org.apache.fory.annotation.Int64Type; +import org.apache.fory.annotation.UInt32Type; +import org.apache.fory.config.Int32Encoding; +import org.apache.fory.config.Int64Encoding; + +public class NestedStruct { + private Map< + @UInt32Type(encoding = Int32Encoding.FIXED) Long, + List<@Int64Type(encoding = Int64Encoding.TAGGED) Long>> + values; +} +``` + +专用无符号列表承载类型默认使用 `list` Schema,因此其元素注解会保留在列表元数据中。只有当字段应使用紧凑的 `array` Schema 时,才添加 `@ArrayType`。 + +基本类型无符号数组可以使用标量元素注解来生成紧凑的 `array` 元数据: + +```java +import org.apache.fory.annotation.UInt32Type; + +public class IdBatch { + private @UInt32Type int[] ids; +} +``` + +## 完整示例 + +```java +import org.apache.fory.Fory; +import org.apache.fory.annotation.ForyField; +import org.apache.fory.annotation.Ignore; +import org.apache.fory.annotation.Int64Type; +import org.apache.fory.annotation.Nullable; +import org.apache.fory.annotation.UInt64Type; +import org.apache.fory.config.Int64Encoding; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class Document { + // 带 tag ID 的字段(兼容模式推荐) + @ForyField(id = 0) + private String title; + + @ForyField(id = 1) + private int version; + + // 可空字段 + @Nullable + @ForyField(id = 2) + private String description; + + // 集合字段 + @ForyField(id = 3) + private List tags; + + @ForyField(id = 4) + private Map metadata; + + @ForyField(id = 5) + private Set categories; + + // 使用不同编码的整数 + @ForyField(id = 6) + private @UInt64Type(encoding = Int64Encoding.VARINT) long viewCount; // varint 编码 + + @ForyField(id = 7) + private @UInt64Type(encoding = Int64Encoding.FIXED) long fileSize; // fixed 编码 + + @ForyField(id = 8) + private @UInt64Type(encoding = Int64Encoding.TAGGED) long checksum; // tagged 编码 + + // 为共享/循环引用进行引用跟踪的字段 + @Nullable + @ForyField(id = 9) + @Ref + private Document parent; + + // 被忽略的字段(不序列化) + private transient Object cache; + + // Getter 和 setter... +} + +// 用法 +public class Main { + public static void main(String[] args) { + Fory fory = Fory.builder() + .withXlang(true) + .withCompatible(true) + .withRefTracking(true) + .build(); + + fory.register(Document.class, 100); + + Document doc = new Document(); + doc.setTitle("My Document"); + doc.setVersion(1); + doc.setDescription("A sample document"); + + // 序列化 + byte[] data = fory.serialize(doc); + + // 反序列化 + Document decoded = (Document) fory.deserialize(data); + } +} +``` + +## 跨语言兼容性 + +当序列化的数据需要由其他语言(Python、Rust、C++、Go)读取时,请使用字段 ID 和匹配的类型注解: + +```java +public class CrossLangData { + // 使用字段 ID 以实现跨语言兼容性 + @ForyField(id = 0) + private @Int32Type(encoding = Int32Encoding.VARINT) int intVar; + + @ForyField(id = 1) + private @UInt64Type(encoding = Int64Encoding.FIXED) long longFixed; + + @ForyField(id = 2) + private @UInt64Type(encoding = Int64Encoding.TAGGED) long longTagged; + + @Nullable + @ForyField(id = 3) + private String optionalValue; +} +``` + +## Schema 演进 + +兼容模式支持 Schema 演进。建议配置字段 ID 以降低序列化成本: + +```java +// 版本 1 +public class DataV1 { + @ForyField(id = 0) + private long id; + + @ForyField(id = 1) + private String name; +} + +// 版本 2:新增字段 +public class DataV2 { + @ForyField(id = 0) + private long id; + + @ForyField(id = 1) + private String name; + + @Nullable + @ForyField(id = 2) + private String email; // 新字段 +} +``` + +使用 V1 序列化的数据可以用 V2 反序列化(新字段将为 `null`)。 + +也可以省略字段 ID(元数据中会使用字段名,开销更大): + +```java +public class Data { + private long id; + private String name; +} +``` + +## 枚举元数据 + +在 xlang 模式下,Java 枚举按数值 tag 序列化。默认 tag 是声明顺序 ordinal。当枚举需要不依赖声明顺序的稳定 ID 时,请用 `@ForyEnumId` 精确注解一个 ID 来源,或为每个枚举常量显式标注 tag 值。 + +```java +import org.apache.fory.annotation.ForyEnumId; + +enum Status { + Unknown(10), + Running(20), + Finished(30); + + private final int id; + + Status(int id) { + this.id = id; + } + + @ForyEnumId + public int getId() { + return id; + } +} +``` + +Java 还支持在一个枚举实例字段上标注 `@ForyEnumId`,或直接为每个枚举常量标注,例如 `@ForyEnumId(10) Unknown`。 + +`@ForyEnumId` 只支持三种配置方式: + +1. 注解一个枚举实例字段,并在其中存储数值 ID。 +2. 注解一个无参数的 public 实例方法,例如 `getId()`。 +3. 直接为每个枚举常量标注显式值,例如 `@ForyEnumId(10) Unknown`。 + +校验规则: + +1. 对同一个枚举,只能使用这三种方式中的一种。 +2. 字段和方法注解必须让 `value()` 保持默认值 `-1`。 +3. 一旦任何常量使用 `@ForyEnumId`,每个枚举常量都必须且只能标注一次。 +4. 所有 ID 必须非负、唯一,并且能放入 Java `int`。 + +查找行为: + +1. 没有 `@ForyEnumId` 时,Fory 写入声明顺序 ordinal。 +2. 有 `@ForyEnumId` 时,Fory 写入配置的稳定数值 tag。 +3. 小而稠密的 tag 在内部使用数组查找;稀疏且较大的 tag 会回退到 map。 + +只有当 Java 原生模式对等端应按名称而不是数值 tag 匹配枚举常量时,才使用 `serializeEnumByName(true)`: + +```java +Fory fory = Fory.builder() + .withXlang(false) + .serializeEnumByName(true) + .build(); +``` + +这个运行时选项不会改变 xlang 枚举编码;xlang 使用数值枚举 tag。对于跨语言载荷,或任何数值编码 ID 必须保持稳定的 Schema,应优先使用 `@ForyEnumId`。 + +## 原生模式与 Xlang 模式 + +字段配置会因序列化模式不同而表现不同: + +### 原生模式(仅 Java) + +为了获得最大兼容性,原生模式具有**更宽松的默认值**: + +- **可空**:引用类型默认可空 +- **引用跟踪**:对象引用默认启用(`String`、装箱类型和时间类型除外) +- **多态**:所有非 final 类默认支持多态 + +在原生模式下,通常**不需要配置字段注解**,除非你希望: + +- 通过使用字段 ID 减小序列化大小 +- 通过禁用不必要的引用跟踪优化性能 +- 为特定字段控制整数编码 + +```java +// 原生模式:没有任何注解也能工作 +public class User { + private long id; + private String name; + private List tags; // 默认可空并进行引用跟踪 +} +``` + +### Xlang 模式(跨语言) + +由于不同语言之间类型系统存在差异,xlang 模式具有**更严格的默认值**: + +- **可空**:字段默认不可空 +- **引用跟踪**:默认禁用,除非字段类型使用 `@Ref` +- **多态**:具体类型默认非多态 + +在 xlang 模式下,以下情况**需要配置字段**: + +- 字段可以为 null(使用 `@Nullable`) +- 字段需要为共享/循环对象启用引用跟踪(使用 `@Ref`) +- 整数类型需要特定编码以实现跨语言兼容性 +- 希望减小元数据大小(使用字段 ID) + +```java +// Xlang 模式:可空/引用字段需要显式配置 +public class User { + @ForyField(id = 0) + private long id; + + @ForyField(id = 1) + private String name; + + @Nullable + @ForyField(id = 2) // 必须声明 @Nullable + private String email; + + @Nullable + @ForyField(id = 3) + @Ref // 共享对象必须声明 @Ref + private User friend; +} +``` + +### 默认值汇总 + +| 选项 | 原生模式默认值 | Xlang 模式默认值 | +| ---------- | ---------------------- | ------------------------------ | +| `nullable` | `true`(引用类型) | `false` | +| `ref` | `true` | `false` | +| `dynamic` | `true`(非 final) | `AUTO`(具体类型为 final) | + +## 最佳实践 + +1. **配置字段 ID**:兼容模式推荐使用,以降低序列化成本 +2. **对可空字段使用 `@Nullable`**:可以为 null 的字段必须使用 +3. **为共享对象启用引用跟踪**:当对象共享或循环时使用 `@Ref` +4. **对敏感数据使用 `@Ignore` 或 `transient`**:密码、令牌、内部状态 +5. **选择合适的编码**:小值使用 `varint`,全范围值使用 `fixed` +6. **保持 ID 稳定**:字段 ID 一旦分配就不要更改 +7. **为跨语言兼容配置无符号类型**:与 Rust、Go、C++ 中的无符号数字互操作时使用 + +## 注解参考 + +| 注解 | 说明 | +| ----------------------------- | ---------------------------- | +| `@ForyField(id = N)` | 用于减小元数据大小的字段 tag ID | +| `@Nullable` | 将字段或嵌套类型标记为可空 | +| `@Ref` | 启用引用跟踪 | +| `@ForyField(dynamic = ...)` | 控制 struct 字段的多态 | +| `@Ignore` | 从序列化中排除字段 | +| `@Int32Type(encoding = ...)` | 32 位有符号整数编码 | +| `@Int64Type(encoding = ...)` | 64 位有符号整数编码 | +| `@UInt8Type` | 无符号 8 位整数 | +| `@UInt16Type` | 无符号 16 位整数 | +| `@UInt32Type(encoding = ...)` | 无符号 32 位整数编码 | +| `@UInt64Type(encoding = ...)` | 无符号 64 位整数编码 | + +## 相关主题 + +- [基础序列化](basic-serialization.md) - Fory 序列化入门 +- [配置](configuration.md) - 运行时构建器选项 +- [Schema 演进](schema-evolution.md) - 兼容模式和 Schema 演进 +- [Xlang 序列化](xlang-serialization.md) - 与 Python、Rust、C++、Go 的互操作 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/static-generated-serializers.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/static-generated-serializers.md new file mode 100644 index 00000000000..6ac8a7a157f --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/static-generated-serializers.md @@ -0,0 +1,143 @@ +--- +title: 静态生成序列化器 +sidebar_position: 8 +id: static_generated_serializers +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. +--- + +静态生成序列化器是在应用构建期间由 javac 生成的 Java 序列化器。当运行时代码生成被禁用或不可用时,它们很有用。 + +适用场景: + +- 在 Android 上运行。 +- 在普通 JVM 上使用 `ForyBuilder#withCodegen(false)`,但仍希望使用生成的序列化器。 +- Android 模型类使用 Fory type-use 注解,例如 `@Ref`、`@UInt8Type` 或 `@Float16Type`。 + +对于 GraalVM native image,请改为遵循 [GraalVM 支持](graalvm-support.md)。 + +## 安装注解处理器 + +将 `fory-annotation-processor` 添加到编译可序列化类的模块的注解处理器路径中: + +```xml + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.apache.fory + fory-annotation-processor + ${fory.version} + + + + + + +``` + +生成的序列化器在运行时依赖 `fory-core`。应用通过添加注解处理器来选择启用;`fory-core` 不依赖它。 + +## 标注类 + +使用 `@ForyStruct` 标注每个可序列化类: + +```java +import org.apache.fory.annotation.ForyStruct; + +@ForyStruct +public class Order { + public long id; + public String note; + + public Order() {} +} +``` + +处理器会在与被标注类相同的 Java 包中生成序列化器类。对于 `Order`,生成的类为: + +- xlang 模式使用 `Order_ForySerializer`。 +- Java native 模式使用 `Order_ForyNativeSerializer`。 + +对于 `Outer.Inner` 这样的静态嵌套类型,生成的顶层类为 `Outer_Inner_ForySerializer` 和 `Outer_Inner_ForyNativeSerializer`。 + +## 字段调试跟踪 + +当需要生成的序列化器包含字段级调试跟踪钩子时,在 `@ForyStruct` 旁添加 `@ForyDebug`。生成的代码只有在 `ENABLE_FORY_DEBUG_OUTPUT=1` 时才会打印这些跟踪。 + +```java +import org.apache.fory.annotation.ForyDebug; +import org.apache.fory.annotation.ForyStruct; + +@ForyStruct +@ForyDebug +public class DebugOrder { + public long id; + public String note; + + public DebugOrder() {} +} +``` + +## 运行时使用 + +在以下情况下,Fory 会使用可用的静态生成序列化器: + +- Android。 +- 使用 `ForyBuilder#withCodegen(false)` 的普通 JVM。 +- 目标 struct 有生成序列化器时的兼容模式读取。 + +在 `codegen=true` 的普通 JVM 上,Fory 仍会优先使用运行时生成的序列化器。 + +运行时会根据已注册的目标类名解析生成的序列化器。应用代码不应直接引用生成的序列化器类。 + +## 字段访问规则 + +生成的序列化器必须能在编译时访问序列化字段或其访问器。 + +- 当 Java 包访问规则允许同包生成的序列化器使用字段时,public、protected 和 package-private 字段可以被直接访问。 +- Private 序列化字段必须有可访问的非 private getter 和 setter 方法,或者使用 `transient` 或 Fory `@Ignore` 排除。 +- 当 public、protected 和 package-private getter/setter 方法可从生成的序列化器包访问时,会被接受。 +- 普通可变类不支持 final 字段,因为生成的 read 和 copy 方法必须能为字段赋值。基于构造函数的不可变值应使用 records。 + +对于 records,生成的序列化器使用 public record 访问器,并通过 canonical record constructor 构造值。被忽略的 record components 会在序列化和复制时跳过,生成的 read/copy 会为其构造函数参数使用 Java 默认值。 + +## Android 上的 Type-Use 注解 + +在 Android 上,当类在嵌套类型上使用 Fory type-use 注解时,必须使用静态生成序列化器: + +```java +import java.util.List; +import org.apache.fory.annotation.ForyStruct; +import org.apache.fory.annotation.UInt8Type; + +@ForyStruct +public class ImageBlock { + public List<@UInt8Type Integer> pixels; +} +``` + +如果没有生成的序列化器元信息,Android 可能无法为 Fory 暴露足够的嵌套类型信息,以保留 `@Ref`、`@Int8Type`、`@UInt8Type`、`@Float16Type` 或 `@BFloat16Type` 等注解。 + +注解处理器会在 `META-INF/proguard/` 下为 Fory 实际使用的序列化器构造函数生成 consumer R8/ProGuard 规则。Android 应用不应手动添加宽泛的生成序列化器 keep 规则。 + +## 兼容模式读取 + +静态生成序列化器同时支持普通序列化和兼容模式读取。兼容模式读取会将远端字段匹配到本地字段,跳过本地已不存在的字段,并为远端载荷中缺失的字段保留 Java 默认值。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/troubleshooting.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/troubleshooting.md new file mode 100644 index 00000000000..39b6a4ad246 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/troubleshooting.md @@ -0,0 +1,203 @@ +--- +title: 故障排查 +sidebar_position: 14 +id: troubleshooting +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. +--- + +本页介绍常见问题及其解决方案。 + +## 类不一致与类版本检查 + +如果你创建 Fory 时没有将 `CompatibleMode` 设为 `org.apache.fory.config.CompatibleMode.COMPATIBLE`,并且遇到奇怪的序列化错误,那么原因可能是序列化端和反序列化端之间的类不一致。 + +在这种情况下,你可以调用 `ForyBuilder#withClassVersionCheck` 来创建 Fory 进行校验。如果反序列化抛出 `org.apache.fory.exception.ClassNotCompatibleException`,就说明类不一致,此时应改为使用 `ForyBuilder#withCompatibleMode(CompatibleMode.COMPATIBLE)` 创建 Fory。 + +```java +// 启用类版本检查以诊断问题 +Fory fory = Fory.builder() + .withLanguage(Language.JAVA) + .withClassVersionCheck(true) + .build(); + +// 如果抛出 ClassNotCompatibleException,则改用兼容模式 +Fory fory = Fory.builder() + .withLanguage(Language.JAVA) + .withCompatibleMode(CompatibleMode.COMPATIBLE) + .build(); +``` + +**注意**:`CompatibleMode.COMPATIBLE` 会带来更多的性能和空间开销。如果序列化端和反序列化端的类始终一致,就不要默认开启它。 + +## 使用了错误的反序列化 API + +请将 `serialize` 与某个 `deserialize` 重载搭配使用: + +| 序列化 API | 反序列化 API | +| ----------------- | ----------------- | +| `Fory#serialize` | `Fory#deserialize` | + +**错误示例:** + +```java +// ❌ 错误:使用不兼容的目标类型做反序列化 +byte[] bytes = fory.serialize(struct1); +Struct2 result = fory.deserialize(bytes, Struct2.class); // 可能抛出 ClassCastException +``` + +**正确示例:** + +```java +byte[] bytes = fory.serialize(object); +Object result = fory.deserialize(bytes); + +byte[] typedBytes = fory.serialize(object); +MyClass typedResult = fory.deserialize(typedBytes, MyClass.class); +``` + +## 将 POJO 反序列化为另一种类型 + +如果你想序列化一个 POJO,然后把它反序列化为另一种 POJO 类型,就必须使用 `CompatibleMode.COMPATIBLE`: + +```java +public class DeserializeIntoType { + static class Struct1 { + int f1; + String f2; + + public Struct1(int f1, String f2) { + this.f1 = f1; + this.f2 = f2; + } + } + + static class Struct2 { + int f1; + String f2; + double f3; + } + + static ThreadSafeFory fory = Fory.builder() + .withCompatibleMode(CompatibleMode.COMPATIBLE).buildThreadSafeFory(); + + public static void main(String[] args) { + Struct1 struct1 = new Struct1(10, "abc"); + byte[] data = fory.serialize(struct1); + Struct2 struct2 = fory.deserialize(data, Struct2.class); + } +} +``` + +## 常见错误消息 + +### `"Class not registered"` + +**原因**:启用了类注册要求,但该类没有注册。 + +**解决方案**:在序列化前注册该类: + +```java +fory.register(MyClass.class); +// 或使用显式 ID +fory.register(MyClass.class, 100); +``` + +### `"ClassNotCompatibleException"` + +**原因**:序列化端和反序列化端之间的类 schema 不一致。 + +**解决方案**:使用兼容模式: + +```java +Fory fory = Fory.builder() + .withCompatibleMode(CompatibleMode.COMPATIBLE) + .build(); +``` + +### `"Max depth exceeded"` + +**原因**:对象图过深,可能意味着循环引用攻击。 + +**解决方案**:如果数据本身合法,就提高最大深度;否则检查是否存在恶意数据: + +```java +Fory fory = Fory.builder() + .withMaxDepth(100) // 相比默认值 50 提高上限 + .build(); +``` + +### `"Serializer not found"` + +**原因**:该类型没有注册对应的序列化器。 + +**解决方案**:注册自定义序列化器: + +```java +fory.registerSerializer(MyClass.class, new MyClassSerializer(fory.getTypeResolver())); +``` + +## 性能问题 + +### 首次序列化较慢 + +**原因**:第一次序列化时发生了 JIT 编译。 + +**解决方案**:启用异步编译: + +```java +Fory fory = Fory.builder() + .withAsyncCompilation(true) + .build(); +``` + +### 内存使用过高 + +**原因**:对象图较大,或引用跟踪带来额外开销。 + +**解决方案**: + +- 如果不需要,关闭引用跟踪:`.withRefTracking(false)` +- 使用自定义内存分配器做池化 +- 对大数据集考虑使用行格式 + +### 序列化结果偏大 + +**原因**:元数据开销过大,或数据未压缩。 + +**解决方案**: + +- 启用压缩:`.withIntCompressed(true)`、`.withLongCompressed(true)` +- 只在真正需要时使用兼容模式 +- 注册类以避免写入类名 + +## 调试技巧 + +1. **启用类版本检查** 来诊断 Schema 问题。 +2. **检查 API 是否正确配对**,确保 serialize/deserialize 组合正确。 +3. **核对注册顺序**,两端必须保持一致。 +4. **启用日志** 观察内部行为: + +```java +LoggerFactory.setLogLevel(LogLevel.DEBUG_LEVEL); +``` + +## 相关主题 + +- [配置](configuration.md) - 所有 ForyBuilder 选项 +- [Schema 演进](schema-evolution.md) - 兼容模式详情 +- [类型注册](type-registration.md) - 注册最佳实践 +- [迁移指南](troubleshooting.md) - 升级 Fory 版本 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/type-registration.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/type-registration.md new file mode 100644 index 00000000000..88baec0cedf --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/type-registration.md @@ -0,0 +1,113 @@ +--- +title: 类型注册与安全 +sidebar_position: 3 +id: type_registration +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. +--- + +本页介绍类注册机制与安全配置。 + +## 类注册 + +`ForyBuilder#requireClassRegistration` 可用于关闭类注册要求。这会允许反序列化未知类型的对象,灵活性更高,但**如果这些类包含恶意代码,就可能不安全**。 + +**除非你能确保运行环境安全,否则不要关闭类注册。** 当这个选项被关闭时,反序列化未知或不受信任的类型时,`init/equals/hashCode` 中的恶意代码可能被执行。 + +类注册不仅可以降低安全风险,也能避免写入类名带来的额外开销。 + +### 按 ID 注册 + +你可以通过 `Fory#register` 注册类: + +```java +Fory fory = xxx; +fory.register(SomeClass.class); +fory.register(SomeClass1.class, 1); +``` + +注意,类注册顺序很重要。序列化端和反序列化端应该保持相同的注册顺序。 + +内部类型 ID 的 `0-32` 预留给内置 xlang 类型。Java 原生内建类型从 `Types.NONE + 1` 开始,用户 ID 的编码形式是 `(user_id << 8) | internal_type_id`。 + +### 按名称注册 + +按 ID 注册有更好的性能和更小的空间开销。但在某些场景下,维护大量 type ID 也很复杂。这时,推荐使用 `register(Class cls, String namespace, String typeName)` 按名称注册类: + +```java +fory.register(Foo.class, "demo", "Foo"); +``` + +如果类型名称不会冲突,可以将 `namespace` 留空以减小序列化体积。 + +**不要在应追求紧凑编码的场景中优先使用这个 API,因为它相比按 ID 注册会显著增加序列化体积。** + +## 安全配置 + +### Type Checker + +如果你调用 `ForyBuilder#requireClassRegistration(false)` 来关闭类注册检查,就可以通过 `ForyBuilder#withTypeChecker` 或 `TypeResolver#setTypeChecker` 配置 `org.apache.fory.resolver.TypeChecker`,从而控制哪些类允许被序列化。 + +例如,你可以允许所有以 `org.example.*` 开头的类: + +```java +Fory fory = Fory.builder() + .requireClassRegistration(false) + .withTypeChecker((typeResolver, className) -> className.startsWith("org.example.")) + .build(); +``` + +### AllowListChecker + +Fory 提供了 `org.apache.fory.resolver.AllowListChecker`,这是一个基于允许/禁止列表的检查器,可简化类检查机制的定制: + +```java +AllowListChecker checker = new AllowListChecker(AllowListChecker.CheckLevel.STRICT); +checker.allowClass("org.example.*"); +ThreadSafeFory fory = Fory.builder() + .requireClassRegistration(false) + .withTypeChecker(checker) + .buildThreadSafeFory(); +``` + +`withTypeChecker` 会在每个新建运行时上立即安装检查器,这也能避免在关闭类注册却没有配置检查器时产生通用启动警告。如果你需要在构建之后替换检查器,仍然可以继续使用 `TypeResolver#setTypeChecker` 或 `ThreadSafeFory#setTypeChecker`。 + +## 限制最大反序列化深度 + +Fory 提供 `ForyBuilder#withMaxDepth` 用于限制最大反序列化深度。默认最大深度为 50。 + +当达到最大深度时,Fory 会抛出 `ForyException`。这可用于防止恶意数据导致的栈溢出等问题。 + +```java +Fory fory = Fory.builder() + .withLanguage(Language.JAVA) + .withMaxDepth(100) // 设置自定义最大深度 + .build(); +``` + +## 最佳实践 + +1. **生产环境始终启用类注册**:使用 `requireClassRegistration(true)`。 +2. **优先使用按 ID 注册**:数字 ID 比字符串名称更快。 +3. **保持注册顺序一致**:序列化端和反序列化端必须相同。 +4. **设置合适的最大深度**:防止栈溢出攻击。 +5. **需要细粒度控制时使用 AllowListChecker**:适合灵活的类过滤场景。 + +## 相关主题 + +- [配置](configuration.md) - ForyBuilder 的安全相关选项 +- [自定义序列化器](custom-serializers.md) - 注册自定义序列化器 +- [故障排查](troubleshooting.md) - 常见注册问题 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/virtual-threads.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/virtual-threads.md new file mode 100644 index 00000000000..40f46007cc1 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/virtual-threads.md @@ -0,0 +1,114 @@ +--- +title: 虚拟线程 +sidebar_position: 8 +id: java_virtual_threads +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. +--- + +Apache Fory Java 在虚拟线程工作负载下应使用 `buildThreadSafeFory()`。它会构建一个固定大小的共享 `ThreadPoolFory`,其大小为 `4 * availableProcessors()`。如果你需要不同的固定池大小,请使用 `buildThreadSafeForyPool(poolSize)`。 + +## 使用二进制输入/输出 API + +在使用虚拟线程时,应始终使用 Fory 的二进制输入/输出 API: + +- `serialize(Object)` 或 `serialize(MemoryBuffer, Object)` +- `deserialize(byte[])` 或 `deserialize(MemoryBuffer)` + +典型用法: + +```java +ThreadSafeFory fory = Fory.builder() + .requireClassRegistration(false) + .buildThreadSafeFory(); + +byte[] bytes = fory.serialize(request); +Object value = fory.deserialize(bytes); +``` + +## 在大量虚拟线程场景下不要使用 Stream API + +对于大量依赖虚拟线程的工作负载,不要使用基于 stream 或 channel 的 API: + +- `serialize(OutputStream, Object)` +- `deserialize(ForyInputStream)` +- `deserialize(ForyReadableChannel)` + +这些 API 会在整个阻塞调用期间一直占用一个池化的 `Fory` 实例。在大量虚拟线程场景下,这意味着许多 `Fory` 实例会在等待 I/O 时持续处于占用状态。每个 `Fory` 实例通常会使用大约 `30~50 KB` 内存,因此在阻塞 I/O 期间保留大量实例会很快累积出明显的内存开销。 + +只有当你的虚拟线程数量至多是几百个,并且这些额外保留的 `Fory` 内存仍然可以接受时,才建议在虚拟线程中使用 stream API。 + +## 为什么二进制 API 更合适 + +序列化和反序列化属于 CPU 工作。Fory 本身足够快,因此这部分 CPU 时间通常远短于网络传输时间。 + +在大多数情况下,你并不需要让网络传输与 Fory 反序列化重叠执行。Fory 反序列化的耗时通常不到网络传输时间的 `1/10`,因此相比尝试通过 Fory 逐步流式处理一个对象图,优化传输路径往往更重要。 + +大多数 RPC 系统本身也是基于带帧的字节消息,而不是 Java 对象流。例如,gRPC 使用长度定界帧,这与 Fory 的二进制 API 天然契合。 + +一个适合虚拟线程的模式是: + +1. 读取一条完整的带帧消息到字节数组中 +2. 调用 `fory.deserialize(bytes)` +3. 生成响应对象 +4. 调用 `fory.serialize(response)` +5. 将响应字节写成下一段带帧数据 + +## 推荐模式 + +```java +byte[] requestBytes = readOneFrame(channel); +Request request = (Request) fory.deserialize(requestBytes); + +Response response = handle(request); +byte[] responseBytes = fory.serialize(response); +writeOneFrame(channel, responseBytes); +``` + +这种方式让 Fory 只参与高效的 CPU 密集部分,而把阻塞 I/O 留在序列化器之外。 + +## 超大载荷:分块的长度定界流式处理 + +对于大多数场景,上述常规的带帧字节模式已经足够。只有在载荷非常大,并且你希望让传输与序列化/反序列化重叠时,才需要考虑分块流式处理。 + +即便如此,也不要使用 Fory 自带的 stream API。正确做法是:把一个大载荷拆成多个子对象图,将每个子对象图分别序列化为 `byte[]`,然后按以下顺序写出: + +1. 帧长度 +2. 分块字节 + +在虚拟线程中进行反序列化时: + +1. 读取帧长度 +2. 精确读取对应字节数 +3. 调用 `fory.deserialize(chunkBytes)` + +这样可以让传输按块推进,而 Fory 始终只处理完整的二进制帧。 + +```java +for (Object chunk : splitIntoSubGraphs(largePayload)) { + byte[] bytes = fory.serialize(chunk); + writeFrame(output, bytes); +} + +while (hasMoreFrames(input)) { + int length = readLength(input); + byte[] bytes = readBytes(input, length); + Object chunk = fory.deserialize(bytes); + consumeChunk(chunk); +} +``` + +长度定界帧非常常见,gRPC 也使用长度定界帧而不是 Java 对象流,因此这种模式与典型 RPC 和虚拟线程传输模型非常契合。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/xlang-serialization.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/xlang-serialization.md new file mode 100644 index 00000000000..1a23608a4d7 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/java/xlang-serialization.md @@ -0,0 +1,231 @@ +--- +title: Xlang 序列化 +sidebar_position: 2 +id: xlang_serialization +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. +--- + +Apache Fory™ 通过 xlang 序列化格式支持 Java 和其他语言(Python、Rust、Go、JavaScript 等)之间的无缝数据交换。这使得多语言微服务、多语言数据管道和跨平台数据共享成为可能。 + +## 启用xlang 模式 + +要序列化供其他语言使用的数据,使用 `Language.XLANG` 模式: + +```java +import org.apache.fory.*; +import org.apache.fory.config.*; + +// 使用 XLANG 模式创建 Fory 实例 +Fory fory = Fory.builder() + .withLanguage(Language.XLANG) + .withRefTracking(true) // 为复杂图启用引用跟踪 + .build(); +``` + +## 为跨语言兼容性注册类型 + +类型必须使用**一致的 ID 或名称**在所有语言中注册。Fory 支持两种注册方法: + +### 按 ID 注册(推荐用于性能) + +```java +public record Person(String name, int age) {} + +// 使用数字 ID 注册 - 更快更紧凑 +fory.register(Person.class, 1); + +Person person = new Person("Alice", 30); +byte[] bytes = fory.serialize(person); +// bytes 可以被 Python、Rust、Go 等反序列化。 +``` + +**优点**:更快的序列化,更小的二进制大小 +**权衡**:需要协调以避免跨团队/服务的 ID 冲突 + +### 按名称注册(推荐用于灵活性) + +```java +public record Person(String name, int age) {} + +// 使用字符串名称注册 - 更灵活 +fory.register(Person.class, "example.Person"); + +Person person = new Person("Alice", 30); +byte[] bytes = fory.serialize(person); +// bytes 可以被 Python、Rust、Go 等反序列化。 +``` + +**优点**:不容易冲突,跨团队管理更容易,无需协调 +**权衡**:由于字符串编码,二进制大小稍大 + +## 跨语言示例:Java ↔ Python + +### Java(序列化器) + +```java +import org.apache.fory.*; +import org.apache.fory.config.*; + +public record Person(String name, int age) {} + +public class Example { + public static void main(String[] args) { + Fory fory = Fory.builder() + .withLanguage(Language.XLANG) + .withRefTracking(true) + .build(); + + // 使用一致的名称注册 + fory.register(Person.class, "example.Person"); + + Person person = new Person("Bob", 25); + byte[] bytes = fory.serialize(person); + + // 通过网络/文件/队列将 bytes 发送到 Python 服务 + } +} +``` + +### Python(反序列化器) + +```python +import pyfory +from dataclasses import dataclass + +@dataclass +class Person: + name: str + age: pyfory.int32 + +# 在 xlang 模式下创建 Fory +fory = pyfory.Fory(ref_tracking=True) + +# 使用与 Java 相同的名称注册 +fory.register_type(Person, typename="example.Person") + +# 从 Java 反序列化 bytes +person = fory.deserialize(bytes_from_java) +print(f"{person.name}, {person.age}") # 输出:Bob, 25 +``` + +## 处理循环引用和共享引用 + +启用引用跟踪时,xlang 模式支持循环引用和共享引用: + +```java +public class Node { + public String value; + public Node next; + public Node parent; +} + +Fory fory = Fory.builder() + .withLanguage(Language.XLANG) + .withRefTracking(true) // 循环引用需要 + .build(); + +fory.register(Node.class, "example.Node"); + +// 创建循环引用 +Node node1 = new Node(); +node1.value = "A"; +Node node2 = new Node(); +node2.value = "B"; +node1.next = node2; +node2.parent = node1; // 循环引用 + +byte[] bytes = fory.serialize(node1); +// Python/Rust/Go 可以正确反序列化这个,并保留循环引用 +``` + +## 类型映射考虑 + +并非所有 Java 类型在其他语言中都有等价物。使用 xlang 模式时: + +- 使用**原始类型**(`int`、`long`、`double`、`String`)以获得最大兼容性 +- 使用**标准集合**(`List`、`Map`、`Set`)而不是特定于语言的集合 +- 避免 **Java 特定类型**,如 `Optional`、`BigDecimal`(除非目标语言支持它们) +- 查看[类型映射指南](https://fory.apache.org/docs/specification/xlang_type_mapping)获取完整的兼容性矩阵 + +### 兼容类型 + +```java +public record UserData( + String name, // ✅ 兼容 + int age, // ✅ 兼容 + List tags, // ✅ 兼容 + Map scores // ✅ 兼容 +) {} +``` + +### 有问题的类型 + +```java +public record UserData( + Optional name, // ❌ 不跨语言兼容 + BigDecimal balance, // ❌ 支持有限 + EnumSet statuses // ❌ Java 特定集合 +) {} +``` + +## 性能考虑 + +xlang 模式相比仅 Java 模式有额外的开销: + +- **类型元数据编码**:每个类型添加额外字节 +- **类型解析**:在反序列化期间需要名称/ID 查找 + +**为了最佳性能**: + +- 尽可能使用**基于 ID 的注册**(更小的编码) +- 如果不需要循环引用,**禁用引用跟踪**(`withRefTracking(false)`) +- 当只需要 Java 序列化时**使用 Java 模式**(`Language.JAVA`) + +## 跨语言最佳实践 + +1. **一致的注册**:确保所有服务使用相同的 ID/名称注册类型 +2. **版本兼容性**:使用兼容模式跨服务进行 schema 演化 + +## xlang 序列化故障排除 + +### "Type not registered" 错误 + +- 验证类型在两端都使用相同的 ID/名称注册 +- 检查类型名称是否有拼写错误或大小写差异 + +### "Type mismatch" 错误 + +- 确保字段类型在语言之间兼容 +- 查看[类型映射指南](https://fory.apache.org/docs/next/specification/xlang_type_mapping) + +### 数据损坏或意外值 + +- 验证两端都使用 `Language.XLANG` 模式 +- 确保两端具有兼容的 Fory 版本 + +## 另请参阅 + +- [xlang 序列化规范](https://fory.apache.org/docs/next/specification/fory_xlang_serialization_spec) +- [类型映射参考](https://fory.apache.org/docs/next/specification/xlang_type_mapping) +- [Python 跨语言指南](../python/xlang-serialization.md) +- [Rust 跨语言指南](../rust/xlang-serialization.md) + +## 相关主题 + +- [Schema 演化](schema-evolution.md) - 兼容模式 +- [类型注册](type-registration.md) - 注册方法 +- [行格式](row-format.md) - 跨语言行格式 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/javascript/_category_.json b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/javascript/_category_.json new file mode 100644 index 00000000000..2434e92ab88 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/javascript/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "JavaScript", + "position": 6, + "collapsible": true, + "collapsed": true +} diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/javascript/basic-serialization.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/javascript/basic-serialization.md new file mode 100644 index 00000000000..78d3926d357 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/javascript/basic-serialization.md @@ -0,0 +1,219 @@ +--- +title: 基础序列化 +sidebar_position: 1 +id: basic_serialization +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. +--- + +本指南介绍 Apache Fory JavaScript 中的核心序列化 API。 + +## 创建 `Fory` 实例 + +```ts +import Fory from "@apache-fory/core"; + +const fory = new Fory(); +``` + +创建一个实例,注册你的 schema,并重复复用它。Fory 会在首次调用 `register` 后缓存生成的序列化器,因此如果每个请求都重新创建实例,就会浪费这部分工作。 + +## 使用 `Type.struct` 定义 Schema + +最常见的方式是先定义 schema,再进行注册。 + +```ts +import Fory, { Type } from "@apache-fory/core"; + +const accountType = Type.struct( + { typeName: "example.account" }, + { + id: Type.int64(), + owner: Type.string(), + active: Type.bool(), + nickname: Type.string().setNullable(true), + }, +); + +const fory = new Fory(); +const { serialize, deserialize } = fory.register(accountType); +``` + +## 序列化与反序列化 + +```ts +const bytes = serialize({ + id: 42n, + owner: "Alice", + active: true, + nickname: null, +}); + +const value = deserialize(bytes); +console.log(value); +// { id: 42n, owner: 'Alice', active: true, nickname: null } +``` + +返回的 `bytes` 值是 `Uint8Array`/平台缓冲区,可以通过网络发送或写入存储。 + +## 根级动态序列化 + +`Fory` 也支持在不先绑定特定 schema 序列化器的情况下,对动态根值进行序列化。 + +```ts +const fory = new Fory(); + +const bytes = fory.serialize( + new Map([ + ["name", "Alice"], + ["age", 30], + ]), +); + +const value = fory.deserialize(bytes); +``` + +这对动态载荷很方便,但对于稳定接口和跨语言契约,显式 schema 通常更合适。 + +## 原始值 + +```ts +const fory = new Fory(); + +fory.deserialize(fory.serialize(true)); +// true + +fory.deserialize(fory.serialize("hello")); +// 'hello' + +fory.deserialize(fory.serialize(123)); +// 123 + +fory.deserialize(fory.serialize(123n)); +// 123n + +fory.deserialize(fory.serialize(new Date("2021-10-20T09:13:00Z"))); +// Date +``` + +### `number` 与 `bigint` + +JavaScript 的 `number` 是 64 位浮点数,无法精确表示所有 64 位整数。对于跨语言契约,或任何需要精确整数宽度的场景,请在 schema 中使用显式字段类型: + +- `Type.int32()`:32 位整数;使用 JavaScript `number` +- `Type.int64()`:64 位整数;使用 JavaScript `bigint` +- `Type.float32()` / `Type.float64()`:浮点数 + +动态根序列化(即不使用 schema,直接调用 `fory.serialize(someNumber)`)会推断类型,但 API 不保证推断结果稳定。对于任何稳定契约,都应使用 schema。 + +## 数组、Map 和 Set + +```ts +const inventoryType = Type.struct("example.inventory", { + tags: Type.array(Type.string()), + counts: Type.map(Type.string(), Type.int32()), + labels: Type.set(Type.string()), +}); + +const fory = new Fory({ ref: true }); +const { serialize, deserialize } = fory.register(inventoryType); + +const bytes = serialize({ + tags: ["hot", "new"], + counts: new Map([ + ["apple", 3], + ["pear", 8], + ]), + labels: new Set(["featured", "seasonal"]), +}); + +const value = deserialize(bytes); +``` + +## 嵌套 Struct + +```ts +const addressType = Type.struct("example.address", { + city: Type.string(), + country: Type.string(), +}); + +const userType = Type.struct("example.user", { + name: Type.string(), + address: Type.struct("example.address", { + city: Type.string(), + country: Type.string(), + }), +}); + +const fory = new Fory(); +const { serialize, deserialize } = fory.register(userType); + +const bytes = serialize({ + name: "Alice", + address: { city: "Hangzhou", country: "CN" }, +}); + +const user = deserialize(bytes); +``` + +如果嵌套值可能缺失,请将其标记为可空: + +```ts +const wrapperType = Type.struct("example.wrapper", { + child: Type.struct("example.child", { + name: Type.string(), + }).setNullable(true), +}); +``` + +## 基于 Decorator 的注册 + +TypeScript decorator 也受支持。 + +```ts +import Fory, { Type } from "@apache-fory/core"; + +@Type.struct("example.user") +class User { + @Type.int64() + id!: bigint; + + @Type.string() + name!: string; +} + +const fory = new Fory(); +const { serialize, deserialize } = fory.register(User); + +const user = new User(); +user.id = 1n; +user.name = "Alice"; + +const copy = deserialize(serialize(user)); +console.log(copy instanceof User); // true +``` + +## 可空性 + +在基于 schema 的 struct 中,字段的可空性需要显式声明。 + +```ts +const nullableType = Type.struct("example.optional_user", { + name: Type.string(), + email: Type.string().setNullable(true), +}); +``` diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/javascript/configuration.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/javascript/configuration.md new file mode 100644 index 00000000000..0f52349daa8 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/javascript/configuration.md @@ -0,0 +1,117 @@ +--- +title: 配置 +sidebar_position: 2 +id: configuration +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. +--- + +Fory JavaScript 是仅支持 xlang 的运行时。`new Fory()` 会写入 xlang 载荷,并默认使用兼容 Schema 演进。JavaScript API 中没有原生模式开关。 + +## 基本配置 + +```ts +import Fory from "@apache-fory/core"; + +const fory = new Fory(); +``` + +每个应用区域创建一个 `Fory` 实例并复用它。注册会为每个 schema 生成并缓存序列化器代码。 + +## 构造函数选项 + +```ts +import Fory from "@apache-fory/core"; +import hps from "@apache-fory/hps"; + +const fory = new Fory({ + ref: true, + compatible: true, + maxDepth: 100, + maxTypeFields: 512, + maxTypeMetaBytes: 4096, + maxSchemaVersionsPerType: 10, + maxAverageSchemaVersionsPerType: 3, + maxBinarySize: 64 * 1024 * 1024, + maxCollectionSize: 1_000_000, + hps, +}); +``` + +| 选项 | 默认值 | 说明 | +| --------------------------------- | ----------- | ---------------------------------------------------------------- | +| `ref` | `false` | 为共享或循环对象图启用引用跟踪 | +| `compatible` | `true` | 允许新增或删除字段而不破坏现有消息 | +| `maxDepth` | `50` | 最大嵌套深度。必须 `>= 2`。对于深度嵌套结构可以调大 | +| `maxTypeFields` | `512` | 一个收到的远端 struct metadata body 最大字段数 | +| `maxTypeMetaBytes` | `4096` | 一个收到的 TypeMeta body 最大编码字节数 | +| `maxSchemaVersionsPerType` | `10` | 一个逻辑类型最大远端 metadata 版本数 | +| `maxAverageSchemaVersionsPerType` | `3` | 所有远端类型的平均 metadata 版本数 | +| `maxBinarySize` | 64 MiB | 任意单个二进制字段可接受的最大字节数 | +| `maxCollectionSize` | `1_000_000` | 任意 list、set 或 map 可接受的最大元素数 | +| `useSliceString` | `false` | Node.js 的可选字符串读取优化。除非已做基准测试,否则保持默认值 | +| `hps` | 未设置 | 来自 `@apache-fory/hps` 的可选快速字符串辅助库(Node.js 20+) | +| `hooks.afterCodeGenerated` | 未设置 | 用于检查生成的序列化器代码的回调,便于调试 | + +## 引用跟踪 + +必须先启用全局引用跟踪,字段级引用元信息才会生效: + +```ts +const fory = new Fory({ ref: true }); +``` + +然后在 schema 中标记需要引用跟踪的字段,例如 `Type.struct("example.node").setTrackingRef(true)`。参见[引用](references.md)和 [Schema 元信息](schema-metadata.md)。 + +## 兼容 Schema 演进 + +兼容模式是默认设置: + +```ts +const fory = new Fory(); +``` + +对于滚动升级、独立部署的服务以及跨语言载荷,请使用该默认设置。你可以通过 `evolving: false` 为某个稳定 struct 关闭它;参见 [Schema 演进](schema-evolution.md)。 + +## 可选 HPS 字符串路径 + +`@apache-fory/hps` 提供可选的 Node.js 字符串快速路径: + +```ts +import hps from "@apache-fory/hps"; + +const fory = new Fory({ hps }); +``` + +除非你运行在 Node.js 20+ 且已经对工作负载做过基准测试,否则保持未设置。 + +## 安全 + +安全相关配置: + +- 在反序列化不可信载荷前,只注册预期的 schema。 +- 根据服务可接受的最大载荷形状设置 `maxDepth`、`maxBinarySize` 和 `maxCollectionSize`。 +- 除非数据不是恶意输入,且可信 peer 会发送更大的远端 metadata,否则保持 `maxTypeFields` 和 `maxTypeMetaBytes` 的默认值。 +- 除非数据不是恶意输入,且可信 peer 会发送大量远端 schema 版本,否则保持 `maxSchemaVersionsPerType` 和 `maxAverageSchemaVersionsPerType` 的默认值。 +- 对不可信输入,优先使用显式的 `Type.struct(...)` schema,而不是 `Type.any()`。 +- 只传入与你部署的运行时版本配套的官方包中的 `hps`。 + +## 相关主题 + +- [基本序列化](basic-serialization.md) +- [Schema 元信息](schema-metadata.md) +- [Schema 演进](schema-evolution.md) +- [引用](references.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/javascript/grpc-support.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/javascript/grpc-support.md new file mode 100644 index 00000000000..95e83fe1e42 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/javascript/grpc-support.md @@ -0,0 +1,306 @@ +--- +title: gRPC 支持 +sidebar_position: 25 +id: grpc_support +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. +--- + +Fory 可以为包含 service 定义的 schema 生成 JavaScript service companion。生成的 service code +使用普通 gRPC transport,但 request 和 response 对象使用 Fory 序列化,而不是 protobuf。 + +当 RPC 两端都由同一份 Fory IDL、protobuf IDL 或 FlatBuffers IDL 生成,并且两端都期望 +Fory 编码的 message body 时,可以使用这种模式。如果 API 必须被通用 protobuf client、 +reflection 工具或期望 protobuf message bytes 的组件消费,请使用标准 protobuf gRPC 代码生成。 + +使用 `--grpc` 生成 Node.js server/client 代码;使用 `--grpc-web` 生成调用 gRPC-Web 兼容 +server 或 proxy 的浏览器 client。 + +## 添加依赖 + +生成的 model 文件依赖 `@apache-fory/core`。 + +Node.js gRPC companion 导入 `@grpc/grpc-js`: + +```bash +npm install @apache-fory/core @grpc/grpc-js +``` + +浏览器 gRPC-Web companion 导入 `grpc-web`: + +```bash +npm install @apache-fory/core grpc-web +``` + +Fory 不会把 gRPC package 作为硬依赖。只需添加应用实际使用的 transport package。 + +## 定义 Service + +Service 定义可以来自 Fory IDL、protobuf IDL 或 FlatBuffers `rpc_service`。Fory IDL service +示例如下: + +```protobuf +package demo.greeter; + +message HelloRequest { + string name = 1; +} + +message HelloReply { + string reply = 1; +} + +service Greeter { + rpc SayHello (HelloRequest) returns (HelloReply); +} +``` + +生成 Node.js gRPC binding: + +```bash +foryc service.fdl --javascript_out=./generated/javascript --grpc +``` + +生成浏览器 gRPC-Web binding: + +```bash +foryc service.fdl --javascript_out=./generated/javascript --grpc-web +``` + +也可以同时生成: + +```bash +foryc service.fdl --javascript_out=./generated/javascript --grpc --grpc-web +``` + +输出包含: + +| 文件 | 用途 | +| --------------------- | --------------------------------------- | +| `service.ts` | interface、enum、union 和 schema helper | +| `service_grpc.ts` | Node.js `@grpc/grpc-js` server/client | +| `service_grpc_web.ts` | 浏览器 `grpc-web` client | + +## 实现 Node.js Server + +```ts +import * as grpc from "@grpc/grpc-js"; +import { + GreeterHandlers, + addGreeterService, +} from "./generated/javascript/service_grpc"; + +const greeter: GreeterHandlers = { + sayHello(call, callback) { + callback(null, { + reply: `Hello, ${call.request.name}`, + }); + }, +}; + +const server = new grpc.Server(); +addGreeterService(server, greeter); +server.bindAsync( + "0.0.0.0:50051", + grpc.ServerCredentials.createInsecure(), + (error, port) => { + if (error) { + throw error; + } + server.start(); + console.log(`listening on ${port}`); + }, +); +``` + +## 创建 Node.js Client + +```ts +import * as grpc from "@grpc/grpc-js"; +import { createGreeterClient } from "./generated/javascript/service_grpc"; + +const client = createGreeterClient( + "localhost:50051", + grpc.credentials.createInsecure(), +); + +client.sayHello({ name: "Fory" }, (error, reply) => { + if (error) { + throw error; + } + console.log(reply.reply); +}); +``` + +可以继续使用普通 `@grpc/grpc-js` metadata、call option、credential、deadline 和 interceptor。 + +## 创建 Browser Client + +```ts +import { createGreeterWebClient } from "./generated/javascript/service_grpc_web"; + +const client = createGreeterWebClient("https://api.example.com", { + wireFormat: "grpcweb", +}); + +client.sayHello({ name: "Fory" }, null, (error, reply) => { + if (error) { + console.error(error.message); + return; + } + console.log(reply.reply); +}); +``` + +Unary 调用也可以使用生成的 promise client: + +```ts +import { createGreeterWebPromiseClient } from "./generated/javascript/service_grpc_web"; + +const client = createGreeterWebPromiseClient("https://api.example.com"); +const reply = await client.sayHello({ name: "Fory" }); +console.log(reply.reply); +``` + +## Streaming RPC + +Node.js companion 支持所有 gRPC streaming shape。浏览器 gRPC-Web companion 支持 unary 和 +server-streaming;gRPC-Web 不支持 client-streaming 或 bidirectional streaming,compiler 会拒绝 +这些 shape 的 `--grpc-web` 生成。 + +```protobuf +service Greeter { + rpc SayHello (HelloRequest) returns (HelloReply); + rpc LotsOfReplies (HelloRequest) returns (stream HelloReply); + rpc LotsOfGreetings (stream HelloRequest) returns (HelloReply); + rpc Chat (stream HelloRequest) returns (stream HelloReply); +} +``` + +Node.js server 实现使用普通 `@grpc/grpc-js` streaming call object。生成 companion 只负责把 Fory +payload 接入 gRPC,deadline、credential、metadata、interceptor 和错误语义仍遵循 gRPC 库行为。 + +```ts +const greeter: GreeterHandlers = { + sayHello(call, callback) { + callback(null, { reply: `Hello, ${call.request.name}` }); + }, + + lotsOfReplies(call) { + call.write({ reply: `Hello, ${call.request.name}` }); + call.write({ reply: `Welcome, ${call.request.name}` }); + call.end(); + }, + + lotsOfGreetings(call, callback) { + const names: string[] = []; + call.on("data", (request) => { + names.push(request.name); + }); + call.on("end", () => { + callback(null, { reply: `Hello, ${names.join(", ")}` }); + }); + }, + + chat(call) { + call.on("data", (request) => { + call.write({ reply: `Hello, ${request.name}` }); + }); + call.on("end", () => { + call.end(); + }); + }, +}; +``` + +Node.js client 使用与 RPC shape 对应的生成方法: + +```ts +const replies = client.lotsOfReplies({ name: "Fory" }); +replies.on("data", (reply) => { + console.log(reply.reply); +}); + +const greetings = client.lotsOfGreetings((error, reply) => { + if (error) { + throw error; + } + console.log(reply.reply); +}); +greetings.write({ name: "Alice" }); +greetings.write({ name: "Bob" }); +greetings.end(); + +const chat = client.chat(); +chat.on("data", (reply) => { + console.log(reply.reply); +}); +chat.write({ name: "Alice" }); +chat.write({ name: "Bob" }); +chat.end(); +``` + +包含 server-streaming 方法的 service 中,生成的 gRPC-Web companion 默认使用 `grpcwebtext` +编码格式。只有 unary 方法的 service 默认使用 `grpcweb`。也可以显式指定: + +```ts +const client = createGreeterWebClient("https://api.example.com", { + wireFormat: "grpcwebtext", +}); +``` + +浏览器 client 可以用 callback client 消费 server-streaming RPC: + +```ts +const stream = client.lotsOfReplies({ name: "Fory" }); + +stream.on("data", (reply) => { + console.log(reply.reply); +}); +stream.on("error", (error) => { + console.error(error.message); +}); +stream.on("end", () => { + console.log("stream ended"); +}); +``` + +## gRPC 运行时行为 + +生成的 service code 只替换 request/response 序列化。常规 gRPC service 行为仍由 transport package +提供: + +- TLS 和 credential +- Metadata 和 status code +- Deadline 和取消 +- Client/server interceptor +- 负载均衡和部署相关 proxy 配置 + +## 故障排查 + +### 缺少 gRPC Package + +Node.js 添加 `@grpc/grpc-js`,浏览器添加 `grpc-web`。生成 model 仍需要 `@apache-fory/core`。 + +### gRPC-Web Client-Streaming 或 Bidirectional RPC 被拒绝 + +gRPC-Web 不支持 client-streaming 或 bidirectional streaming。对于这些 shape,请使用 `--grpc` +生成 Node.js companion,或只向浏览器 client 暴露 unary 和 server-streaming 方法。 + +### Protobuf Client 无法读取响应 + +Fory gRPC 使用 Fory 二进制协议 payload,不是 protobuf wire-format message。请在两端使用同一份 +schema 生成的 Fory gRPC companion。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/javascript/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/javascript/index.md new file mode 100644 index 00000000000..c09ae2a97ed --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/javascript/index.md @@ -0,0 +1,141 @@ +--- +title: JavaScript 序列化指南 +sidebar_position: 0 +id: index +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. +--- + +Apache Fory JavaScript 让你可以把 JavaScript 和 TypeScript 对象序列化为字节,并再把它们反序列化回来,包括与 Java、Python、Go、Rust、Swift 以及其他 Fory 支持的语言编写的服务进行跨语言互通。 + +## 为什么选择 Fory JavaScript? + +- **跨语言**:可以在 JavaScript 中序列化,在 Java、Python、Go 等语言中反序列化,无需编写胶水代码 +- **高性能**:序列化器代码会在首次注册 schema 时生成并缓存,而不是每次调用时都重新生成 +- **具备引用感知能力**:启用后可支持共享引用和循环对象图 +- **显式 schema**:字段类型、可空性和多态行为通过 `Type.*` builder 或 TypeScript decorator 一次性声明 +- **安全默认值**:可配置的深度、二进制大小和集合大小限制可以拒绝超出预期的大载荷或深层嵌套载荷 +- **现代类型支持**:支持 `bigint`、typed array、`Map`、`Set`、`Date`、`float16` 和 `bfloat16` + +## 安装 + +从 npm 安装 JavaScript 包: + +```bash +npm install @apache-fory/core +``` + +可选的 Node.js 字符串快速路径支持由 `@apache-fory/hps` 提供: + +```bash +npm install @apache-fory/core @apache-fory/hps +``` + +`@apache-fory/hps` 依赖 Node.js 20+,并且是可选的。如果不可用,Fory 仍可正常工作;只需在配置中省略 `hps`。 + +## 快速开始 + +```ts +import Fory, { Type } from "@apache-fory/core"; + +const userType = Type.struct( + { typeName: "example.user" }, + { + id: Type.int64(), + name: Type.string(), + age: Type.int32(), + }, +); + +const fory = new Fory(); +const { serialize, deserialize } = fory.register(userType); + +const bytes = serialize({ + id: 1n, + name: "Alice", + age: 30, +}); + +const user = deserialize(bytes); +console.log(user); +// { id: 1n, name: 'Alice', age: 30 } +``` + +## 工作原理 + +Fory 是由 schema 驱动的。你先用 `Type.*` builder 或 TypeScript decorator 描述一次数据结构,然后调用 `fory.register(schema)`。这会返回一个可高频复用、调用开销较低的 `{ serialize, deserialize }` 对。 + +```ts +// 1. 定义 schema +const personType = Type.struct("example.person", { + name: Type.string(), + email: Type.string().setNullable(true), +}); + +// 2. 注册一次 +const fory = new Fory(); +const { serialize, deserialize } = fory.register(personType); + +// 3. 按需重复使用 +const bytes = serialize({ name: "Alice", email: null }); +const person = deserialize(bytes); +``` + +每个应用创建一个 `Fory` 实例并持续复用即可;如果每个请求都新建实例,就会浪费 schema 注册带来的缓存收益。 + +## 配置 + +```ts +import Fory from "@apache-fory/core"; +import hps from "@apache-fory/hps"; + +const fory = new Fory({ + ref: true, + compatible: true, + maxDepth: 100, + maxBinarySize: 64 * 1024 * 1024, + maxCollectionSize: 1_000_000, + hps, +}); +``` + +| 选项 | 默认值 | 说明 | +| -------------------------- | ----------- | ------------------------------------------------------------------------------------ | +| `ref` | `false` | 为共享引用或循环对象图启用引用跟踪 | +| `compatible` | `false` | 允许在不破坏现有消息的前提下新增或删除字段 | +| `maxDepth` | `50` | 最大嵌套深度,必须 `>= 2`。如果结构嵌套很深,可适当调大 | +| `maxBinarySize` | 64 MiB | 任意单个二进制字段可接受的最大字节数 | +| `maxCollectionSize` | `1_000_000` | 任意 list、set 或 map 中可接受的最大元素数 | +| `useSliceString` | `false` | Node.js 下的可选字符串读取优化。除非做过基准测试,否则保持默认即可 | +| `hps` | unset | 来自 `@apache-fory/hps` 的可选快速字符串辅助模块(Node.js 20+) | +| `hooks.afterCodeGenerated` | unset | 用于查看生成后的序列化器代码的回调,对调试很有帮助 | + +## 文档导航 + +| 主题 | 说明 | +| --------------------------------------------- | ------------------------------------------------------ | +| [基础序列化](basic-serialization.md) | 核心 API 与日常用法 | +| [类型注册](type-registration.md) | 数值 ID、名称、decorator 与 schema 注册方式 | +| [支持的类型](supported-types.md) | 原始类型、集合、时间、枚举与 struct 的映射方式 | +| [引用](references.md) | 共享引用与循环对象图 | +| [Schema 演进](schema-evolution.md) | 兼容模式与 struct 演进 | +| [跨语言](xlang-serialization.md) | 互操作指导与类型映射规则 | +| [故障排查](troubleshooting.md) | 常见问题、限制项与调试技巧 | + +## 相关资源 + +- [Xlang 序列化规范](../../specification/xlang_serialization_spec.md) +- [跨语言类型映射](../../specification/xlang_type_mapping.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/javascript/references.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/javascript/references.md new file mode 100644 index 00000000000..aab829be06d --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/javascript/references.md @@ -0,0 +1,111 @@ +--- +title: 引用 +sidebar_position: 50 +id: references +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. +--- + +默认情况下,Fory 会把每个值都当作独立副本处理。如果同一个对象出现在两个字段中,它会被序列化两次;反序列化后,你得到的也是两个彼此独立的副本。以下情况应启用引用跟踪: + +- 同一个对象实例会在对象图中的多个位置被引用 +- 数据中包含循环结构,例如一个节点指向自己 +- 往返之后必须保留对象身份 + +对于普通的树状数据,应保持引用跟踪关闭,因为它会引入少量额外开销。 + +## 第一步:在 `Fory` 实例上启用引用跟踪 + +```ts +const fory = new Fory({ ref: true }); +``` + +## 第二步:标记可能出现共享引用或循环引用的字段 + +对于每个值可能被共享或形成循环的字段,都需要在字段 schema 上调用 `.setTrackingRef(true)`: + +```ts +const nodeType = Type.struct("example.node", { + value: Type.string(), + next: Type.struct("example.node").setNullable(true).setTrackingRef(true), +}); +``` + +全局开关和字段级开关必须**同时**启用。缺少任何一个,值都会被复制,而不是按引用恢复。 + +## 循环自引用示例 + +```ts +import Fory, { Type } from "@apache-fory/core"; + +const nodeType = Type.struct("example.node", { + name: Type.string(), + selfRef: Type.struct("example.node").setNullable(true).setTrackingRef(true), +}); + +const fory = new Fory({ ref: true }); +const { serialize, deserialize } = fory.register(nodeType); + +const node: any = { name: "root", selfRef: null }; +node.selfRef = node; + +const copy = deserialize(serialize(node)); +console.log(copy.selfRef === copy); // true +``` + +## 共享嵌套引用示例 + +```ts +const innerType = Type.struct(501, { + value: Type.string(), +}); + +const outerType = Type.struct(502, { + left: Type.struct(501).setNullable(true).setTrackingRef(true), + right: Type.struct(501).setNullable(true).setTrackingRef(true), +}); + +const fory = new Fory({ ref: true }); +const { serialize, deserialize } = fory.register(outerType); + +const shared = { value: "same-object" }; +const copy = deserialize(serialize({ left: shared, right: shared })); +console.log(copy.left === copy.right); // true +``` + +## 何时启用 + +以下情况建议启用引用跟踪: + +- 同一个对象实例会被多个字段重复引用 +- 你的对象图可能存在环 +- 反序列化后对象身份是否保持一致很重要 + +以下情况建议关闭: + +- 数据是普通树结构 +- 你希望获得最低开销 +- 对象身份并不重要 + +## 跨语言说明 + +引用跟踪是 Fory 二进制协议的一部分,并且可以跨运行时工作。为了让行为一致,两端都必须启用引用跟踪,并把相同字段标记为引用跟踪字段。 + +## 相关主题 + +- [基础序列化](basic-serialization.md) +- [Schema 演进](schema-evolution.md) +- [跨语言](xlang-serialization.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/javascript/schema-evolution.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/javascript/schema-evolution.md new file mode 100644 index 00000000000..49857044801 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/javascript/schema-evolution.md @@ -0,0 +1,99 @@ +--- +title: Schema 演进 +sidebar_position: 60 +id: schema_evolution +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. +--- + +Schema 演进允许不同版本的服务安全地交换消息。也就是说,v2 写入端生成的消息,v1 读取端仍然可以理解,反之亦然。 + +## 两种模式 + +- **Schema-consistent 模式**(默认):更紧凑,但双方必须拥有完全相同的 schema。适合所有服务统一升级的场景。 +- **兼容模式**:会写入额外的字段元信息,使读取端能够跳过未知字段并容忍缺失字段。适合独立部署或滚动升级场景。 + +## 启用兼容模式 + +```ts +const fory = new Fory({ compatible: true }); +``` + +以下情况建议使用: + +- 服务会独立发布 schema 变更 +- 较旧的读取端可能会看到较新的载荷 +- 较新的读取端可能会看到字段尚未添加之前产生的旧载荷 + +## 示例 + +写入端 schema: + +```ts +const writerType = Type.struct( + { typeId: 1001 }, + { + name: Type.string(), + age: Type.int32(), + }, +); +``` + +字段更少的读取端 schema: + +```ts +const readerType = Type.struct( + { typeId: 1001 }, + { + name: Type.string(), + }, +); +``` + +启用 `compatible: true` 后,读取端会忽略自己不认识的字段,并为未知字段填充默认值。 + +## 为单个 Struct 关闭演进 + +即使实例启用了 `compatible: true`,你仍然可以为某个特定 struct 关闭演进元信息: + +```ts +const fixedType = Type.struct( + { typeId: 1002, evolving: false }, + { + name: Type.string(), + }, +); +``` + +`evolving: false` 会让该 struct 的消息更小,但**写入端和读取端必须在这个设置上保持一致**。如果一侧以 `evolving: false` 写入,而另一侧按兼容元信息读取,反序列化将失败。 + +## 何时使用每种模式 + +| | Schema-consistent | Compatible | +| ------------------------------- | ----------------- | ------------------- | +| 服务总是同时更新 | ✔ 最佳选择 | 可用,但有浪费 | +| 独立部署 | 会出错 | ✔ 最佳选择 | +| 需要尽可能小的消息 | ✔ | 稍大一些 | +| 滚动升级 | 有风险 | ✔ 安全 | + +## 跨语言要求 + +兼容模式只能帮你处理类型**字段**层面的 schema 差异。对于类型标识本身,你仍然需要在各端保持一致,即相同的数值 ID 或相同的 `namespace + typeName`。参见 [跨语言](xlang-serialization.md)。 + +## 相关主题 + +- [类型注册](type-registration.md) +- [跨语言](xlang-serialization.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/javascript/schema-metadata.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/javascript/schema-metadata.md new file mode 100644 index 00000000000..8645db0c7ab --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/javascript/schema-metadata.md @@ -0,0 +1,156 @@ +--- +title: Schema 元信息 +sidebar_position: 35 +id: schema_metadata +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. +--- + +JavaScript schema 元信息通过 `Type.*` 构建器或 TypeScript 装饰器声明。元信息定义类型身份、字段类型、可空性、引用跟踪、动态字段以及每个 struct 的 Schema 演进行为。 + +## 类型身份 + +Struct 和 enum 可以使用数字 ID,也可以使用 namespace/name 对。请为一个类型选择一种身份策略,并在所有读写该载荷的运行时中保持一致。 + +```ts +import { Type } from "@apache-fory/core"; + +const byId = Type.struct( + { typeId: 1001 }, + { + id: Type.int64(), + name: Type.string(), + }, +); + +const byName = Type.struct( + { namespace: "example", typeName: "user" }, + { + id: Type.int64(), + name: Type.string(), + }, +); +``` + +## 装饰器元信息 + +装饰器会把 schema 放在 TypeScript 类声明旁边: + +```ts +@Type.struct({ typeName: "example.user" }) +class User { + @Type.int64() + id!: bigint; + + @Type.string() + name!: string; +} +``` + +装饰器元信息等价于通过 `fory.register(...)` 注册的构建器元信息。 + +## 字段类型 + +使用显式的标量构建器来获得稳定契约: + +```ts +Type.int8(); +Type.int16(); +Type.int32(); +Type.int64(); // JavaScript 值是 bigint +Type.uint32(); +Type.uint64(); // JavaScript 值是 bigint +Type.float16(); +Type.bfloat16(); +Type.float32(); +Type.float64(); +Type.string(); +Type.binary(); +``` + +使用集合构建器描述嵌套值: + +```ts +Type.list(Type.string()); +Type.map(Type.string(), Type.int32()); +Type.set(Type.string()); +Type.int32Array(); +Type.float64Array(); +``` + +## 可空性 + +字段默认不可空,除非 schema 另有声明: + +```ts +const userType = Type.struct("example.user", { + name: Type.string(), + email: Type.string().setNullable(true), +}); +``` + +向不可空字段传入 `null` 会抛出异常。 + +## 引用跟踪 + +当同一个对象实例可能出现在多个字段中,或者对象图可能形成循环时,请启用全局引用跟踪,并标记需要引用跟踪的字段: + +```ts +import Fory, { Type } from "@apache-fory/core"; + +const fory = new Fory({ ref: true }); + +const nodeType = Type.struct("example.node", { + next: Type.struct("example.node").setNullable(true).setTrackingRef(true), +}); +``` + +除非同时设置了 `new Fory({ ref: true })`,否则字段级引用元信息不会生效。 + +## 动态字段 + +当字段在运行时可以保存不同的 Fory 值时,使用 `Type.any()`: + +```ts +const eventType = Type.struct("example.event", { + kind: Type.string(), + payload: Type.any(), +}); +``` + +对于有声明类型的 struct 字段,`.setDynamic(Dynamic.FALSE)` 始终按声明类型处理值,`.setDynamic(Dynamic.TRUE)` 始终写入运行时类型。默认的 `Dynamic.AUTO` 适用于大多数字段。 + +## 每个 Struct 的 Schema 演进 + +JavaScript 默认使用兼容 Schema 演进。对于应省略演进元信息的稳定 struct,设置 `evolving: false`: + +```ts +const fixedType = Type.struct( + { typeId: 1002, evolving: false }, + { + name: Type.string(), + }, +); +``` + +写入端和读取端都必须约定使用 `evolving: false`。 + +## 相关主题 + +- [配置](configuration.md) +- [类型注册](type-registration.md) +- [支持的类型](supported-types.md) +- [Schema 演进](schema-evolution.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/javascript/supported-types.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/javascript/supported-types.md new file mode 100644 index 00000000000..9122646461d --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/javascript/supported-types.md @@ -0,0 +1,177 @@ +--- +title: 支持的类型 +sidebar_position: 40 +id: supported_types +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. +--- + +本页列出 Fory 支持的 JavaScript 和 TypeScript 类型,并说明在跨语言兼容场景下何时需要对类型选择保持明确和谨慎。 + +## 原始类型与标量类型 + +| JavaScript 值 | Fory schema | 说明 | +| ------------------ | ----------------------------------------------------------------------------------- | ---------------------------------------------- | +| `boolean` | `Type.bool()` | | +| `number` | `Type.int8()` / `Type.int16()` / `Type.int32()` / `Type.float32()` / `Type.float64()` | 选择与对端语言一致的位宽 | +| `bigint` | `Type.int64()` / `Type.varInt64()` / `Type.uint64()` | 64 位整数应使用 `bigint` | +| `string` | `Type.string()` | | +| `Uint8Array` | `Type.binary()` | 二进制 blob | +| `Date` | `Type.timestamp()` | 序列化/反序列化结果均为 `Date` | +| `Date` | `Type.date()` | 只包含日期,不包含时间;反序列化结果为 `Date` | +| duration(毫秒) | `Type.duration()` | 在 JavaScript 中暴露为毫秒数 | +| `number` | `Type.float16()` | 半精度浮点数 | +| `BFloat16` / `number` | `Type.bfloat16()` | 反序列化结果为 `BFloat16` | + +## 整数类型 + +JavaScript 的 `number` 是 64 位浮点数,无法安全表示所有 64 位整数,超过 `Number.MAX_SAFE_INTEGER` 的整数会丢失精度。请使用显式 schema,使其与对端语言期望的位宽一致: + +```ts +Type.int8(); // -128 to 127 +Type.int16(); // -32,768 to 32,767 +Type.int32(); // matches Java int, Go int32, C# int +Type.varInt32(); // variable-length encoding; smaller for small values +Type.int64(); // use with bigint; matches Java long, Go int64 +Type.varInt64(); +Type.sliInt64(); +Type.uint8(); +Type.uint16(); +Type.uint32(); +Type.varUInt32(); +Type.uint64(); // use with bigint +Type.varUInt64(); +Type.taggedUInt64(); +``` + +**经验法则**:凡是在其他语言中映射为 64 位整数的值,在 JavaScript 侧都应使用 `Type.int64()` 或 `Type.uint64()`,并以 `bigint` 形式传入。 + +## 浮点类型 + +```ts +Type.float16(); +Type.float32(); +Type.float64(); +Type.bfloat16(); +``` + +当需要与使用低精度数值格式的运行时或载荷互操作时,`float16` 和 `bfloat16` 会很有用。 + +## 数组与 Typed Array + +### 通用数组 + +```ts +Type.array(Type.string()); +Type.array( + Type.struct("example.item", { + id: Type.int64(), + }), +); +``` + +它们会映射为 JavaScript 数组。 + +## 优化过的数值数组 + +对于数值数组,请使用专门的 typed array schema。它们更紧凑,并且会映射到原生 typed array: + +```ts +Type.boolArray(); // boolean[] in JS +Type.int16Array(); // Int16Array +Type.int32Array(); // Int32Array +Type.int64Array(); // BigInt64Array +Type.float32Array(); // Float32Array +Type.float64Array(); // Float64Array +Type.float16Array(); // number[] +Type.bfloat16Array(); // BFloat16[] +``` + +对于非数值数组或 struct 数组,应改用 `Type.array(elementType)`。 + +## Map 与 Set + +```ts +Type.map(Type.string(), Type.int32()); +Type.set(Type.string()); +``` + +它们会映射为 JavaScript `Map` 和 `Set`。 + +## Struct + +```ts +Type.struct("example.user", { + id: Type.int64(), + name: Type.string(), + tags: Type.array(Type.string()), +}); +``` + +Struct 可以以内联方式声明,也可以通过 decorator 声明,或者嵌套在其他 schema 中。 + +## 枚举 + +```ts +Type.enum("example.color", { + Red: 1, + Green: 2, + Blue: 3, +}); +``` + +Fory 按对象中的 ordinal position 编码枚举值,而不是按它们的取值进行编码。两端都必须以相同顺序声明枚举成员。与其他语言互操作时,必须保证成员顺序一致,而不仅仅是值相同。 + +## 可空字段 + +当字段可能为 `null` 时,请使用 `.setNullable(true)`。 + +```ts +Type.string().setNullable(true); +``` + +## 动态字段 + +当字段在运行时可能承载不同类型的值时,请使用 `Type.any()`。 + +```ts +const eventType = Type.struct("example.event", { + kind: Type.string(), + payload: Type.any(), +}); +``` + +如果字段类型是已知的,应优先使用显式字段 schema,因为 `Type.any()` 更难在不同语言间保持对齐。 + +## 引用跟踪字段 + +当同一个对象实例可能出现在多个字段中,或者你的对象图存在循环时,应为对应字段单独启用引用跟踪: + +```ts +Type.struct("example.node").setTrackingRef(true).setNullable(true); +``` + +这需要同时配置 `new Fory({ ref: true })`。参见 [引用](references.md)。 + +## 扩展类型 + +对于需要完全自定义编码的类型,可以使用 `Type.ext(...)`,并向 `fory.register(...)` 传入自定义序列化器。这属于高级用法;大多数场景下,标准的 `Type.struct` 已经足够。 + +## 相关主题 + +- [基础序列化](basic-serialization.md) +- [引用](references.md) +- [跨语言](xlang-serialization.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/javascript/troubleshooting.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/javascript/troubleshooting.md new file mode 100644 index 00000000000..1eabe8c0c94 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/javascript/troubleshooting.md @@ -0,0 +1,105 @@ +--- +title: 故障排查 +sidebar_position: 90 +id: troubleshooting +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. +--- + +本页介绍使用 Fory JavaScript 时常见的问题。 + +## 无法反序列化非跨语言载荷 + +Fory JavaScript 运行时只能读取 Fory 的跨语言载荷。如果生产端是 Java 或 Go 服务,并且使用的是语言原生格式,那么 JavaScript 侧无法解码。 + +修复方式:把生产端切换到跨语言模式。Java 请使用 `.withLanguage(Language.XLANG)`,Go 请使用 `WithXlang(true)`。 + +## `maxDepth must be an integer >= 2` + +这表示你传入了无效的 `maxDepth` 值。它必须是大于等于 2 的正整数。 + +```ts +new Fory({ maxDepth: 100 }); +``` + +只有当你的数据确实存在很深的嵌套时,才应提高这个值。 + +## `Binary size ... exceeds maxBinarySize` + +某个二进制字段或整条消息超过了安全限制。如果这个大小符合预期,且数据源可信,可以提高限制: + +```ts +new Fory({ maxBinarySize: 128 * 1024 * 1024 }); +``` + +## `Collection size ... exceeds maxCollectionSize` + +某个 list、set 或 map 的元素数量超过了配置上限。这通常意味着数据异常地大。如果这是合法场景,可以提高限制: + +```ts +new Fory({ maxCollectionSize: 2_000_000 }); +``` + +## `Field "..." is not nullable` + +你向一个未声明为可空的字段传入了 `null`。修复方式:在字段 schema 上添加 `.setNullable(true)`: + +```ts +const userType = Type.struct("example.user", { + name: Type.string(), + email: Type.string().setNullable(true), // ← this field can be null +}); +``` + +## 反序列化后对象不是同一个实例 + +默认情况下,Fory 不保留对象身份。两个字段如果指向同一个对象,反序列化后会变成两个彼此独立的副本。 + +修复方式:同时启用以下**两个**开关: + +1. 在实例上配置 `new Fory({ ref: true })` +2. 在具体字段上调用 `.setTrackingRef(true)` + +参见 [引用](references.md)。 + +## 大整数会以 `bigint` 返回 + +这是预期行为。对于任何 64 位整数字段,例如 `Type.int64()`、`Type.uint64()`,Fory 都会使用 `bigint`。如果你确实需要 `number`,请使用更小的整数类型,比如 `Type.int32()`,但前提是该值确实能装进 32 位。 + +## 查看生成的序列化器代码 + +如果你需要排查 Fory 在底层生成了什么,可以通过 hook 查看生成后的序列化器代码: + +```ts +const fory = new Fory({ + hooks: { + afterCodeGenerated(code) { + console.log(code); + return code; + }, + }, +}); +``` + +## `@apache-fory/hps` 安装失败 + +`@apache-fory/hps` 是一个可选的 Node.js 加速模块。如果它安装失败,例如当前平台不支持原生模块,只需把它从依赖中移除即可。即使没有它,Fory 也能正常工作。 + +## 相关主题 + +- [基础序列化](basic-serialization.md) +- [引用](references.md) +- [跨语言](xlang-serialization.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/javascript/type-registration.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/javascript/type-registration.md new file mode 100644 index 00000000000..37bf32dcaf4 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/javascript/type-registration.md @@ -0,0 +1,200 @@ +--- +title: 类型注册 +sidebar_position: 30 +id: type_registration +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. +--- + +你要序列化的每个 struct 和 enum,在使用前都必须先注册到 `Fory` 实例中。注册会告诉 Fory:如何在消息中标识该类型,以及如何对其进行编码和解码。 + +## 注册 Struct + +你可以使用数值 ID 或名称来标识一个 struct。选择一种策略,并在所有共享这类消息的语言中保持一致。 + +### 按数值 ID 注册 + +编码更紧凑。当团队规模较小、可以协调 ID 分配时,这是很好的选择。 + +```ts +const userType = Type.struct( + { typeId: 1001 }, + { + id: Type.int64(), + name: Type.string(), + }, +); + +const fory = new Fory(); +const { serialize, deserialize } = fory.register(userType); +``` + +所有读写该类型的运行时都必须使用同一个数值。 + +### 按名称注册 + +更容易跨团队协同,但消息中的元信息会稍大一些。 + +```ts +const userType = Type.struct( + { typeName: "example.user" }, + { + id: Type.int64(), + name: Type.string(), + }, +); + +const fory = new Fory(); +const { serialize, deserialize } = fory.register(userType); +``` + +你也可以显式拆分 `namespace` 和类型名: + +```ts +const userType = Type.struct( + { namespace: "example", typeName: "user" }, + { + id: Type.int64(), + name: Type.string(), + }, +); +``` + +> **同一个类型不要在不同运行时中混用两种策略。** 如果一侧使用数值 ID,另一侧使用名称,反序列化会失败。 + +## 使用 Decorator 注册 + +```ts +@Type.struct({ typeId: 1001 }) +class User { + @Type.int64() + id!: bigint; + + @Type.string() + name!: string; +} + +const fory = new Fory(); +const { serialize, deserialize } = fory.register(User); +``` + +当你希望 TypeScript 类声明与 schema 定义放在一起时,基于 decorator 的注册会很方便。 + +## 注册 Enum + +Fory JavaScript 同时支持普通 JavaScript 风格的枚举对象和 TypeScript enum。 + +### JavaScript 对象枚举 + +```ts +const Color = { + Red: 1, + Green: 2, + Blue: 3, +}; + +const fory = new Fory(); +const colorSerde = fory.register(Type.enum("example.color", Color)); +``` + +### TypeScript enum + +```ts +enum Status { + Pending = "pending", + Active = "active", +} + +const fory = new Fory(); +fory.register(Type.enum("example.status", Status)); +``` + +## 注册作用域 + +注册是以 `Fory` 实例为作用域的。如果你创建了两个实例,就需要在两个实例中都注册 schema。 + +## `register` 的返回值 + +`fory.register(schema)` 会返回一个绑定后的序列化器对: + +```ts +const { serialize, deserialize } = fory.register(orderType); + +// serialize returns Uint8Array bytes +const bytes = serialize({ id: 1n, total: 99.99 }); + +// deserialize returns the decoded value +const order = deserialize(bytes); +``` + +把这个返回对保存起来并重复复用,它就是性能最优的调用路径。 + +## 字段选项 + +### 可空字段 + +如果字段可能为 `null`,请显式标记。向不可空字段传入 `null` 会抛出异常。 + +```ts +Type.string().setNullable(true); +``` + +### 字段上的引用跟踪 + +当同一个对象实例可能出现在多个字段中时,需要启用字段级引用跟踪,详见 [引用](references.md): + +```ts +Type.struct("example.node").setTrackingRef(true); +``` + +只有在同时设置了 `new Fory({ ref: true })` 时,这个选项才会生效。 + +### 多态字段 + +当字段在运行时可能承载不同类型的值时,可以使用 `Type.any()`: + +```ts +const eventType = Type.struct("example.event", { + kind: Type.string(), + payload: Type.any(), +}); +``` + +如果你需要更细粒度地控制某个 struct 字段如何处理运行时类型,可以调用 `.setDynamic(Dynamic.FALSE)`,表示始终按声明类型处理;或者调用 `.setDynamic(Dynamic.TRUE)`,表示始终写入运行时类型。默认值 `Dynamic.AUTO` 适用于绝大多数场景。 + +## 如何选择 ID 与名称 + +以下情况适合使用**数值 ID**: + +- 你希望消息尽可能小 +- 你的组织能够保证 ID 稳定且全局唯一 +- 服务之间协同非常紧密 + +以下情况适合使用**名称**: + +- 不同团队独立定义类型 +- schema 本身已经通过 package/module name 标识 +- 可以接受稍大的元信息开销 + +## 跨语言 + +如果要让消息在 JavaScript 与其他运行时之间往返,双方必须对某个类型使用相同的类型标识:相同的数值 ID,或相同的 `namespace + typeName`。参见 [跨语言](xlang-serialization.md)。 + +## 相关主题 + +- [基础序列化](basic-serialization.md) +- [Schema 演进](schema-evolution.md) +- [跨语言](xlang-serialization.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/javascript/xlang-serialization.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/javascript/xlang-serialization.md new file mode 100644 index 00000000000..15a6460525e --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/javascript/xlang-serialization.md @@ -0,0 +1,128 @@ +--- +title: Xlang 序列化 +sidebar_position: 2 +id: xlang_serialization +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. +--- + +Fory JavaScript 与 Java、Python、Go、Rust、Swift 和 C++ 的 Fory 运行时使用相同的二进制格式进行序列化。你可以在 JavaScript 中写入消息,再在 Java 中读取它,反过来也一样,无需额外的转换层。 + +需要注意: + +- Fory JavaScript 运行时只读写跨语言载荷,不支持任何语言原生格式。 +- 当前暂不支持 out-of-band mode。 + +## 成功完成往返的要求 + +要让一条消息能在 JavaScript 与另一种运行时之间稳定往返,双方必须满足: + +1. 两端具有**相同的类型标识**,即相同的数值 ID,或相同的 `namespace + typeName` +2. **字段类型兼容**,例如 JavaScript 中的 `Type.int32()` 字段应对应 Java `int`、Go `int32`、C# `int` +3. **可空性一致**,如果一侧把字段标记为可空,另一侧也应如此 +4. 如果使用 Schema 演进,双方的 `compatible` 模式必须一致 +5. 如果数据包含共享引用或循环引用,双方的引用跟踪配置也必须一致 + +## 分步说明:从 JavaScript 到其他运行时 + +1. 在 JavaScript 中使用与其他运行时相同的类型名称或数值 ID 定义 schema +2. 在两端都注册该 schema +3. 对齐字段类型、可空性和 `compatible` 设置 +4. 在发布前对真实载荷做端到端测试 + +JavaScript 侧: + +```ts +import Fory, { Type } from "@apache-fory/core"; + +const messageType = Type.struct( + { typeName: "example.message" }, + { + id: Type.int64(), + content: Type.string(), + }, +); + +const fory = new Fory(); +const { serialize } = fory.register(messageType); + +const bytes = serialize({ + id: 1n, + content: "hello from JavaScript", +}); +``` + +在另一侧,请使用对应运行时的 API 注册同一个 `example.message` 类型,即相同的名称或相同的数值 ID: + +- [Java guide](../java/index.md) +- [Python guide](../python/index.md) +- [Go guide](../go/index.md) +- [Rust guide](../rust/index.md) + +## 字段命名 + +Fory 按字段名匹配字段。当模型在多种语言中定义时,应保持字段名一致,或者至少采用能够在不同语言间无歧义映射的命名方案,例如统一使用 `snake_case`。 + +当使用 `compatible: true` 进行 Schema 演进时,字段顺序的差异是允许的,但字段名本身仍必须一致。 + +## 数值类型 + +JavaScript 的 `number` 是 64 位浮点数,无法与其他语言中的所有整数类型一一对应。因此应使用显式 schema 类型: + +- `Type.int32()`:用于 32 位整数,对应 Java `int`、Go `int32`、C# `int` +- `Type.int64()`:配合 `bigint` 值使用,用于 64 位整数,对应 Java `long`、Go `int64` +- `Type.float32()` 或 `Type.float64()`:用于浮点数 + +## 日期与时间 + +- `Type.timestamp()`:表示一个时间点;往返后仍是 JavaScript `Date` +- `Type.date()`:表示不带时间的日期;反序列化结果为 `Date` +- `Type.duration()`:在 JavaScript 中暴露为毫秒数 + +## 多态字段 + +`Type.any()` 允许字段在运行时承载不同类型的值,但它在跨语言场景中更难保持一致。只要可能,就应优先使用显式字段 schema。 + +```ts +const wrapperType = Type.struct( + { typeId: 3001 }, + { + payload: Type.any(), + }, +); +``` + +## 枚举 + +枚举成员的**顺序**必须在不同语言间保持一致。Fory 按 ordinal position 而不是按枚举值对枚举进行编码。 + +```ts +const Color = { Red: 1, Green: 2, Blue: 3 }; +const fory = new Fory(); +fory.register(Type.enum({ typeId: 210 }, Color)); +``` + +在每个对端运行时中都应使用相同的类型 ID 或类型名。 + +## 安全限制 + +`maxDepth`、`maxBinarySize` 和 `maxCollectionSize` 这些选项用于保护 JavaScript 运行时,防止接收过大载荷。它们不会改变二进制格式,只决定本地运行时愿意接受什么样的数据。 + +## 相关主题 + +- [支持的类型](supported-types.md) +- [Schema 演进](schema-evolution.md) +- [Xlang 序列化规范](../../specification/xlang_serialization_spec.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/kotlin/_category_.json b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/kotlin/_category_.json new file mode 100644 index 00000000000..18540b48012 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/kotlin/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "Kotlin", + "position": 11, + "collapsible": true, + "collapsed": true +} diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/kotlin/android-support.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/kotlin/android-support.md new file mode 100644 index 00000000000..a4b20ee04af --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/kotlin/android-support.md @@ -0,0 +1,110 @@ +--- +title: Android 支持 +sidebar_position: 6 +id: android_support +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. +--- + +Apache Fory Kotlin 支持 Kotlin/JVM 和 Android。Android 支持建立在现有 Fory +Java 运行时以及 `fory-kotlin` 提供的 Kotlin 运行时序列化器之上。Kotlin schema +序列化器由 `fory-kotlin-ksp` 在构建时生成。 + +本页说明 Android 设置和 release 构建约束。Kotlin KSP 序列化器模型本身请参见 +[静态生成序列化器](static-generated-serializers.md)。如果你的 Android 项目也包含 +Java `@ForyStruct` 类,请使用 +[Java 静态生成序列化器](../java/static-generated-serializers.md)中记录的 Java +注解处理器。 + +## 依赖 + +将 `fory-kotlin` 添加到使用 Fory 的 Android 模块。将 `fory-kotlin-ksp` 添加到 +编译 Kotlin `@ForyStruct` 模型类的模块。 + +```kotlin +plugins { + id("com.android.application") + id("org.jetbrains.kotlin.android") + id("com.google.devtools.ksp") +} + +dependencies { + implementation("org.apache.fory:fory-kotlin:") + ksp("org.apache.fory:fory-kotlin-ksp:") +} +``` + +对于 Android 库模块,请在拥有这些带注解 Kotlin 类的库模块中应用 KSP。生成的 +序列化器和生成的 consumer R8 规则必须随该库产物一起打包。 + +## 运行时设置 + +使用 `ForyKotlin.builder().withXlang(true)` 创建运行时,然后通过 Kotlin +`register` 扩展或普通 Fory Java 注册 API 注册应用类。 + +```kotlin +import org.apache.fory.kotlin.ForyKotlin +import org.apache.fory.kotlin.register + +val fory = ForyKotlin.builder() + .withXlang(true) + .requireClassRegistration(true) + .build() + +fory.register("example", "User") +``` + +不要在应用代码中引用生成的序列化器类。运行时会从已注册的目标类解析生成的 +序列化器。 + +## Xlang Schema 模式 + +参与 Fory 跨语言 schema 序列化的 Android Kotlin 结构体应使用 KSP 生成序列化器。 +生成序列化器会避免把运行时反射作为 Kotlin schema 元数据来源,并调用与其他 +生成序列化器相同的 Fory Java 运行时基础设施。 + +Kotlin KSP 生成序列化器只用于 xlang/schema 序列化。它们不会替代 Java 原生对象 +序列化器,也不会保留具体 JVM 集合实现身份。例如,Kotlin `List` 字段的 +schema 是 `list`;反序列化只保证得到的值可以赋给声明的字段类型。 + +## Minified Release 构建 + +请使用 minified release 构建验证 Fory Android 行为。Debug 构建不能证明生成的 +序列化器、生成的构造函数入口点或 Kotlin 元数据能在 R8 后保留下来。 + +KSP 会在 `META-INF/proguard/` 下输出生成的 consumer R8/ProGuard 规则,用于 +Fory 所需的生成序列化器构造函数和 Kotlin 元数据。Android 应用不应需要为生成的 +Kotlin 序列化器手写宽泛 keep 规则。如果自定义打包设置丢弃了生成的 +`META-INF/proguard/` 资源,应修复打包路径,而不是为每个生成序列化器添加宽泛 +keep 规则。 + +Apache Fory 仓库通过 `integration_tests/android_tests` 验证这条路径,其中包括 +release-minified instrumented tests。 + +## Android 应用中的 Java 模型 + +Kotlin KSP 只处理 Kotlin 源码。如果 Android 应用包含带 `@ForyStruct` 注解的 +Java 类,请为这些 Java 源码配置 Java `fory-annotation-processor`。 + +当 Java 模型类在嵌套类型上使用 Fory 类型使用位置注解(例如 +`List<@UInt8Type Integer>`)时,静态生成的 Java 序列化器在 Android 上也很重要。 +这条路径请参见 +[Java 静态生成序列化器](../java/static-generated-serializers.md)。 + +## 不支持的目标 + +`fory-kotlin` 和 `fory-kotlin-ksp` 只面向 Kotlin/JVM 和 Android。不支持 +Kotlin/Native 和 Kotlin/JS。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/kotlin/configuration.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/kotlin/configuration.md new file mode 100644 index 00000000000..1586775c3bd --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/kotlin/configuration.md @@ -0,0 +1,139 @@ +--- +title: 配置 +sidebar_position: 1 +id: configuration +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. +--- + +本页介绍 Kotlin 专属运行时配置和 Fory 实例创建。 + +## Xlang 设置 + +Fory Kotlin 遵循 Java builder 默认值:启用 xlang 模式和兼容 Schema 演进。 +跨语言 Kotlin 载荷、Schema IDL 生成的 Kotlin 模型,以及 KSP 生成的 xlang +序列化器都应使用这条路径。 + +```kotlin +import org.apache.fory.kotlin.ForyKotlin + +val fory = ForyKotlin.builder() + .withXlang(true) + .requireClassRegistration(true) + .build() +``` + +## Native 模式设置 + +对于需要原生 JVM 对象行为的同语言 Kotlin/JVM 载荷,请显式使用 native 模式: + +```kotlin +import org.apache.fory.kotlin.ForyKotlin + +val fory = ForyKotlin.builder().withXlang(false) + .requireClassRegistration(true) + .build() +``` + +## 线程安全 + +创建 Fory 实例成本不低。实例应在多次序列化之间共享。 + +### 单线程用法 + +```kotlin +import org.apache.fory.Fory +import org.apache.fory.kotlin.ForyKotlin + +object ForyHolder { + val fory: Fory = ForyKotlin.builder() + .withXlang(true) + .requireClassRegistration(true) + .build() +} +``` + +### 多线程用法 + +对于多线程应用,请使用 `ThreadSafeFory`: + +```kotlin +import org.apache.fory.ThreadSafeFory +import org.apache.fory.kotlin.ForyKotlin + +object ForyHolder { + val fory: ThreadSafeFory = ForyKotlin.builder() + .withXlang(true) + .requireClassRegistration(true) + .buildThreadSafeFory() +} +``` + +### 使用 Builder 方法 + +```kotlin +// Thread-safe Fory +val fory: ThreadSafeFory = ForyKotlin.builder() + .withXlang(true) + .requireClassRegistration(true) + .buildThreadSafeFory() +``` + +## 配置 + +Fory Java 的所有配置选项都可用。完整列表请参见 +[Java 配置](../java/configuration.md)。 + +Kotlin native 模式载荷的常见选项: + +```kotlin +import org.apache.fory.kotlin.ForyKotlin + +val fory = ForyKotlin.builder().withXlang(false) + // Enable reference tracking for circular references + .withRefTracking(true) + // Enable schema evolution support for native-mode payloads + .withCompatible(true) + // Bound remote schema metadata resource usage + .withMaxTypeFields(512) + .withMaxTypeMetaBytes(4096) + // Enable async compilation for better startup performance + .withAsyncCompilation(true) + // Compression options + .withIntCompressed(true) + .withLongCompressed(true) + .build() +``` + +## 安全 + +生产环境以及任何不受信任的 payload 来源都应保持启用类注册: + +```kotlin +val fory = ForyKotlin.builder() + .requireClassRegistration(true) + .withMaxDepth(50) + .withMaxTypeFields(512) + .withMaxTypeMetaBytes(4096) + .build() +``` + +安全相关配置: + +- 保持 `requireClassRegistration(true)`,并注册应用类或生成的 module。 +- 使用 `withMaxDepth(...)` 拒绝异常深的对象图。 +- 除非数据不是恶意输入,且可信 peer 会发送更大的 metadata 或大量 schema 版本,否则保持 `withMaxTypeFields(...)`、`withMaxTypeMetaBytes(...)` 以及远端 schema-version 限制的默认值。 +- Allow-listing 和 unknown-class 控制请遵循 [Java 配置](../java/configuration.md#forybuilder-选项)。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/kotlin/default-values.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/kotlin/default-values.md new file mode 100644 index 00000000000..29120268ea3 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/kotlin/default-values.md @@ -0,0 +1,137 @@ +--- +title: 默认值 +sidebar_position: 3 +id: default_values +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. +--- + +在使用兼容模式时,Fory 支持在反序列化期间处理 Kotlin 数据类的默认值。此特性使得数据类 schema 演化时能够保持前向/后向兼容性。 + +## 概述 + +当 Kotlin 数据类具有带默认值的参数时,Fory 可以: + +1. **检测默认值**:使用 Kotlin 反射 +2. **应用默认值**:当序列化数据中缺少字段时,在反序列化期间应用默认值 +3. **支持 schema 演化**:允许添加带默认值的新字段,而不会破坏现有的序列化数据 + +## 用法 + +在以下情况下,此特性会自动启用: + +- 启用了兼容模式(`withCompatibleMode(CompatibleMode.COMPATIBLE)`) +- 已注册 Kotlin 序列化器(`KotlinSerializers.registerSerializers(fory)`) +- 序列化数据中缺少某个字段,但目标类中该字段存在并具有默认值 + +## 示例 + +```kotlin +import org.apache.fory.Fory +import org.apache.fory.config.CompatibleMode +import org.apache.fory.serializer.kotlin.KotlinSerializers + +// 原始数据类 +data class User(val name: String, val age: Int) + +// 演化后的数据类,带有新字段和默认值 +data class UserV2(val name: String, val age: Int, val email: String = "default@example.com") + +fun main() { + val fory = Fory.builder() + .withCompatibleMode(CompatibleMode.COMPATIBLE) + .build() + KotlinSerializers.registerSerializers(fory) + fory.register(User::class.java) + fory.register(UserV2::class.java) + + // 使用旧 schema 序列化 + val oldUser = User("John", 30) + val serialized = fory.serialize(oldUser) + + // 使用新 schema 反序列化 - 缺失的字段获得默认值 + val newUser = fory.deserialize(serialized) as UserV2 + println(newUser) // UserV2(name=John, age=30, email=default@example.com) +} +``` + +## 支持的默认值类型 + +以下类型支持默认值: + +- **原始类型**:`Int`、`Long`、`Double`、`Float`、`Boolean`、`Byte`、`Short`、`Char` +- **无符号类型**:`UInt`、`ULong`、`UByte`、`UShort` +- **字符串**:`String` +- **集合**:`List`、`Set`、`Map`(带默认实例) +- **自定义对象**:任何可以通过反射实例化的对象 + +## 复杂的默认值 + +默认值可以是复杂的表达式: + +```kotlin +data class ConfigV1(val name: String) + +data class ConfigV2( + val name: String, + val settings: Map = mapOf("default" to "value"), + val tags: List = listOf("default"), + val enabled: Boolean = true, + val retryCount: Int = 3 +) + +val fory = Fory.builder() + .withCompatibleMode(CompatibleMode.COMPATIBLE) + .build() +KotlinSerializers.registerSerializers(fory) + +val original = ConfigV1("myConfig") +val serialized = fory.serialize(original) + +val deserialized = fory.deserialize(serialized) as ConfigV2 +// deserialized.name == "myConfig" +// deserialized.settings == mapOf("default" to "value") +// deserialized.tags == listOf("default") +// deserialized.enabled == true +// deserialized.retryCount == 3 +``` + +## 可空字段与默认值 + +也支持带默认值的可空字段: + +```kotlin +data class PersonV1(val name: String) + +data class PersonV2( + val name: String, + val nickname: String? = null, + val age: Int? = null +) + +val original = PersonV1("John") +val serialized = fory.serialize(original) + +val deserialized = fory.deserialize(serialized) as PersonV2 +// deserialized.name == "John" +// deserialized.nickname == null (默认值) +// deserialized.age == null (默认值) +``` + +## 相关主题 + +- [Schema 演化](../java/schema-evolution.md) - Java 中的前向/后向兼容性 +- [Fory 创建](configuration.md) - 使用兼容模式设置 Fory diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/kotlin/grpc-support.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/kotlin/grpc-support.md new file mode 100644 index 00000000000..ce9b7b81fa5 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/kotlin/grpc-support.md @@ -0,0 +1,257 @@ +--- +title: Kotlin gRPC 支持 +sidebar_position: 6 +id: grpc_support +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. +--- + +Fory IDL 可以生成 Kotlin coroutine gRPC companion。生成的 gRPC 文件使用普通 grpc-java 和 +grpc-kotlin API,每个 request/response message 使用 Fory 序列化。 + +当 RPC 两端都由同一份 Fory IDL、protobuf IDL 或 FlatBuffers IDL 生成,并且两端都期望 +Fory 编码的 message body 时,可以使用这种模式。如果 API 必须被通用 protobuf client、 +reflection 工具或期望 protobuf message bytes 的组件消费,请使用标准 protobuf gRPC 代码生成。 + +## 添加依赖 + +在编译生成源码的应用或 service module 中添加 Fory Kotlin、KSP、grpc-java、grpc-kotlin、 +coroutines 和一个 grpc-java transport: + +```kotlin +plugins { + id("com.google.devtools.ksp") version "" +} + +dependencies { + implementation("org.apache.fory:fory-kotlin:") + ksp("org.apache.fory:fory-kotlin-ksp:") + + implementation("io.grpc:grpc-api:") + implementation("io.grpc:grpc-stub:") + implementation("io.grpc:grpc-kotlin-stub:") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:") + + runtimeOnly("io.grpc:grpc-netty-shaded:") +} +``` + +如果应用已经统一使用其他 grpc-java transport,可以替换 `grpc-netty-shaded`。生成的 Kotlin +Fory gRPC 不需要 `grpc-protobuf` 来编码 payload。 + +## 定义 Service + +Service 定义可以来自 Fory IDL、protobuf IDL 或 FlatBuffers `rpc_service`。Fory IDL service +示例如下: + +```protobuf +package demo.greeter; + +message HelloRequest { + string name = 1; +} + +message HelloReply { + string reply = 1; +} + +service Greeter { + rpc SayHello (HelloRequest) returns (HelloReply); +} +``` + +使用 `--grpc` 生成 Kotlin model 和 gRPC companion: + +```bash +foryc service.fdl --kotlin_out=./generated/kotlin --grpc +``` + +该 schema 会生成: + +| 文件 | 用途 | +| ------------------- | ------------------------------------- | +| `HelloRequest.kt` | request 的 Fory model 类型 | +| `HelloReply.kt` | response 的 Fory model 类型 | +| `ServiceForyModule` | 生成类型的 Fory 注册 module | +| `GreeterGrpcKt.kt` | Coroutine service base、stub 和 codec | + +编译生成 model 文件时需要运行 KSP,以便 runtime 可以使用 schema serializer。生成的 request 和 +response 类型由 service companion 使用的 schema module 注册,service 实现不需要手动注册 serializer。 + +## 实现 Server + +实现生成的 coroutine base class,并注册到普通 grpc-java server: + +```kotlin +import demo.greeter.GreeterGrpcKt +import demo.greeter.HelloReply +import demo.greeter.HelloRequest +import io.grpc.ServerBuilder + +class GreeterService : GreeterGrpcKt.GreeterCoroutineImplBase() { + override suspend fun sayHello(request: HelloRequest): HelloReply = + HelloReply(reply = "Hello, ${request.name}") +} + +val server = ServerBuilder + .forPort(50051) + .addService(GreeterService()) + .build() + .start() +``` + +未实现的生成方法会返回 gRPC `UNIMPLEMENTED`。Service 方法抛出的异常遵循 grpc-kotlin server 行为。 + +## 创建 Client + +从 grpc-java channel 直接构造生成的 coroutine stub: + +```kotlin +import demo.greeter.GreeterGrpcKt +import demo.greeter.HelloRequest +import io.grpc.ManagedChannelBuilder + +val channel = ManagedChannelBuilder + .forAddress("localhost", 50051) + .usePlaintext() + .build() + +val stub = GreeterGrpcKt.GreeterCoroutineStub(channel) +val reply = stub.sayHello(HelloRequest(name = "Fory")) +``` + +Channel 构造、关闭、deadline、credential、interceptor、load balancing、retry 和 server lifecycle +仍由 grpc-java/grpc-kotlin 负责。 + +## Streaming RPC + +Fory service 可以使用 gRPC 的所有 streaming shape: + +```protobuf +service Greeter { + rpc SayHello (HelloRequest) returns (HelloReply); + rpc LotsOfReplies (HelloRequest) returns (stream HelloReply); + rpc LotsOfGreetings (stream HelloRequest) returns (HelloReply); + rpc Chat (stream HelloRequest) returns (stream HelloReply); +} +``` + +Streaming RPC 使用 `kotlinx.coroutines.flow.Flow`。 + +| IDL shape | Server 方法 | Client 方法 | +| ----------------------------------------- | ----------------------------------------- | ----------------------------------------- | +| `rpc A (Req) returns (Res)` | `suspend fun a(request: Req): Res` | `suspend fun a(request: Req): Res` | +| `rpc A (Req) returns (stream Res)` | `fun a(request: Req): Flow` | `fun a(request: Req): Flow` | +| `rpc A (stream Req) returns (Res)` | `suspend fun a(requests: Flow): Res` | `suspend fun a(requests: Flow): Res` | +| `rpc A (stream Req) returns (stream Res)` | `fun a(requests: Flow): Flow` | `fun a(requests: Flow): Flow` | + +生成 method path 保留 schema 中的 service 和 method 名称,例如 `/demo.greeter.Greeter/SayHello`。 + +Server 可以直接返回或消费 `Flow`: + +```kotlin +import demo.greeter.GreeterGrpcKt +import demo.greeter.HelloReply +import demo.greeter.HelloRequest +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.toList + +class GreeterService : GreeterGrpcKt.GreeterCoroutineImplBase() { + override fun lotsOfReplies(request: HelloRequest): Flow = flow { + emit(HelloReply(reply = "Hello, ${request.name}")) + emit(HelloReply(reply = "Welcome, ${request.name}")) + } + + override suspend fun lotsOfGreetings( + requests: Flow + ): HelloReply { + val names = requests.toList().joinToString(", ") { it.name } + return HelloReply(reply = names) + } + + override fun chat(requests: Flow): Flow = + requests.map { request -> + HelloReply(reply = "Hello, ${request.name}") + } +} +``` + +生成的 client 暴露对应的 coroutine 和 `Flow` API: + +```kotlin +import demo.greeter.HelloRequest +import kotlinx.coroutines.flow.flowOf + +stub.lotsOfReplies(HelloRequest(name = "Fory")).collect { reply -> + println(reply.reply) +} + +val summary = stub.lotsOfGreetings( + flowOf( + HelloRequest(name = "Ada"), + HelloRequest(name = "Grace"), + ) +) +println(summary.reply) + +stub.chat( + flowOf( + HelloRequest(name = "Fory"), + HelloRequest(name = "RPC"), + ) +).collect { reply -> + println(reply.reply) +} +``` + +## gRPC 运行时行为 + +生成的 service code 只替换 request/response 序列化。常规 gRPC service 行为仍由 grpc-java 和 +grpc-kotlin 提供: + +- Deadline 和取消 +- TLS 和认证 +- 名称解析与负载均衡 +- Client/server interceptor +- Status code 和 metadata +- Channel 池化与生命周期管理 + +## 互操作性 + +生成的 Kotlin service companion 在 gRPC frame 中使用 Fory 二进制 payload。它可以与从同一 schema +生成的其他 Fory gRPC companion 互操作,例如 Java、Go、Python 和 Rust。通用 protobuf gRPC +client 无法解码这些 payload。 + +## 故障排查 + +### 缺少生成的 service 文件 + +同时传入 `--grpc` 和 `--kotlin_out`。没有 service 定义的 schema 只会生成 model 文件和 schema module。 + +### 运行时找不到 serializer class + +确保生成的 Kotlin model source 运行了 KSP,并且 `fory-kotlin-ksp` 与 `fory-kotlin` 使用同一个 Fory 版本。 + +### gRPC 类无法解析 + +向应用 module 添加 grpc-java 和 grpc-kotlin 依赖。Fory Kotlin artifact 不会自动添加这些依赖。 + +### Protobuf client 无法读取响应 + +Fory gRPC 使用 Fory 二进制协议 payload,不是 protobuf wire-format message。请在两端使用同一份 +schema 生成的 Fory gRPC companion。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/kotlin/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/kotlin/index.md new file mode 100644 index 00000000000..d5a9007d94a --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/kotlin/index.md @@ -0,0 +1,108 @@ +--- +title: Kotlin 序列化指南 +sidebar_position: 0 +id: serialization_index +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. +--- + +Apache Fory™ Kotlin 基于 Fory Java 构建,为 Kotlin 类型提供优化的序列化器。大多数标准 Kotlin 类型都可以直接使用默认的 Fory Java 实现,而 Fory Kotlin 则为 Kotlin 特有类型补充了额外支持。 + +支持的类型包括: + +- `data class` 序列化 +- 无符号原始类型:`UByte`、`UShort`、`UInt`、`ULong` +- 无符号数组:`UByteArray`、`UShortArray`、`UIntArray`、`ULongArray` +- 标准库类型:`Pair`、`Triple`、`Result` +- 范围:`IntRange`、`LongRange`、`CharRange` 以及各种 progression +- 集合:`ArrayDeque`、空集合(`emptyList`、`emptyMap`、`emptySet`) +- `kotlin.time.Duration`、`kotlin.text.Regex`、`kotlin.uuid.Uuid` + +## 特性 + +Fory Kotlin 继承了 Fory Java 的全部特性,并增加了 Kotlin 特定优化: + +- **高性能**:JIT 代码生成、零拷贝,性能可比传统序列化快 20 到 170 倍 +- **Kotlin 类型支持**:为数据类、无符号类型、范围和标准库类型提供优化序列化器 +- **默认值支持**:在 Schema 演进期间自动处理 Kotlin 数据类的默认参数 +- **Schema 演进**:支持类 Schema 变更时的前向和后向兼容 + +完整特性列表请参见 [Java 特性](../java/index.md#特性)。 + +## 安装 + +### Maven + +```xml + + org.apache.fory + fory-kotlin + 1.3.0 + +``` + +### Gradle + +```kotlin +implementation("org.apache.fory:fory-kotlin:1.3.0") +``` + +## 快速开始 + +```kotlin +import org.apache.fory.Fory +import org.apache.fory.ThreadSafeFory +import org.apache.fory.serializer.kotlin.KotlinSerializers + +data class Person(val name: String, val id: Long, val github: String) +data class Point(val x: Int, val y: Int, val z: Int) + +fun main() { + // 创建 Fory 实例(应复用) + val fory: ThreadSafeFory = Fory.builder() + .requireClassRegistration(true) + .buildThreadSafeFory() + + // 注册 Kotlin 序列化器 + KotlinSerializers.registerSerializers(fory) + + // 注册你的类 + fory.register(Person::class.java) + fory.register(Point::class.java) + + val p = Person("Shawn Yang", 1, "https://github.com/chaokunyang") + println(fory.deserialize(fory.serialize(p))) + println(fory.deserialize(fory.serialize(Point(1, 2, 3)))) +} +``` + +## 基于 Fory Java 构建 + +Fory Kotlin 基于 Fory Java 构建。Fory Java 中的大多数配置选项、特性和概念都可直接应用于 Kotlin。可参考 Java 文档了解: + +- [配置](../java/configuration.md) - 所有 ForyBuilder 选项 +- [基础序列化](../java/basic-serialization.md) - 序列化模式与 API +- [类型注册](../java/type-registration.md) - 类注册与安全性 +- [Schema 演进](../java/schema-evolution.md) - 前向和后向兼容 +- [自定义序列化器](../java/custom-serializers.md) - 实现自定义序列化器 +- [压缩](../java/compression.md) - Int、long 和字符串压缩 +- [故障排查](../java/troubleshooting.md) - 常见问题与解决方案 + +## Kotlin 特定文档 + +- [Fory 创建](configuration.md) - Kotlin 特定的 Fory 设置要求 +- [类型序列化](type-serialization.md) - Kotlin 类型的序列化 +- [默认值](default-values.md) - Kotlin 数据类默认值支持 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/kotlin/schema-metadata.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/kotlin/schema-metadata.md new file mode 100644 index 00000000000..f2f4bbcddee --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/kotlin/schema-metadata.md @@ -0,0 +1,122 @@ +--- +title: Schema 元数据 +sidebar_position: 3 +id: schema_metadata +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. +--- + +Kotlin schema 元数据由 KSP 生成的 xlang 序列化器使用。Schema 概念复用 Java +Fory 注解;只有在需要 Kotlin 专属整数编码元数据时,才使用 Kotlin 类型使用位置 +注解。 + +## 结构体字段 + +使用 `@ForyStruct` 标注 Kotlin schema 类,并用 `@ForyField(id = N)` 标注构造 +函数属性: + +```kotlin +import org.apache.fory.annotation.ForyField +import org.apache.fory.annotation.ForyStruct +import org.apache.fory.kotlin.Fixed +import org.apache.fory.kotlin.VarInt + +@ForyStruct +data class User( + @ForyField(id = 1) + val id: @Fixed UInt, + + @ForyField(id = 2) + val score: @VarInt Long, + + @ForyField(id = 3) + val tags: List, +) +``` + +在构造函数属性上使用 `@ForyField(id = 1)`。对于有字段支撑的属性,也可以使用 +`@field:ForyField(id = 1)`。不要使用 `@get:ForyField` 或 `@set:ForyField`; +访问器不是 schema 字段,处理器会拒绝它们。 + +## 可空性 + +使用 Kotlin `?` 描述可空 schema 位置。集合和 map 内部的可空性也会保留: + +```kotlin +@ForyStruct +data class NullabilityExample( + @ForyField(id = 1) + val names: List, + + @ForyField(id = 2) + val optionalNames: List, + + @ForyField(id = 3) + val nullableList: List?, +) +``` + +不要在手写的、基于构造函数的 Kotlin 结构体中使用 Fory `@Nullable`。KSP 处理器会 +从 Kotlin 源码读取可空性,并拒绝冲突的可空注解。 + +## 引用跟踪 + +Kotlin 生成序列化器会保留字段、list 元素和 map 值上的 `@Ref` 元数据: + +```kotlin +import org.apache.fory.annotation.Ref + +@ForyStruct +data class Node( + @ForyField(id = 1) + val children: List<@Ref Node>, + + @ForyField(id = 2) + @Ref + val parent: Node?, +) +``` + +全局引用跟踪仍由运行时配置决定。参见[配置](configuration.md)。 + +## 整数编码 + +Kotlin 类型使用位置编码注解映射到 Fory xlang 整数编码: + +| 注解 | 有效 Kotlin 类型 | +| ---------- | ------------------------------ | +| `@Fixed` | `Int`, `Long`, `UInt`, `ULong` | +| `@VarInt` | `Int`, `Long`, `UInt`, `ULong` | +| `@Tagged` | `Long`, `ULong` | + +如果没有注解,xlang `Int`、`Long`、`UInt` 和 `ULong` 使用 varint 编码。 + +## 集合与密集数组 + +集合声明承载的是 schema 形态,而不是 JVM 实现身份。`List` 编码为 +`list`,`Map` 编码为 `map`。 + +支持密集基本类型数组和无符号数组字段,包括 `BooleanArray`、`ByteArray`、 +`IntArray`、`LongArray`、`FloatArray`、`DoubleArray`、`UByteArray`、`UShortArray`、 +`UIntArray` 和 `ULongArray`。除非类型使用位置带有 Java `@ArrayType` 注解, +否则 `ByteArray` 会编码为 Fory `binary`。 + +## 相关主题 + +- [静态生成序列化器](static-generated-serializers.md) +- [配置](configuration.md) +- [默认值](default-values.md) +- [Android 支持](android-support.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/kotlin/static-generated-serializers.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/kotlin/static-generated-serializers.md new file mode 100644 index 00000000000..b3551cddc71 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/kotlin/static-generated-serializers.md @@ -0,0 +1,278 @@ +--- +title: 静态生成序列化器 +sidebar_position: 5 +id: static_generated_serializers +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. +--- + +当 Kotlin 类需要参与 Fory 跨语言 schema 序列化时,使用 `fory-kotlin-ksp`。 +该处理器会在构建时生成 Kotlin 源码序列化器。这些序列化器会调用现有的 Fory +Java 运行时,包括 `WriteContext`、`ReadContext` 和 `MemoryBuffer`;不存在 +Kotlin 专用协议。 + +静态生成的 Kotlin 序列化器面向 Kotlin/JVM 和 Android 的 xlang/schema 模式。 +它们不是 Java 原生对象序列化器,也不会保留 JVM 对象图实现细节,例如确切的 +具体集合类。 + +## 添加 KSP + +在运行时添加 `fory-kotlin`,并在编译 `@ForyStruct` Kotlin 类的模块中将 +`fory-kotlin-ksp` 作为 KSP 处理器运行。 + +```kotlin +plugins { + id("com.google.devtools.ksp") version "" +} + +dependencies { + implementation("org.apache.fory:fory-kotlin:") + ksp("org.apache.fory:fory-kotlin-ksp:") +} +``` + +对于 Android,请在拥有 Kotlin 模型类的 Android 模块或库模块中配置 KSP。 + +## 定义结构体 + +Schema 概念复用 Java Fory 注解。只有在需要覆盖整数编码时,才使用 Kotlin +类型使用位置注解。 + +```kotlin +import org.apache.fory.annotation.ForyField +import org.apache.fory.annotation.ForyStruct +import org.apache.fory.kotlin.Fixed +import org.apache.fory.kotlin.VarInt + +@ForyStruct +data class User( + @ForyField(id = 1) + val id: @Fixed UInt, + + @ForyField(id = 2) + val score: @VarInt Long, + + @ForyField(id = 3) + val tags: List, +) +``` + +在构造函数属性上使用 `@ForyField(id = 1)`。对于有字段支撑的属性,也可以使用 +`@field:ForyField(id = 1)`。不要使用 `@get:ForyField` 或 `@set:ForyField`; +访问器不是 schema 字段,处理器会拒绝它们。 + +## 支持的结构体 + +处理器会为具名包中的 public 或 internal、具体、非泛型类生成序列化器。受支持的 +类必须有主构造函数,且被序列化的参数必须是 `val` 或 `var` 属性。`data class` +是常见用法,但不是必需条件。 + +当 KSP 运行在拥有该结构体的同一个 Kotlin 模块中时,支持 internal Kotlin +结构体类。生成的 Kotlin 序列化器同样是 internal,因此它可以调用 internal +构造函数,并在 override 中暴露 internal 类型,同时仍然生成 Fory Java 运行时 +可以加载的 JVM 类。该 Kotlin 模块外的应用代码仍然不能直接引用 internal +结构体,因此注册必须从能够看到该类的代码中完成。 + +处理器会拒绝以下声明: + +- `private` 结构体类。 +- 局部、匿名或嵌套的 `@ForyStruct` 类。 +- Kotlin `object` 声明。 +- 作为序列化目标的接口、抽象类和 sealed class。 +- 泛型 `@ForyStruct` 类。 +- private 构造函数属性。 +- private 或 protected 主构造函数。 + +兼容读取支持 Kotlin 默认构造函数参数。一个结构体最多可以有 12 个带默认值的 +构造函数字段。 + +基于构造函数的生成序列化器支持宽主构造函数。兼容读取会在生成的旁路状态中 +跟踪远端字段是否存在,而不是使用构造函数 bit mask。 + +## 可空性 + +使用 Kotlin `?` 描述可空 schema 位置。集合和 map 内部的可空性也会保留。 + +```kotlin +@ForyStruct +data class NullabilityExample( + @ForyField(id = 1) + val a: List, + + @ForyField(id = 2) + val b: List, + + @ForyField(id = 3) + val c: List?, + + @ForyField(id = 4) + val d: List?, +) +``` + +不要在手写的、基于构造函数的 Kotlin 结构体中使用 Fory `@Nullable`。KSP +处理器会拒绝它,因此 schema 始终从 Kotlin 源码可空性读取。编译器生成的 +Kotlin IDL 源码遵循同一规则,并用 Kotlin `?` 表示可空字段。 + +## 引用 + +Kotlin 生成序列化器会保留字段、list 元素和 map 值上的 `@Ref` 元数据。构造函数 +拥有的读取路径通过主构造函数构造 Kotlin 值。需要发布引用的 Schema IDL 类会 +生成为可变的无参类,其 KSP 生成序列化器会在读取字段前发布实例。在这两种形态 +中,字段描述符、嵌套可空性和 `@Ref` 元数据都由 KSP 负责。 + +## 集合 + +集合声明承载的是 schema 形态,而不是 JVM 实现身份。例如,`List` 编码为 +`list`,`Map` 编码为 `map`。 + +反序列化只保证结果可以赋值给声明的字段类型。Fory 不会保留原始运行时值究竟是 +`ArrayList`、`LinkedList`、`Collections.unmodifiableList`、同步集合包装器, +还是其他 JVM 特有集合实现。 + +支持的集合声明包括 Kotlin 和 Java 的 list、set、map 类型。可变集合接口字段会 +反序列化为可赋值给声明类型的可变实现。没有显式 comparator 的有序集合(例如 +`TreeSet` 和 `ConcurrentSkipListSet`)只接受非 null 标量或字符串元素。并发 +map 声明只接受非 null 值,因为 JVM 并发 map 实现会拒绝 null 条目。 + +`Set<*>`、`Map<*, T>`、`Map<*, *>` 和原始 Java 集合会被拒绝。`List<*>` 和 +`Map` 会被接受,并使用动态可空值。 + +## 密集数组 + +支持 Kotlin 密集基本类型数组和无符号数组字段: + +- `BooleanArray` +- `ByteArray` +- `ShortArray` +- `IntArray` +- `LongArray` +- `FloatArray` +- `DoubleArray` +- `UByteArray` +- `UShortArray` +- `UIntArray` +- `ULongArray` + +字段、集合元素、map 值和 union case 中都支持拥有明确 Kotlin 承载类型的密集 +数组。`array` 和 `array` 使用 Java core 的 `Float16Array` +和 `BFloat16Array` 承载类型。 + +除非 `ByteArray` 类型使用位置带有 Java `@ArrayType` 注解,否则 `ByteArray` +会编码为 Fory `binary`。生成的 Kotlin IDL 对 `array` 使用 +`@ArrayType ByteArray`,包括嵌套集合和 map 位置。 + +当 `T` 是非 null 布尔或数值密集数组元素类型时,顶层 `List` 字段也支持 +`@ArrayType`。此时该字段编码为密集 `array` schema,生成的读取代码会把 +解码后的 JVM list 元素转换回声明的 Kotlin 元素承载类型。 + +## 整数编码 + +Kotlin 类型使用位置编码注解映射到 Fory xlang 整数编码: + +| 注解 | 有效 Kotlin 类型 | +| ---------- | ------------------------------ | +| `@Fixed` | `Int`, `Long`, `UInt`, `ULong` | +| `@VarInt` | `Int`, `Long`, `UInt`, `ULong` | +| `@Tagged` | `Long`, `ULong` | + +如果没有注解,xlang `Int`、`Long`、`UInt` 和 `ULong` 使用 varint 编码。这是 +xlang 模式的要求,不受 Java 原生模式数值压缩选项控制。 + +## Duration + +Xlang `duration` 映射到 `kotlin.time.Duration`。无限 Kotlin duration 无法用 +xlang duration 载荷表示,序列化时会失败。 + +## Sealed Union + +KSP 会为带 `@ForyUnion` 注解的顶层 sealed class 生成序列化器。每个 schema +case 都是一个带 `@ForyCase` 注解的嵌套类,并且有一个名为 `value` 的构造函数 +属性。Case ID `0` 保留给未知 case 承载类型: + +```kotlin +@ForyUnion +sealed class Animal { + @ForyCase(id = 0) + data class UnknownCase(val caseId: Int, val value: Any?) : Animal() + + @ForyCase(id = 1) + data class DogCase(val value: Dog) : Animal() +} +``` + +生成的 schema 模块通过 `KotlinSerializers.registerUnion` 注册 sealed union。 +运行时会自动发现生成的 `_ForySerializer`,因此调用方不需要传入序列化器 +实例。 + +## 注册类 + +使用 Kotlin `register` 扩展注册 Kotlin 结构体类。xlang 命名空间和类型名由 +你选择;生成的序列化器不会替你选择 ID 或名称。 + +```kotlin +import org.apache.fory.kotlin.ForyKotlin +import org.apache.fory.kotlin.register + +val fory = ForyKotlin.builder() + .withXlang(true) + .requireClassRegistration(true) + .build() + +fory.register("example", "User") +``` + +`ForyKotlin.builder()` 会为 Fory 实例安装 Kotlin 运行时引导逻辑。 +`fory.register(...)` 扩展会注册 xlang schema 类型名,并从目标类解析生成的 +序列化器。 + +不要在应用代码中注册或引用生成的序列化器类。运行时会从已注册的目标类解析它们。 + +生成的 Schema IDL 模块使用同一路径。它们会按需调用 +`KotlinSerializers.registerType`、`registerSerializer`、`registerEnum` 和 +`registerUnion`,并且不会生成 Java 文件。 + +## 生成名称 + +生成的序列化器会输出到与目标类相同的包中。名称为 `_ForySerializer`。 +对于嵌套二进制名称,`$` 会编码为 `_`;源码中的下划线会编码为 `_u_`。 + +这些名称属于实现细节。它们对诊断和 Android shrink 有意义,但用户代码只应注册 +目标类。 + +如果注册了由构造函数拥有的 Kotlin xlang 结构体,但缺少对应的 KSP 生成序列化器, +Fory 会以配置错误失败。注册生成的 Kotlin 类之前,请先用 KSP 编译生成的 IDL +源码。 + +## Android 与 R8 + +Android 应用通常不需要为生成的 Kotlin 序列化器手写 keep 规则。KSP 会在 +`META-INF/proguard/` 下生成 consumer R8/ProGuard 规则,用于 Fory 使用的生成 +序列化器构造函数,以及检测必需 Kotlin 生成序列化器所需的 Kotlin 元数据。 + +对于库模块,请把生成的 `META-INF/proguard/` 资源打包进产物。对于 Android 应用 +模块,请确保 KSP 设置会把生成资源包含到 minified variant 中。 + +Android Gradle 设置和 release-minified 验证指南请参见 +[Android 支持](android-support.md)。 + +## 原生对象模式 + +Kotlin KSP 生成序列化器只面向 xlang/schema 模式。它们不会替代 Fory Java 原生 +对象序列化器,也不会保留 JVM 对象图身份。如果使用 `withXlang(false)` 创建 Fory, +Fory 会改用普通 Java 和 Kotlin 运行时序列化器。 + +该模块不支持 Kotlin/Native 和 Kotlin/JS。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/kotlin/type-serialization.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/kotlin/type-serialization.md new file mode 100644 index 00000000000..5663a5b8330 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/kotlin/type-serialization.md @@ -0,0 +1,184 @@ +--- +title: 类型序列化 +sidebar_position: 2 +id: type_serialization +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. +--- + +本页介绍 Kotlin 特定类型的序列化。 + +## 设置 + +所有示例都假定以下设置: + +```kotlin +import org.apache.fory.Fory +import org.apache.fory.serializer.kotlin.KotlinSerializers + +val fory = Fory.builder() + .requireClassRegistration(false) + .build() + +KotlinSerializers.registerSerializers(fory) +``` + +## 数据类 + +```kotlin +data class Person(val name: String, val age: Int, val id: Long) + +fory.register(Person::class.java) + +val p = Person("John", 30, 1L) +println(fory.deserialize(fory.serialize(p))) +``` + +## 无符号原始类型 + +完全支持 Kotlin 无符号类型: + +```kotlin +val uByte: UByte = 255u +val uShort: UShort = 65535u +val uInt: UInt = 4294967295u +val uLong: ULong = 18446744073709551615u + +println(fory.deserialize(fory.serialize(uByte))) +println(fory.deserialize(fory.serialize(uShort))) +println(fory.deserialize(fory.serialize(uInt))) +println(fory.deserialize(fory.serialize(uLong))) +``` + +## 无符号数组 + +```kotlin +val uByteArray = ubyteArrayOf(1u, 2u, 255u) +val uShortArray = ushortArrayOf(1u, 2u, 65535u) +val uIntArray = uintArrayOf(1u, 2u, 4294967295u) +val uLongArray = ulongArrayOf(1u, 2u, 18446744073709551615u) + +println(fory.deserialize(fory.serialize(uByteArray)).contentToString()) +println(fory.deserialize(fory.serialize(uShortArray)).contentToString()) +println(fory.deserialize(fory.serialize(uIntArray)).contentToString()) +println(fory.deserialize(fory.serialize(uLongArray)).contentToString()) +``` + +## 标准库类型 + +### Pair 和 Triple + +```kotlin +val pair = Pair("key", 42) +val triple = Triple("a", "b", "c") + +println(fory.deserialize(fory.serialize(pair))) +println(fory.deserialize(fory.serialize(triple))) +``` + +### Result + +```kotlin +val success: Result = Result.success(42) +val failure: Result = Result.failure(Exception("error")) + +println(fory.deserialize(fory.serialize(success))) +println(fory.deserialize(fory.serialize(failure))) +``` + +## 范围和等差数列 + +```kotlin +val intRange = 1..10 +val longRange = 1L..100L +val charRange = 'a'..'z' + +println(fory.deserialize(fory.serialize(intRange))) +println(fory.deserialize(fory.serialize(longRange))) +println(fory.deserialize(fory.serialize(charRange))) + +// 等差数列 +val intProgression = 1..10 step 2 +val longProgression = 1L..100L step 10 + +println(fory.deserialize(fory.serialize(intProgression))) +println(fory.deserialize(fory.serialize(longProgression))) +``` + +## 集合 + +### ArrayDeque + +```kotlin +val deque = ArrayDeque() +deque.addFirst("first") +deque.addLast("last") + +println(fory.deserialize(fory.serialize(deque))) +``` + +### 空集合 + +```kotlin +val emptyList = emptyList() +val emptySet = emptySet() +val emptyMap = emptyMap() + +println(fory.deserialize(fory.serialize(emptyList))) +println(fory.deserialize(fory.serialize(emptySet))) +println(fory.deserialize(fory.serialize(emptyMap))) +``` + +## Duration + +```kotlin +import kotlin.time.Duration +import kotlin.time.Duration.Companion.hours +import kotlin.time.Duration.Companion.minutes + +val duration: Duration = 2.hours + 30.minutes + +println(fory.deserialize(fory.serialize(duration))) +``` + +## Regex + +```kotlin +val regex = Regex("[a-zA-Z]+") + +println(fory.deserialize(fory.serialize(regex))) +``` + +## UUID(Kotlin 2.0+) + +```kotlin +import kotlin.uuid.Uuid + +val uuid = Uuid.random() + +println(fory.deserialize(fory.serialize(uuid))) +``` + +## 开箱即用的类型 + +以下类型可以使用默认的 Fory Java 实现,无需使用 `KotlinSerializers`: + +- **原始类型**:`Byte`、`Boolean`、`Int`、`Short`、`Long`、`Char`、`Float`、`Double` +- **字符串**:`String` +- **集合**:`ArrayList`、`HashMap`、`HashSet`、`LinkedHashSet`、`LinkedHashMap` +- **数组**:`Array`、`BooleanArray`、`ByteArray`、`CharArray`、`DoubleArray`、`FloatArray`、`IntArray`、`LongArray`、`ShortArray` + +但是,建议始终调用 `KotlinSerializers.registerSerializers(fory)` 以确保正确支持所有 Kotlin 类型。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/python/_category_.json b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/python/_category_.json new file mode 100644 index 00000000000..b37a319075d --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/python/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "Python", + "position": 2, + "collapsible": true, + "collapsed": true +} diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/python/basic-serialization.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/python/basic-serialization.md new file mode 100644 index 00000000000..fe910511aed --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/python/basic-serialization.md @@ -0,0 +1,145 @@ +--- +title: 基础序列化 +sidebar_position: 2 +id: basic_serialization +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. +--- + +本页介绍 pyfory 中的基础序列化模式。 + +## 基础对象序列化 + +使用简单的 API 序列化和反序列化 Python 对象: + +```python +import pyfory + +# 创建 Fory 实例 +fory = pyfory.Fory(xlang=True) + +# 序列化任何 Python 对象 +data = fory.dumps({"name": "Alice", "age": 30, "scores": [95, 87, 92]}) + +# 反序列化回 Python 对象 +obj = fory.loads(data) +print(obj) # {'name': 'Alice', 'age': 30, 'scores': [95, 87, 92]} +``` + +**注意**:`dumps()`/`loads()` 是 `serialize()`/`deserialize()` 的别名。两个 API 完全相同,使用任何一个都可以。 + +## 自定义类序列化 + +Fory 自动处理 dataclass 和自定义类型: + +```python +import pyfory +from dataclasses import dataclass +from typing import List, Dict + +@dataclass +class Person: + name: str + age: int + scores: List[int] + metadata: Dict[str, str] + +# Python 模式 - 支持所有 Python 类型,包括 dataclass +fory = pyfory.Fory(xlang=False, ref=True) +fory.register(Person) +person = Person("Bob", 25, [88, 92, 85], {"team": "engineering"}) +data = fory.serialize(person) +result = fory.deserialize(data) +print(result) # Person(name='Bob', age=25, ...) +``` + +## 引用跟踪和循环引用 + +安全处理共享引用和循环依赖: + +```python +import pyfory + +f = pyfory.Fory(ref=True) # 启用引用跟踪 + +# 安全处理循环引用 +class Node: + def __init__(self, value): + self.value = value + self.children = [] + self.parent = None + +root = Node("root") +child = Node("child") +child.parent = root # 循环引用 +root.children.append(child) + +# 序列化不会导致无限递归 +data = f.serialize(root) +result = f.deserialize(data) +assert result.children[0].parent is result # 引用被保留 +``` + +## API 参考 + +### 序列化方法 + +```python +# 序列化为字节 +data: bytes = fory.serialize(obj) +data: bytes = fory.dumps(obj) # 别名 + +# 从字节反序列化 +obj = fory.deserialize(data) +obj = fory.loads(data) # 别名 +``` + +### 使用带外缓冲区 + +```python +# 使用缓冲区回调序列化 +buffer_objects = [] +data = fory.serialize(obj, buffer_callback=buffer_objects.append) + +# 使用缓冲区反序列化 +buffers = [obj.getbuffer() for obj in buffer_objects] +obj = fory.deserialize(data, buffers=buffers) +``` + +## 性能提示 + +1. **如果不需要则禁用 `ref=True`**:引用跟踪有开销 +2. **使用 type_id 而不是 typename**:整数 ID 比字符串名称更快 +3. **重用 Fory 实例**:创建一次,多次使用 +4. **启用 Cython**:确保 `ENABLE_FORY_CYTHON_SERIALIZATION=1` + +```python +# 好:重用实例 +fory = pyfory.Fory() +for obj in objects: + data = fory.dumps(obj) + +# 坏:每次创建新实例 +for obj in objects: + fory = pyfory.Fory() # 浪费! + data = fory.dumps(obj) +``` + +## 相关主题 + +- [配置](configuration.md) - Fory 参数 +- [类型注册](type-registration.md) - 注册模式 +- [Python 原生模式](native-serialization.md) - 函数和 lambda diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/python/configuration.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/python/configuration.md new file mode 100644 index 00000000000..4e5158b03b9 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/python/configuration.md @@ -0,0 +1,206 @@ +--- +title: 配置 +sidebar_position: 1 +id: configuration +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. +--- + +本页介绍 Fory 配置参数和语言模式。 + +## Fory 类 + +主要序列化接口: + +```python +class Fory: + def __init__( + self, + xlang: bool = False, + ref: bool = False, + strict: bool = True, + compatible: bool = False, + max_depth: int = 50, + max_type_fields: int = 512, + max_type_meta_bytes: int = 4096, + max_schema_versions_per_type: int = 10, + max_average_schema_versions_per_type: int = 3 + ) +``` + +## ThreadSafeFory 类 + +使用线程本地存储的线程安全序列化接口: + +```python +class ThreadSafeFory: + def __init__( + self, + xlang: bool = False, + ref: bool = False, + strict: bool = True, + compatible: bool = False, + max_depth: int = 50, + max_type_fields: int = 512, + max_type_meta_bytes: int = 4096, + max_schema_versions_per_type: int = 10, + max_average_schema_versions_per_type: int = 3 + ) +``` + +## 参数 + +| 参数 | 类型 | 默认值 | 描述 | +| -------------------------------------- | ------ | ------- | --------------------------------------------------------------------------------------------------------------------------------------- | +| `xlang` | `bool` | `False` | 启用跨语言序列化。当为 `False` 时,启用 Python 原生模式,支持所有 Python 对象。当为 `True` 时,启用跨语言模式,兼容 Java、Go、Rust 等。 | +| `ref` | `bool` | `False` | 启用引用跟踪以支持共享/循环引用。如果数据没有共享引用,禁用此选项可获得更好性能。 | +| `strict` | `bool` | `True` | 出于安全考虑需要类型注册。**生产环境强烈推荐**。仅在受信任的环境中禁用。 | +| `compatible` | `bool` | `False` | 在跨语言模式中启用 schema 演化,允许在保持兼容性的同时添加/删除字段。 | +| `max_depth` | `int` | `50` | 反序列化的最大深度,用于安全防护,防止栈溢出攻击。 | +| `max_type_fields` | `int` | `512` | 一个收到的远端 struct metadata body 中可接受的最大字段数。 | +| `max_type_meta_bytes` | `int` | `4096` | 一个收到的 TypeDef body 可接受的最大编码 body 字节数,不包含 8 字节 header 和扩展 size varint。 | +| `max_schema_versions_per_type` | `int` | `10` | 一个逻辑类型可接受的最大远端 metadata 版本数。 | +| `max_average_schema_versions_per_type` | `int` | `3` | 所有已接受远端类型的平均 metadata 版本数限制;有效全局下限为 `8192` 个 schema。 | + +## 核心方法 + +```python +# 序列化(serialize/deserialize 与 dumps/loads 完全相同) +data: bytes = fory.serialize(obj) +obj = fory.deserialize(data) + +# 替代 API(别名) +data: bytes = fory.dumps(obj) +obj = fory.loads(data) + +# 按 ID 注册类型(用于 Python 模式) +fory.register(MyClass, type_id=123) +fory.register(MyClass, type_id=123, serializer=custom_serializer) + +# 按名称注册类型(用于跨语言模式) +fory.register(MyClass, typename="my.package.MyClass") +fory.register(MyClass, typename="my.package.MyClass", serializer=custom_serializer) +``` + +## 语言模式比较 + +| 特性 | Python 模式 (`xlang=False`) | 跨语言模式 (`xlang=True`) | +| --------------- | -------------------------------- | ---------------------------------- | +| **使用场景** | 纯 Python 应用 | 多语言系统 | +| **兼容性** | 仅限 Python | Java、Go、Rust、C++、JavaScript 等 | +| **支持的类型** | 所有 Python 类型 | 仅限跨语言兼容类型 | +| **函数/Lambda** | ✓ 支持 | ✗ 不允许 | +| **本地类** | ✓ 支持 | ✗ 不允许 | +| **动态类** | ✓ 支持 | ✗ 不允许 | +| **Schema 演化** | ✓ 支持(需要 `compatible=True`) | ✓ 支持(需要 `compatible=True`) | +| **性能** | 极快 | 非常快 | +| **数据大小** | 紧凑 | 紧凑(带类型元数据) | + +## Python 模式 (`xlang=False`) + +Python 模式支持所有 Python 类型,包括函数、类和闭包: + +```python +import pyfory + +# 完全 Python 兼容模式 +fory = pyfory.Fory(xlang=False, ref=True, strict=False) + +# 支持所有 Python 对象: +data = fory.dumps({ + 'function': lambda x: x * 2, # 函数和 lambda + 'class': type('Dynamic', (), {}), # 动态类 + 'method': str.upper, # 方法 + 'nested': {'circular_ref': None} # 循环引用(当 ref=True 时) +}) + +# 可直接替代 pickle/cloudpickle +import pickle +obj = [1, 2, {"nested": [3, 4]}] +assert fory.loads(fory.dumps(obj)) == pickle.loads(pickle.dumps(obj)) +``` + +## 跨语言模式 (`xlang=True`) + +跨语言模式将类型限制为所有 Fory 实现间兼容的类型: + +```python +import pyfory + +# 跨语言兼容模式 +f = pyfory.Fory(xlang=True, ref=True) + +# 仅支持跨语言兼容类型 +f.register(MyDataClass, typename="com.example.MyDataClass") + +# 数据可以被 Java、Go、Rust 等读取 +data = f.serialize(MyDataClass(field1="value", field2=42)) +``` + +## 示例配置 + +### 生产环境配置 + +```python +import pyfory + +# 生产环境推荐设置 +fory = pyfory.Fory( + xlang=False, # 如果需要跨语言支持则使用 True + ref=False, # 如果有共享/循环引用则启用 + strict=True, # 关键:生产环境始终为 True + compatible=False, # 仅在需要 schema 演化时启用 + max_depth=20, # 根据数据结构深度调整 + max_type_fields=512, + max_type_meta_bytes=4096, + max_schema_versions_per_type=10, + max_average_schema_versions_per_type=3, +) + +# 预先注册所有类型 +fory.register(UserModel, type_id=100) +fory.register(OrderModel, type_id=101) +fory.register(ProductModel, type_id=102) +``` + +收到的远端 metadata 也会受到限制: + +- `max_type_fields` 限制一个收到的 struct metadata body 中的字段数。 +- `max_type_meta_bytes` 限制一个收到的 TypeDef body 可接受的编码 body 字节数。 +- `max_schema_versions_per_type` 限制一个逻辑类型可接受的远端 metadata 版本数。 +- `max_average_schema_versions_per_type` 限制所有已接受远端类型的平均版本数。 + +这些限制不会改变 `strict`、policy、动态加载、unknown-class handling 或 Schema 演进语义。 + +### 开发环境配置 + +```python +import pyfory + +# 开发设置(更宽松) +fory = pyfory.Fory( + xlang=False, + ref=True, + strict=False, # 开发时允许任何类型 + max_depth=1000 # 开发时更高的限制 +) +``` + +## 相关主题 + +- [基础序列化](basic-serialization.md) - 使用已配置的 Fory +- [类型注册](type-registration.md) - 注册模式 +- [安全性](configuration.md) - 安全最佳实践 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/python/custom-serializers.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/python/custom-serializers.md new file mode 100644 index 00000000000..64d47039625 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/python/custom-serializers.md @@ -0,0 +1,138 @@ +--- +title: 自定义序列化器 +sidebar_position: 4 +id: custom_serializers +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. +--- + +为特殊类型实现自定义序列化逻辑。 + +## 实现自定义序列化器 + +在 Python 模式和跨语言模式下,都只需实现一套 `write/read`: + +```python +import pyfory +from pyfory.serializer import Serializer +from dataclasses import dataclass + +@dataclass +class Foo: + f1: int + f2: str + +class FooSerializer(Serializer): + def __init__(self, type_resolver, cls): + super().__init__(type_resolver, cls) + + def write(self, write_context, obj: Foo): + # 自定义序列化逻辑 + write_context.write_varint32(obj.f1) + write_context.write_string(obj.f2) + + def read(self, read_context): + # 自定义反序列化逻辑 + f1 = read_context.read_varint32() + f2 = read_context.read_string() + return Foo(f1, f2) + +f = pyfory.Fory() +f.register(Foo, type_id=100, serializer=FooSerializer(f.type_resolver, Foo)) + +# 现在 Foo 使用自定义序列化器 +data = f.dumps(Foo(42, "hello")) +result = f.loads(data) +print(result) # Foo(f1=42, f2='hello') +``` + +## Buffer API + +### 写入方法 + +```python +# 整数 +buffer.write_int8(value) +buffer.write_int16(value) +buffer.write_int32(value) +buffer.write_int64(value) + +# 变长整数 +buffer.write_varint32(value) +buffer.write_varint64(value) + +# 浮点数 +buffer.write_float32(value) +buffer.write_float64(value) + +# 字符串和字节 +buffer.write_string(value) +buffer.write_bytes(value) + +# 布尔值 +buffer.write_bool(value) +``` + +### 读取方法 + +```python +# 整数 +value = buffer.read_int8() +value = buffer.read_int16() +value = buffer.read_int32() +value = buffer.read_int64() + +# 变长整数 +value = buffer.read_varint32() +value = buffer.read_varint64() + +# 浮点数 +value = buffer.read_float32() +value = buffer.read_float64() + +# 字符串和字节 +value = buffer.read_string() +value = buffer.read_bytes(length) + +# 布尔值 +value = buffer.read_bool() +``` + +## 何时使用自定义序列化器 + +- 来自其他包的外部类型 +- 具有特殊序列化需求的类型 +- 旧数据格式兼容性 +- 性能关键的自定义编码 +- 自动序列化效果不好的类型 + +## 注册自定义序列化器 + +```python +fory = pyfory.Fory() + +# 使用 type_id 注册 +fory.register(MyClass, type_id=100, serializer=MySerializer(fory.type_resolver, MyClass)) + +# 使用 typename 注册(用于 xlang) +fory.register(MyClass, typename="com.example.MyClass", serializer=MySerializer(fory.type_resolver, MyClass)) +``` + +## 相关主题 + +- [类型注册](type-registration.md) - 注册模式 +- [配置](configuration.md) - Fory 参数 +- [跨语言](xlang-serialization.md) - xlang 的 xwrite/xread diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/python/grpc-support.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/python/grpc-support.md new file mode 100644 index 00000000000..c2579e7cfa9 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/python/grpc-support.md @@ -0,0 +1,302 @@ +--- +title: gRPC 支持 +sidebar_position: 13 +id: grpc_support +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. +--- + +Fory 可以为包含 service 定义的 schema 生成 Python gRPC service companion。生成 module +使用 `grpcio` 负责传输,并使用 Fory 序列化 request 和 response 对象。 + +当每个 RPC peer 都由同一份 Fory IDL、protobuf IDL 或 FlatBuffers IDL 生成,并且你希望 +使用 gRPC 传输语义与 Fory payload 编码时,可以使用这种模式。如果 client 或工具必须直接 +消费 protobuf message bytes,请使用标准 protobuf gRPC 代码生成。 + +Python gRPC 生成默认使用 `grpc.aio` AsyncIO API。生成的 servicer base 使用 +`async def` 方法,生成的 stub 搭配 `grpc.aio.Channel` 实例使用,streaming RPC 使用 +async iterable。同步 `grpcio` companion 仍可通过 `--grpc-python-mode=sync` 生成。 + +## 安装依赖 + +将 `grpcio` 与 `pyfory` 一起安装。生成的 companion 会 import `grpc`,并且在默认模式下 +import `grpc.aio`;但 `pyfory` 不会把 gRPC 作为硬依赖。 + +```bash +pip install pyfory grpcio +``` + +## 定义 Service + +Service 定义可以来自 Fory IDL、protobuf IDL 或 FlatBuffers `rpc_service` 定义。 +Fory IDL service 示例: + +```protobuf +package demo.greeter; + +message HelloRequest { + string name = 1; +} + +message HelloReply { + string reply = 1; +} + +service Greeter { + rpc SayHello (HelloRequest) returns (HelloReply); +} +``` + +使用 `--grpc` 生成 Python model 和 gRPC companion 代码: + +```bash +foryc service.fdl --python_out=./generated/python --grpc +``` + +对这个 schema,Python generator 会输出: + +| 文件 | 用途 | +| ---------------------- | ----------------------------------------- | +| `demo_greeter.py` | Fory dataclass 和注册辅助逻辑 | +| `demo_greeter_grpc.py` | `grpc.aio` stub、servicer base 和注册函数 | + +Module 名称来自 Fory package,点号会替换为下划线。没有 package 的 schema 使用 +`generated.py` 和 `generated_grpc.py`。 + +## 实现 Async Server + +继承生成的 servicer,并将它注册到 `grpc.aio` server。生成的 Python 方法名使用 +snake_case,而 gRPC wire path 保留原始 IDL method 名称。 + +```python +import asyncio + +import grpc.aio + +import demo_greeter +import demo_greeter_grpc + + +class Greeter(demo_greeter_grpc.GreeterServicer): + async def say_hello(self, request, context): + return demo_greeter.HelloReply(reply=f"Hello, {request.name}") + + +async def serve(): + server = grpc.aio.server() + demo_greeter_grpc.add_servicer(Greeter(), server) + server.add_insecure_port("[::]:50051") + await server.start() + await server.wait_for_termination() + + +if __name__ == "__main__": + asyncio.run(serve()) +``` + +生成的 request 和 response 类型由生成 companion 序列化,因此 service 实现不需要手动 +执行 Fory 注册。 + +## 创建 Async Client + +通过 `grpc.aio` channel 使用生成的 stub。生产 client 通常传入配置了 TLS/认证的 channel: + +```python +import asyncio + +import grpc +import grpc.aio + +import demo_greeter +import demo_greeter_grpc + + +async def main(): + credentials = grpc.ssl_channel_credentials() + async with grpc.aio.secure_channel("api.example.com:443", credentials) as channel: + stub = demo_greeter_grpc.GreeterStub(channel) + reply = await stub.say_hello(demo_greeter.HelloRequest(name="Fory")) + print(reply.reply) + + +if __name__ == "__main__": + asyncio.run(main()) +``` + +本地测试和开发可以显式使用 insecure channel: + +```python +# Test-only channel. Use a TLS/auth-configured grpc.aio.Channel in production. +async with grpc.aio.insecure_channel("localhost:50051") as channel: + stub = demo_greeter_grpc.GreeterStub(channel) +``` + +Channel option、credential、deadline、metadata、retry 和 interceptor 仍由 `grpcio` 负责。 + +## Streaming RPC + +Fory service 定义可以使用 unary、server-streaming、client-streaming 和 bidirectional +streaming RPC 形态: + +```protobuf +service Greeter { + rpc SayHello (HelloRequest) returns (HelloReply); + rpc LotsOfReplies (HelloRequest) returns (stream HelloReply); + rpc LotsOfGreetings (stream HelloRequest) returns (HelloReply); + rpc Chat (stream HelloRequest) returns (stream HelloReply); +} +``` + +默认 Python gRPC 输出遵循 `grpc.aio` 约定: + +| IDL shape | Servicer 方法形态 | Stub 方法形态 | +| ----------------------------------------- | ---------------------------------------------- | ------------------------------ | +| `rpc A (Req) returns (Res)` | `async def` 返回一个 response 对象 | awaitable 返回一个 response 对象 | +| `rpc A (Req) returns (stream Res)` | `async def` yield response 对象 | 返回 response async iterator | +| `rpc A (stream Req) returns (Res)` | 消费 async iterator 并返回 response | 接收 request async iterator | +| `rpc A (stream Req) returns (stream Res)` | 消费并 yield async iterator | 接收并返回 async iterator | + +Servicer 方法使用 snake_case 名称;生成 descriptor 会保留精确的 IDL service 和 +method 名称作为 gRPC path。 + +Server 实现使用 async 方法和 async iteration: + +```python +class Greeter(demo_greeter_grpc.GreeterServicer): + async def lots_of_replies(self, request, context): + yield demo_greeter.HelloReply(reply=f"Hello, {request.name}") + yield demo_greeter.HelloReply(reply=f"Welcome, {request.name}") + + async def lots_of_greetings(self, request_iterator, context): + names = [] + async for request in request_iterator: + names.append(request.name) + return demo_greeter.HelloReply(reply=", ".join(names)) + + async def chat(self, request_iterator, context): + async for request in request_iterator: + yield demo_greeter.HelloReply(reply=f"Hello, {request.name}") +``` + +生成的 client 使用 `grpc.aio` streaming 调用形态: + +```python +credentials = grpc.ssl_channel_credentials() +async with grpc.aio.secure_channel("api.example.com:443", credentials) as channel: + stub = demo_greeter_grpc.GreeterStub(channel) + + async for reply in stub.lots_of_replies( + demo_greeter.HelloRequest(name="Fory") + ): + print(reply.reply) + + async def greeting_requests(): + yield demo_greeter.HelloRequest(name="Ada") + yield demo_greeter.HelloRequest(name="Grace") + + summary = await stub.lots_of_greetings(greeting_requests()) + print(summary.reply) + + async def chat_requests(): + yield demo_greeter.HelloRequest(name="Fory") + yield demo_greeter.HelloRequest(name="RPC") + + async for reply in stub.chat(chat_requests()): + print(reply.reply) +``` + +## Sync 模式 + +已有同步 `grpcio` 应用,或不运行 asyncio event loop 的环境,可以使用 sync 模式。显式生成 +sync companion: + +```bash +foryc service.fdl --python_out=./generated/python --grpc --grpc-python-mode=sync +``` + +Sync 模式输出相同的 `_grpc.py` 文件名和 public name,但 servicer 方法使用普通 +`def`,应用使用 `grpc.server(...)` 和标准 `grpc.Channel` 实例。 + +Unary sync server 示例: + +```python +from concurrent import futures + +import grpc + +import demo_greeter +import demo_greeter_grpc + + +class Greeter(demo_greeter_grpc.GreeterServicer): + def say_hello(self, request, context): + return demo_greeter.HelloReply(reply=f"Hello, {request.name}") + + +server = grpc.server(futures.ThreadPoolExecutor(max_workers=8)) +demo_greeter_grpc.add_servicer(Greeter(), server) +server.add_insecure_port("[::]:50051") +server.start() +server.wait_for_termination() +``` + +Unary sync client 示例: + +```python +import grpc + +import demo_greeter +import demo_greeter_grpc + + +with grpc.insecure_channel("localhost:50051") as channel: + stub = demo_greeter_grpc.GreeterStub(channel) + reply = stub.say_hello(demo_greeter.HelloRequest(name="Fory")) + print(reply.reply) +``` + +Sync streaming 遵循普通 `grpcio` iterator 和 generator 约定。 + +## gRPC 运行时行为 + +生成的 service companion 只提供 Fory serialization callback。运行行为仍遵循标准 +`grpcio`: + +- Deadline 和取消 +- TLS 和认证 credential +- Client/server interceptor +- Status code、details 和 metadata +- 默认模式下的 async event loop、channel 和 server 生命周期 +- Sync 模式下同步 server 的线程池大小 + +## 故障排查 + +### 缺少 `grpc` + +安装 `grpcio`: + +```bash +pip install grpcio +``` + +### `UNIMPLEMENTED` + +确认生成的 servicer 已注册到 server,并且 client 与 server 来自相同 package、service 和 method 名称。 + +### Protobuf Client 无法解码 + +Fory gRPC 使用 Fory 二进制协议 payload,不是 protobuf wire-format message。两端应使用同一份 +schema 生成的 Fory gRPC companion。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/python/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/python/index.md new file mode 100644 index 00000000000..baf71b9c90e --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/python/index.md @@ -0,0 +1,160 @@ +--- +title: Python 序列化指南 +sidebar_position: 0 +id: serialization_index +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. +--- + +**Apache Fory™** 是一个极速的多语言序列化框架,基于 **JIT 编译**和**零拷贝**技术,在保持易用性和安全性的同时提供**超高性能**。 + +`pyfory` 提供 Apache Fory™ 的 Python 实现,为数据处理任务提供高性能对象序列化和先进的行格式能力。 + +## 核心特性 + +### 灵活的序列化模式 + +- **Python 原生模式**:完全 Python 兼容,可替代 pickle/cloudpickle +- **跨语言模式**:针对多语言数据交换优化 +- **行格式**:用于分析工作负载的零拷贝行格式 + +### 多功能序列化特性 + +- **共享/循环引用支持**:在 Python 原生和跨语言模式中支持复杂对象图 +- **多态支持**:自定义类型的自动类型分发 +- **Schema 演化支持**:在跨语言模式下使用 dataclass 时的向后/向前兼容性 +- **带外缓冲区支持**:零拷贝序列化大型数据结构(如 NumPy 数组和 Pandas DataFrame),兼容 pickle 协议 5 + +### 极速性能 + +- **超快性能**:相比其他序列化框架 +- **运行时代码生成**和 **Cython 加速**的核心实现,实现最优性能 + +### 紧凑数据大小 + +- **紧凑的对象图协议**:最小空间开销——相比 pickle/cloudpickle 减少高达 3 倍大小 +- **元数据打包与共享**:最小化类型向前/向后兼容性的空间开销 + +### 安全性与安全 + +- **严格模式**:通过类型注册和检查防止反序列化不受信任的类型 +- **引用跟踪**:安全处理循环引用 + +## 安装 + +### 基础安装 + +```bash +pip install pyfory +``` + +### 可选依赖 + +```bash +# 安装行格式支持(需要 Apache Arrow) +pip install pyfory[format] + +# 从源码安装用于开发 +git clone https://github.com/apache/fory.git +cd fory/python +pip install -e ".[dev,format]" +``` + +### 系统要求 + +- **Python**:3.8 或更高版本 +- **操作系统**:Linux、macOS、Windows + +## 线程安全 + +`pyfory` 提供 `ThreadSafeFory` 用于线程安全序列化,使用线程本地存储: + +```python +import pyfory +import threading +from dataclasses import dataclass + +@dataclass +class Person: + name: str + age: int + +# 创建线程安全的 Fory 实例 +fory = pyfory.ThreadSafeFory(xlang=False, ref=True) +fory.register(Person) + +# 在多线程中安全使用 +def serialize_in_thread(thread_id): + person = Person(name=f"User{thread_id}", age=25 + thread_id) + data = fory.serialize(person) + result = fory.deserialize(data) + print(f"Thread {thread_id}: {result}") + +threads = [threading.Thread(target=serialize_in_thread, args=(i,)) for i in range(10)] +for t in threads: t.start() +for t in threads: t.join() +``` + +**核心特性:** + +- **实例池**:维护一个受锁保护的 `Fory` 实例池,确保线程安全 +- **共享配置**:所有注册必须预先完成,并应用于所有实例 +- **相同 API**:与 `Fory` 类相同的方法,可直接替换 +- **注册安全**:防止首次使用后注册,确保一致性 + +**适用场景:** + +- **多线程应用程序**:Web 服务器、并发工作线程、并行处理 +- **共享 Fory 实例**:当多个线程需要序列化/反序列化数据时 +- **线程池**:使用线程池或 concurrent.futures 的应用程序 + +## 快速开始 + +```python +import pyfory +from dataclasses import dataclass + +@dataclass +class Person: + name: str + age: int + +# 创建 Fory 实例 +fory = pyfory.Fory(xlang=False, ref=True) +fory.register(Person) + +person = Person("Alice", 30) +data = fory.serialize(person) +result = fory.deserialize(data) +print(result) # Person(name='Alice', age=30) +``` + +## 后续步骤 + +- [配置](configuration.md) - Fory 参数和模式 +- [基础序列化](basic-serialization.md) - 基础使用模式 +- [Python 原生模式](native-serialization.md) - 函数、lambda、类 +- [跨语言](xlang-serialization.md) - XLANG 模式 +- [行格式](row-format.md) - 零拷贝行格式 +- [安全性](configuration.md) - 安全最佳实践 + +## 链接 + +- **文档**:https://fory.apache.org/docs/latest/python_guide/ +- **GitHub**:https://github.com/apache/fory +- **PyPI**:https://pypi.org/project/pyfory/ +- **问题反馈**:https://github.com/apache/fory/issues +- **Slack**:https://join.slack.com/t/fory-project/shared_invite/zt-36g0qouzm-kcQSvV_dtfbtBKHRwT5gsw diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/python/native-serialization.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/python/native-serialization.md new file mode 100644 index 00000000000..d3de0e32311 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/python/native-serialization.md @@ -0,0 +1,205 @@ +--- +title: 原生序列化 +sidebar_position: 3 +id: native_serialization +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. +--- + +`pyfory` 提供了 Python 原生序列化模式,提供与 pickle/cloudpickle 相同的功能,但具有**显著更好的性能、更小的数据大小和增强的安全特性**。 + +## 概述 + +二进制协议和 API 与 Fory 的 xlang 模式类似,但 Python 原生模式可以序列化任何 Python 对象——包括全局函数、局部函数、lambda、局部类以及使用 `__getstate__/__reduce__/__reduce_ex__` 自定义序列化的类型,这些在 xlang 模式中是不允许的。 + +要使用 Python 原生模式,创建 `Fory` 时设置 `xlang=False`: + +```python +import pyfory +fory = pyfory.Fory(xlang=False, ref=False, strict=True) +``` + +## 可直接替代 Pickle/Cloudpickle + +`pyfory` 可以使用以下配置序列化任何 Python 对象: + +- **对于循环引用**:设置 `ref=True` 启用引用跟踪 +- **对于函数/类**:设置 `strict=False` 允许反序列化动态类型 + +**⚠️ 安全警告**:当 `strict=False` 时,Fory 将反序列化任意类型,如果数据来自不受信任的源,这可能带来安全风险。仅在完全信任数据源的受控环境中使用 `strict=False`。 + +### 常见用法 + +```python +import pyfory + +# 创建 Fory 实例 +fory = pyfory.Fory(xlang=False, ref=True, strict=False) + +# 序列化常见 Python 对象 +data = fory.dumps({"name": "Alice", "age": 30, "scores": [95, 87, 92]}) +print(fory.loads(data)) + +# 序列化自定义对象 +from dataclasses import dataclass + +@dataclass +class Person: + name: str + age: int + +person = Person("Bob", 25) +data = fory.dumps(person) +print(fory.loads(data)) # Person(name='Bob', age=25) +``` + +## 序列化全局函数 + +捕获并序列化在模块级别定义的函数。Fory 反序列化并返回相同的函数对象: + +```python +import pyfory + +fory = pyfory.Fory(xlang=False, ref=True, strict=False) + +def my_global_function(x): + return 10 * x + +data = fory.dumps(my_global_function) +print(fory.loads(data)(10)) # 100 +``` + +## 序列化局部函数/Lambda + +序列化带闭包的函数和 lambda 表达式。Fory 自动捕获闭包变量: + +```python +import pyfory + +fory = pyfory.Fory(xlang=False, ref=True, strict=False) + +# 带闭包的局部函数 +def my_function(): + local_var = 10 + def local_func(x): + return x * local_var + return local_func + +data = fory.dumps(my_function()) +print(fory.loads(data)(10)) # 100 + +# Lambda +data = fory.dumps(lambda x: 10 * x) +print(fory.loads(data)(10)) # 100 +``` + +## 序列化全局类/方法 + +序列化类对象、实例方法、类方法和静态方法: + +```python +from dataclasses import dataclass +import pyfory +fory = pyfory.Fory(xlang=False, ref=True, strict=False) + +@dataclass +class Person: + name: str + age: int + + def f(self, x): + return self.age * x + + @classmethod + def g(cls, x): + return 10 * x + + @staticmethod + def h(x): + return 10 * x + +# 序列化全局类 +print(fory.loads(fory.dumps(Person))("Bob", 25)) # Person(name='Bob', age=25) + +# 序列化实例方法 +print(fory.loads(fory.dumps(Person("Bob", 20).f))(10)) # 200 + +# 序列化类方法 +print(fory.loads(fory.dumps(Person.g))(10)) # 100 + +# 序列化静态方法 +print(fory.loads(fory.dumps(Person.h))(10)) # 100 +``` + +## 序列化局部类/方法 + +序列化在函数内定义的类及其方法: + +```python +from dataclasses import dataclass +import pyfory +fory = pyfory.Fory(xlang=False, ref=True, strict=False) + +def create_local_class(): + class LocalClass: + def f(self, x): + return 10 * x + + @classmethod + def g(cls, x): + return 10 * x + + @staticmethod + def h(x): + return 10 * x + return LocalClass + +# 序列化局部类 +data = fory.dumps(create_local_class()) +print(fory.loads(data)().f(10)) # 100 + +# 序列化局部类实例方法 +data = fory.dumps(create_local_class()().f) +print(fory.loads(data)(10)) # 100 + +# 序列化局部类方法 +data = fory.dumps(create_local_class().g) +print(fory.loads(data)(10)) # 100 + +# 序列化局部类静态方法 +data = fory.dumps(create_local_class().h) +print(fory.loads(data)(10)) # 100 +``` + +## 性能比较 + +```python +import pyfory +import pickle +import timeit + +fory = pyfory.Fory(xlang=False, ref=True, strict=False) + +obj = {f"key{i}": f"value{i}" for i in range(10000)} +print(f"Fory: {timeit.timeit(lambda: fory.dumps(obj), number=1000):.3f}s") +print(f"Pickle: {timeit.timeit(lambda: pickle.dumps(obj), number=1000):.3f}s") +``` + +## 相关主题 + +- [配置](configuration.md) - Python 模式配置 +- [带外序列化](out-of-band.md) - 零拷贝缓冲区 +- [安全性](configuration.md) - DeserializationPolicy diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/python/numpy-integration.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/python/numpy-integration.md new file mode 100644 index 00000000000..20c0b9de40c --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/python/numpy-integration.md @@ -0,0 +1,103 @@ +--- +title: NumPy 与科学计算 +sidebar_position: 8 +id: numpy_integration +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. +--- + +Fory 原生支持 numpy 数组,提供优化的序列化。 + +## NumPy 数组序列化 + +大型数组在可能的情况下使用零拷贝: + +```python +import pyfory +import numpy as np + +f = pyfory.Fory() + +# 原生支持 Numpy 数组 +arrays = { + 'matrix': np.random.rand(1000, 1000), + 'vector': np.arange(10000), + 'bool_mask': np.random.choice([True, False], size=5000) +} + +data = f.serialize(arrays) +result = f.deserialize(data) + +# 对于兼容的数组类型使用零拷贝 +assert np.array_equal(arrays['matrix'], result['matrix']) +``` + +## Pandas DataFrame + +Fory 可以高效序列化 Pandas DataFrame: + +```python +import pyfory +import pandas as pd +import numpy as np + +f = pyfory.Fory(xlang=False, ref=False, strict=False) + +df = pd.DataFrame({ + 'a': np.arange(1000, dtype=np.float64), + 'b': np.arange(1000, dtype=np.int64), + 'c': ['text'] * 1000 +}) + +data = f.serialize(df) +result = f.deserialize(data) + +assert df.equals(result) +``` + +## 使用带外缓冲区的零拷贝 + +对于大型数组的最大性能,使用带外序列化: + +```python +import pyfory +import numpy as np + +f = pyfory.Fory(xlang=False, ref=False, strict=False) + +# 大型数组 +array = np.random.rand(10000, 1000) + +# 带外零拷贝 +buffer_objects = [] +data = f.serialize(array, buffer_callback=buffer_objects.append) +buffers = [obj.getbuffer() for obj in buffer_objects] + +result = f.deserialize(data, buffers=buffers) +assert np.array_equal(array, result) +``` + +## 支持的数组类型 + +- `np.ndarray`(所有 dtype) +- `np.matrix` +- 结构化数组 +- 记录数组 + +## 相关主题 + +- [带外序列化](out-of-band.md) - 零拷贝缓冲区 +- [基础序列化](basic-serialization.md) - 标准用法 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/python/out-of-band.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/python/out-of-band.md new file mode 100644 index 00000000000..a0f3b11a380 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/python/out-of-band.md @@ -0,0 +1,179 @@ +--- +title: 带外序列化 +sidebar_position: 7 +id: out_of_band +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. +--- + +Fory 支持 pickle5 兼容的带外缓冲区序列化,用于高效零拷贝处理大型数据结构。 + +## 概述 + +带外序列化将元数据与实际数据缓冲区分离,允许: + +- **零拷贝传输**:使用 `memoryview` 通过网络或 IPC 发送数据时 +- **提高性能**:用于大型数据集 +- **Pickle5 兼容性**:使用 `pickle.PickleBuffer` +- **灵活的流支持**:写入任何可写对象(文件、BytesIO、套接字等) + +## 基础带外序列化 + +```python +import pyfory +import numpy as np + +fory = pyfory.Fory(xlang=False, ref=False, strict=False) + +# 大型 numpy 数组 +array = np.arange(10000, dtype=np.float64) + +# 使用带外缓冲区序列化 +buffer_objects = [] +serialized_data = fory.serialize(array, buffer_callback=buffer_objects.append) + +# 将缓冲区对象转换为 memoryview 以进行零拷贝传输 +# 对于连续缓冲区(bytes、numpy 数组),这是零拷贝 +# 对于非连续数据,可能会创建副本以确保连续性 +buffers = [obj.getbuffer() for obj in buffer_objects] + +# 使用带外缓冲区反序列化(接受 memoryview、bytes 或 Buffer) +deserialized_array = fory.deserialize(serialized_data, buffers=buffers) + +assert np.array_equal(array, deserialized_array) +``` + +## 使用 Pandas DataFrame 的带外序列化 + +```python +import pyfory +import pandas as pd +import numpy as np + +fory = pyfory.Fory(xlang=False, ref=False, strict=False) + +# 创建带数值列的 DataFrame +df = pd.DataFrame({ + 'a': np.arange(1000, dtype=np.float64), + 'b': np.arange(1000, dtype=np.int64), + 'c': ['text'] * 1000 +}) + +# 使用带外缓冲区序列化 +buffer_objects = [] +serialized_data = fory.serialize(df, buffer_callback=buffer_objects.append) +buffers = [obj.getbuffer() for obj in buffer_objects] + +# 反序列化 +deserialized_df = fory.deserialize(serialized_data, buffers=buffers) + +assert df.equals(deserialized_df) +``` + +## 选择性带外序列化 + +通过提供回调来控制哪些缓冲区带外传输,该回调返回 `True` 保持数据内联或返回 `False` 带外发送: + +```python +import pyfory +import numpy as np + +fory = pyfory.Fory(xlang=False, ref=True, strict=False) + +arr1 = np.arange(1000, dtype=np.float64) +arr2 = np.arange(2000, dtype=np.float64) +data = [arr1, arr2] + +buffer_objects = [] +counter = 0 + +def selective_callback(buffer_object): + global counter + counter += 1 + # 只有偶数编号的缓冲区带外发送 + if counter % 2 == 0: + buffer_objects.append(buffer_object) + return False # 带外 + return True # 内联 + +serialized = fory.serialize(data, buffer_callback=selective_callback) +buffers = [obj.getbuffer() for obj in buffer_objects] +deserialized = fory.deserialize(serialized, buffers=buffers) +``` + +## Pickle5 兼容性 + +Fory 的带外序列化完全兼容 pickle 协议 5: + +```python +import pyfory +import pickle + +fory = pyfory.Fory(xlang=False, ref=False, strict=False) + +# 自动支持 PickleBuffer 对象 +data = b"Large binary data" +pickle_buffer = pickle.PickleBuffer(data) + +# 使用缓冲区回调序列化以进行带外处理 +buffer_objects = [] +serialized = fory.serialize(pickle_buffer, buffer_callback=buffer_objects.append) +buffers = [obj.getbuffer() for obj in buffer_objects] + +# 使用缓冲区反序列化 +deserialized = fory.deserialize(serialized, buffers=buffers) +assert bytes(deserialized.raw()) == data +``` + +## 将缓冲区写入不同的流 + +`BufferObject.write_to()` 方法接受任何可写流对象: + +```python +import pyfory +import numpy as np +import io + +fory = pyfory.Fory(xlang=False, ref=False, strict=False) + +array = np.arange(1000, dtype=np.float64) + +# 收集带外缓冲区 +buffer_objects = [] +serialized = fory.serialize(array, buffer_callback=buffer_objects.append) + +# 写入不同的流类型 +for buffer_obj in buffer_objects: + # 写入 BytesIO(内存流) + bytes_stream = io.BytesIO() + buffer_obj.write_to(bytes_stream) + + # 写入文件 + with open('/tmp/buffer_data.bin', 'wb') as f: + buffer_obj.write_to(f) + + # 获取零拷贝 memoryview(用于连续缓冲区) + mv = buffer_obj.getbuffer() + assert isinstance(mv, memoryview) +``` + +**注意**:对于连续内存缓冲区(如 bytes、numpy 数组),`getbuffer()` 返回零拷贝 `memoryview`。对于非连续数据,可能会创建副本以确保连续性。 + +## 相关主题 + +- [NumPy 集成](numpy-integration.md) - NumPy 数组序列化 +- [基础序列化](basic-serialization.md) - 标准序列化 +- [配置](configuration.md) - Fory 参数 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/python/row-format.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/python/row-format.md new file mode 100644 index 00000000000..d05aec1e161 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/python/row-format.md @@ -0,0 +1,169 @@ +--- +title: 行格式 +sidebar_position: 11 +id: row_format +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. +--- + +Apache Fory™ 提供随机访问行格式,可以在不完全反序列化的情况下从二进制数据中读取嵌套字段。 + +## 概述 + +当只需要部分数据访问时,行格式可以大幅减少处理大型对象的开销。它还支持内存映射文件,实现超低内存占用。 + +**核心优势:** + +| 特性 | 描述 | +| ------------ | ---------------------------------------- | +| 零拷贝访问 | 无需反序列化整个对象即可读取嵌套字段 | +| 内存效率 | 直接从磁盘内存映射大型数据集 | +| 跨语言 | Python、Java、C++ 之间的二进制格式兼容 | +| 部分反序列化 | 仅反序列化所需的特定元素 | +| 高性能 | 跳过不必要的数据解析,适用于分析工作负载 | + +## 基础用法 + +```python +import pyfory +import pyarrow as pa +from dataclasses import dataclass +from typing import List, Dict + +@dataclass +class Bar: + f1: str + f2: List[pa.int64] + +@dataclass +class Foo: + f1: pa.int32 + f2: List[pa.int32] + f3: Dict[str, pa.int32] + f4: List[Bar] + +# 为行格式创建编码器 +encoder = pyfory.encoder(Foo) + +# 创建大型数据集 +foo = Foo( + f1=10, + f2=list(range(1_000_000)), + f3={f"k{i}": i for i in range(1_000_000)}, + f4=[Bar(f1=f"s{i}", f2=list(range(10))) for i in range(1_000_000)] +) + +# 编码为行格式 +binary: bytes = encoder.to_row(foo).to_bytes() + +# 零拷贝访问 - 无需完全反序列化! +foo_row = pyfory.RowData(encoder.schema, binary) +print(foo_row.f2[100000]) # 直接访问第 100,000 个元素 +print(foo_row.f4[100000].f1) # 直接访问嵌套字段 +print(foo_row.f4[200000].f2[5]) # 直接访问深层嵌套字段 +``` + +## 跨语言兼容性 + +行格式可以跨语言无缝工作。相同的二进制数据可以从 Java 和 C++ 访问。 + +### Java + +```java +public class Bar { + String f1; + List f2; +} + +public class Foo { + int f1; + List f2; + Map f3; + List f4; +} + +RowEncoder encoder = Encoders.bean(Foo.class); + +// 编码为行格式(与 Python 跨语言兼容) +BinaryRow binaryRow = encoder.toRow(foo); + +// 零拷贝随机访问,无需完全反序列化 +BinaryArray f2Array = binaryRow.getArray(1); // 访问 f2 列表 +BinaryArray f4Array = binaryRow.getArray(3); // 访问 f4 列表 +BinaryRow bar10 = f4Array.getStruct(10); // 访问第 11 个 Bar +long value = bar10.getArray(1).getInt64(5); // 访问 bar.f2 的第 6 个元素 + +// 部分反序列化 - 仅反序列化所需内容 +RowEncoder barEncoder = Encoders.bean(Bar.class); +Bar bar1 = barEncoder.fromRow(f4Array.getStruct(10)); // 仅反序列化第 11 个 Bar +Bar bar2 = barEncoder.fromRow(f4Array.getStruct(20)); // 仅反序列化第 21 个 Bar +``` + +### C++ + +```cpp +#include "fory/encoder/row_encoder.h" +#include "fory/row/writer.h" + +struct Bar { + std::string f1; + std::vector f2; +}; + +FORY_FIELD_INFO(Bar, f1, f2); + +struct Foo { + int32_t f1; + std::vector f2; + std::map f3; + std::vector f4; +}; + +FORY_FIELD_INFO(Foo, f1, f2, f3, f4); + +fory::encoder::RowEncoder encoder; +encoder.Encode(foo); +auto row = encoder.GetWriter().ToRow(); + +// 零拷贝随机访问,无需完全反序列化 +auto f2_array = row->GetArray(1); // 访问 f2 列表 +auto f4_array = row->GetArray(3); // 访问 f4 列表 +auto bar10 = f4_array->GetStruct(10); // 访问第 11 个 Bar +int64_t value = bar10->GetArray(1)->GetInt64(5); // 访问 bar.f2 的第 6 个元素 +std::string str = bar10->GetString(0); // 访问 bar.f1 +``` + +## 安装 + +行格式需要 Apache Arrow: + +```bash +pip install pyfory[format] +``` + +## 何时使用行格式 + +- **分析工作负载**:当您只需要访问特定字段时 +- **大型数据集**:当完全反序列化成本太高时 +- **内存映射文件**:处理大于 RAM 的数据 +- **数据管道**:在不完全对象重建的情况下处理数据 +- **跨语言数据共享**:当数据需要从多种语言访问时 + +## 相关主题 + +- [跨语言序列化](xlang-serialization.md) - XLANG 模式 +- [基础序列化](basic-serialization.md) - 对象序列化 +- [行格式规范](https://fory.apache.org/docs/specification/row_format_spec) - 协议详情 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/python/schema-evolution.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/python/schema-evolution.md new file mode 100644 index 00000000000..d52a5060085 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/python/schema-evolution.md @@ -0,0 +1,77 @@ +--- +title: Schema 演化 +sidebar_position: 6 +id: schema_evolution +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. +--- + +Apache Fory™ 在兼容模式下支持 schema 演化,允许在保持兼容性的同时添加/删除字段。 + +## 启用兼容模式 + +```python +import pyfory + +f = pyfory.Fory(xlang=True, compatible=True) +``` + +## Schema 演化示例 + +```python +import pyfory +from dataclasses import dataclass + +# 版本 1:原始类 +@dataclass +class User: + name: str + age: int + +f = pyfory.Fory(xlang=True, compatible=True) +f.register(User, typename="User") +data = f.dumps(User("Alice", 30)) + +# 版本 2:添加新字段(向后兼容) +@dataclass +class User: + name: str + age: int + email: str = "unknown@example.com" # 带默认值的新字段 + +# 仍然可以反序列化旧数据 +user = f.loads(data) +print(user.email) # "unknown@example.com" +``` + +## 支持的变更 + +- **添加新字段**:带默认值 +- **删除字段**:具有额外字段的旧数据将被跳过 +- **重新排序字段**:字段按名称匹配,而不是位置 + +## 最佳实践 + +1. **始终为新字段提供默认值** +2. **使用 typename 实现跨语言兼容性** +3. **在部署前测试 schema 变更** +4. **为团队记录 schema 版本** + +## 相关主题 + +- [配置](configuration.md) - 兼容模式设置 +- [跨语言](xlang-serialization.md) - 跨语言的 schema 演化 +- [类型注册](type-registration.md) - 注册模式 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/python/schema-metadata.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/python/schema-metadata.md new file mode 100644 index 00000000000..7bb9535d5c8 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/python/schema-metadata.md @@ -0,0 +1,516 @@ +--- +title: Schema 元信息 +sidebar_position: 7 +id: schema_metadata +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. +--- + +本页说明如何在 Python 中为序列化配置字段级元信息。 + +## 概览 + +Apache Fory™ 通过以下方式提供字段级配置: + +- **`pyfory.field()`**:配置字段元信息(id、nullable、ref、ignore、dynamic) +- **类型注解**:控制整数编码(varint、fixed、tagged) +- **`Optional[T]`**:将字段标记为可空 + +这些配置支持: + +- **Tag ID**:分配紧凑的数字 ID,降低 struct 字段元信息大小开销 +- **可空性**:控制字段是否可以为 null +- **引用跟踪**:为共享对象启用引用跟踪 +- **跳过字段**:从序列化中排除字段 +- **编码控制**:指定整数的编码方式(varint、fixed、tagged) +- **多态**:控制是否为 struct 字段写入类型信息 + +## 基本语法 + +将 `@dataclass` 装饰器与类型注解和 `pyfory.field()` 配合使用: + +```python +from dataclasses import dataclass +from typing import Optional +import pyfory + +@dataclass +class Person: + name: str = pyfory.field(id=0) + age: pyfory.Int32 = pyfory.field(id=1, default=0) + nickname: Optional[str] = pyfory.field(id=2, nullable=True, default=None) +``` + +## `pyfory.field()` 函数 + +使用 `pyfory.field()` 配置字段级元信息: + +```python +@dataclass +class User: + id: pyfory.Int64 = pyfory.field(id=0, default=0) + name: str = pyfory.field(id=1, default="") + email: Optional[str] = pyfory.field(id=2, nullable=True, default=None) + friends: List["User"] = pyfory.field(id=3, ref=True, default_factory=list) + _cache: dict = pyfory.field(ignore=True, default_factory=dict) +``` + +### 参数 + +| 参数 | 类型 | 默认值 | 说明 | +| ----------------- | -------- | --------- | -------------------- | +| `id` | `int` | 省略 | 非负字段 tag ID | +| `nullable` | `bool` | `False` | 字段是否可以为 null | +| `ref` | `bool` | `False` | 启用引用跟踪 | +| `ignore` | `bool` | `False` | 从序列化中排除字段 | +| `dynamic` | `bool` | `None` | 控制是否写入类型信息 | +| `default` | Any | `MISSING` | 字段的默认值 | +| `default_factory` | Callable | `MISSING` | 默认值的工厂函数 | + +## 字段 ID(`id`) + +为字段分配数字 ID,以最小化 struct 字段元信息大小开销: + +```python +@dataclass +class User: + id: pyfory.Int64 = pyfory.field(id=0, default=0) + name: str = pyfory.field(id=1, default="") + age: pyfory.Int32 = pyfory.field(id=2, default=0) +``` + +**优点**: + +- 序列化尺寸更小(元信息中使用数字 ID,而不是字段名) +- 降低 struct 字段元信息开销 +- 允许重命名字段而不破坏二进制兼容性 + +**建议**:在兼容模式下建议配置字段 ID,因为它可以降低序列化成本。 + +**注意事项**: + +- ID 在同一个类内必须唯一 +- ID 必须 >= 0 +- 如果未指定,元信息会使用字段名(开销更大) + +**不使用字段 ID**(元信息中使用字段名): + +```python +@dataclass +class User: + id: pyfory.Int64 = 0 + name: str = "" +``` + +## 可空字段(`nullable`) + +对可以为 `None` 的字段使用 `nullable=True`: + +```python +from typing import Optional + +@dataclass +class Record: + # 可空字符串字段 + optional_name: Optional[str] = pyfory.field(id=0, nullable=True, default=None) + + # 可空整数字段 + optional_count: Optional[pyfory.Int32] = pyfory.field(id=1, nullable=True, default=None) +``` + +**注意事项**: + +- `Optional[T]` 字段必须设置 `nullable=True` +- 非 optional 字段默认 `nullable=False` + +## 引用跟踪(`ref`) + +为可能共享的字段启用引用跟踪。循环 Python 对象图需要启用全局引用跟踪的 Python 原生模式。 + +```python +@dataclass +class RefOuter: + # 两个字段都可能指向同一个内部对象 + inner1: Optional[RefInner] = pyfory.field(id=0, ref=True, nullable=True, default=None) + inner2: Optional[RefInner] = pyfory.field(id=1, ref=True, nullable=True, default=None) + + +@dataclass +class CircularRef: + name: str = pyfory.field(id=0, default="") + # 用于循环引用的自引用字段 + self_ref: Optional["CircularRef"] = pyfory.field(id=1, ref=True, nullable=True, default=None) +``` + +**使用场景**: + +- 为可能形成循环或共享的字段启用 +- 同一个对象被多个字段引用时启用 + +**注意事项**: + +- 必须启用全局 `Fory(ref=True)`。 +- 对于 schema 字段,字段级 `ref=True` 和全局 `ref=True` 都必须启用。 + +## 跳过字段(`ignore`) + +从序列化中排除字段: + +```python +@dataclass +class User: + id: pyfory.Int64 = pyfory.field(id=0, default=0) + name: str = pyfory.field(id=1, default="") + # 不会被序列化 + _cache: dict = pyfory.field(ignore=True, default_factory=dict) + _internal_state: str = pyfory.field(ignore=True, default="") +``` + +## 动态字段(`dynamic`) + +控制是否为 struct 字段写入类型信息。这对多态支持至关重要: + +```python +from abc import ABC, abstractmethod + +class Shape(ABC): + @abstractmethod + def area(self) -> float: + pass + +@dataclass +class Circle(Shape): + radius: float = 0.0 + + def area(self) -> float: + return 3.14159 * self.radius * self.radius + +@dataclass +class Container: + # 抽象类:dynamic 始终为 True(写入类型信息) + shape: Shape = pyfory.field(id=0) + + # 为具体类型强制写入类型信息(支持运行时子类型) + circle: Circle = pyfory.field(id=1, dynamic=True) + + # 对具体类型跳过类型信息(直接使用声明类型) + fixed_circle: Circle = pyfory.field(id=2, dynamic=False) +``` + +**默认行为**: + +| 模式 | 抽象类 | 具体对象类型 | 数值/str/time 类型 | +| ---------- | ------ | ------------ | ------------------ | +| 原生模式 | `True` | `True` | `False` | +| Xlang 模式 | `True` | `False` | `False` | + +**注意事项**: + +- **抽象类**:`dynamic` 始终为 `True`(必须写入类型信息) +- **原生模式**:对象类型的 `dynamic` 默认为 `True`,数值/str/time 类型默认为 `False` +- **Xlang 模式**:具体类型的 `dynamic` 默认为 `False` +- 当具体字段可能保存子类实例时,使用 `dynamic=True` +- 当类型已知且需要性能优化时,使用 `dynamic=False` + +## 整数类型注解 + +Fory 提供类型注解来控制整数编码: + +### 有符号整数 + +```python +@dataclass +class SignedIntegers: + byte_val: pyfory.Int8 = 0 # 8 位有符号 + short_val: pyfory.Int16 = 0 # 16 位有符号 + int_val: pyfory.Int32 = 0 # 32 位有符号(varint 编码) + long_val: pyfory.Int64 = 0 # 64 位有符号(varint 编码) +``` + +### 无符号整数 + +```python +@dataclass +class UnsignedIntegers: + # 定长编码 + u8_val: pyfory.UInt8 = 0 # 8 位无符号(fixed) + u16_val: pyfory.UInt16 = 0 # 16 位无符号(fixed) + + # 变长编码(u32/u64 的默认值) + u32_var: pyfory.UInt32 = 0 # 32 位无符号(varint) + u64_var: pyfory.UInt64 = 0 # 64 位无符号(varint) + + # 显式定长编码 + u32_fixed: pyfory.FixedUInt32 = 0 # 32 位无符号(fixed 4 字节) + u64_fixed: pyfory.FixedUInt64 = 0 # 64 位无符号(fixed 8 字节) + + # Tagged 编码(包含类型 tag) + u64_tagged: pyfory.TaggedUInt64 = 0 # 64 位无符号(tagged) +``` + +### 浮点数 + +```python +@dataclass +class FloatingPoint: + float_val: pyfory.Float32 = 0.0 # 32 位浮点数 + double_val: pyfory.Float64 = 0.0 # 64 位双精度浮点数 +``` + +### 编码汇总 + +| 类型 | 编码 | 大小 | +| --------------------- | -------- | --------- | +| `pyfory.Int8` | fixed | 1 字节 | +| `pyfory.Int16` | fixed | 2 字节 | +| `pyfory.Int32` | varint | 1-5 字节 | +| `pyfory.Int64` | varint | 1-10 字节 | +| `pyfory.FixedInt32` | fixed | 4 字节 | +| `pyfory.FixedInt64` | fixed | 8 字节 | +| `pyfory.TaggedInt64` | tagged | 1-9 字节 | +| `pyfory.UInt8` | fixed | 1 字节 | +| `pyfory.UInt16` | fixed | 2 字节 | +| `pyfory.UInt32` | varint | 1-5 字节 | +| `pyfory.UInt64` | varint | 1-10 字节 | +| `pyfory.FixedUInt32` | fixed | 4 字节 | +| `pyfory.FixedUInt64` | fixed | 8 字节 | +| `pyfory.TaggedUInt64` | tagged | 1-9 字节 | +| `pyfory.Float32` | fixed | 4 字节 | +| `pyfory.Float64` | fixed | 8 字节 | + +**使用时机**: + +- `varint`:最适合经常较小的值(int32/int64/uint32/uint64 的默认值) +- `fixed`:最适合使用完整范围的值(例如时间戳、哈希) +- `tagged`:需要保留类型信息时使用(仅 int64/uint64) + +## 嵌套容器类型注解 + +整数编码别名可以在声明的集合 schema 内使用。无论在纯 Python 还是 Cython 模式中,Fory 都会对每个嵌套元素、键和值使用声明的字段 schema: + +```python +from dataclasses import dataclass, field +from typing import Dict, List +import pyfory + + +@dataclass +class Counters: + values: Dict[pyfory.FixedInt32, List[pyfory.TaggedInt64]] = field(default_factory=dict) +``` + +对于 `values`,map 的键会写成定长 int32 值,每个嵌套 list 元素会写成 tagged int64。运行时类型推断仅用于动态或未知的容器 schema。 + +在兼容模式下,读取端使用远端 schema 元信息消费字段字节。只有在解码值能够安全满足本地声明 schema 时,Python 才会赋值。相同符号性和宽度范围内的不同整数编码是兼容的;相同符号性的窄化转换只会在范围校验后赋值。 + +## 完整示例 + +```python +from dataclasses import dataclass +from typing import Optional, List, Dict, Set +import pyfory + + +@dataclass +class Document: + # 带 tag ID 的字段(兼容模式推荐) + title: str = pyfory.field(id=0, default="") + version: pyfory.Int32 = pyfory.field(id=1, default=0) + + # 可空字段 + description: Optional[str] = pyfory.field(id=2, nullable=True, default=None) + + # 集合字段 + tags: List[str] = pyfory.field(id=3, default_factory=list) + metadata: Dict[str, str] = pyfory.field(id=4, default_factory=dict) + categories: Set[str] = pyfory.field(id=5, default_factory=set) + + # 使用不同编码的无符号整数 + view_count: pyfory.UInt64 = pyfory.field(id=6, default=0) # varint 编码 + file_size: pyfory.FixedUInt64 = pyfory.field(id=7, default=0) # fixed 编码 + checksum: pyfory.TaggedUInt64 = pyfory.field(id=8, default=0) # tagged 编码 + + # 用于共享/循环引用的引用跟踪字段 + parent: Optional["Document"] = pyfory.field(id=9, ref=True, nullable=True, default=None) + + # 被忽略的字段(不序列化) + _cache: dict = pyfory.field(ignore=True, default_factory=dict) + + +def main(): + fory = pyfory.Fory(xlang=True, ref=True) + fory.register_type(Document, type_id=100) + + doc = Document( + title="My Document", + version=1, + description="A sample document", + tags=["tag1", "tag2"], + metadata={"key": "value"}, + categories={"cat1"}, + view_count=42, + file_size=1024, + checksum=123456789, + parent=None, + ) + + # 序列化 + data = fory.serialize(doc) + + # 反序列化 + decoded = fory.deserialize(data) + assert decoded.title == doc.title + assert decoded.version == doc.version + + +if __name__ == "__main__": + main() +``` + +## 跨语言兼容性 + +当序列化的数据需要被其他语言(Java、Rust、C++、Go)读取时,请使用字段 ID 和匹配的类型注解: + +```python +@dataclass +class CrossLangData: + # 使用字段 ID 实现跨语言兼容性 + int_var: pyfory.Int32 = pyfory.field(id=0, default=0) + long_fixed: pyfory.FixedUInt64 = pyfory.field(id=1, default=0) + long_tagged: pyfory.TaggedUInt64 = pyfory.field(id=2, default=0) + optional_value: Optional[str] = pyfory.field(id=3, nullable=True, default=None) +``` + +## Schema 演进 + +兼容模式支持 Schema 演进。建议配置字段 ID 以降低序列化成本: + +```python +# 版本 1 +@dataclass +class DataV1: + id: pyfory.Int64 = pyfory.field(id=0, default=0) + name: str = pyfory.field(id=1, default="") + + +# 版本 2:新增字段 +@dataclass +class DataV2: + id: pyfory.Int64 = pyfory.field(id=0, default=0) + name: str = pyfory.field(id=1, default="") + email: Optional[str] = pyfory.field(id=2, nullable=True, default=None) # 新字段 +``` + +使用 V1 序列化的数据可以用 V2 反序列化(新字段会是 `None`)。 + +也可以省略字段 ID(元信息中会使用字段名,开销更大): + +```python +@dataclass +class Data: + id: pyfory.Int64 = 0 + name: str = "" +``` + +## 原生模式与 Xlang 模式 + +字段配置的行为取决于序列化模式: + +### 原生模式(仅 Python) + +原生模式使用**更宽松的默认值**以获得最大兼容性: + +- **可空性**:`str` 和数值类型默认不可空,除非使用 `Optional` +- **引用跟踪**:默认对对象引用启用(`str` 和数值类型除外) + +在原生模式中,通常**不需要配置字段注解**,除非你希望: + +- 通过使用字段 ID 降低序列化尺寸 +- 通过禁用不必要的引用跟踪来优化性能 + +```python +# 原生模式:不需要 schema 元信息也能工作 +@dataclass +class User: + id: int = 0 + name: str = "" + tags: List[str] = None +``` + +### Xlang 模式(跨语言) + +由于语言之间的类型系统差异,Xlang 模式使用**更严格的默认值**: + +- **可空性**:字段默认不可空(`nullable=False`) +- **引用跟踪**:默认禁用(`ref=False`) + +在 xlang 模式中,当出现以下情况时,你**需要配置字段**: + +- 字段可以为 None(使用带 `nullable=True` 的 `Optional[T]`) +- 字段需要为共享/循环对象启用引用跟踪(使用 `ref=True`) +- 整数类型需要用于跨语言兼容性的特定编码 +- 你希望降低元信息大小(使用字段 ID) + +```python +# Xlang 模式:可空/ref 字段需要显式配置 +@dataclass +class User: + id: pyfory.Int64 = pyfory.field(id=0, default=0) + name: str = pyfory.field(id=1, default="") + email: Optional[str] = pyfory.field(id=2, nullable=True, default=None) # 必须声明可空 + friend: Optional["User"] = pyfory.field(id=3, ref=True, nullable=True, default=None) # 必须声明 ref +``` + +### 默认值汇总 + +| 选项 | 原生模式默认值 | Xlang 模式默认值 | +| ---------- | ------------------------------------------ | ------------------- | +| `nullable` | `str`/数值类型为 `False`;其他类型默认可空 | `False` | +| `ref` | `True`(`str` 和数值类型除外) | `False` | +| `dynamic` | `True`(数值/str/time 类型除外) | `False`(具体类型) | + +## 最佳实践 + +1. **配置字段 ID**:在兼容模式下推荐使用,可降低序列化成本 +2. **使用带 `nullable=True` 的 `Optional[T]`**:xlang 模式中可空字段必需 +3. **为共享对象启用引用跟踪**:当对象共享或形成循环时使用 `ref=True` +4. **对敏感数据使用 `ignore=True`**:密码、令牌、内部状态 +5. **选择合适的编码**:小值使用 `varint`,完整范围值使用 `fixed` +6. **保持 ID 稳定**:一旦分配,不要更改字段 ID + +## 选项参考 + +| 配置 | 说明 | +| ------------------------------------------ | -------------------------------- | +| `pyfory.field(id=N)` | 用于降低元信息大小的字段 tag ID | +| `pyfory.field(nullable=True)` | 将字段标记为可空 | +| `pyfory.field(ref=True)` | 启用引用跟踪 | +| `pyfory.field(ignore=True)` | 从序列化中排除字段 | +| `pyfory.field(dynamic=True)` | 强制写入类型信息 | +| `pyfory.field(dynamic=False)` | 跳过类型信息(使用声明类型) | +| `Optional[T]` | 可空字段的类型提示 | +| `pyfory.Int32`, `pyfory.Int64` | 有符号整数(varint 编码) | +| `pyfory.FixedInt32`, `pyfory.FixedInt64` | 定长有符号整数 | +| `pyfory.TaggedInt64` | int64 的 tagged 编码 | +| `pyfory.UInt32`, `pyfory.UInt64` | 无符号整数(varint 编码) | +| `pyfory.FixedUInt32`, `pyfory.FixedUInt64` | 定长无符号整数 | +| `pyfory.TaggedUInt64` | uint64 的 tagged 编码 | + +## 相关主题 + +- [基本序列化](basic-serialization.md) - Fory 序列化入门 +- [Schema 演进](schema-evolution.md) - 兼容模式和 Schema 演进 +- [Xlang 序列化](xlang-serialization.md) - 与 Java、Rust、C++、Go 互操作 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/python/troubleshooting.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/python/troubleshooting.md new file mode 100644 index 00000000000..066816f7ee2 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/python/troubleshooting.md @@ -0,0 +1,186 @@ +--- +title: 故障排除 +sidebar_position: 13 +id: troubleshooting +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. +--- + +本页介绍常见问题及其解决方案。 + +## 常见问题 + +### 格式功能的 ImportError + +```python +# 解决方案:安装行格式支持 +pip install pyfory[format] + +# 或从源码安装格式支持 +pip install -e ".[format]" +``` + +### 序列化性能慢 + +```python +# 检查是否启用了 Cython 加速 +import pyfory +print(pyfory.ENABLE_FORY_CYTHON_SERIALIZATION) # 应该为 True + +# 如果为 False,Cython 扩展可能未正确编译 +# 重新安装:pip install --force-reinstall --no-cache-dir pyfory +``` + +### 跨语言兼容性问题 + +```python +# 使用一致的命名进行显式类型注册 +f = pyfory.Fory(xlang=True) +f.register(MyClass, typename="com.package.MyClass") # 在所有语言中使用相同的名称 +``` + +### 循环引用错误或重复数据 + +```python +# 启用引用跟踪 +f = pyfory.Fory(ref=True) # 循环引用所需 + +# 循环引用示例 +class Node: + def __init__(self, value): + self.value = value + self.next = None + +node1 = Node(1) +node2 = Node(2) +node1.next = node2 +node2.next = node1 # 循环引用 + +data = f.dumps(node1) +result = f.loads(data) +assert result.next.next is result # 循环引用被保留 +``` + +### Schema 演化不工作 + +```python +# 启用兼容模式用于 schema 演化 +f = pyfory.Fory(xlang=True, compatible=True) + +# 版本 1:原始类 +@dataclass +class User: + name: str + age: int + +f.register(User, typename="User") +data = f.dumps(User("Alice", 30)) + +# 版本 2:添加新字段(向后兼容) +@dataclass +class User: + name: str + age: int + email: str = "unknown@example.com" # 带默认值的新字段 + +# 仍然可以反序列化旧数据 +user = f.loads(data) +print(user.email) # "unknown@example.com" +``` + +### 严格模式下的类型注册错误 + +```python +# 在序列化之前注册所有自定义类型 +f = pyfory.Fory(strict=True) + +# 使用前必须注册 +f.register(MyClass, type_id=100) +f.register(AnotherClass, type_id=101) + +# 或禁用严格模式(生产环境不推荐) +f = pyfory.Fory(strict=False) # 仅在受信任的环境中使用 +``` + +## 调试模式 + +在导入 pyfory 之前设置环境变量以禁用 Cython 进行调试: + +```python +import os +os.environ['ENABLE_FORY_CYTHON_SERIALIZATION'] = '0' +import pyfory # 现在使用纯 Python 实现 + +# 这对以下情况很有用: +# 1. 调试协议问题 +# 2. 理解序列化行为 +# 3. 无需重新编译 Cython 的开发 +``` + +## 错误处理 + +优雅地处理常见的序列化错误: + +```python +import pyfory +from pyfory.error import TypeUnregisteredError, TypeNotCompatibleError + +fory = pyfory.Fory(strict=True) + +try: + data = fory.dumps(my_object) +except TypeUnregisteredError as e: + print(f"类型未注册:{e}") + # 注册类型并重试 + fory.register(type(my_object), type_id=100) + data = fory.dumps(my_object) +except Exception as e: + print(f"序列化失败:{e}") + +try: + obj = fory.loads(data) +except TypeNotCompatibleError as e: + print(f"Schema 不匹配:{e}") + # 处理版本不匹配 +except Exception as e: + print(f"反序列化失败:{e}") +``` + +## 开发环境设置 + +```bash +git clone https://github.com/apache/fory.git +cd fory/python + +# 安装依赖 +pip install -e ".[dev,format]" + +# 运行测试 +pytest -v -s . + +# 运行特定测试 +pytest -v -s pyfory/tests/test_serializer.py + +# 格式化代码 +ruff format . +ruff check --fix . +``` + +## 相关主题 + +- [配置](configuration.md) - Fory 参数 +- [类型注册](type-registration.md) - 注册最佳实践 +- [安全性](configuration.md) - 安全配置 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/python/type-registration.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/python/type-registration.md new file mode 100644 index 00000000000..58914e0df8a --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/python/type-registration.md @@ -0,0 +1,146 @@ +--- +title: 类型注册与安全性 +sidebar_position: 3 +id: type_registration +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. +--- + +本页介绍类型注册机制和安全配置。 + +## 类型注册 + +在严格模式下,只有已注册的类型才能被反序列化。这可以防止任意代码执行: + +```python +import pyfory + +# 严格模式(生产环境推荐) +f = pyfory.Fory(strict=True) + +class SafeClass: + def __init__(self, data): + self.data = data + +# 在严格模式下必须注册类型 +f.register(SafeClass, typename="com.example.SafeClass") + +# 现在序列化可以正常工作 +obj = SafeClass("safe data") +data = f.serialize(obj) +result = f.deserialize(data) + +# 未注册的类型将引发异常 +class UnsafeClass: + pass + +# 在严格模式下这将失败 +try: + f.serialize(UnsafeClass()) +except Exception as e: + print("安全保护已激活!") +``` + +## 注册模式 + +### 模式 1:简单注册 + +```python +fory.register(MyClass, type_id=100) +``` + +### 模式 2:使用类型名称的跨语言注册 + +```python +fory.register(MyClass, typename="com.example.MyClass") +``` + +### 模式 3:使用自定义序列化器 + +```python +fory.register(MyClass, type_id=100, serializer=MySerializer(fory.type_resolver, MyClass)) +``` + +### 模式 4:批量注册 + +```python +type_id = 100 +for model_class in [User, Order, Product, Invoice]: + fory.register(model_class, type_id=type_id) + type_id += 1 +``` + +## 安全模式 + +### 严格模式(推荐) + +```python +# 生产环境始终使用 strict=True +fory = pyfory.Fory(strict=True) + +# 显式注册允许的类型 +fory.register(UserModel, type_id=100) +fory.register(OrderModel, type_id=101) +``` + +### 非严格模式 + +**⚠️ 安全警告**:当 `strict=False` 时,Fory 将反序列化任意类型,如果数据来自不受信任的源,这可能带来安全风险。仅在完全信任数据源的受控环境中使用 `strict=False`。 + +```python +# 仅在受信任的环境中 +fory = pyfory.Fory(xlang=False, ref=True, strict=False) +``` + +如果确实需要使用 `strict=False`,请配置 `DeserializationPolicy`: + +```python +from pyfory import DeserializationPolicy + +class SafePolicy(DeserializationPolicy): + def validate_class(self, cls, is_local, **kwargs): + # 阻止危险模块 + if cls.__module__ in {'subprocess', 'os', '__builtin__'}: + raise ValueError(f"已阻止:{cls}") + return None + +fory = pyfory.Fory(xlang=False, strict=False, policy=SafePolicy()) +``` + +## 最大深度保护 + +限制反序列化深度以防止栈溢出攻击: + +```python +fory = pyfory.Fory( + strict=True, + max_depth=100 # 根据数据结构深度调整 +) +``` + +## 最佳实践 + +1. **生产环境始终使用 `strict=True`** +2. **使用 `type_id` 提高性能**,使用 `typename` 提高灵活性 +3. **在任何序列化之前预先注册所有类型** +4. **根据数据结构设置适当的 `max_depth`** +5. **必要时使用 `DeserializationPolicy`** 当需要 `strict=False` 时 + +## 相关主题 + +- [配置](configuration.md) - Fory 参数 +- [安全性](configuration.md) - DeserializationPolicy 详情 +- [自定义序列化器](custom-serializers.md) - 自定义序列化 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/python/xlang-serialization.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/python/xlang-serialization.md new file mode 100644 index 00000000000..aea79f804c7 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/python/xlang-serialization.md @@ -0,0 +1,140 @@ +--- +title: Xlang 序列化 +sidebar_position: 2 +id: xlang_serialization +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. +--- + +`pyfory` 支持跨语言对象图序列化,允许您在 Python 中序列化数据,并在 Java、Go、Rust 或其他支持的语言中反序列化。 + +## 启用xlang 模式 + +要使用 xlang 模式,创建 `Fory` 时设置 `xlang=True`: + +```python +import pyfory +fory = pyfory.Fory(xlang=True, ref=False, strict=True) +``` + +## 跨语言示例 + +### Python(序列化器) + +```python +import pyfory +from dataclasses import dataclass + +# xlang 模式实现互操作性 +f = pyfory.Fory(xlang=True, ref=True) + +# 注册类型以实现跨语言兼容性 +@dataclass +class Person: + name: str + age: pyfory.int32 + +f.register(Person, typename="example.Person") + +person = Person("Charlie", 35) +binary_data = f.serialize(person) +# binary_data 现在可以发送到 Java、Go 等 +``` + +### Java(反序列化器) + +```java +import org.apache.fory.*; + +public class Person { + public String name; + public int age; +} + +Fory fory = Fory.builder() + .withLanguage(Language.XLANG) + .withRefTracking(true) + .build(); + +fory.register(Person.class, "example.Person"); +Person person = (Person) fory.deserialize(binaryData); +``` + +### Rust(反序列化器) + +```rust +use fory::Fory; +use fory::ForyObject; + +#[derive(ForyObject)] +struct Person { + name: String, + age: i32, +} + +let mut fory = Fory::default() + .compatible(true) + .xlang(true); + +fory.register_by_namespace::("example", "Person"); +let person: Person = fory.deserialize(&binary_data)?; +``` + +## 跨语言的类型注解 + +使用 pyfory 类型注解进行显式跨语言类型映射: + +```python +from dataclasses import dataclass +import pyfory + +@dataclass +class TypedData: + int_value: pyfory.int32 # 32 位整数 + long_value: pyfory.int64 # 64 位整数 + float_value: pyfory.float32 # 32 位浮点数 + double_value: pyfory.float64 # 64 位浮点数 +``` + +## 类型映射 + +| Python | Java | Rust | Go | +| ---------------- | -------- | --------- | --------- | +| `str` | `String` | `String` | `string` | +| `int` | `long` | `i64` | `int64` | +| `pyfory.int32` | `int` | `i32` | `int32` | +| `pyfory.int64` | `long` | `i64` | `int64` | +| `float` | `double` | `f64` | `float64` | +| `pyfory.float32` | `float` | `f32` | `float32` | +| `list` | `List` | `Vec` | `[]T` | +| `dict` | `Map` | `HashMap` | `map[K]V` | + +## 与 Python 原生模式的区别 + +二进制协议和 API 与 `pyfory` 的 Python 原生模式类似,但 Python 原生模式可以序列化任何 Python 对象——包括全局函数、局部函数、lambda、局部类以及使用 `__getstate__/__reduce__/__reduce_ex__` 自定义序列化的类型,这些在 xlang 模式中**不允许**。 + +## 另请参阅 + +- [xlang 序列化规范](https://fory.apache.org/docs/next/specification/fory_xlang_serialization_spec) +- [类型映射参考](https://fory.apache.org/docs/next/specification/xlang_type_mapping) +- [Java 跨语言指南](../java/xlang-serialization.md) +- [Rust 跨语言指南](../rust/xlang-serialization.md) + +## 相关主题 + +- [配置](configuration.md) - XLANG 模式设置 +- [Schema 演化](schema-evolution.md) - 兼容模式 +- [类型注册](type-registration.md) - 注册模式 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/rust/_category_.json b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/rust/_category_.json new file mode 100644 index 00000000000..e891086214a --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/rust/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "Rust", + "position": 3, + "collapsible": true, + "collapsed": true +} diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/rust/basic-serialization.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/rust/basic-serialization.md new file mode 100644 index 00000000000..c9f65dda280 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/rust/basic-serialization.md @@ -0,0 +1,191 @@ +--- +title: 基础序列化 +sidebar_position: 2 +id: basic_serialization +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. +--- + +本页涵盖基础对象图序列化和支持的类型。 + +## 对象图序列化 + +Apache Fory™ 提供复杂对象图的自动序列化,保留对象之间的结构和关系。`#[derive(ForyObject)]` 宏在编译时生成高效的序列化代码,消除运行时开销。 + +**核心功能:** + +- 任意深度的嵌套结构体序列化 +- 集合类型(Vec、HashMap、HashSet、BTreeMap) +- 使用 `Option` 的可选字段 +- 原始类型和字符串的自动处理 +- 使用变长整数的高效二进制编码 + +```rust +use fory::{Fory, Error}; +use fory::ForyObject; +use std::collections::HashMap; + +#[derive(ForyObject, Debug, PartialEq)] +struct Person { + name: String, + age: i32, + address: Address, + hobbies: Vec, + metadata: HashMap, +} + +#[derive(ForyObject, Debug, PartialEq)] +struct Address { + street: String, + city: String, + country: String, +} + +let mut fory = Fory::default(); +fory.register::
(100); +fory.register::(200); + +let person = Person { + name: "John Doe".to_string(), + age: 30, + address: Address { + street: "123 Main St".to_string(), + city: "New York".to_string(), + country: "USA".to_string(), + }, + hobbies: vec!["reading".to_string(), "coding".to_string()], + metadata: HashMap::from([ + ("role".to_string(), "developer".to_string()), + ]), +}; + +let bytes = fory.serialize(&person); +let decoded: Person = fory.deserialize(&bytes)?; +assert_eq!(person, decoded); +``` + +## 支持的类型 + +### 原始类型 + +| Rust 类型 | 描述 | +| ------------------------- | ------------ | +| `bool` | 布尔值 | +| `i8`, `i16`, `i32`, `i64` | 有符号整数 | +| `f32`, `f64` | 浮点数 | +| `String` | UTF-8 字符串 | + +### 集合类型 + +| Rust 类型 | 描述 | +| ---------------- | -------- | +| `Vec` | 动态数组 | +| `VecDeque` | 双端队列 | +| `LinkedList` | 双向链表 | +| `HashMap` | 哈希映射 | +| `BTreeMap` | 有序映射 | +| `HashSet` | 哈希集合 | +| `BTreeSet` | 有序集合 | +| `BinaryHeap` | 二叉堆 | +| `Option` | 可选值 | + +### 智能指针 + +| Rust 类型 | 描述 | +| ------------ | -------------------------------------- | +| `Box` | 堆分配 | +| `Rc` | 引用计数(跟踪共享引用) | +| `Arc` | 线程安全引用计数(跟踪共享引用) | +| `RcWeak` | 指向 `Rc` 的弱引用(打破循环引用) | +| `ArcWeak` | 指向 `Arc` 的弱引用(打破循环引用) | +| `RefCell` | 内部可变性(运行时借用检查) | +| `Mutex` | 线程安全内部可变性 | + +### 日期和时间 + +| Rust 类型 | 描述 | +| ----------- | -------------------------------- | +| `Date` | 不带时区的日期,存储为 epoch 天数 | +| `Timestamp` | 时间点,存储为 epoch 秒和纳秒 | +| `Duration` | 有符号时长,存储为秒和规范化纳秒 | + +内置承载类型提供无额外依赖的构造器、访问器、转换和 checked 算术: + +```rust +use fory::{Date, Duration, Timestamp}; + +let date = Date::from_epoch_days(19_782); +assert_eq!(date.checked_add_days(1)?.epoch_days(), 19_783); + +let timestamp = Timestamp::from_epoch_millis(-1); +assert_eq!(timestamp.to_epoch_millis()?, -1); + +let duration = Duration::from_parts(1, 1_500_000_000)?; +assert_eq!(duration.to_millis()?, 2_500); +let later = timestamp.checked_add_duration(duration)?; +``` + +启用 Rust `chrono` feature 后,也支持 `chrono::NaiveDate`、`chrono::NaiveDateTime` 和 +`chrono::Duration`: + +```toml +[dependencies] +fory = { version = "1.3.0", features = ["chrono"] } +``` + +### 自定义类型 + +| 宏 | 描述 | +| ----------------------- | ------------ | +| `#[derive(ForyObject)]` | 对象图序列化 | +| `#[derive(ForyRow)]` | 行格式序列化 | + +## 序列化 API + +```rust +use fory::{Fory, Reader}; + +let mut fory = Fory::default(); +fory.register::(1)?; + +let obj = MyStruct { /* ... */ }; + +// 基础序列化/反序列化 +let bytes = fory.serialize(&obj)?; +let decoded: MyStruct = fory.deserialize(&bytes)?; + +// 序列化到现有缓冲区 +let mut buf: Vec = vec![]; +fory.serialize_to(&mut buf, &obj)?; + +// 从 reader 反序列化 +let mut reader = Reader::new(&buf); +let decoded: MyStruct = fory.deserialize_from(&mut reader)?; +``` + +## 性能优化建议 + +- **零拷贝反序列化**:行格式支持直接内存访问而无需复制 +- **缓冲区预分配**:在序列化期间最小化内存分配 +- **紧凑编码**:变长编码以提高空间效率 +- **小端序**:为现代 CPU 架构优化 +- **引用去重**:共享对象仅序列化一次 + +## 相关主题 + +- [类型注册](type-registration.md) - 注册类型 +- [引用](references.md) - 共享引用和循环引用 +- [自定义序列化器](custom-serializers.md) - 手动序列化 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/rust/configuration.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/rust/configuration.md new file mode 100644 index 00000000000..e0a8d06a7e7 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/rust/configuration.md @@ -0,0 +1,154 @@ +--- +title: 配置 +sidebar_position: 1 +id: configuration +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. +--- + +本页涵盖 Fory 配置选项和序列化模式。 + +## 序列化模式 + +Apache Fory™ 支持两种序列化模式: + +### SchemaConsistent 模式(默认) + +类型声明必须在对等方之间完全匹配: + +```rust +let fory = Fory::default(); // 默认为 SchemaConsistent +``` + +### Compatible 模式 + +允许独立的 schema 演化: + +```rust +let fory = Fory::default().compatible(true); +``` + +## 配置选项 + +### 最大动态对象嵌套深度 + +Apache Fory™ 提供针对反序列化期间深度嵌套动态对象导致的栈溢出保护。默认情况下,trait 对象和容器的最大嵌套深度设置为 5 层。 + +**默认配置:** + +```rust +let fory = Fory::default(); // max_dyn_depth = 5 +``` + +**自定义深度限制:** + +```rust +let fory = Fory::default().max_dyn_depth(10); // 允许最多 10 层 +``` + +**何时调整:** + +- **增加**:用于合法的深度嵌套数据结构 +- **减少**:用于更严格的安全要求或浅层数据结构 + +**受保护的类型:** + +- `Box`、`Rc`、`Arc` +- `Box`、`Rc`、`Arc`(trait 对象) +- `RcWeak`、`ArcWeak` +- 集合类型(Vec、HashMap、HashSet) +- Compatible 模式下的嵌套结构体类型 + +注意:静态数据类型(非动态类型)本质上是安全的,不受深度限制约束,因为它们的结构在编译时就已知。 + +### 远端 Schema Metadata 限制 + +兼容模式可能接收用于 Schema 演进的远端 metadata。以下限制用于约束 metadata 大小和可接受的 schema 版本数: + +```rust +let fory = Fory::builder() + .max_type_fields(512) + .max_type_meta_bytes(4096) + .max_schema_versions_per_type(10) + .max_average_schema_versions_per_type(3) + .build(); +``` + +- `max_type_fields` 默认值为 `512`,限制一个收到的 struct metadata body 中的字段数。 +- `max_type_meta_bytes` 默认值为 `4096`,限制一个收到的 TypeDef 或 TypeMeta body 的编码 body 字节数,不包含 8 字节 header 和扩展 size varint。 +- `max_schema_versions_per_type` 默认值为 `10`,限制一个逻辑类型可接受的远端 metadata 版本数。 +- `max_average_schema_versions_per_type` 默认值为 `3`,限制所有已接受远端类型的平均版本数;有效全局下限为 `8192` 个 schema。 + +### 跨语言模式 + +启用跨语言序列化: + +```rust +let fory = Fory::default() + .compatible(true) + .xlang(true); +``` + +## 构建器模式 + +```rust +use fory::Fory; + +// 默认配置 +let fory = Fory::default(); + +// 用于 schema 演化的兼容模式 +let fory = Fory::default().compatible(true); + +// 跨语言模式 +let fory = Fory::default() + .compatible(true) + .xlang(true); + +// 自定义深度限制 +let fory = Fory::default().max_dyn_depth(10); + +// 组合配置 +let fory = Fory::default() + .compatible(true) + .xlang(true) + .max_dyn_depth(10); +``` + +## 配置摘要 + +| 选项 | 描述 | 默认值 | +| --------------------------------------------- | --------------------------------- | ------- | +| `compatible(bool)` | 启用 schema 演化 | `false` | +| `xlang(bool)` | 启用跨语言模式 | `false` | +| `max_dyn_depth(u32)` | 动态类型的最大嵌套深度 | `5` | +| `max_type_fields(usize)` | 一个收到的 struct metadata body 最大字段数 | `512` | +| `max_type_meta_bytes(usize)` | 一个收到的 metadata body 最大编码字节数 | `4096` | +| `max_schema_versions_per_type(usize)` | 一个逻辑类型最大远端 metadata 版本数 | `10` | +| `max_average_schema_versions_per_type(usize)` | 所有远端类型的平均 metadata 版本数 | `3` | + +## 安全建议 + +- 反序列化不可信 payload 前,先注册应用 struct 和 trait-object 实现。 +- 使用 `max_dyn_depth(...)` 拒绝异常深的动态对象图。 +- 除非数据不是恶意输入,且可信 peer 会发送更大的 metadata 或大量 schema 版本,否则保持远端 schema metadata 限制的默认值。 +- 对不可信输入,优先使用具体类型字段,避免宽泛的 `dyn Any` 或 trait-object 字段。 + +## 相关主题 + +- [基础序列化](basic-serialization.md) - 使用已配置的 Fory +- [Schema 演化](schema-evolution.md) - Compatible 模式详情 +- [跨语言](xlang-serialization.md) - XLANG 模式 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/rust/custom-serializers.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/rust/custom-serializers.md new file mode 100644 index 00000000000..a51d0b9b3b6 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/rust/custom-serializers.md @@ -0,0 +1,147 @@ +--- +title: 自定义序列化器 +sidebar_position: 4 +id: custom_serializers +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. +--- + +对于不支持 `#[derive(ForyObject)]` 的类型,可以手动实现 `Serializer` trait。 + +## 何时使用自定义序列化器 + +- 来自其他 crate 的外部类型 +- 具有特殊序列化要求的类型 +- 旧数据格式兼容性 +- 性能关键的自定义编码 + +## 实现 Serializer Trait + +```rust +use fory::{Fory, ReadContext, WriteContext, Serializer, ForyDefault, Error}; +use std::any::Any; + +#[derive(Debug, PartialEq)] +struct CustomType { + value: i32, + name: String, +} + +impl Serializer for CustomType { + fn fory_write_data(&self, context: &mut WriteContext, is_field: bool) { + context.writer.write_i32(self.value); + context.writer.write_varuint32(self.name.len() as u32); + context.writer.write_utf8_string(&self.name); + } + + fn fory_read_data(context: &mut ReadContext, is_field: bool) -> Result { + let value = context.reader.read_i32(); + let len = context.reader.read_varuint32() as usize; + let name = context.reader.read_utf8_string(len); + Ok(Self { value, name }) + } + + fn fory_type_id_dyn(&self, type_resolver: &TypeResolver) -> u32 { + Self::fory_get_type_id(type_resolver) + } + + fn as_any(&self) -> &dyn Any { + self + } +} + +impl ForyDefault for CustomType { + fn fory_default() -> Self { + Self::default() + } +} +``` + +## 注册自定义序列化器 + +```rust +let mut fory = Fory::default(); +fory.register_serializer::(100); + +let custom = CustomType { + value: 42, + name: "test".to_string(), +}; +let bytes = fory.serialize(&custom); +let decoded: CustomType = fory.deserialize(&bytes)?; +assert_eq!(custom, decoded); +``` + +## WriteContext 和 ReadContext + +`WriteContext` 和 `ReadContext` 提供对以下内容的访问: + +- **writer/reader**:二进制缓冲区操作 +- **type_resolver**:类型注册信息 +- **ref_resolver**:引用跟踪(用于共享/循环引用) + +### 常用 Writer 方法 + +```rust +// 原始类型 +context.writer.write_i8(value); +context.writer.write_i16(value); +context.writer.write_i32(value); +context.writer.write_i64(value); +context.writer.write_f32(value); +context.writer.write_f64(value); +context.writer.write_bool(value); + +// 变长整数 +context.writer.write_varint32(value); +context.writer.write_varuint32(value); + +// 字符串 +context.writer.write_utf8_string(&string); +``` + +### 常用 Reader 方法 + +```rust +// 原始类型 +let value = context.reader.read_i8(); +let value = context.reader.read_i16(); +let value = context.reader.read_i32(); +let value = context.reader.read_i64(); +let value = context.reader.read_f32(); +let value = context.reader.read_f64(); +let value = context.reader.read_bool(); + +// 变长整数 +let value = context.reader.read_varint32(); +let value = context.reader.read_varuint32(); + +// 字符串 +let string = context.reader.read_utf8_string(len); +``` + +## 最佳实践 + +1. **使用变长编码**:对可能较小的整数使用变长编码 +2. **首先写入长度**:对变长数据先写入长度 +3. **正确处理错误**:在 read 方法中正确处理错误 +4. **实现 ForyDefault**:以支持 schema 演化 + +## 相关主题 + +- [类型注册](type-registration.md) - 注册序列化器 +- [基础序列化](basic-serialization.md) - 使用 ForyObject derive +- [Schema 演化](schema-evolution.md) - Compatible 模式 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/rust/grpc-support.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/rust/grpc-support.md new file mode 100644 index 00000000000..f48fd2fa119 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/rust/grpc-support.md @@ -0,0 +1,311 @@ +--- +title: gRPC 支持 +sidebar_position: 12 +id: grpc_support +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. +--- + +Fory 可以为包含 service 定义的 schema 生成 Rust gRPC service companion。生成代码使用 +`tonic` 负责传输,使用 Fory 序列化 request 和 response payload。 + +当 RPC 两端都由同一份 Fory IDL、protobuf IDL 或 FlatBuffers IDL 生成,并且你希望使用 +gRPC 的传输语义与 Fory payload 编码时,可以使用这种模式。如果客户端或工具必须直接读取 +protobuf message bytes,请继续使用标准 protobuf gRPC 代码生成。 + +## 添加依赖 + +编译生成 service 文件的 crate 需要添加 `tonic` 和 `bytes`。Fory Rust crate 不会将 +gRPC 作为硬依赖。异步 server/client 还需要 `tokio`;如果 service 实现需要构造 +streaming response 或 request stream,可添加 `tokio-stream`。 + +```toml +[dependencies] +fory = "1.3.0" +bytes = "1" +tonic = { version = "0.14", features = ["transport"] } +tokio = { version = "1", features = ["macros", "rt-multi-thread"] } +tokio-stream = "0.1" +``` + +请使用与应用服务栈兼容的依赖版本。 + +## 定义 Service + +Service 定义可以来自 Fory IDL、protobuf IDL 或 FlatBuffers `rpc_service`。例如: + +```protobuf +package demo.greeter; + +message HelloRequest { + string name = 1; +} + +message HelloReply { + string reply = 1; +} + +service Greeter { + rpc SayHello (HelloRequest) returns (HelloReply); +} +``` + +使用 `--grpc` 生成 Rust model 和 gRPC companion: + +```bash +foryc service.fdl --rust_out=./generated/rust --grpc +``` + +该 schema 会生成: + +| 文件 | 用途 | +| ------------------------------ | ------------------------------------------ | +| `demo_greeter.rs` | Fory model 类型和注册辅助逻辑 | +| `demo_greeter_service.rs` | 异步 service trait 与 gRPC path 常量 | +| `demo_greeter_service_grpc.rs` | tonic client、server wrapper 和 Fory codec | + +将生成文件加入 crate root: + +```rust +pub mod demo_greeter; +pub mod demo_greeter_service; +pub mod demo_greeter_service_grpc; +``` + +## 实现 Server + +实现生成的异步 trait,并把生成的 server wrapper 添加到普通 `tonic` server: + +```rust +use demo_greeter::{HelloReply, HelloRequest}; +use demo_greeter_service::Greeter; +use demo_greeter_service_grpc::greeter_server::GreeterServer; +use tonic::{Request, Response, Status}; + +#[derive(Default)] +struct MyGreeter; + +#[tonic::async_trait] +impl Greeter for MyGreeter { + async fn say_hello( + &self, + request: Request, + ) -> Result, Status> { + let request = request.into_inner(); + Ok(Response::new(HelloReply { + reply: format!("Hello, {}", request.name), + })) + } +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + let addr = "[::1]:50051".parse()?; + tonic::transport::Server::builder() + .add_service(GreeterServer::new(MyGreeter::default())) + .serve(addr) + .await?; + Ok(()) +} +``` + +生成的 service code 会负责序列化 request 和 response 类型,service 实现中不需要手动注册 Fory。 + +## 创建 Client + +使用生成的 tonic client: + +```rust +use demo_greeter::HelloRequest; +use demo_greeter_service_grpc::greeter_client::GreeterClient; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let mut client = GreeterClient::connect("http://[::1]:50051").await?; + let response = client + .say_hello(HelloRequest { + name: "Fory".to_string(), + }) + .await?; + println!("{}", response.into_inner().reply); + Ok(()) +} +``` + +Channel 配置、TLS、deadline、metadata、interceptor 和传输生命周期仍由 `tonic` 管理。 + +## Streaming RPC + +Fory service 支持 unary、server-streaming、client-streaming 和 bidirectional streaming: + +```protobuf +service Greeter { + rpc SayHello (HelloRequest) returns (HelloReply); + rpc LotsOfReplies (HelloRequest) returns (stream HelloReply); + rpc LotsOfGreetings (stream HelloRequest) returns (HelloReply); + rpc Chat (stream HelloRequest) returns (stream HelloReply); +} +``` + +生成 Rust 代码遵循 tonic 约定: + +- Unary 方法使用 `tonic::Request`,返回 `tonic::Response`。 +- Server-streaming 方法返回内部值为 stream 的 response。 +- Client-streaming 和 bidirectional 方法接收 `tonic::Streaming`。 +- 生成的 client module 暴露与 service 方法对应的异步方法。 +- 每个 message frame 都使用生成 codec,包括 streaming frame。 + +生成 trait 签名是 service 实现中具体 associated stream type 的准确信息来源: + +```rust +use demo_greeter::{HelloReply, HelloRequest}; +use demo_greeter_service::Greeter; +use std::pin::Pin; +use tokio_stream::{self as stream, Stream, StreamExt}; +use tonic::{Request, Response, Status}; + +#[derive(Default)] +struct MyGreeter; + +type ReplyStream = + Pin> + Send + 'static>>; + +#[tonic::async_trait] +impl Greeter for MyGreeter { + type LotsOfRepliesStream = ReplyStream; + type ChatStream = ReplyStream; + + async fn lots_of_replies( + &self, + request: Request, + ) -> Result, Status> { + let name = request.into_inner().name; + let replies = vec![ + Ok(HelloReply { + reply: format!("Hello, {name}"), + }), + Ok(HelloReply { + reply: format!("Welcome, {name}"), + }), + ]; + Ok(Response::new(Box::pin(stream::iter(replies)))) + } + + async fn lots_of_greetings( + &self, + request: Request>, + ) -> Result, Status> { + let mut requests = request.into_inner(); + let mut names = Vec::new(); + while let Some(request) = requests.next().await { + names.push(request?.name); + } + Ok(Response::new(HelloReply { + reply: names.join(", "), + })) + } + + async fn chat( + &self, + request: Request>, + ) -> Result, Status> { + let replies = request.into_inner().map(|request| { + request.map(|request| HelloReply { + reply: format!("Hello, {}", request.name), + }) + }); + Ok(Response::new(Box::pin(replies))) + } +} +``` + +生成的 client 返回 tonic streaming response: + +```rust +use demo_greeter::HelloRequest; +use demo_greeter_service_grpc::greeter_client::GreeterClient; +use tokio_stream as stream; + +let mut client = GreeterClient::connect("http://[::1]:50051").await?; + +let mut replies = client + .lots_of_replies(HelloRequest { + name: "Fory".to_string(), + }) + .await? + .into_inner(); +while let Some(reply) = replies.message().await? { + println!("{}", reply.reply); +} + +let greetings = stream::iter(vec![ + HelloRequest { + name: "Ada".to_string(), + }, + HelloRequest { + name: "Grace".to_string(), + }, +]); +let summary = client.lots_of_greetings(greetings).await?.into_inner(); +println!("{}", summary.reply); + +let chat_requests = stream::iter(vec![ + HelloRequest { + name: "Fory".to_string(), + }, + HelloRequest { + name: "RPC".to_string(), + }, +]); +let mut chat = client.chat(chat_requests).await?.into_inner(); +while let Some(reply) = chat.message().await? { + println!("{}", reply.reply); +} +``` + +生成 descriptor 会保留 IDL 中的 service 和 method 名称作为 gRPC path。 + +## 线程安全与 Payload 类型 + +Rust gRPC payload 必须满足 `Send + 'static`,这样 tonic 才能在线程间移动 request/response。 +如果 request 或 response schema 使用非线程安全的引用元信息,Rust gRPC 生成会拒绝该 service。 + +## gRPC 运行时行为 + +生成的 service companion 只提供 Fory 序列化和 tonic binding。deadline、取消、TLS、认证、 +Tower middleware、interceptor、status code、metadata、channel/server 生命周期和 backpressure +仍遵循标准 tonic 行为。 + +## 故障排查 + +### 缺少 `tonic` 或 `bytes` + +在编译生成 service 文件的 crate 中添加上述依赖。 + +### `UNIMPLEMENTED` + +确认已通过 `Server::builder().add_service(...)` 添加生成的 server wrapper,并且 client 与 +server 来自相同的 package、service 和 method 名称。 + +### 生成时报非线程安全引用错误 + +Rust gRPC payload 必须满足 `Send + 'static`。请调整 request/response schema 使用线程安全 +引用形态,或不要把非线程安全类型放在 RPC 边界上。 + +### Protobuf Client 无法解码 Service + +Fory gRPC companion 不使用 protobuf wire encoding。请使用 Fory 生成的 client 调用 Fory 生成的 +service,或为通用 protobuf client 提供单独的 protobuf service endpoint。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/rust/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/rust/index.md new file mode 100644 index 00000000000..4b05b6f92dc --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/rust/index.md @@ -0,0 +1,184 @@ +--- +title: Rust 序列化指南 +sidebar_position: 0 +id: serialization_index +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. +--- + +**Apache Fory™** 是一个高性能的多语言序列化框架,基于 **JIT 编译**与**零拷贝**技术,在保持易用性和安全性的同时提供出色性能。 + +Rust 实现提供灵活而高性能的序列化能力,具备自动内存管理与编译时类型安全。 + +## 为什么选择 Apache Fory™ Rust? + +- **高性能**:零拷贝反序列化与优化的二进制协议 +- **跨语言**:可在 Java、Python、C++、Go、JavaScript 和 Rust 之间无缝序列化与反序列化数据 +- **类型安全**:通过 derive macro 实现编译时类型检查 +- **循环引用**:借助 `Rc` / `Arc` 与弱指针自动跟踪共享引用和循环引用 +- **多态支持**:支持序列化 `Box`、`Rc` 和 `Arc` 等 trait 对象 +- **Schema 演进**:兼容模式支持独立的 Schema 变更 +- **双格式支持**:对象图序列化与零拷贝行格式 + +## Crate + +| Crate | 说明 | 版本 | +| --------------------------------------------------------------------------- | ------------------------------ | ----------------------------------------------------------------------------------------------------- | +| [`fory`](https://github.com/apache/fory/blob/main/rust/fory) | 带 derive macro 的高级 API | [![crates.io](https://img.shields.io/crates/v/fory.svg)](https://crates.io/crates/fory) | +| [`fory-core`](https://github.com/apache/fory/blob/main/rust/fory-core/) | 核心序列化引擎 | [![crates.io](https://img.shields.io/crates/v/fory-core.svg)](https://crates.io/crates/fory-core) | +| [`fory-derive`](https://github.com/apache/fory/blob/main/rust/fory-derive/) | 过程宏 | [![crates.io](https://img.shields.io/crates/v/fory-derive.svg)](https://crates.io/crates/fory-derive) | + +## 快速开始 + +在你的 `Cargo.toml` 中添加 Apache Fory™: + +```toml +[dependencies] +fory = "1.3.0" +``` + +### 基础示例 + +```rust +use fory::{Fory, Error, Reader}; +use fory::ForyObject; + +#[derive(ForyObject, Debug, PartialEq)] +struct User { + name: String, + age: i32, + email: String, +} + +fn main() -> Result<(), Error> { + let mut fory = Fory::default(); + fory.register::(1)?; + + let user = User { + name: "Alice".to_string(), + age: 30, + email: "alice@example.com".to_string(), + }; + + // 序列化 + let bytes = fory.serialize(&user)?; + // 反序列化 + let decoded: User = fory.deserialize(&bytes)?; + assert_eq!(user, decoded); + + // 序列化到指定缓冲区 + let mut buf: Vec = vec![]; + fory.serialize_to(&mut buf, &user)?; + // 从指定缓冲区反序列化 + let mut reader = Reader::new(&buf); + let decoded: User = fory.deserialize_from(&mut reader)?; + assert_eq!(user, decoded); + Ok(()) +} +``` + +## 线程安全 + +Apache Fory™ Rust 完全线程安全:`Fory` 同时实现了 `Send` 和 `Sync`,因此一个配置好的实例可以在线程之间共享并发使用。内部读写上下文池通过线程安全原语延迟初始化,使工作线程无需额外协调即可复用缓冲区。 + +```rust +use fory::{Fory, Error}; +use fory::ForyObject; +use std::sync::Arc; +use std::thread; + +#[derive(ForyObject, Clone, Copy, Debug, PartialEq)] +struct Item { + value: i32, +} + +fn main() -> Result<(), Error> { + let mut fory = Fory::default(); + fory.register::(1000)?; + + let fory = Arc::new(fory); + let handles: Vec<_> = (0..8) + .map(|i| { + let shared = Arc::clone(&fory); + thread::spawn(move || { + let item = Item { value: i }; + shared.serialize(&item) + }) + }) + .collect(); + + for handle in handles { + let bytes = handle.join().unwrap()?; + let item: Item = fory.deserialize(&bytes)?; + assert!(item.value >= 0); + } + + Ok(()) +} +``` + +**提示:** 请在启动线程之前完成注册操作,例如 `fory.register::(id)`,确保每个工作线程看到一致的元数据。配置完成后,将实例包在 `Arc` 中即可安全地分发序列化和反序列化任务。 + +## 架构 + +Rust 实现由三个主要 crate 组成: + +```text +fory/ # 高级 API +├── src/lib.rs # 公共 API 导出 + +fory-core/ # 核心序列化引擎 +├── src/ +│ ├── fory.rs # 主序列化入口 +│ ├── buffer.rs # 二进制缓冲区管理 +│ ├── serializer/ # 类型特定序列化器 +│ ├── resolver/ # 类型解析与元数据 +│ ├── meta/ # 元字符串压缩 +│ ├── row/ # 行格式实现 +│ └── types.rs # 类型定义 + +fory-derive/ # 过程宏 +├── src/ +│ ├── object/ # ForyObject 宏 +│ └── fory_row.rs # ForyRow 宏 +``` + +## 使用场景 + +### 对象序列化 + +- 含嵌套对象和引用的复杂数据结构 +- 微服务中的跨语言通信 +- 具备完整类型安全的通用序列化 +- 使用兼容模式进行 Schema 演进 +- 带循环引用的图状数据结构 + +### 行格式序列化 + +- 高吞吐数据处理 +- 需要快速字段访问的分析型负载 +- 内存受限环境 +- 实时数据流应用 +- 零拷贝场景 + +## 后续步骤 + +- [配置](configuration.md) - Fory 构建器选项与模式 +- [基础序列化](basic-serialization.md) - 对象图序列化 +- [引用](references.md) - 共享引用与循环引用 +- [多态](polymorphism.md) - Trait 对象序列化 +- [跨语言](xlang-serialization.md) - XLANG 模式 +- [行格式](row-format.md) - 零拷贝行格式 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/rust/native-serialization.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/rust/native-serialization.md new file mode 100644 index 00000000000..2c54a95ef81 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/rust/native-serialization.md @@ -0,0 +1,214 @@ +--- +title: Native 序列化 +sidebar_position: 3 +id: native_serialization +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. +--- + +Rust native 序列化是通过 `.xlang(false)` 选择的 Rust 专用编码模式。当所有写入端和读取端都是 Rust, +并且载荷需要保留 Rust 对象图行为而不是可移植的 xlang 类型系统时,请使用该模式。 + +当字节必须由 Java、Python、C++、Go、JavaScript 或其他非 Rust Fory 运行时读取时,请使用默认的 Rust 模式 +[Xlang Serialization](xlang-serialization.md)。 + +## 何时使用 Native 序列化 + +在以下场景使用 native 序列化: + +- 载荷只由 Rust 应用产生和消费。 +- 数据模型使用 Rust 特有的对象图能力,例如 `Rc`、`Arc`、弱指针、`RefCell`、`Mutex`、trait object 或 `dyn Any`。 +- 需要为同步升级的服务提供 Schema 一致的 Rust 载荷。 +- 需要为 Rust 专用滚动部署提供兼容 Schema 演进。 +- 希望使用来自 `#[derive(ForyStruct)]` 的编译期序列化器,同时不受可移植 xlang 映射约束。 + +## 创建 Native 运行时 + +```rust +use fory::{Error, Fory, ForyStruct}; + +#[derive(ForyStruct, Debug, PartialEq)] +struct Order { + id: i64, + amount: f64, +} + +fn main() -> Result<(), Error> { + let mut fory = Fory::builder().xlang(false).build(); + fory.register::(100)?; + + let order = Order { id: 1, amount: 42.5 }; + let bytes = fory.serialize(&order)?; + let decoded: Order = fory.deserialize(&bytes)?; + assert_eq!(order, decoded); + Ok(()) +} +``` + +在跨线程共享 `Fory` 实例前完成注册。配置完成后,`Fory` 可以通过 `Arc` 共享。 + +## Schema 演进 + +Native 序列化默认使用 Schema 一致模式。只有在 Rust 专用写入端和读取端版本可能不一致时,才启用兼容模式: + +```rust +let mut writer = Fory::builder().xlang(false).compatible(true).build(); +let mut reader = Fory::builder().xlang(false).compatible(true).build(); +``` + +兼容模式使用 Schema 元数据,在字段身份保持兼容时容忍字段的新增、删除或重排。参见 +[Schema Evolution](schema-evolution.md)。 + +## 注册 + +序列化前注册应用 struct 和类似 enum 的类型: + +```rust +fory.register::(100)?; +fory.register_by_name::("example", "Order")?; +``` + +使用显式数字 ID 可获得紧凑载荷和稳定部署。当不同团队通过名称协调类型身份时,使用 namespace/type-name 注册。 + +## Rust 对象能力范围 + +Native 序列化覆盖 Rust 特有的对象能力范围: + +- 带 `#[derive(ForyStruct)]` 的 struct 和 tuple struct。 +- Fory derive 宏支持的 enum 和类似 union 的模型。 +- `Vec`、map、set、tuple、array 和可选值。 +- `Box`、`Rc`、`Arc`、`RcWeak` 和 `ArcWeak`。 +- `RefCell` 和 `Mutex`。 +- `Box`、`Rc` 和 `Arc` 等 trait object。 +- 使用 `Rc` 和 `Arc` 的运行时类型分派。 +- 日期和时间承载类型,包括可选的 `chrono` 支持。 + +聚焦示例见 [Basic Serialization](basic-serialization.md)、[References](references.md) 和 +[Trait Object Serialization](polymorphism.md)。 + +## 共享引用和循环引用 + +Native 模式可以使用 `Rc` 和 `Arc` 保留共享引用: + +```rust +use fory::{Error, Fory}; +use std::rc::Rc; + +fn main() -> Result<(), Error> { + let fory = Fory::builder().xlang(false).build(); + let shared = Rc::new(String::from("shared")); + let values = vec![shared.clone(), shared.clone()]; + + let bytes = fory.serialize(&values)?; + let decoded: Vec> = fory.deserialize(&bytes)?; + assert!(Rc::ptr_eq(&decoded[0], &decoded[1])); + Ok(()) +} +``` + +当弱指针或显式循环图需要引用跟踪时,使用 `.track_ref(true)`: + +```rust +let mut fory = Fory::builder().xlang(false).track_ref(true).build(); +``` + +当目标仍然存活时,弱指针会序列化为指向目标的引用;当目标已被释放时,会序列化为 null。 + +## Trait Object + +Trait object 是 Rust 运行时能力,属于 native 序列化范围: + +```rust +use fory::{register_trait_type, Error, Fory, ForyStruct, Serializer}; + +trait Animal: Serializer { + fn name(&self) -> &str; +} + +#[derive(ForyStruct)] +struct Dog { + name: String, +} + +impl Animal for Dog { + fn name(&self) -> &str { + &self.name + } +} + +register_trait_type!(Animal, Dog); + +fn main() -> Result<(), Error> { + let mut fory = Fory::builder().xlang(false).compatible(true).build(); + fory.register::(100)?; + + let value: Box = Box::new(Dog { name: "Milo".into() }); + let bytes = fory.serialize(&value)?; + let decoded: Box = fory.deserialize(&bytes)?; + assert_eq!(decoded.name(), "Milo"); + Ok(()) +} +``` + +注册每一个可能出现在 trait object 背后的具体实现。 + +## 性能指南 + +- 复用配置好的 `Fory` 实例,并在并发使用前注册类型。 +- 对同步升级的 Rust 服务保持 native Schema 一致模式。 +- 仅在需要 Rust 专用 Schema 演进时启用 `.compatible(true)`。 +- 对应用 struct 使用 derive 生成的序列化器。 +- 仅在需要弱指针或循环图的场景中使用 `.track_ref(true)`。 +- 在热点路径上优先使用具体类型字段,而不是 `dyn Any` 或 trait object。 + +## Native 与 Xlang 对比 + +| 需求 | 使用 native 序列化 | 使用 xlang 序列化 | +| ---------------------------------------- | ------------------------ | ----------------------- | +| Rust 专用载荷 | 是 | 可选 | +| 非 Rust 读取端或写入端 | 否 | 是 | +| `Rc`、`Arc`、弱指针 | 是 | 否 | +| Trait object 和 `dyn Any` | 是 | 否 | +| Schema 一致的同语言载荷 | 是 | 否 | +| 默认兼容 Schema 演进 | 否 | 是 | +| 跨运行时的可移植类型映射 | 否 | 是 | + +## 故障排查 + +### 非 Rust 运行时无法读取载荷 + +写入端正在使用 native 序列化。请使用 `.xlang(true)` 重新构建,并与每个对端运行时对齐类型注册。 + +### 弱指针无法解析 + +使用 `.track_ref(true)`,并确保序列化时目标对象仍然存活。已释放的弱引用目标会反序列化为 null。 + +### Trait object 无法反序列化 + +注册 trait 映射以及每一个可能出现在该 trait object 背后的具体实现。 + +### 字段变更后滚动部署失败 + +Native 序列化默认使用 Schema 一致模式。当 Schema 可能不同时,在写入端和读取端都使用 `.compatible(true)`。 + +## 相关主题 + +- [Xlang Serialization](xlang-serialization.md) - 跨运行时 Rust 载荷 +- [Configuration](configuration.md) - Builder 选项 +- [Basic Serialization](basic-serialization.md) - 对象图序列化 +- [Shared & Circular References](references.md) - `Rc`、`Arc` 和弱指针 +- [Trait Object Serialization](polymorphism.md) - Trait object 和动态分派 +- [Schema Evolution](schema-evolution.md) - 兼容模式 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/rust/polymorphism.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/rust/polymorphism.md new file mode 100644 index 00000000000..a1a5a3c1c82 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/rust/polymorphism.md @@ -0,0 +1,232 @@ +--- +title: Trait 对象序列化 +sidebar_position: 6 +id: polymorphism +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. +--- + +Apache Fory™ 通过 trait 对象支持多态序列化,实现动态分发和类型灵活性。 + +## 支持的 Trait 对象类型 + +- `Box` - 拥有所有权的 trait 对象 +- `Rc` - 引用计数 trait 对象 +- `Arc` - 线程安全引用计数 trait 对象 +- `Vec>`、`HashMap>` - trait 对象集合 + +## 基础 Trait 对象序列化 + +```rust +use fory::{Fory, register_trait_type}; +use fory::Serializer; +use fory::ForyObject; + +trait Animal: Serializer { + fn speak(&self) -> String; + fn name(&self) -> &str; +} + +#[derive(ForyObject)] +struct Dog { name: String, breed: String } + +impl Animal for Dog { + fn speak(&self) -> String { "Woof!".to_string() } + fn name(&self) -> &str { &self.name } +} + +#[derive(ForyObject)] +struct Cat { name: String, color: String } + +impl Animal for Cat { + fn speak(&self) -> String { "Meow!".to_string() } + fn name(&self) -> &str { &self.name } +} + +// 注册 trait 实现 +register_trait_type!(Animal, Dog, Cat); + +#[derive(ForyObject)] +struct Zoo { + star_animal: Box, +} + +let mut fory = Fory::default().compatible(true); +fory.register::(100); +fory.register::(101); +fory.register::(102); + +let zoo = Zoo { + star_animal: Box::new(Dog { + name: "Buddy".to_string(), + breed: "Labrador".to_string(), + }), +}; + +let bytes = fory.serialize(&zoo); +let decoded: Zoo = fory.deserialize(&bytes)?; + +assert_eq!(decoded.star_animal.name(), "Buddy"); +assert_eq!(decoded.star_animal.speak(), "Woof!"); +``` + +## 序列化 dyn Any Trait 对象 + +Apache Fory™ 支持序列化 `Rc` 和 `Arc` 以实现运行时类型分发: + +**要点:** + +- 适用于任何实现 `Serializer` 的类型 +- 反序列化后需要向下转型以访问具体类型 +- 序列化期间保留类型信息 +- 对插件系统和动态类型处理很有用 + +```rust +use std::rc::Rc; +use std::any::Any; + +let dog_rc: Rc = Rc::new(Dog { + name: "Rex".to_string(), + breed: "Golden".to_string() +}); + +// 转换为 Rc 以进行序列化 +let dog_any: Rc = dog_rc.clone(); + +// 序列化 Any 包装器 +let bytes = fory.serialize(&dog_any); +let decoded: Rc = fory.deserialize(&bytes)?; + +// 向下转型回具体类型 +let unwrapped = decoded.downcast_ref::().unwrap(); +assert_eq!(unwrapped.name, "Rex"); +``` + +对于线程安全场景,使用 `Arc`: + +```rust +use std::sync::Arc; +use std::any::Any; + +let dog_arc: Arc = Arc::new(Dog { + name: "Buddy".to_string(), + breed: "Labrador".to_string() +}); + +// 转换为 Arc +let dog_any: Arc = dog_arc.clone(); + +let bytes = fory.serialize(&dog_any); +let decoded: Arc = fory.deserialize(&bytes)?; + +// 向下转型为具体类型 +let unwrapped = decoded.downcast_ref::().unwrap(); +assert_eq!(unwrapped.name, "Buddy"); +``` + +## 结构体中基于 Rc/Arc 的 Trait 对象 + +对于具有 `Rc` 或 `Arc` 的字段,Fory 自动处理转换: + +```rust +use std::sync::Arc; +use std::rc::Rc; +use std::collections::HashMap; + +#[derive(ForyObject)] +struct AnimalShelter { + animals_rc: Vec>, + animals_arc: Vec>, + registry: HashMap>, +} + +let mut fory = Fory::default().compatible(true); +fory.register::(100); +fory.register::(101); +fory.register::(102); + +let shelter = AnimalShelter { + animals_rc: vec![ + Rc::new(Dog { name: "Rex".to_string(), breed: "Golden".to_string() }), + Rc::new(Cat { name: "Mittens".to_string(), color: "Gray".to_string() }), + ], + animals_arc: vec![ + Arc::new(Dog { name: "Buddy".to_string(), breed: "Labrador".to_string() }), + ], + registry: HashMap::from([ + ("pet1".to_string(), Arc::new(Dog { + name: "Max".to_string(), + breed: "Shepherd".to_string() + }) as Arc), + ]), +}; + +let bytes = fory.serialize(&shelter); +let decoded: AnimalShelter = fory.deserialize(&bytes)?; + +assert_eq!(decoded.animals_rc[0].name(), "Rex"); +assert_eq!(decoded.animals_arc[0].speak(), "Woof!"); +``` + +## 独立 Trait 对象序列化 + +由于 Rust 的孤儿规则,`Rc` 和 `Arc` 不能直接实现 `Serializer`。对于独立序列化(不在结构体字段内),`register_trait_type!` 宏生成包装类型。 + +**注意:** 如果不想使用包装类型,可以改为序列化为 `Rc` 或 `Arc`(参见上面的 dyn Any 部分)。 + +`register_trait_type!` 宏生成 `AnimalRc` 和 `AnimalArc` 包装类型: + +```rust +// 对于 Rc +let dog_rc: Rc = Rc::new(Dog { + name: "Rex".to_string(), + breed: "Golden".to_string() +}); +let wrapper = AnimalRc::from(dog_rc); + +let bytes = fory.serialize(&wrapper); +let decoded: AnimalRc = fory.deserialize(&bytes)?; + +// 解包回 Rc +let unwrapped: Rc = decoded.unwrap(); +assert_eq!(unwrapped.name(), "Rex"); + +// 对于 Arc +let dog_arc: Arc = Arc::new(Dog { + name: "Buddy".to_string(), + breed: "Labrador".to_string() +}); +let wrapper = AnimalArc::from(dog_arc); + +let bytes = fory.serialize(&wrapper); +let decoded: AnimalArc = fory.deserialize(&bytes)?; + +let unwrapped: Arc = decoded.unwrap(); +assert_eq!(unwrapped.name(), "Buddy"); +``` + +## 最佳实践 + +1. **使用 `register_trait_type!`** 注册所有 trait 实现 +2. **启用 compatible 模式**:对 trait 对象使用 `.compatible(true)` +3. **注册所有具体类型**:在序列化之前 +4. **优先使用 dyn Any**:对于更简单的独立序列化 + +## 相关主题 + +- [引用](references.md) - Rc/Arc 共享引用 +- [Schema 演化](schema-evolution.md) - Compatible 模式 +- [类型注册](type-registration.md) - 注册类型 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/rust/references.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/rust/references.md new file mode 100644 index 00000000000..b0a72809197 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/rust/references.md @@ -0,0 +1,213 @@ +--- +title: 共享和循环引用 +sidebar_position: 5 +id: references +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. +--- + +Apache Fory™ 使用 `Rc` 和 `Arc` 自动跟踪和保留共享对象的引用身份。 + +## 共享引用 + +当同一对象被多次引用时,Fory 仅序列化一次,并对后续出现使用引用 ID。这确保了: + +- **空间效率**:序列化输出中无数据重复 +- **引用身份保留**:反序列化的对象保持相同的共享关系 +- **循环引用支持**:使用 `RcWeak` 和 `ArcWeak` 打破循环 + +### 使用 Rc 的共享引用 + +```rust +use fory::Fory; +use std::rc::Rc; + +let fory = Fory::default(); + +// 创建共享值 +let shared = Rc::new(String::from("shared_value")); + +// 多次引用它 +let data = vec![shared.clone(), shared.clone(), shared.clone()]; + +// 共享值仅序列化一次 +let bytes = fory.serialize(&data); +let decoded: Vec> = fory.deserialize(&bytes)?; + +// 验证引用身份被保留 +assert_eq!(decoded.len(), 3); +assert_eq!(*decoded[0], "shared_value"); + +// 所有三个 Rc 指针指向同一对象 +assert!(Rc::ptr_eq(&decoded[0], &decoded[1])); +assert!(Rc::ptr_eq(&decoded[1], &decoded[2])); +``` + +### 使用 Arc 的共享引用 + +对于线程安全的共享引用,使用 `Arc`: + +```rust +use fory::Fory; +use std::sync::Arc; + +let fory = Fory::default(); + +let shared = Arc::new(String::from("shared_value")); +let data = vec![shared.clone(), shared.clone()]; + +let bytes = fory.serialize(&data); +let decoded: Vec> = fory.deserialize(&bytes)?; + +assert!(Arc::ptr_eq(&decoded[0], &decoded[1])); +``` + +## 使用弱指针的循环引用 + +要序列化类似父子关系或双向链表结构的循环引用,使用 `RcWeak` 或 `ArcWeak` 来打破循环。 + +**工作原理:** + +- 弱指针序列化为对其目标对象的引用 +- 如果强指针已被丢弃,弱指针序列化为 `Null` +- 前向引用(弱指针出现在目标之前)通过回调解析 +- 弱指针的所有克隆共享相同的内部单元以进行自动更新 + +### 使用 RcWeak 的循环引用 + +```rust +use fory::{Fory, Error}; +use fory::ForyObject; +use fory::RcWeak; +use std::rc::Rc; +use std::cell::RefCell; + +#[derive(ForyObject, Debug)] +struct Node { + value: i32, + parent: RcWeak>, + children: Vec>>, +} + +let mut fory = Fory::default(); +fory.register::(2000); + +// 构建父子树 +let parent = Rc::new(RefCell::new(Node { + value: 1, + parent: RcWeak::new(), + children: vec![], +})); + +let child1 = Rc::new(RefCell::new(Node { + value: 2, + parent: RcWeak::from(&parent), + children: vec![], +})); + +let child2 = Rc::new(RefCell::new(Node { + value: 3, + parent: RcWeak::from(&parent), + children: vec![], +})); + +parent.borrow_mut().children.push(child1.clone()); +parent.borrow_mut().children.push(child2.clone()); + +// 序列化和反序列化循环结构 +let bytes = fory.serialize(&parent); +let decoded: Rc> = fory.deserialize(&bytes)?; + +// 验证循环关系 +assert_eq!(decoded.borrow().children.len(), 2); +for child in &decoded.borrow().children { + let upgraded_parent = child.borrow().parent.upgrade().unwrap(); + assert!(Rc::ptr_eq(&decoded, &upgraded_parent)); +} +``` + +### 使用 Arc 的线程安全循环图 + +```rust +use fory::{Fory, Error}; +use fory::ForyObject; +use fory::ArcWeak; +use std::sync::{Arc, Mutex}; + +#[derive(ForyObject)] +struct Node { + val: i32, + parent: ArcWeak>, + children: Vec>>, +} + +let mut fory = Fory::default(); +fory.register::(6000); + +let parent = Arc::new(Mutex::new(Node { + val: 10, + parent: ArcWeak::new(), + children: vec![], +})); + +let child1 = Arc::new(Mutex::new(Node { + val: 20, + parent: ArcWeak::from(&parent), + children: vec![], +})); + +let child2 = Arc::new(Mutex::new(Node { + val: 30, + parent: ArcWeak::from(&parent), + children: vec![], +})); + +parent.lock().unwrap().children.push(child1.clone()); +parent.lock().unwrap().children.push(child2.clone()); + +let bytes = fory.serialize(&parent); +let decoded: Arc> = fory.deserialize(&bytes)?; + +assert_eq!(decoded.lock().unwrap().children.len(), 2); +for child in &decoded.lock().unwrap().children { + let upgraded_parent = child.lock().unwrap().parent.upgrade().unwrap(); + assert!(Arc::ptr_eq(&decoded, &upgraded_parent)); +} +``` + +## 支持的智能指针类型 + +| 类型 | 描述 | +| ------------ | ------------------------------------ | +| `Rc` | 引用计数,跟踪共享引用 | +| `Arc` | 线程安全引用计数,跟踪共享引用 | +| `RcWeak` | 指向 `Rc` 的弱引用,打破循环引用 | +| `ArcWeak` | 指向 `Arc` 的弱引用,打破循环引用 | +| `RefCell` | 内部可变性,运行时借用检查 | +| `Mutex` | 线程安全内部可变性 | + +## 最佳实践 + +1. **使用 Rc/Arc 共享数据**:让 Fory 处理去重 +2. **使用弱指针处理循环**:防止无限递归 +3. **对线程安全场景优先使用 Arc**:当数据跨越线程边界时 +4. **与 RefCell/Mutex 结合使用**:用于内部可变性 + +## 相关主题 + +- [基础序列化](basic-serialization.md) - 支持的类型 +- [多态](polymorphism.md) - 使用 Rc/Arc 的 Trait 对象 +- [配置](configuration.md) - 引用跟踪选项 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/rust/row-format.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/rust/row-format.md new file mode 100644 index 00000000000..4745797e496 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/rust/row-format.md @@ -0,0 +1,126 @@ +--- +title: 行格式 +sidebar_position: 9 +id: row_format +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. +--- + +Apache Fory™ 提供高性能的**行格式**以实现零拷贝反序列化。 + +## 概述 + +与在内存中重构完整对象的传统对象序列化不同,行格式支持直接从二进制数据**随机访问**字段,无需完整反序列化。 + +**主要优势:** + +- **零拷贝访问**:无需分配或复制数据即可读取字段 +- **部分反序列化**:仅访问所需的字段 +- **内存映射文件**:处理大于 RAM 的数据 +- **缓存友好**:顺序内存布局以提高 CPU 缓存利用率 +- **延迟计算**:将昂贵的操作延迟到字段访问时 + +## 何时使用行格式 + +- 具有选择性字段访问的分析工作负载 +- 仅需要字段子集的大型数据集 +- 内存受限环境 +- 高吞吐量数据管道 +- 从内存映射文件或共享内存读取 + +## 基础用法 + +```rust +use fory::{to_row, from_row}; +use fory::ForyRow; +use std::collections::BTreeMap; + +#[derive(ForyRow)] +struct UserProfile { + id: i64, + username: String, + email: String, + scores: Vec, + preferences: BTreeMap, + is_active: bool, +} + +let profile = UserProfile { + id: 12345, + username: "alice".to_string(), + email: "alice@example.com".to_string(), + scores: vec![95, 87, 92, 88], + preferences: BTreeMap::from([ + ("theme".to_string(), "dark".to_string()), + ("language".to_string(), "en".to_string()), + ]), + is_active: true, +}; + +// 序列化为行格式 +let row_data = to_row(&profile); + +// 零拷贝反序列化 - 无对象分配! +let row = from_row::(&row_data); + +// 直接从二进制数据访问字段 +assert_eq!(row.id(), 12345); +assert_eq!(row.username(), "alice"); +assert_eq!(row.email(), "alice@example.com"); +assert_eq!(row.is_active(), true); + +// 高效访问集合 +let scores = row.scores(); +assert_eq!(scores.size(), 4); +assert_eq!(scores.get(0), 95); +assert_eq!(scores.get(1), 87); + +let prefs = row.preferences(); +assert_eq!(prefs.keys().size(), 2); +assert_eq!(prefs.keys().get(0), "language"); +assert_eq!(prefs.values().get(0), "en"); +``` + +## 工作原理 + +- 字段在二进制行中编码,原始类型使用固定偏移量 +- 变长数据(字符串、集合)使用偏移指针存储 +- Null 位图跟踪哪些字段存在 +- 通过递归行编码支持嵌套结构 + +## 性能比较 + +| 操作 | 对象格式 | 行格式 | +| ------------ | ------------------ | -------------------- | +| 完整反序列化 | 分配所有对象 | 零分配 | +| 单字段访问 | 需要完整反序列化 | 直接偏移读取 | +| 内存使用 | 内存中的完整对象图 | 仅已访问字段在内存中 | +| 适用于 | 小对象,完整访问 | 大对象,选择性访问 | + +## ForyRow vs ForyObject + +| 功能 | `#[derive(ForyRow)]` | `#[derive(ForyObject)]` | +| -------- | -------------------- | ----------------------- | +| 反序列化 | 零拷贝,延迟 | 完整对象重构 | +| 字段访问 | 直接从二进制 | 正常结构体访问 | +| 内存使用 | 最小 | 完整对象 | +| 最适合 | 分析,大数据 | 通用序列化 | + +## 相关主题 + +- [基础序列化](basic-serialization.md) - 对象图序列化 +- [跨语言](xlang-serialization.md) - 跨语言的行格式 +- [行格式规范](https://fory.apache.org/docs/specification/row_format_spec) - 协议细节 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/rust/schema-evolution.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/rust/schema-evolution.md new file mode 100644 index 00000000000..6f2acdf5ab3 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/rust/schema-evolution.md @@ -0,0 +1,204 @@ +--- +title: Schema 演化 +sidebar_position: 7 +id: schema_evolution +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. +--- + +Apache Fory™ 在 **Compatible 模式**下支持 schema 演化,允许序列化和反序列化对等方具有不同的类型定义。 + +## Compatible 模式 + +使用 `compatible(true)` 启用 schema 演化: + +```rust +use fory::Fory; +use fory::ForyObject; +use std::collections::HashMap; + +#[derive(ForyObject, Debug)] +struct PersonV1 { + name: String, + age: i32, + address: String, +} + +#[derive(ForyObject, Debug)] +struct PersonV2 { + name: String, + age: i32, + // address 已移除 + // phone 已添加 + phone: Option, + metadata: HashMap, +} + +let mut fory1 = Fory::default().compatible(true); +fory1.register::(1); + +let mut fory2 = Fory::default().compatible(true); +fory2.register::(1); + +let person_v1 = PersonV1 { + name: "Alice".to_string(), + age: 30, + address: "123 Main St".to_string(), +}; + +// 使用 V1 序列化 +let bytes = fory1.serialize(&person_v1); + +// 使用 V2 反序列化 - 缺失的字段获得默认值 +let person_v2: PersonV2 = fory2.deserialize(&bytes)?; +assert_eq!(person_v2.name, "Alice"); +assert_eq!(person_v2.age, 30); +assert_eq!(person_v2.phone, None); +``` + +## Schema 演化功能 + +- 添加具有默认值的新字段 +- 移除过时字段(在反序列化期间跳过) +- 更改字段可空性(`T` ↔ `Option`) +- 重新排序字段(按名称匹配,而非位置) +- 对缺失字段的类型安全回退到默认值 + +## 兼容性规则 + +- 字段名称必须匹配(区分大小写) +- 不支持类型更改(可空/非可空除外) +- 嵌套结构体类型必须在两端都注册 + +## 枚举支持 + +Apache Fory™ 支持三种类型的枚举变体,在 Compatible 模式下具有完整的 schema 演化支持: + +**变体类型:** + +- **Unit**:C 风格枚举(`Status::Active`) +- **Unnamed**:元组风格变体(`Message::Pair(String, i32)`) +- **Named**:结构体风格变体(`Event::Click { x: i32, y: i32 }`) + +```rust +use fory::{Fory, ForyObject}; + +#[derive(Default, ForyObject, Debug, PartialEq)] +enum Value { + #[default] + Null, + Bool(bool), + Number(f64), + Text(String), + Object { name: String, value: i32 }, +} + +let mut fory = Fory::default(); +fory.register::(1)?; + +let value = Value::Object { name: "score".to_string(), value: 100 }; +let bytes = fory.serialize(&value)?; +let decoded: Value = fory.deserialize(&bytes)?; +assert_eq!(value, decoded); +``` + +### 枚举 Schema 演化 + +Compatible 模式通过变体类型编码(2 位)实现强大的 schema 演化: + +- `0b0` = Unit,`0b1` = Unnamed,`0b10` = Named + +```rust +use fory::{Fory, ForyObject}; + +// 旧版本 +#[derive(ForyObject)] +enum OldEvent { + Click { x: i32, y: i32 }, + Scroll { delta: f64 }, +} + +// 新版本 - 添加了字段和变体 +#[derive(Default, ForyObject)] +enum NewEvent { + #[default] + Unknown, + Click { x: i32, y: i32, timestamp: u64 }, // 添加了字段 + Scroll { delta: f64 }, + KeyPress(String), // 新变体 +} + +let mut fory = Fory::builder().compatible().build(); + +// 使用旧 schema 序列化 +let old_bytes = fory.serialize(&OldEvent::Click { x: 100, y: 200 })?; + +// 使用新 schema 反序列化 - timestamp 获得默认值 (0) +let new_event: NewEvent = fory.deserialize(&old_bytes)?; +assert!(matches!(new_event, NewEvent::Click { x: 100, y: 200, timestamp: 0 })); +``` + +**演化能力:** + +- **未知变体** → 回退到默认变体 +- **Named 变体字段** → 添加/移除字段(缺失字段使用默认值) +- **Unnamed 变体元素** → 添加/移除元素(额外的跳过,缺失的使用默认值) +- **变体类型不匹配** → 自动使用当前变体的默认值 + +**最佳实践:** + +- 始终使用 `#[default]` 标记默认变体 +- Named 变体比 unnamed 变体提供更好的演化能力 +- 对跨版本通信使用 compatible 模式 + +## 元组支持 + +Apache Fory™ 在兼容和非兼容模式下均支持最多 22 个元素的元组,并具有高效的序列化。 + +**功能:** + +- 对 1 到 22 个元素的元组自动序列化 +- 异构类型支持(每个元素可以是不同的类型) +- Compatible 模式下的 schema 演化(处理缺失/额外元素) + +**序列化模式:** + +1. **非兼容模式**:按顺序序列化元素,不使用集合头部,以实现最小开销 +2. **Compatible 模式**:使用带有类型元数据的集合协议以支持 schema 演化 + +```rust +use fory::{Fory, Error}; + +let mut fory = Fory::default(); + +// 具有异构类型的元组 +let data: (i32, String, bool, Vec) = ( + 42, + "hello".to_string(), + true, + vec![1, 2, 3], +); + +let bytes = fory.serialize(&data)?; +let decoded: (i32, String, bool, Vec) = fory.deserialize(&bytes)?; +assert_eq!(data, decoded); +``` + +## 相关主题 + +- [配置](configuration.md) - 启用 compatible 模式 +- [多态](polymorphism.md) - 具有 schema 演化的 Trait 对象 +- [跨语言](xlang-serialization.md) - 跨语言的 schema 演化 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/rust/schema-metadata.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/rust/schema-metadata.md new file mode 100644 index 00000000000..508516cec6e --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/rust/schema-metadata.md @@ -0,0 +1,457 @@ +--- +title: Schema 元数据 +sidebar_position: 6 +id: schema_metadata +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. +--- + +本页说明如何在 Rust 中为序列化配置字段级元数据。 + +## 概览 {#overview} + +Apache Fory™ 提供 `#[fory(...)]` 属性宏,用于在编译期指定可选的字段级元数据。它支持: + +- **Tag ID**:分配紧凑的数字 ID,以减少结构体字段元信息的大小开销 +- **可空性**:控制字段是否可以为 null +- **引用跟踪**:为共享所有权类型启用引用跟踪 +- **跳过字段**:从序列化中排除字段 +- **编码控制**:指定整数的编码方式(varint、fixed、tagged) + +## 基本语法 {#basic-syntax} + +`#[fory(...)]` 属性放在单个结构体字段上: + +```rust +use fory::ForyStruct; + +#[derive(ForyStruct)] +struct Person { + #[fory(id = 0)] + name: String, + + #[fory(id = 1)] + age: i32, + + #[fory(id = 2, nullable)] + nickname: Option, +} +``` + +多个选项用逗号分隔。 + +## 可用选项 {#available-options} + +### 字段 ID(`id = N`) {#field-id-id--n} + +为字段分配数字 ID,以减少结构体字段元信息的大小开销: + +```rust +#[derive(ForyStruct)] +struct User { + #[fory(id = 0)] + id: i64, + + #[fory(id = 1)] + name: String, + + #[fory(id = 2)] + age: i32, +} +``` + +**优点**: + +- 序列化体积更小(元数据中使用数字 ID,而不是字段名) +- 允许重命名字段而不破坏二进制兼容性 + +**建议**:建议在兼容模式下配置字段 ID,因为这能降低序列化成本。 + +**说明**: + +- ID 在同一结构体内必须唯一 +- ID 必须是非负数 +- 如果未指定,则在元数据中使用字段名(开销更大) + +### 跳过字段(`skip`) {#skipping-fields-skip} + +从序列化中排除字段: + +```rust +#[derive(ForyStruct)] +struct User { + #[fory(id = 0)] + id: i64, + + #[fory(id = 1)] + name: String, + + #[fory(skip)] + password: String, // 不会被序列化 +} +``` + +`password` 字段不会包含在序列化输出中,反序列化后会保持其默认值。 + +### 可空(`nullable`) {#nullable-nullable} + +控制是否为字段写入 null 标志: + +```rust +use fory::{Fory, RcWeak}; + +#[derive(ForyStruct)] +struct Record { + // RcWeak 默认可空,这里覆盖为不可空 + #[fory(id = 0, nullable = false)] + required_ref: RcWeak, +} +``` + +**默认行为**: + +| 类型 | 默认可空 | +| ------------------------- | -------- | +| `Option` | `true` | +| `RcWeak`, `ArcWeak` | `true` | +| 所有其他类型 | `false` | + +**说明**: + +- 对 `Option`、`RcWeak`、`ArcWeak`,可空性默认为 true +- 对所有其他类型,可空性默认为 false +- 对默认可空的类型,可使用 `nullable = false` 覆盖默认值 + +### 引用跟踪(`ref`) {#reference-tracking-ref} + +控制共享所有权类型的逐字段引用跟踪: + +```rust +use std::rc::Rc; +use std::sync::Arc; + +#[derive(ForyStruct)] +struct Container { + // 启用引用跟踪(Rc/Arc 默认启用) + #[fory(id = 0, ref = true)] + shared_data: Rc, + + // 禁用引用跟踪 + #[fory(id = 1, ref = false)] + unique_data: Rc, +} +``` + +**默认行为**: + +| 类型 | 默认引用跟踪 | +| --------------------------------- | ------------------ | +| `Rc`, `Arc` | `true` | +| `RcWeak`, `ArcWeak` | `true` | +| `Option>`, `Option>` | `true`(继承而来) | +| 所有其他类型 | `false` | + +**使用场景**: + +- 对可能存在循环或共享的字段启用 +- 对始终唯一的字段禁用(优化) + +### 编码(`encoding`) {#encoding-encoding} + +控制整数字段的编码方式: + +```rust +#[derive(ForyStruct)] +struct Metrics { + // 变长编码(小值占用更少空间) + #[fory(id = 0, encoding = varint)] + count: i64, + + // 定长编码(大小固定) + #[fory(id = 1, encoding = fixed)] + timestamp: i64, + + // 带 tag 的编码(包含类型 tag,仅 u64) + #[fory(id = 2, encoding = tagged)] + value: u64, +} +``` + +**支持的编码**: + +| 类型 | 选项 | 默认值 | +| ------------ | --------------------------- | -------- | +| `i32`, `u32` | `varint`, `fixed` | `varint` | +| `i64`, `u64` | `varint`, `fixed`, `tagged` | `varint` | + +**何时使用**: + +- `varint`:最适合通常较小的值(默认) +- `fixed`:最适合使用完整取值范围的值(例如时间戳、哈希) +- `tagged`:需要保留类型信息时使用(仅 u64) + +### 嵌套集合配置 {#nested-collection-configuration} + +当覆盖配置属于嵌套元素而不是外层字段时,使用 `list(element(...))` 和 `map(key(...), value(...))`: + +```rust +use std::collections::HashMap; + +#[derive(ForyStruct)] +struct Data { + #[fory(list(element(encoding = fixed)))] + fixed_values: Vec, + + #[fory(map(key(encoding = fixed), value(nullable = true, encoding = tagged)))] + values_by_id: HashMap, Option>, +} +``` + +`compress` 已被移除。请直接使用 `encoding = varint` 或 `encoding = fixed`。 + +## 类型分类 {#type-classification} + +Fory 会对字段类型分类,以确定默认行为: + +| 类型类别 | 示例 | 默认可空 | 默认引用跟踪 | +| --------- | ---------------------------- | -------- | ------------ | +| Primitive | `i8`, `i32`, `f64`, `bool` | `false` | `false` | +| Option | `Option` | `true` | `false` | +| Rc | `Rc` | `false` | `true` | +| Arc | `Arc` | `false` | `true` | +| RcWeak | `RcWeak`(fory 类型) | `true` | `true` | +| ArcWeak | `ArcWeak`(fory 类型) | `true` | `true` | +| Other | `String`, `Vec`, 用户类型 | `false` | `false` | + +**特殊情况**:`Option>` 和 `Option>` 会继承内部类型的引用跟踪行为。 + +## 完整示例 {#complete-example} + +```rust +use fory::ForyStruct; +use std::rc::Rc; + +#[derive(ForyStruct, Default)] +struct Document { + // 带 tag ID 的必需字段 + #[fory(id = 0)] + title: String, + + #[fory(id = 1)] + version: i32, + + // 可选字段(Option 默认可空) + #[fory(id = 2)] + description: Option, + + // 启用引用跟踪的共享指针 + #[fory(id = 3)] + parent: Rc, + + // 可空 + 引用跟踪 + #[fory(id = 4, nullable)] + related: Option>, + + // 使用 varint 编码的计数器(小值) + #[fory(id = 5, encoding = varint)] + view_count: u64, + + // 使用 fixed 编码的时间戳(完整范围值) + #[fory(id = 6, encoding = fixed)] + created_at: i64, + + // 跳过敏感字段 + #[fory(skip)] + internal_state: String, +} + +fn main() { + let fory = fory::Fory::builder().xlang(false).build(); + + let doc = Document { + title: "My Document".to_string(), + version: 1, + description: Some("A sample document".to_string()), + parent: Rc::new(Document::default()), + related: None, // 允许,因为可空 + view_count: 42, + created_at: 1704067200, + internal_state: "secret".to_string(), // 会被跳过 + }; + + let bytes = fory.serialize(&doc).unwrap(); + let decoded: Document = fory.deserialize(&bytes).unwrap(); +} +``` + +## 编译期校验 {#compile-time-validation} + +无效配置会在编译期被捕获: + +```rust +// 错误:重复的字段 ID +#[derive(ForyStruct)] +struct Bad { + #[fory(id = 0)] + field1: String, + + #[fory(id = 0)] // 编译错误:重复 id + field2: String, +} + +// 错误:无效的 id 值 +#[derive(ForyStruct)] +struct Bad2 { + #[fory(id = -1)] // 编译错误:id 必须是非负数 + field: String, +} + +// 错误:i32 使用了无效编码 +#[derive(ForyStruct)] +struct Bad3 { + #[fory(encoding = tagged)] // 编译错误:tagged 仅对 i64/u64 有效 + field: i32, +} +``` + +## 跨语言兼容性 {#cross-language-compatibility} + +当序列化的数据需要由其他语言(Java、C++、Go、Python)读取时,请使用 schema 元数据匹配编码预期: + +```rust +#[derive(ForyStruct)] +struct CrossLangData { + // 匹配使用 varint 的 Java Integer + #[fory(id = 0, encoding = varint)] + int_var: i32, + + // 匹配使用 fixed 的 Java Integer + #[fory(id = 1, encoding = fixed)] + int_fixed: i32, + + // 匹配使用 tagged 编码的 Java Long + #[fory(id = 2, encoding = tagged)] + long_tagged: u64, + + // 可空指针匹配 Java 可空引用 + #[fory(id = 3, nullable)] + optional: Option, +} +``` + +## Schema 演进 {#schema-evolution} + +兼容模式支持 Schema 演进。建议配置字段 ID,以降低序列化成本: + +```rust +// 版本 1 +#[derive(ForyStruct)] +struct DataV1 { + #[fory(id = 0)] + id: i64, + + #[fory(id = 1)] + name: String, +} + +// 版本 2:新增字段 +#[derive(ForyStruct)] +struct DataV2 { + #[fory(id = 0)] + id: i64, + + #[fory(id = 1)] + name: String, + + #[fory(id = 2)] + email: Option, // 新的可空字段 +} +``` + +用 V1 序列化的数据可以用 V2 反序列化(新字段将为 `None`)。 + +也可以省略字段 ID(元数据中会使用字段名,开销更大): + +```rust +#[derive(ForyStruct)] +struct Data { + id: i64, + name: String, +} +``` + +## 默认值 {#default-values} + +- **可空性**:`Option`、`RcWeak` 和 `ArcWeak` 默认可空;所有其他类型不可空 +- **引用跟踪**:`Rc`、`Arc`、`RcWeak` 和 `ArcWeak` 默认启用引用跟踪;所有其他类型默认禁用 + +在以下情况下,你**需要配置字段**: + +- 字段可以为 None(使用 `Option`) +- 字段需要为共享/循环对象启用引用跟踪(使用 `ref = true`) +- 整数类型需要特定编码以实现跨语言兼容性 +- 你想减少元数据大小(使用字段 ID) + +```rust +// Xlang 模式:需要显式配置 +#[derive(ForyStruct)] +struct User { + #[fory(id = 0)] + name: String, // 默认不可空 + + #[fory(id = 1)] + email: Option, // 可空(Option) + + #[fory(id = 2, ref = true)] + friend: Rc, // 引用跟踪(Rc 默认启用) +} +``` + +### 默认值摘要 {#default-values-summary} + +| 类型 | 默认可空 | 默认引用跟踪 | +| ------------------------- | -------- | ------------ | +| Primitives, `String` | `false` | `false` | +| `Option` | `true` | `false` | +| `Rc`, `Arc` | `false` | `true` | +| `RcWeak`, `ArcWeak` | `true` | `true` | + +## 最佳实践 {#best-practices} + +1. **配置字段 ID**:建议在兼容模式下使用,以降低序列化成本 +2. **对敏感数据使用 `skip`**:密码、令牌、内部状态 +3. **为共享对象启用引用跟踪**:当同一指针出现多次时 +4. **为唯一字段禁用引用跟踪**:当你确定字段唯一时的优化 +5. **选择合适的编码**:小值使用 `varint`,完整范围值使用 `fixed` +6. **保持 ID 稳定**:一旦分配,就不要更改字段 ID + +## 选项参考 {#options-reference} + +| Option | 语法 | 描述 | 适用于 | +| ---------- | -------------------------------- | ------------------------------ | -------------------------- | +| `id` | `id = N` | 用于减少元数据大小的字段 tag ID | 所有字段 | +| `skip` | `skip` | 从序列化中排除字段 | 所有字段 | +| `nullable` | `nullable` 或 `nullable = bool` | 控制 null 标志写入 | 所有字段 | +| `ref` | `ref` 或 `ref = bool` | 控制引用跟踪 | `Rc`、`Arc`、weak 类型 | +| `encoding` | `encoding = varint/fixed/tagged` | 整数编码方法 | `i32`、`u32`、`i64`、`u64` | +| `list` | `list(element(...))` | 元素 schema 元数据 | `Vec` | +| `map` | `map(key(...), value(...))` | key/value schema 元数据 | `HashMap` | + +## 相关主题 {#related-topics} + +- [基本序列化](basic-serialization.md) - Fory 序列化入门 +- [Schema 演进](schema-evolution.md) - 兼容模式与 Schema 演进 +- [Xlang 序列化](xlang-serialization.md) - 与 Java、C++、Go、Python 互操作 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/rust/troubleshooting.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/rust/troubleshooting.md new file mode 100644 index 00000000000..13787e22f1e --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/rust/troubleshooting.md @@ -0,0 +1,164 @@ +--- +title: 故障排除 +sidebar_position: 10 +id: troubleshooting +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. +--- + +本页涵盖 Apache Fory™ Rust 的常见问题和调试技术。 + +## 常见问题 + +### 类型注册表错误 + +**错误**:`TypeId ... not found in type_info registry` + +**原因**:该类型从未在当前 `Fory` 实例中注册。 + +**解决方案**:在序列化之前注册类型: + +```rust +let mut fory = Fory::default(); +fory.register::(100)?; // 使用前注册 +``` + +确认: + +- 每个可序列化的结构体或 trait 实现都调用 `fory.register::(type_id)` +- 在反序列化端重用相同的 ID + +### 类型不匹配错误 + +**原因**:字段类型不兼容或 schema 已更改。 + +**解决方案**: + +- 启用 compatible 模式以支持 schema 演化 +- 确保跨版本字段类型匹配 + +```rust +let fory = Fory::default().compatible(true); +``` + +## 调试技术 + +### 启用错误时 Panic 以获取回溯 + +在 `RUST_BACKTRACE=1` 旁边切换 `FORY_PANIC_ON_ERROR=1`,在构造错误的确切位置 panic: + +```bash +RUST_BACKTRACE=1 FORY_PANIC_ON_ERROR=1 cargo test --features tests +``` + +之后重置该变量以避免中止面向用户的代码路径。 + +### 结构体字段跟踪 + +在 `#[derive(ForyObject)]` 旁边添加 `#[fory(debug)]` 属性以发出钩子调用: + +```rust +#[derive(ForyObject)] +#[fory(debug)] +struct MyStruct { + field1: i32, + field2: String, +} +``` + +使用调试钩子编译后,调用这些函数以插入自定义回调: + +- `set_before_write_field_func` +- `set_after_write_field_func` +- `set_before_read_field_func` +- `set_after_read_field_func` + +当你想要恢复默认值时,使用 `reset_struct_debug_hooks()`。 + +### 轻量级日志 + +在没有自定义钩子的情况下,启用 `ENABLE_FORY_DEBUG_OUTPUT=1` 以打印字段级读/写事件: + +```bash +ENABLE_FORY_DEBUG_OUTPUT=1 cargo test --features tests +``` + +这在调查对齐或游标不匹配时特别有用。 + +### 检查生成的代码 + +使用 `cargo expand` 检查 Fory derive 宏生成的代码: + +```bash +cargo expand --test mod $mod$::$file$ > expanded.rs +``` + +## 运行测试 + +### 运行所有测试 + +```bash +cargo test --features tests +``` + +### 运行特定测试 + +```bash +cargo test -p tests --test $test_file $test_method +``` + +### 使用调试运行测试 + +```bash +RUST_BACKTRACE=1 FORY_PANIC_ON_ERROR=1 ENABLE_FORY_DEBUG_OUTPUT=1 \ + cargo test --test mod $dir$::$test_file::$test_method -- --nocapture +``` + +## 测试时的卫生 + +一些集成测试期望 `FORY_PANIC_ON_ERROR` 保持未设置状态。仅在集中调试会话时导出它: + +```bash +# 仅用于特定调试 +FORY_PANIC_ON_ERROR=1 cargo test -p tests --test specific_test -- --nocapture + +# 正常测试运行(不在错误时 panic) +cargo test --features tests +``` + +## 错误处理最佳实践 + +优先使用 `fory_core::error::Error` 上的静态构造函数: + +- `Error::type_mismatch` +- `Error::invalid_data` +- `Error::unknown` + +这样可以保持诊断的一致性,并使选择性 panic 正确工作。 + +## 快速参考 + +| 环境变量 | 目的 | +| ---------------------------- | ----------------------- | +| `RUST_BACKTRACE=1` | 启用堆栈跟踪 | +| `FORY_PANIC_ON_ERROR=1` | 在错误位置 panic 以调试 | +| `ENABLE_FORY_DEBUG_OUTPUT=1` | 打印字段级读/写事件 | + +## 相关主题 + +- [配置](configuration.md) - Fory 选项 +- [类型注册](type-registration.md) - 注册最佳实践 +- [Schema 演化](schema-evolution.md) - Compatible 模式 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/rust/type-registration.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/rust/type-registration.md new file mode 100644 index 00000000000..0401ad2b1b5 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/rust/type-registration.md @@ -0,0 +1,126 @@ +--- +title: 类型注册 +sidebar_position: 3 +id: type_registration +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. +--- + +本页涵盖 Apache Fory™ Rust 中的类型注册方法。 + +## 按 ID 注册 + +使用数字 ID 注册类型以实现快速、紧凑的序列化: + +```rust +use fory::Fory; +use fory::ForyObject; + +#[derive(ForyObject)] +struct User { + name: String, + age: i32, +} + +let mut fory = Fory::default(); +fory.register::(1)?; + +let user = User { + name: "Alice".to_string(), + age: 30, +}; + +let bytes = fory.serialize(&user)?; +let decoded: User = fory.deserialize(&bytes)?; +``` + +## 按命名空间注册 + +为了跨语言兼容性,使用命名空间和类型名称注册: + +```rust +let mut fory = Fory::default() + .compatible(true) + .xlang(true); + +// 使用基于命名空间的命名注册 +fory.register_by_namespace::("com.example", "MyStruct"); +``` + +## 注册自定义序列化器 + +对于需要自定义序列化逻辑的类型: + +```rust +let mut fory = Fory::default(); +fory.register_serializer::(100); +``` + +## 注册顺序 + +当使用没有显式 ID 的基于 ID 的注册时,注册顺序很重要。确保序列化和反序列化之间的注册顺序一致: + +```rust +// 序列化器端 +let mut fory = Fory::default(); +fory.register::(1)?; +fory.register::(2)?; +fory.register::(3)?; + +// 反序列化器端 - 必须使用相同顺序 +let mut fory = Fory::default(); +fory.register::(1)?; +fory.register::(2)?; +fory.register::(3)?; +``` + +## 线程安全注册 + +在生成线程之前执行所有注册: + +```rust +use std::sync::Arc; +use std::thread; + +let mut fory = Fory::default(); +fory.register::(1)?; +fory.register::(2)?; + +// 现在可以在线程间共享 +let fory = Arc::new(fory); + +let handles: Vec<_> = (0..4) + .map(|_| { + let shared = Arc::clone(&fory); + thread::spawn(move || { + // 使用 fory 进行序列化 + }) + }) + .collect(); +``` + +## 最佳实践 + +1. **使用一致的 ID**:在所有语言中为相同类型使用相同的类型 ID 以实现跨语言兼容性 +2. **在线程化之前注册**:在生成线程之前完成所有注册 +3. **对 xlang 使用命名空间**:使类型名称在各语言之间保持一致 +4. **显式 ID 以保持稳定性**:在生产环境中避免自动生成的 ID + +## 相关主题 + +- [配置](configuration.md) - Fory 构建器选项 +- [跨语言](xlang-serialization.md) - XLANG 模式注册 +- [自定义序列化器](custom-serializers.md) - 自定义序列化 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/rust/xlang-serialization.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/rust/xlang-serialization.md new file mode 100644 index 00000000000..4cde6e3fb2d --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/rust/xlang-serialization.md @@ -0,0 +1,165 @@ +--- +title: Xlang 序列化 +sidebar_position: 2 +id: xlang_serialization +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. +--- + +Apache Fory™ 支持在多种语言(包括 Java、Python、C++、Go 和 JavaScript)之间无缝进行数据交换。 + +## 启用xlang 模式 + +```rust +use fory::Fory; + +// 启用xlang 模式 +let mut fory = Fory::default() + .compatible(true) + .xlang(true); + +// 使用跨语言一致的 ID 注册类型 +fory.register::(100); + +// 或使用基于命名空间的注册 +fory.register_by_namespace::("com.example", "MyStruct"); +``` + +## 跨语言类型注册 + +### 按 ID 注册 + +对于使用跨语言一致 ID 的快速、紧凑序列化: + +```rust +let mut fory = Fory::default() + .compatible(true) + .xlang(true); + +fory.register::(100); // 在 Java、Python 等中使用相同的 ID +``` + +### 按命名空间注册 + +对于更灵活的类型命名: + +```rust +fory.register_by_namespace::("com.example", "User"); +``` + +## 跨语言示例 + +### Rust(序列化器) + +```rust +use fory::Fory; +use fory::ForyObject; + +#[derive(ForyObject)] +struct Person { + name: String, + age: i32, +} + +let mut fory = Fory::default() + .compatible(true) + .xlang(true); + +fory.register::(100); + +let person = Person { + name: "Alice".to_string(), + age: 30, +}; + +let bytes = fory.serialize(&person)?; +// bytes 可以被 Java、Python 等反序列化 +``` + +### Java(反序列化器) + +```java +import org.apache.fory.*; +import org.apache.fory.config.*; + +public class Person { + public String name; + public int age; +} + +Fory fory = Fory.builder() + .withLanguage(Language.XLANG) + .withRefTracking(true) + .build(); + +fory.register(Person.class, 100); // 与 Rust 使用相同的 ID + +Person person = (Person) fory.deserialize(bytesFromRust); +``` + +### Python(反序列化器) + +```python +import pyfory +from dataclasses import dataclass + +@dataclass +class Person: + name: str + age: pyfory.int32 + +fory = pyfory.Fory(ref_tracking=True) +fory.register_type(Person, type_id=100) # 与 Rust 使用相同的 ID + +person = fory.deserialize(bytes_from_rust) +``` + +## 类型映射 + +有关跨语言完整类型映射,请参阅 [xlang_type_mapping.md](https://fory.apache.org/docs/specification/xlang_type_mapping)。 + +### 常见类型映射 + +| Rust | Java | Python | +| -------------- | ---------- | ------------- | +| `i32` | `int` | `int32` | +| `i64` | `long` | `int64` | +| `f32` | `float` | `float32` | +| `f64` | `double` | `float64` | +| `String` | `String` | `str` | +| `Vec` | `List` | `List[T]` | +| `HashMap` | `Map` | `Dict[K,V]` | +| `Option` | 可空 `T` | `Optional[T]` | + +## 最佳实践 + +1. **使用一致的类型 ID**:在所有语言中使用相同的 ID +2. **启用 compatible 模式**:用于 schema 演化 +3. **注册所有类型**:在序列化之前 +4. **测试跨语言**:在开发期间测试兼容性 + +## 另请参阅 + +- [xlang 序列化规范](https://fory.apache.org/docs/next/specification/fory_xlang_serialization_spec) +- [类型映射参考](https://fory.apache.org/docs/next/specification/xlang_type_mapping) +- [Java 跨语言指南](../java/xlang-serialization.md) +- [Python 跨语言指南](../python/xlang-serialization.md) + +## 相关主题 + +- [配置](configuration.md) - XLANG 模式配置 +- [Schema 演化](schema-evolution.md) - Compatible 模式 +- [类型注册](type-registration.md) - 注册方法 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/scala/_category_.json b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/scala/_category_.json new file mode 100644 index 00000000000..a0191e28738 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/scala/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "Scala", + "position": 10, + "collapsible": true, + "collapsed": true +} diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/scala/configuration.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/scala/configuration.md new file mode 100644 index 00000000000..59edf6cf951 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/scala/configuration.md @@ -0,0 +1,173 @@ +--- +title: 配置 +sidebar_position: 1 +id: configuration +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. +--- + +本页介绍 Scala 专用的运行时配置和 Fory 实例创建。 + +## Xlang 设置 + +Fory Scala 遵循 Java builder 默认值:启用 xlang 模式,并使用兼容 Schema 演进。跨语言 Scala 载荷、schema IDL 生成的 Scala models,以及 macro-derived xlang serializers 都应使用这一路径。 + +```scala +import org.apache.fory.scala.ForyScala + +val fory = ForyScala.builder() + .withXlang(true) + .build() +``` + +序列化前注册应用类: + +```scala +fory.register(classOf[Person]) +fory.register(classOf[Point]) +``` + +## Native 模式设置 + +对于需要原生 JVM 对象行为的同语言 Scala/JVM 载荷,必须: + +1. 使用 `ForyScala.builder().withXlang(false)` 创建运行时,或者通过 `Fory.builder().withXlang(false).withModule(ForyScala)` 安装 `ForyScala`。 +2. 序列化前注册应用类。 + +```scala +import org.apache.fory.scala.ForyScala + +val fory = ForyScala.builder().withXlang(false) + .build() +``` + +### 注册 Scala 内部类型 + +根据你序列化的对象类型,可能需要注册一些 Scala 内部类型: + +```scala +fory.register(Class.forName("scala.Enumeration.Val")) +``` + +为避免这种注册,可以禁用类注册: + +```scala +val fory = ForyScala.builder().withXlang(false) + .requireClassRegistration(false) + .build() +``` + +> **注意**:禁用类注册允许反序列化未知类型。这更灵活,但如果类包含恶意代码,可能不安全。 + +### 引用跟踪 + +循环引用在 Scala 中很常见。应使用 `withRefTracking(true)` 启用引用跟踪: + +```scala +val fory = ForyScala.builder().withXlang(false) + .withRefTracking(true) + .build() +``` + +> **注意**:如果未启用引用跟踪,在序列化 Scala Enumeration 时,某些 Scala 版本可能出现 [StackOverflowError](https://github.com/apache/fory/issues/1032)。 + +## 线程安全 + +Fory 实例创建成本不低。实例应在多次序列化之间共享。 + +### 单线程使用 + +```scala +import org.apache.fory.Fory +import org.apache.fory.scala.ForyScala + +object ForyHolder { + val fory: Fory = ForyScala.builder() + .withXlang(true) + .build() +} +``` + +### 多线程使用 + +对于多线程应用,请使用 `ThreadSafeFory`: + +```scala +import org.apache.fory.ThreadSafeFory +import org.apache.fory.scala.ForyScala + +object ForyHolder { + val fory: ThreadSafeFory = ForyScala.builder() + .withXlang(true) + .buildThreadSafeFory() +} +``` + +## 配置项 + +Fory Java 的所有配置项都可用。完整列表见 [Java 配置](../java/configuration.md)。 + +Scala native-mode 载荷的常用配置项: + +```scala +import org.apache.fory.scala.ForyScala + +val fory = ForyScala.builder().withXlang(false) + // 为循环引用启用引用跟踪 + .withRefTracking(true) + // 为 native-mode 载荷启用 Schema 演进支持 + .withCompatible(true) + // 启用异步编译以获得更好的启动性能 + .withAsyncCompilation(true) + .build() +``` + +## Xlang 模式 + +对于 Scala xlang 或 schema IDL 生成代码,请使用默认 xlang 模式并注册生成的 schema module: + +```scala +import org.apache.fory.scala.ForyScala +import example.ExampleForyModule + +val fory = ForyScala.builder() + .withXlang(true) + .withRefTracking(true) + .withModule(ExampleForyModule) + .build() +``` + +在 xlang 模式下,Scala collections 使用规范的 `list`、`set` 和 `map` 载荷,而不是 Scala factory 载荷。生成的 optional 字段使用 `Option[T]`。 + +## 安全 + +Scala 使用 Java 运行时配置表面。生产环境以及任何不受信任的载荷来源都应保持启用类注册: + +```scala +val fory = ForyScala.builder() + .requireClassRegistration(true) + .withMaxDepth(50) + .withMaxTypeFields(512) + .withMaxTypeMetaBytes(4096) + .build() +``` + +安全相关配置: + +- 保持 `requireClassRegistration(true)`,并注册应用类或生成的 modules。 +- 使用 `withMaxDepth(...)` 拒绝异常深的对象图。 +- 除非数据不是恶意输入,且可信 peer 会发送更大的 metadata 或大量 schema 版本,否则保持 `withMaxTypeFields(...)`、`withMaxTypeMetaBytes(...)` 以及远端 schema-version 限制的默认值。 +- Allow-listing 和 unknown-class 控制请遵循 [Java 配置](../java/configuration.md#forybuilder-选项)。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/scala/default-values.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/scala/default-values.md new file mode 100644 index 00000000000..2f1d6fd1d7f --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/scala/default-values.md @@ -0,0 +1,186 @@ +--- +title: 默认值 +sidebar_position: 3 +id: default_values +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. +--- + +Fory 在使用兼容模式时支持在反序列化期间使用 Scala 类默认值。此特性在 case 类或常规 Scala 类具有默认参数时支持前向/后向兼容性。 + +## 概述 + +当 Scala 类具有默认参数时,Scala 编译器会在伴生对象中(对于 case 类)或在类本身中(对于常规 Scala 类)生成诸如 `apply$default$1`、`apply$default$2` 等方法来返回默认值。Fory 可以检测这些方法,并在反序列化对象时,当某些字段在序列化数据中缺失时使用它们。 + +## 支持的类类型 + +Fory 支持以下类型的默认值: + +- 具有默认参数的 **Case 类** +- 在主构造函数中具有默认参数的**常规 Scala 类** +- 具有默认参数的**嵌套 case 类** + +## 工作原理 + +1. **检测**:Fory 通过检查是否存在默认值方法(`apply$default$N` 或 `$default$N`)来检测类是否为 Scala 类。 + +2. **默认值发现**: + - 对于 case 类:Fory 扫描伴生对象中名为 `apply$default$1`、`apply$default$2` 等的方法。 + - 对于常规 Scala 类:Fory 扫描类本身中名为 `$default$1`、`$default$2` 等的方法。 + +3. **字段映射**:在反序列化期间,Fory 识别目标类中存在但序列化数据中缺失的字段。 + +4. **值应用**:从序列化数据中读取所有可用字段后,Fory 将默认值应用于任何缺失的字段。 + +## 使用方法 + +当满足以下条件时,此特性会自动启用: + +- 启用兼容模式(`withCompatibleMode(CompatibleMode.COMPATIBLE)`) +- 目标类被检测为具有默认值的 Scala 类 +- 序列化数据中缺少某个字段,但该字段存在于目标类中 + +无需额外配置。 + +## 示例 + +### 具有默认值的 Case 类 + +```scala +import org.apache.fory.Fory +import org.apache.fory.config.CompatibleMode +import org.apache.fory.serializer.scala.ScalaSerializers + +// 没有默认值的类(用于序列化) +case class PersonV1(name: String) + +// 有默认值的类(用于反序列化) +case class PersonV2(name: String, age: Int = 25, city: String = "Unknown") + +val fory = Fory.builder() + .withCompatibleMode(CompatibleMode.COMPATIBLE) + .withScalaOptimizationEnabled(true) + .build() + +ScalaSerializers.registerSerializers(fory) + +// 使用没有默认值的类进行序列化 +val original = PersonV1("John") +val serialized = fory.serialize(original) + +// 反序列化到有默认值的类 +// 缺失的字段将使用默认值 +val deserialized = fory.deserialize(serialized).asInstanceOf[PersonV2] +// deserialized.name == "John" +// deserialized.age == 25(默认值) +// deserialized.city == "Unknown"(默认值) +``` + +### 具有默认值的常规 Scala 类 + +```scala +// 没有默认值的类(用于序列化) +class EmployeeV1(val name: String) + +// 有默认值的类(用于反序列化) +class EmployeeV2( + val name: String, + val age: Int = 30, + val department: String = "Engineering" +) + +val fory = Fory.builder() + .withCompatibleMode(CompatibleMode.COMPATIBLE) + .withScalaOptimizationEnabled(true) + .build() + +ScalaSerializers.registerSerializers(fory) + +// 使用没有默认值的类进行序列化 +val original = new EmployeeV1("Jane") +val serialized = fory.serialize(original) + +// 反序列化到有默认值的类 +val deserialized = fory.deserialize(serialized).asInstanceOf[EmployeeV2] +// deserialized.name == "Jane" +// deserialized.age == 30(默认值) +// deserialized.department == "Engineering"(默认值) +``` + +### 复杂默认值 + +默认值可以是复杂表达式: + +```scala +// 没有默认值的类(用于序列化) +case class ConfigV1(name: String) + +// 有默认值的类(用于反序列化) +case class ConfigV2( + name: String, + settings: Map[String, String] = Map("default" -> "value"), + tags: List[String] = List("default"), + enabled: Boolean = true +) + +val fory = Fory.builder() + .withCompatibleMode(CompatibleMode.COMPATIBLE) + .withScalaOptimizationEnabled(true) + .build() + +ScalaSerializers.registerSerializers(fory) + +val original = ConfigV1("myConfig") +val serialized = fory.serialize(original) + +val deserialized = fory.deserialize(serialized).asInstanceOf[ConfigV2] +// deserialized.name == "myConfig" +// deserialized.settings == Map("default" -> "value") +// deserialized.tags == List("default") +// deserialized.enabled == true +``` + +### 嵌套 Case 类 + +```scala +object Models { + // 没有默认值的类(用于序列化) + case class PersonV1(name: String) + + // 有默认值的类(用于反序列化) + case class Address(street: String, city: String = "DefaultCity") + case class PersonV2(name: String, address: Address = Address("DefaultStreet")) +} + +val fory = Fory.builder() + .withCompatibleMode(CompatibleMode.COMPATIBLE) + .withScalaOptimizationEnabled(true) + .build() + +ScalaSerializers.registerSerializers(fory) + +val original = Models.PersonV1("Alice") +val serialized = fory.serialize(original) + +val deserialized = fory.deserialize(serialized).asInstanceOf[Models.PersonV2] +// deserialized.name == "Alice" +// deserialized.address == Address("DefaultStreet", "DefaultCity") +``` + +## 相关主题 + +- [Schema 演化](../java/schema-evolution.md) - Java 中的前向/后向兼容性 +- [Fory 创建](configuration.md) - 使用兼容模式设置 Fory diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/scala/grpc-support.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/scala/grpc-support.md new file mode 100644 index 00000000000..dd1780dff2d --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/scala/grpc-support.md @@ -0,0 +1,169 @@ +--- +title: gRPC 支持 +sidebar_position: 6 +id: grpc_support +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. +--- + +Fory 可以为包含 service 定义的 schema 生成 Scala 3 gRPC service companion。生成代码使用普通 +grpc-java channel、server、deadline、status code、interceptor 和 transport security,但 +request/response 对象使用 Fory 序列化,而不是 protobuf。 + +当 RPC 两端都由同一份 Fory IDL、protobuf IDL 或 FlatBuffers IDL 生成,并且你希望使用 +gRPC 传输语义与 Fory payload 编码时,可以使用这种模式。如果 API 必须被通用 protobuf client +或 reflection 工具消费,请使用标准 protobuf gRPC 代码生成。 + +## 添加依赖 + +生成的 Scala service 文件编译时需要 grpc-java。`fory-scala` artifact 不会把 gRPC 作为硬依赖: + +```sbt +libraryDependencies ++= Seq( + "org.apache.fory" %% "fory-scala" % "", + "io.grpc" % "grpc-api" % "", + "io.grpc" % "grpc-stub" % "", + "io.grpc" % "grpc-netty-shaded" % "" +) +``` + +生成的 Scala model 和 gRPC companion 是 Scala 3 source。`fory-scala` 仍同时 cross-build +Scala 2.13 和 Scala 3。 + +## 定义 Service + +```protobuf +package demo.greeter; + +message HelloRequest { + string name = 1; +} + +message HelloReply { + string reply = 1; +} + +service Greeter { + rpc SayHello (HelloRequest) returns (HelloReply); +} +``` + +生成 Scala model 和 gRPC companion: + +```bash +foryc service.fdl --scala_out=./generated/scala --grpc +``` + +输出包含: + +| 文件 | 用途 | +| ------------------------- | ---------------------------------- | +| `HelloRequest.scala` | request 的 Fory model type | +| `HelloReply.scala` | response 的 Fory model type | +| `GreeterForyModule.scala` | 生成类型的 Fory 注册 module | +| `GreeterGrpc.scala` | grpc-java service base、client 和 codec | + +## 实现 Server + +继承生成的 `GreeterGrpc.GreeterImplBase`,并注册到标准 grpc-java `Server`: + +```scala +package demo.greeter + +import io.grpc.ServerBuilder + +final class GreeterService extends GreeterGrpc.GreeterImplBase { + override def sayHello(request: HelloRequest): HelloReply = + HelloReply(s"Hello, ${request.name}") +} + +@main def runServer(): Unit = { + val server = ServerBuilder + .forPort(50051) + .addService(new GreeterService) + .build() + .start() + server.awaitTermination() +} +``` + +生成代码会注册 request/response 类型,service 实现不需要手动注册 serializer。 + +## 创建 Client + +使用普通 grpc-java channel 和生成 client: + +```scala +package demo.greeter + +import io.grpc.ManagedChannelBuilder +import scala.concurrent.Await +import scala.concurrent.duration.DurationInt + +@main def runClient(): Unit = { + val channel = ManagedChannelBuilder + .forAddress("localhost", 50051) + .usePlaintext() + .build() + try { + val client = GreeterGrpc.newClient(channel) + val call = client.sayHello(HelloRequest("Fory")) + val reply = Await.result(call.asFuture, 30.seconds) + println(reply.reply) + } finally { + channel.shutdownNow() + } +} +``` + +Unary Scala-friendly 方法返回 `RpcFuture[A]`。调用者可以通过 `asFuture` 与 Scala `Future` 组合, +并在需要提前取消 RPC 时调用 `cancel()`。 + +## Streaming RPC + +Fory service 支持与 grpc-java 相同的 streaming shape: + +```protobuf +service Greeter { + rpc SayHello (HelloRequest) returns (HelloReply); + rpc LotsOfReplies (HelloRequest) returns (stream HelloReply); + rpc LotsOfGreetings (stream HelloRequest) returns (HelloReply); + rpc Chat (stream HelloRequest) returns (stream HelloReply); +} +``` + +| IDL shape | Scala client convenience | grpc-java-style 方法 | +| ----------------------- | ------------------------ | -------------------------------------------- | +| Unary | `RpcFuture[Resp]` | async observer、blocking、`ListenableFuture` | +| Server streaming | `RpcIterator[Resp]` | async observer 和 blocking iterator | +| Client streaming | 无 | `StreamObserver` request stream | +| Bidirectional streaming | 无 | request/response `StreamObserver` | + +Server-streaming 的 `RpcIterator[A]` 扩展 Scala `Iterator[A]` 和 `AutoCloseable`。如果调用者提前停止消费, +应调用 `close()` 或 `cancel()` 释放底层 gRPC call。 + +Client-streaming 和 bidirectional streaming 保持 grpc-java `StreamObserver` API,因为 request +stream lifecycle、completion、cancellation 和 flow-control 规则都属于 grpc-java。 + +## 故障排查 + +### 缺少 grpc-java 类 + +添加 `grpc-api`、`grpc-stub` 和一个 transport,例如 `grpc-netty-shaded`。 + +### Protobuf Client 无法读取响应 + +Fory gRPC 使用 Fory 二进制协议 payload。请在两端使用同一份 schema 生成的 Fory gRPC companion。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/scala/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/scala/index.md new file mode 100644 index 00000000000..e6bc455afb6 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/scala/index.md @@ -0,0 +1,100 @@ +--- +title: Scala 序列化指南 +sidebar_position: 0 +id: serialization_index +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. +--- + +Apache Fory™ Scala 基于 Fory Java 构建,为 Scala 类型提供优化的序列化器。它支持完整的 Scala 对象序列化,包括: + +- `case` 类序列化 +- `pojo/bean` 类序列化 +- `object` 单例序列化 +- `collection` 序列化(Seq、List、Map 等) +- `tuple` 与 `either` 类型 +- `Option` 类型 +- Scala 2 和 Scala 3 枚举 + +同时支持 Scala 2 和 Scala 3。 + +## 特性 + +Fory Scala 继承了 Fory Java 的全部特性,并增加了 Scala 特定优化: + +- **高性能**:JIT 代码生成、零拷贝,性能可比传统序列化快 20 到 170 倍 +- **Scala 类型支持**:为 case 类、单例、集合、tuple、Option、Either 提供优化序列化器 +- **默认值支持**:在 Schema 演进期间自动处理 Scala 类的默认参数 +- **单例保持**:`object` 单例在反序列化后仍保持引用相等性 +- **Schema 演进**:支持类 Schema 变更时的前向和后向兼容 + +完整特性列表请参见 [Java 特性](../java/index.md#特性)。 + +## 安装 + +使用 sbt 添加依赖: + +```sbt +libraryDependencies += "org.apache.fory" %% "fory-scala" % "1.3.0" +``` + +## 快速开始 + +```scala +import org.apache.fory.Fory +import org.apache.fory.serializer.scala.ScalaSerializers + +case class Person(name: String, id: Long, github: String) +case class Point(x: Int, y: Int, z: Int) + +object ScalaExample { + // 创建启用 Scala 优化的 Fory + val fory: Fory = Fory.builder() + .withScalaOptimizationEnabled(true) + .build() + + // 为 Scala 注册优化的 Fory 序列化器 + ScalaSerializers.registerSerializers(fory) + + // 注册你的类 + fory.register(classOf[Person]) + fory.register(classOf[Point]) + + def main(args: Array[String]): Unit = { + val p = Person("Shawn Yang", 1, "https://github.com/chaokunyang") + println(fory.deserialize(fory.serialize(p))) + println(fory.deserialize(fory.serialize(Point(1, 2, 3)))) + } +} +``` + +## 基于 Fory Java 构建 + +Fory Scala 基于 Fory Java 构建。Fory Java 中的大多数配置选项、特性和概念都可直接应用于 Scala。可参考 Java 文档了解: + +- [配置](../java/configuration.md) - 所有 ForyBuilder 选项 +- [基础序列化](../java/basic-serialization.md) - 序列化模式与 API +- [类型注册](../java/type-registration.md) - 类注册与安全性 +- [Schema 演进](../java/schema-evolution.md) - 前向和后向兼容 +- [自定义序列化器](../java/custom-serializers.md) - 实现自定义序列化器 +- [压缩](../java/compression.md) - Int、long 和字符串压缩 +- [故障排查](../java/troubleshooting.md) - 常见问题与解决方案 + +## Scala 特定文档 + +- [Fory 创建](configuration.md) - Scala 特定的 Fory 设置要求 +- [类型序列化](type-serialization.md) - Scala 类型的序列化 +- [默认值](default-values.md) - Scala 类默认值支持 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/scala/schema-idl.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/scala/schema-idl.md new file mode 100644 index 00000000000..cfc0b1e4b09 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/scala/schema-idl.md @@ -0,0 +1,138 @@ +--- +title: Schema IDL 与 Xlang +sidebar_position: 5 +id: schema_idl +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. +--- + +Fory schema IDL 的 Scala target 会为 xlang 载荷生成 Scala 3 源码。运行时构件仍会同时为 Scala 2.13 和 Scala 3 cross-build;只有 schema IDL 输出和 quoted macro 派生需要 Scala 3。 + +## 设置 + +生成的 Scala 代码使用 `org.apache.fory.scala` 中的公共 macro API,以及 `org.apache.fory.annotation` 中的共享 JVM 注解。Macro 内部实现位于 `org.apache.fory.scala.internal` 下。 + +```scala +import org.apache.fory.scala.{ForyScala, ForySerializer} +import example.ExampleForyModule + +val fory = ForyScala.builder() + .withXlang(true) + .withRefTracking(true) + .withModule(ExampleForyModule) + .build() +``` + +生成的 schema modules 也是 Fory modules。创建自定义运行时时使用 `.withModule(...)`;如果默认的 xlang-compatible 运行时已经足够,也可以使用生成的无参 `toBytes` 和 `fromBytes` helper。 + +生成的 helper 会先注册 message 类型身份,再安装 message 序列化器。这个两阶段顺序让相互递归的 message graph 可以通过常规 `TypeResolver` 路径构建 descriptor metadata,而不需要临时序列化器或 Java core 中的 Scala 专用注册状态。Enums 和 unions 会直接连同其序列化器一起注册,因为它们派生出的序列化器负责 case dispatch。 + +## 生成的 Messages + +无环 messages 会生成 case classes: + +```scala +import org.apache.fory.annotation.{ForyField, ForyStruct} +import org.apache.fory.scala.ForySerializer + +@ForyStruct +final case class Person( + @ForyField(id = 1) name: String, + @ForyField(id = 2) email: Option[String] +) derives ForySerializer +``` + +Schema `optional T` 字段会存储为 `Option[T]`。 + +编译器检测到处于构造循环中的 messages 会生成带可变序列化字段的普通类,这样反序列化器就能在读取可能指回该对象的字段之前分配并注册对象。顶层 `ref Foo`、嵌套 `list` 或 `any` 字段本身不会强制使用这种形态。编译器会一起分析 message 和 union 依赖,因此 message-to-union-to-message 循环也会让参与循环的 messages 成为普通类。只包含循环嵌套类型的无环 owner messages 仍会保持为 case classes。 + +引用跟踪通过共享的 `@Ref` 注解表达,包括 type-use 位置: + +```scala +@ForyStruct +final class Node() derives ForySerializer { + @ForyField(id = 1) + var children: List[Node @Ref] = List.empty + + @Ref + @ForyField(id = 2) + var parent: Option[Node] = None +} +``` + +`@Ref` 是 Scala macro 和 IDL API 使用的 JVM 引用跟踪注解。顶层 `ref T` 字段应在字段或构造函数参数上使用 `@Ref`。只有嵌套 element/value/payload refs(例如 `list`)才使用 type-use `T @Ref`。 + +生成的 xlang collection 字段使用不可变 Scala collection 类型:`List[T]`、`Set[T]` 和 `Map[K, V]`。运行时 xlang 序列化器也可以重建受支持的可变 collection interfaces,例如 `scala.collection.Seq` 和 `scala.collection.Map`,但除非显式生成,具体的可变 collection classes 不属于 schema IDL 表面。 + +## 生成的 Enums + +IDL enums 只生成 Scala 3 enums。编译器不会生成 Java enum 文件。 + +```scala +import org.apache.fory.annotation.ForyEnumId + +enum Status { + @ForyEnumId(0) + case Unknown + + @ForyEnumId(1) + case Ok +} +``` + +生成的注册使用 `ScalaSerializers.registerEnum(...)`,因此 xlang 模式会使用 case 级 `@ForyEnumId` 元信息中的稳定 Fory enum IDs。 + +## 生成的 Unions + +IDL unions 会生成带 macro-derived 序列化器的 Scala 3 ADT enums: + +```scala +import org.apache.fory.annotation.{ForyCase, ForyUnion, UInt32Type} +import org.apache.fory.config.Int32Encoding +import org.apache.fory.scala.ForySerializer + +@ForyUnion +enum SearchTarget derives ForySerializer { + @ForyCase(id = 0) + case UnknownCase(caseId: Int, value: Any) + + @ForyCase(id = 1) + case UserCase(value: User) + + @ForyCase(id = 2) + case FixedIdCase(value: Long @UInt32Type(encoding = Int32Encoding.FIXED)) +} +``` + +Schema 定义的 union cases 必须使用正 ID。Case ID `0` 预留给 Scala unknown-case carrier,其载荷会存储原始正 case ID 和反序列化出的值。当 reader 遇到更新的正 case ID 时,会返回 `UnknownCase(originalId, value)`,而不是仅因本地不知道该 case ID 就失败。 + +Macro 会直接写入现有的 xlang union envelope。它不会分配临时 Java `Union` carriers。 + +## 手动 Scala 3 派生 + +手写 Scala 3 models 可以派生同一个 serializer typeclass: + +```scala +@ForyStruct +final class Record(@ForyField(id = 1) val id: Int) derives ForySerializer { + @ForyField(id = 2) + var name: String = "" +} +``` + +Macro 会为 constructor-owned fields 生成直接构造函数调用,并为 mutable post-construction fields 生成直接赋值。它会根据 Scala 编译期类型构建 descriptor metadata,包括嵌套 generics、`Option`、arrays、scalar encoding annotations、nullability 和 `@Ref` metadata。Java 反射不是生成 Scala metadata 的事实来源。 + +复制期间,当被复制的 root 可以在复制循环字段之前先分配并注册时,就支持循环图;这正是 schema IDL 对构造循环使用的普通类形态。如果复制从参与循环的不可变 constructor-owned value 开始,例如 Scala enum case 或 case class,则序列化器会给出明确错误,因为在构造完成前无法发布被复制的 identity。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/scala/schema-metadata.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/scala/schema-metadata.md new file mode 100644 index 00000000000..dd05a8c25fd --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/scala/schema-metadata.md @@ -0,0 +1,109 @@ +--- +title: Schema 元信息 +sidebar_position: 3 +id: schema_metadata +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. +--- + +Scala schema 元信息由 schema IDL 生成代码和 Scala 3 macro-derived xlang 序列化器使用。元信息通过共享的 JVM Fory 注解和 Scala 编译期类型信息声明。 + +## Struct 字段 + +Schema messages 可以使用 `@ForyStruct` 和 `@ForyField(id = N)`: + +```scala +import org.apache.fory.annotation.{ForyField, ForyStruct} +import org.apache.fory.scala.ForySerializer + +@ForyStruct +final case class Person( + @ForyField(id = 1) name: String, + @ForyField(id = 2) email: Option[String] +) derives ForySerializer +``` + +Schema `optional T` 字段表示为 `Option[T]`。 + +## 引用跟踪 + +引用跟踪使用共享的 JVM `@Ref` 注解。顶层 `ref T` 字段应在字段或构造函数参数上使用 `@Ref`,嵌套 collection 或 map 载荷应使用 type-use `T @Ref`: + +```scala +import org.apache.fory.annotation.{ForyField, ForyStruct, Ref} + +@ForyStruct +final class Node() derives ForySerializer { + @ForyField(id = 1) + var children: List[Node @Ref] = List.empty + + @Ref + @ForyField(id = 2) + var parent: Option[Node] = None +} +``` + +## Enum IDs + +IDL enums 会生成 Scala 3 enums。稳定的 Fory enum IDs 来自 case 级 `@ForyEnumId` 元信息: + +```scala +import org.apache.fory.annotation.ForyEnumId + +enum Status { + @ForyEnumId(0) + case Unknown + + @ForyEnumId(1) + case Ok +} +``` + +生成的注册使用 `ScalaSerializers.registerEnum(...)`,因此 xlang 模式会使用这些稳定 ID。 + +## Unions + +IDL unions 会生成带 `@ForyUnion` 和 `@ForyCase` 元信息的 Scala 3 ADT enums: + +```scala +import org.apache.fory.annotation.{ForyCase, ForyUnion, UInt32Type} +import org.apache.fory.config.Int32Encoding +import org.apache.fory.scala.ForySerializer + +@ForyUnion +enum SearchTarget derives ForySerializer { + @ForyCase(id = 0) + case UnknownCase(caseId: Int, value: Any) + + @ForyCase(id = 1) + case UserCase(value: User) + + @ForyCase(id = 2) + case FixedIdCase(value: Long @UInt32Type(encoding = Int32Encoding.FIXED)) +} +``` + +Schema 定义的 union cases 必须使用正 ID。Case ID `0` 预留给 reader 遇到更新的正 case ID 时使用的 unknown-case carrier。 + +## 生成的元信息来源 + +Scala macro 会根据 Scala 编译期类型构建 descriptor metadata,包括嵌套 generics、`Option`、arrays、scalar encoding annotations、nullability 和 `@Ref` metadata。Java 反射不是生成 Scala metadata 的事实来源。 + +## 相关主题 + +- [Schema IDL 与 Xlang](schema-idl.md) +- [配置](configuration.md) +- [默认值](default-values.md) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/scala/type-serialization.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/scala/type-serialization.md new file mode 100644 index 00000000000..8c861e257e1 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/scala/type-serialization.md @@ -0,0 +1,171 @@ +--- +title: 类型序列化 +sidebar_position: 2 +id: type_serialization +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. +--- + +本页介绍 Scala 特定类型的序列化。 + +## 设置 + +所有示例假设以下设置: + +```scala +import org.apache.fory.Fory +import org.apache.fory.serializer.scala.ScalaSerializers + +val fory = Fory.builder() + .withScalaOptimizationEnabled(true) + .build() + +ScalaSerializers.registerSerializers(fory) +``` + +## Case 类 + +```scala +case class Person(github: String, age: Int, id: Long) + +fory.register(classOf[Person]) + +val p = Person("https://github.com/chaokunyang", 18, 1) +println(fory.deserialize(fory.serialize(p))) +``` + +## POJO 类 + +```scala +class Foo(f1: Int, f2: String) { + override def toString: String = s"Foo($f1, $f2)" +} + +fory.register(classOf[Foo]) + +println(fory.deserialize(fory.serialize(new Foo(1, "chaokunyang")))) +``` + +## Object 单例 + +Scala `object` 单例被序列化和反序列化为同一个实例: + +```scala +object MySingleton { + val value = 42 +} + +fory.register(MySingleton.getClass) + +val o1 = fory.deserialize(fory.serialize(MySingleton)) +val o2 = fory.deserialize(fory.serialize(MySingleton)) +println(o1 == o2) // true +``` + +## 集合 + +完全支持 Scala 集合: + +```scala +val seq = Seq(1, 2) +val list = List("a", "b") +val map = Map("a" -> 1, "b" -> 2) + +println(fory.deserialize(fory.serialize(seq))) +println(fory.deserialize(fory.serialize(list))) +println(fory.deserialize(fory.serialize(map))) +``` + +## 元组 + +支持所有 Scala 元组类型(Tuple1 到 Tuple22): + +```scala +val tuple2 = (100, 10000L) +println(fory.deserialize(fory.serialize(tuple2))) + +val tuple4 = (100, 10000L, 10000L, "str") +println(fory.deserialize(fory.serialize(tuple4))) +``` + +## 枚举 + +### Scala 3 枚举 + +```scala +enum Color { case Red, Green, Blue } + +fory.register(classOf[Color]) + +println(fory.deserialize(fory.serialize(Color.Green))) +``` + +### Scala 2 Enumeration + +```scala +object ColorEnum extends Enumeration { + type ColorEnum = Value + val Red, Green, Blue = Value +} + +fory.register(Class.forName("scala.Enumeration.Val")) + +println(fory.deserialize(fory.serialize(ColorEnum.Green))) +``` + +> **注意**:对于 Scala 2 Enumeration,可能需要注册 `scala.Enumeration.Val` 或启用引用跟踪以避免 `StackOverflowError`。 + +## Option + +```scala +val some: Option[Long] = Some(100) +println(fory.deserialize(fory.serialize(some))) + +val none: Option[Long] = None +println(fory.deserialize(fory.serialize(none))) +``` + +## Either + +```scala +val right: Either[String, Int] = Right(42) +println(fory.deserialize(fory.serialize(right))) + +val left: Either[String, Int] = Left("error") +println(fory.deserialize(fory.serialize(left))) +``` + +## 嵌套类型 + +完全支持复杂的嵌套结构: + +```scala +case class Address(street: String, city: String) +case class Company(name: String, address: Address) +case class Employee(name: String, company: Company, tags: List[String]) + +fory.register(classOf[Address]) +fory.register(classOf[Company]) +fory.register(classOf[Employee]) + +val employee = Employee( + "John", + Company("Acme", Address("123 Main St", "Springfield")), + List("developer", "scala") +) + +println(fory.deserialize(fory.serialize(employee))) +``` diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/swift/_category_.json b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/swift/_category_.json new file mode 100644 index 00000000000..dfdaf599681 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/swift/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "Swift", + "position": 8, + "collapsible": true, + "collapsed": true +} diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/swift/basic-serialization.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/swift/basic-serialization.md new file mode 100644 index 00000000000..a51088f67ee --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/swift/basic-serialization.md @@ -0,0 +1,114 @@ +--- +title: 基础序列化 +sidebar_position: 1 +id: basic_serialization +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. +--- + +本页介绍 Swift 中的对象图序列化和核心 API 用法。 + +## 对象图序列化 + +在结构体、类或枚举上使用 `@ForyObject`,注册类型后即可进行序列化和反序列化。 + +```swift +import Foundation +import Fory + +@ForyObject +struct Address: Equatable { + var street: String = "" + var zip: Int32 = 0 +} + +@ForyObject +struct Person: Equatable { + var id: Int64 = 0 + var name: String = "" + var nickname: String? = nil + var tags: Set = [] + var scores: [Int32] = [] + var addresses: [Address] = [] + var metadata: [Int8: Int32?] = [:] +} + +let fory = Fory() +fory.register(Address.self, id: 100) +fory.register(Person.self, id: 101) + +let person = Person( + id: 42, + name: "Alice", + nickname: nil, + tags: ["swift", "xlang"], + scores: [10, 20, 30], + addresses: [Address(street: "Main", zip: 94107)], + metadata: [1: 100, 2: nil] +) + +let data = try fory.serialize(person) +let decoded: Person = try fory.deserialize(data) +assert(decoded == person) +``` + +## 与已有缓冲区配合使用 + +你可以把序列化结果追加到现有 `Data`,也可以从 `ByteBuffer` 反序列化。 + +```swift +var output = Data() +try fory.serialize(person, to: &output) + +let inputBuffer = ByteBuffer(data: output) +let fromBuffer: Person = try fory.deserialize(from: inputBuffer) +assert(fromBuffer == person) +``` + +## 内置支持的类型 + +### 基础标量类型 + +- `Bool` +- `Int8`、`Int16`、`Int32`、`Int64`、`Int` +- `UInt8`、`UInt16`、`UInt32`、`UInt64`、`UInt` +- `Float`、`Double` +- `String` +- `Data` + +### 日期与时间类型 + +- `Date` +- `ForyDate` +- `ForyTimestamp` + +### 集合类型 + +- `[T]`,其中 `T: Serializer` +- `Set`,其中 `T: Serializer & Hashable` +- `[K: V]`,其中 `K: Serializer & Hashable`、`V: Serializer` +- 可选值变体 `T?` + +### 动态类型 + +- `Any` +- `AnyObject` +- `any Serializer` +- `AnyHashable` +- `[Any]` +- `[String: Any]` +- `[Int32: Any]` +- `[AnyHashable: Any]` diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/swift/configuration.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/swift/configuration.md new file mode 100644 index 00000000000..31b76aae328 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/swift/configuration.md @@ -0,0 +1,133 @@ +--- +title: 配置 +sidebar_position: 2 +id: configuration +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. +--- + +本页介绍 `ForyConfig` 以及推荐的运行时配置。 + +## ForyConfig + +`Fory` 的配置结构如下: + +```swift +public struct ForyConfig { + public var xlang: Bool + public var trackRef: Bool + public var compatible: Bool + public let checkClassVersion: Bool + public let maxDepth: Int + public let maxTypeFields: Int + public let maxTypeMetaBytes: Int + public let maxSchemaVersionsPerType: Int + public let maxAverageSchemaVersionsPerType: Int +} +``` + +默认配置: + +```swift +let fory = Fory() // xlang=true, trackRef=false, compatible=false +``` + +## 线程模型 + +`Fory` 是单线程运行时,会在调用线程上复用一组读写上下文。建议每个线程复用一个实例,不要把同一个实例并发共享给多个线程。 + +## 配置项 + +### `xlang` + +控制是否启用跨语言协议模式。 + +- `true`:使用 xlang 编码格式,默认值 +- `false`:使用 Swift 原生模式 + +```swift +let fory = Fory(xlang: true) +``` + +### `trackRef` + +为可跟踪引用的类型启用共享引用和循环引用跟踪。 + +- `false`:不维护引用表,适合无环或纯值对象图 +- `true`:保留类对象图中的身份关系 + +```swift +let fory = Fory(xlang: true, trackRef: true) +``` + +### `compatible` + +启用跨版本的兼容 Schema 模式。 + +- `false`:Schema 一致模式,更严格,元信息开销更低 +- `true`:兼容模式,支持字段新增、删除和重排 + +```swift +let fory = Fory(xlang: true, trackRef: false, compatible: true) +``` + +### Size 和 Depth 限制 + +`maxDepth` 限制解码 payload 的嵌套深度。兼容模式下的远端 metadata 也会被限制: + +- `maxTypeFields` 默认值为 `512`,限制一个收到的 struct metadata body 中的字段数。 +- `maxTypeMetaBytes` 默认值为 `4096`,限制一个收到的 TypeMeta body 的编码 body 字节数,不包含 8 字节 header 和扩展 size varint。 +- `maxSchemaVersionsPerType` 默认值为 `10`,限制一个逻辑类型可接受的远端 metadata 版本数。 +- `maxAverageSchemaVersionsPerType` 默认值为 `3`,限制所有已接受远端类型的平均版本数;有效全局下限为 `8192` 个 schema。 + +```swift +let fory = Fory( + maxDepth: 5, + maxTypeFields: 512, + maxTypeMetaBytes: 4096, + maxSchemaVersionsPerType: 10, + maxAverageSchemaVersionsPerType: 3 +) +``` + +## 推荐配置 + +### 本地严格 Schema + +```swift +let fory = Fory(xlang: false, trackRef: false, compatible: false) +``` + +### 跨语言服务载荷 + +```swift +let fory = Fory(xlang: true, trackRef: false, compatible: true) +``` + +### 需要对象身份的图结构载荷 + +```swift +let fory = Fory(xlang: true, trackRef: true, compatible: true) +``` + +## 安全 + +安全相关配置: + +- 在反序列化不可信 payload 前,只注册预期的生成 model。 +- 对 intentional same-schema payload,将 `checkClassVersion` 与 `compatible: false` 配合使用。 +- 根据服务接受的最大嵌套深度设置 `maxDepth`。 +- 除非数据不是恶意输入,且可信 peer 会发送更大的 metadata 或大量 schema 版本,否则保持远端 schema metadata 限制的默认值。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/swift/custom-serializers.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/swift/custom-serializers.md new file mode 100644 index 00000000000..e5c47e340db --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/swift/custom-serializers.md @@ -0,0 +1,84 @@ +--- +title: 自定义序列化器 +sidebar_position: 4 +id: custom_serializers +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. +--- + +对于不适合使用 `@ForyObject` 的类型,或需要特殊编码逻辑的场景,可以手动实现 `Serializer`。 + +## 适用场景 + +- 外部类型需要严格的编码兼容性 +- 需要更紧凑或特殊的编码布局 +- 旧载荷迁移路径 +- 性能敏感的热点路径 + +## 实现 `Serializer` + +```swift +import Foundation +import Fory + +struct UUIDBox: Serializer, Equatable { + var value: UUID = UUID(uuidString: "00000000-0000-0000-0000-000000000000")! + + static func foryDefault() -> UUIDBox { + UUIDBox() + } + + static var staticTypeId: ForyTypeId { + .ext + } + + func foryWriteData(_ context: WriteContext, hasGenerics: Bool) throws { + _ = hasGenerics + try value.uuidString.foryWriteData(context, hasGenerics: false) + } + + static func foryReadData(_ context: ReadContext) throws -> UUIDBox { + let raw = try String.foryReadData(context) + guard let uuid = UUID(uuidString: raw) else { + throw ForyError.invalidData("invalid UUID string: \\(raw)") + } + return UUIDBox(value: uuid) + } +} +``` + +## 注册并使用 + +```swift +let fory = Fory() +fory.register(UUIDBox.self, id: 300) + +let input = UUIDBox(value: UUID()) +let data = try fory.serialize(input) +let output: UUIDBox = try fory.deserialize(data) + +assert(input == output) +``` + +## 如何选择 `staticTypeId` + +手动实现的自定义类型,需要让 `staticTypeId` 与实际编码形态匹配。 + +常见选择: + +- `.structType`:常规结构化对象 +- `.enumType` / `.typedUnion`:枚举或联合类型 +- `.ext`:扩展或自定义类型 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/swift/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/swift/index.md new file mode 100644 index 00000000000..0015c910366 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/swift/index.md @@ -0,0 +1,84 @@ +--- +title: Swift 序列化指南 +sidebar_position: 0 +id: serialization_index +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. +--- + +Apache Fory Swift 提供高性能对象图序列化,同时具备强类型安全、基于宏的代码生成、Schema 演进能力以及跨语言兼容性。 + +## 为什么选择 Fory Swift? + +- 面向 Swift 值类型和引用类型的高性能二进制序列化 +- 使用 `@ForyObject` 宏,几乎无需样板代码即可让模型支持序列化 +- 通过 `xlang` 协议与 Java、Rust、Go、Python 等运行时互通 +- 通过兼容模式支持跨版本 Schema 演进 +- 内置支持动态值,例如 `Any`、`AnyObject`、`any Serializer`、`AnyHashable` +- 支持共享引用和循环引用跟踪,类类型还支持 `weak` 引用场景 + +## 安装 + +通过 Apache Fory GitHub 仓库将 Fory Swift 添加到 Swift Package Manager: + +```swift +dependencies: [ + .package(url: "https://github.com/apache/fory.git", exact: "$version") +], +targets: [ + .target( + name: "MyApp", + dependencies: [ + .product(name: "Fory", package: "fory") + ] + ) +] +``` + +## 指南目录 + +- [配置](configuration.md) +- [基础序列化](basic-serialization.md) +- [类型注册](type-registration.md) +- [自定义序列化器](custom-serializers.md) +- [字段配置](schema-metadata.md) +- [共享引用与循环引用](references.md) +- [多态与动态类型](polymorphism.md) +- [Schema 演进](schema-evolution.md) +- [跨语言序列化](xlang-serialization.md) +- [Row Format 状态](../xlang/row_format.md) +- [故障排查](troubleshooting.md) + +## 快速示例 + +```swift +import Fory + +@ForyObject +struct User: Equatable { + var name: String = "" + var age: Int32 = 0 +} + +let fory = Fory() +fory.register(User.self, id: 1) + +let input = User(name: "alice", age: 30) +let data = try fory.serialize(input) +let output: User = try fory.deserialize(data) + +assert(input == output) +``` diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/swift/polymorphism.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/swift/polymorphism.md new file mode 100644 index 00000000000..b84c0906a7e --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/swift/polymorphism.md @@ -0,0 +1,80 @@ +--- +title: 多态与动态类型 +sidebar_position: 7 +id: polymorphism +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. +--- + +Fory Swift 支持对 `Any`、`AnyObject` 和 `any Serializer` 进行动态序列化。 + +## 顶层动态 API + +```swift +let fory = Fory() + +let dynamic: Any = Int32(7) +let data = try fory.serialize(dynamic) +let decoded: Any = try fory.deserialize(data) +``` + +还提供了等价重载,可用于: + +- `AnyObject` +- `any Serializer` +- `AnyHashable` +- `[Any]` +- `[String: Any]` +- `[Int32: Any]` +- `[AnyHashable: Any]` + +## `@ForyObject` 类型中的动态字段 + +```swift +@ForyObject +struct DynamicHolder { + var value: Any = ForyAnyNullValue() + var list: [Any] = [] + var byName: [String: Any] = [:] + var byId: [Int32: Any] = [:] + var byDynamicKey: [AnyHashable: Any] = [:] +} +``` + +## 仍然需要注册具体类型 + +如果动态值中包含用户定义的运行时类型,仍然需要注册这些具体类型。 + +```swift +@ForyObject +struct Address { + var street: String = "" + var zip: Int32 = 0 +} + +let fory = Fory() +fory.register(Address.self, id: 100) +``` + +## 空值语义 + +- `Any` 的空值表示:`ForyAnyNullValue` +- `AnyObject` 的空值表示:`NSNull` +- 可选动态值在反序列化后会映射到相应的空值表示 + +## 当前限制 + +- `AnyHashable` 键所包裹的运行时值,必须同时满足 `Hashable` 和 Fory 动态序列化支持 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/swift/references.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/swift/references.md new file mode 100644 index 00000000000..c4428795248 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/swift/references.md @@ -0,0 +1,110 @@ +--- +title: 共享引用与循环引用 +sidebar_position: 6 +id: references +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. +--- + +Swift 中的引用跟踪由 `ForyConfig.trackRef` 控制。 + +## 启用引用跟踪 + +```swift +let fory = Fory(xlang: true, trackRef: true, compatible: false) +``` + +启用后,可跟踪引用的类型会保留对象身份和循环结构。 + +## 共享引用示例 + +```swift +import Fory + +@ForyObject +final class Animal { + var name: String = "" + + required init() {} + + init(name: String) { + self.name = name + } +} + +@ForyObject +final class AnimalPair { + var first: Animal? = nil + var second: Animal? = nil + + required init() {} + + init(first: Animal? = nil, second: Animal? = nil) { + self.first = first + self.second = second + } +} + +let fory = Fory(xlang: true, trackRef: true) +fory.register(Animal.self, id: 200) +fory.register(AnimalPair.self, id: 201) + +let shared = Animal(name: "cat") +let input = AnimalPair(first: shared, second: shared) + +let data = try fory.serialize(input) +let decoded: AnimalPair = try fory.deserialize(data) + +assert(decoded.first === decoded.second) +``` + +## 循环引用示例:使用 `weak` + +`trackRef` 会保留引用图,但不会改变 ARC 的所有权规则。为了避免内存泄漏,循环边至少有一侧应使用 `weak`。 + +```swift +import Fory + +@ForyObject +final class Node { + var value: Int32 = 0 + weak var next: Node? = nil + + required init() {} + + init(value: Int32, next: Node? = nil) { + self.value = value + self.next = next + } +} + +let fory = Fory(xlang: true, trackRef: true) +fory.register(Node.self, id: 300) + +let node = Node(value: 7) +node.next = node + +let data = try fory.serialize(node) +let decoded: Node = try fory.deserialize(data) + +assert(decoded.next === decoded) +``` + +## 说明 + +- 值类型,例如 `struct` 和基础值,不具备对象身份语义 +- `trackRef` 控制的是序列化时的引用图身份,而不是 ARC 内存管理 +- 纯值载荷可使用 `trackRef=false` 以减少开销 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/swift/schema-evolution.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/swift/schema-evolution.md new file mode 100644 index 00000000000..2795ee69994 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/swift/schema-evolution.md @@ -0,0 +1,77 @@ +--- +title: Schema 演进 +sidebar_position: 8 +id: schema_evolution +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. +--- + +Fory 通过兼容模式支持 Schema 演进。 + +## 启用兼容模式 + +```swift +let fory = Fory(xlang: true, trackRef: false, compatible: true) +``` + +## 示例:结构体演进 + +```swift +import Fory + +@ForyObject +struct PersonV1 { + var name: String = "" + var age: Int32 = 0 + var address: String = "" +} + +@ForyObject +struct PersonV2 { + var name: String = "" + var age: Int32 = 0 + var phone: String? = nil // 新增字段 +} + +let writer = Fory(xlang: true, compatible: true) +writer.register(PersonV1.self, id: 1) + +let reader = Fory(xlang: true, compatible: true) +reader.register(PersonV2.self, id: 1) + +let v1 = PersonV1(name: "alice", age: 30, address: "main st") +let bytes = try writer.serialize(v1) +let v2: PersonV2 = try reader.deserialize(bytes) + +assert(v2.name == "alice") +assert(v2.age == 30) +assert(v2.phone == nil) +``` + +## 兼容模式下安全的变更 + +- 新增字段 +- 删除旧字段 +- 重排字段顺序 + +## 不安全的变更 + +- 任意修改既有字段的类型,例如从 `Int32` 改成 `String` +- 对端之间使用不一致的注册映射 + +## Schema 一致模式的行为 + +当 `compatible=false` 时,Fory 会校验 schema hash,并在不匹配时快速失败。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/swift/schema-metadata.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/swift/schema-metadata.md new file mode 100644 index 00000000000..1d9465aef1f --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/swift/schema-metadata.md @@ -0,0 +1,149 @@ +--- +title: Schema 元数据 +sidebar_position: 4 +id: schema_metadata +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. +--- + +本页介绍 Swift 中宏级别的 schema 元数据。 + +## 可用的宏属性 {#available-macro-attributes} + +- 结构体/类模型上的 `@ForyStruct` +- C 风格枚举模型上的 `@ForyEnum` +- 带关联值枚举模型上的 `@ForyUnion` 和 `@ForyCase` +- 数值字段上的 `@ForyField(encoding: ...)` +- 用于集合字段元数据的 `@ListField`、`@ArrayField`、`@SetField` 和 `@MapField` + +## `@ForyField(encoding:)` {#foryfieldencoding} + +使用 `@ForyField` 覆盖整数编码策略。 + +```swift +@ForyStruct +struct Metrics: Equatable { + @ForyField(encoding: .fixed) + var u32Fixed: UInt32 = 0 + + @ForyField(encoding: .tagged) + var u64Tagged: UInt64 = 0 +} +``` + +### 支持的组合 {#supported-combinations} + +| Swift type | 支持的 encoding 值 | 默认 encoding | +| ---------- | ------------------------------ | ------------- | +| `Int32` | `.varint`, `.fixed` | `.varint` | +| `UInt32` | `.varint`, `.fixed` | `.varint` | +| `Int64` | `.varint`, `.fixed`, `.tagged` | `.varint` | +| `UInt64` | `.varint`, `.fixed`, `.tagged` | `.varint` | +| `Int` | `.varint`, `.fixed`, `.tagged` | `.varint` | +| `UInt` | `.varint`, `.fixed`, `.tagged` | `.varint` | + +编译期校验会拒绝不支持的组合(例如 `Int32` 搭配 `.tagged`)。 + +## 嵌套集合字段元数据 {#nested-collection-field-metadata} + +当集合字段需要类型特定的编码格式元数据时,例如容器内部使用定长或带 tag 的整数编码,请使用 `@ListField`、`@ArrayField`、`@SetField` 和 `@MapField`。密集的非空 bool、整数和浮点数组使用 `@ArrayField`。 + +```swift +@ForyStruct +struct NestedMetrics: Equatable { + @ListField(element: .encoding(.fixed)) + var values: [Int32?] = [] + + @ArrayField(element: .int32()) + var denseValues: [Int32] = [] + + @SetField(element: .encoding(.fixed)) + var ids: Set = [] + + @MapField(key: .encoding(.fixed), value: .encoding(.tagged)) + var byId: [Int32: UInt64] = [:] + + @MapField(value: .list(element: .encoding(.fixed))) + var groups: [String: [Int32?]] = [:] +} +``` + +带有定长有符号或无符号整数元数据的非空 `List` 元素,会被分类并编码为匹配的 Fory primitive packed-array 类型。`Set` 字段仍分类为 Fory set,包括定长整数 set。 + +当 Swift 属性类型是别名,或因其他原因需要完整提示时,请使用 `@ForyField(type:)`: + +```swift +typealias MetricsMap = [String: [Int32?]] + +@ForyStruct +struct AliasMetrics: Equatable { + @ForyField(type: .map( + key: .string, + value: .list(.int32(nullable: true, encoding: .fixed)) + )) + var metrics: MetricsMap = [:] +} +``` + +Union 载荷通过 `@ForyCase(payload:)` 使用同一套 DSL: + +```swift +@ForyUnion +enum Event: Equatable { + @ForyCase(id: 1) + case created(String) + + @ForyCase(id: 2, payload: .uint64(encoding: .fixed)) + case deleted(UInt64) +} +``` + +## 模型宏要求 {#model-macro-requirements} + +### 结构体和类字段 {#struct-and-class-fields} + +- 存储属性必须声明显式类型 +- 计算属性会被忽略 +- 静态/类属性会被忽略 + +### 类要求 {#class-requirement} + +标注 `@ForyStruct` 的类必须提供 `required init()` 以进行默认构造。 + +```swift +@ForyStruct +final class Node { + var value: Int32 = 0 + var next: Node? = nil + + required init() {} +} +``` + +## 宏类型中的动态 Any 字段 {#dynamic-any-fields-in-macro-types} + +Fory 模型宏支持动态字段和嵌套容器: + +- `Any`, `AnyObject`, `any Serializer` +- `AnyHashable` +- `[Any]` +- `[String: Any]` +- `[Int32: Any]` +- `[AnyHashable: Any]` + +当前限制: + +- 仅当 `K` 为 `String`、`Int32` 或 `AnyHashable` 时,才支持 `Dictionary` diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/swift/troubleshooting.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/swift/troubleshooting.md new file mode 100644 index 00000000000..febed0a27cb --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/swift/troubleshooting.md @@ -0,0 +1,92 @@ +--- +title: 故障排查 +sidebar_position: 11 +id: troubleshooting +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. +--- + +本页介绍 Swift 中常见的问题及调试方法。 + +## 常见运行时错误 + +### `Type not registered: ...` + +原因:当前 `Fory` 实例没有注册用户类型。 + +修复方式: + +```swift +fory.register(MyType.self, id: 100) +``` + +### `Type mismatch: expected ..., got ...` + +原因:对端之间的注册映射或字段类型信息不一致。 + +修复方式: + +- 确保两端使用相同的 type ID 或名称映射 +- 检查字段类型是否兼容 + +### `Invalid data: xlang bitmap mismatch` + +原因:序列化端和反序列化端使用了不同的 `xlang` 配置。 + +修复方式:确保双方使用相同的 `xlang` 模式。 + +### `Invalid data: class version hash mismatch` + +原因:在 `compatible=false` 下发生了 schema 变更。 + +修复方式: + +- 为需要演进的 schema 启用兼容模式 +- 或保持严格的 schema 一致性 + +## 常见宏阶段错误 + +### `@ForyObject requires explicit types for stored properties` + +为所有存储属性补充显式类型声明。 + +### `@ForyObject enum associated values cannot have default values` + +移除枚举关联值上的默认值。 + +### `Set<...> with Any elements is not supported by @ForyObject yet` + +改用 `[Any]` 或明确元素类型的集合。 + +### `Dictionary<..., ...> with Any values is only supported for String, Int32, or AnyHashable keys` + +把 key 类型改为 `String`、`Int32` 或 `AnyHashable`,或者避免在 map value 中使用动态 `Any`。 + +## 调试命令 + +运行 Swift 测试: + +```bash +cd swift +ENABLE_FORY_DEBUG_OUTPUT=1 swift test +``` + +运行由 Java 驱动的 Swift xlang 测试: + +```bash +cd java/fory-core +ENABLE_FORY_DEBUG_OUTPUT=1 FORY_SWIFT_JAVA_CI=1 mvn -T16 test -Dtest=org.apache.fory.xlang.SwiftXlangTest +``` diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/swift/type-registration.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/swift/type-registration.md new file mode 100644 index 00000000000..5fccad9fa4f --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/swift/type-registration.md @@ -0,0 +1,76 @@ +--- +title: 类型注册 +sidebar_position: 3 +id: type_registration +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. +--- + +本页介绍用户定义类型的注册 API。 + +## 为什么必须注册 + +用户类型,例如 `struct`、`class`、enum/union 和 ext 类型,在序列化和反序列化前必须先注册。 + +如果缺少注册,反序列化会失败,并抛出: + +- `Type not registered: ...` + +## 按数值 ID 注册 + +请为序列化端和反序列化端使用同一个稳定 ID。 + +```swift +@ForyObject +struct User { + var name: String = "" + var age: Int32 = 0 +} + +let fory = Fory() +fory.register(User.self, id: 1) +``` + +## 按名称注册 + +### 使用全限定名 + +```swift +try fory.register(User.self, name: "com.example.User") +``` + +`name` 会按 `.` 拆分: + +- namespace: `com.example` +- type name: `User` + +### 显式指定命名空间和类型名 + +```swift +try fory.register(User.self, namespace: "com.example", name: "User") +``` + +## 一致性规则 + +在不同对端之间保持注册映射一致: + +- ID 模式:同一个逻辑类型在所有对端都使用相同数值 ID +- 名称模式:同一个逻辑类型在所有对端都使用相同 namespace 和 type name +- 不要对同一逻辑类型在不同服务里混用 ID 映射和名称映射 + +## 动态类型与注册 + +当你序列化 `Any`、`AnyObject`、`any Serializer` 这类动态值,且其中包含用户定义类型时,具体运行时类型仍然需要提前注册。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/swift/xlang-serialization.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/swift/xlang-serialization.md new file mode 100644 index 00000000000..53b59603543 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/swift/xlang-serialization.md @@ -0,0 +1,97 @@ +--- +title: Xlang 序列化 +sidebar_position: 2 +id: xlang_serialization +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. +--- + +Fory Swift 可以通过 xlang 协议与其他 Fory 运行时交换载荷。 + +## 推荐的跨语言配置 + +```swift +let fory = Fory(xlang: true, trackRef: false, compatible: true) +``` + +## 用共享身份注册类型 + +### 基于 ID 的注册 + +```swift +@ForyObject +struct Order { + var id: Int64 = 0 + var amount: Double = 0 +} + +let fory = Fory(xlang: true, compatible: true) +fory.register(Order.self, id: 100) +``` + +### 基于名称的注册 + +```swift +try fory.register(Order.self, namespace: "com.example", name: "Order") +``` + +## 跨语言规则 + +- 在不同语言之间保持一致的类型注册映射 +- 对独立演进的 Schema 使用兼容模式 +- 对动态字段 `Any` 和 `any Serializer` 中涉及的用户定义具体类型,也要完成注册 + +## Swift IDL 工作流 + +可直接从 Fory IDL / Proto / FBS 输入生成 Swift 模型: + +```bash +foryc schema.fdl --swift_out ./Sources/Generated +``` + +生成的 Swift 代码包括: + +- 带 `@ForyObject` 与 `@ForyField(id: ...)` 元数据的模型 +- tagged union 枚举 +- 支持传递式导入注册的 `ForyRegistration.register(_:)` 辅助方法 +- 生成类型上的 `toBytes` / `fromBytes` 帮助方法 + +在xlang 序列化之前,先调用生成的注册逻辑: + +```swift +let fory = Fory(xlang: true, trackRef: true, compatible: true) +try Addressbook.ForyRegistration.register(fory) + +let payload = try fory.serialize(book) +let decoded: Addressbook.AddressBook = try fory.deserialize(payload) +``` + +### 运行 Swift IDL 集成测试 + +```bash +cd integration_tests/idl_tests +./run_swift_tests.sh +``` + +这会执行 Swift 端 roundtrip 矩阵测试,以及与 Java 对端的 roundtrip 校验,使用 `IDL_PEER_LANG=swift`。 + +## 调试跨语言测试 + +运行 xlang 测试时可以开启调试输出: + +```bash +ENABLE_FORY_DEBUG_OUTPUT=1 FORY_SWIFT_JAVA_CI=1 mvn -T16 test -Dtest=org.apache.fory.xlang.SwiftXlangTest +``` diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/xlang/_category_.json b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/xlang/_category_.json new file mode 100644 index 00000000000..a42db269058 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/xlang/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "跨语言", + "position": 12, + "collapsible": true, + "collapsed": true +} diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/xlang/field-nullability.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/xlang/field-nullability.md new file mode 100644 index 00000000000..caf0bddca28 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/xlang/field-nullability.md @@ -0,0 +1,251 @@ +--- +title: 字段可空性 +sidebar_position: 40 +id: field_nullability +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. +--- + +本页说明 Fory 在跨语言(xlang)序列化模式下如何处理字段可空性。 + +## 默认行为 + +在 xlang 模式下,**字段默认不可空**。这意味着: + +- 值必须始终存在(非 null) +- 不会为字段写入 null 标记字节 +- 序列化结果更紧凑 + +以下类型默认可空: + +- `Optional` (Java, C++) +- Java boxed types (`Integer`, `Long`, `Double`, etc.) +- Go pointer types (`*int32`, `*string`, etc.) +- Rust `Option` +- Python `Optional[T]` + +| 字段类型 | 默认可空 | 写入 Null 标记 | +| ------------------------------------------ | ---------------- | ----------------- | +| 基本类型(`int`、`bool`、`float` 等) | 否 | 否 | +| `String` | 否 | 否 | +| `List`、`Map`、`Set` | 否 | 否 | +| 自定义结构体 | 否 | 否 | +| 枚举 | 否 | 否 | +| Java 装箱类型(`Integer`、`Long` 等) | 是 | 是 | +| Go 指针类型(`*int32`、`*string`) | 是 | 是 | +| `Optional` / `Option` | 是 | 是 | + +## 编码格式 + +可空标记控制是否在字段值之前写入 **null 标记字节**: + +``` +Non-nullable field: [value data] +Nullable field: [null_flag] [value data if not null] +``` + +其中 `null_flag` 为: + +- `-1` (NULL_FLAG):值为 null +- `-2` (NOT_NULL_VALUE_FLAG):值存在 + +## 可空性与引用跟踪 + +二者相关但概念不同: + +| 概念 | 目的 | 标记值 | +| ---------------------- | ------------------------------------ | ------------------------------------------- | +| **可空性** | 允许字段为 null | `-1`(null)、`-2`(非 null) | +| **引用跟踪** | 对共享对象引用去重 | `-1`(null)、`-2`(非 null)、`≥0`(引用 ID) | + +关键区别: + +- **仅可空**:写入 `-1` 或 `-2` 标记,不做引用去重 +- **引用跟踪**:在可空语义上扩展引用 ID(`≥0`),表示此前出现过的对象 +- 两者使用同一个标记字节位置;引用跟踪是可空性的超集 + +When `refTracking=true`, the null flag byte doubles as a ref flag: + +``` +ref_flag = -1 → null value +ref_flag = -2 → new object (first occurrence) +ref_flag >= 0 → reference to object at index ref_flag +``` + +关于引用跟踪的详细行为,请参见[引用跟踪](field-reference-tracking.md)。 + +## 各语言示例 + +### Java + +```java +public class Person { + // Non-nullable by default in xlang mode + String name; // Must not be null + int age; // Primitive, always non-nullable + List tags; // Must not be null + + // Explicitly nullable + @ForyField(nullable = true) + String nickname; // Can be null + + // Optional wrapper - nullable by default + Optional bio; // Can be empty/null +} + +Fory fory = Fory.builder() + .withLanguage(Language.XLANG) + .build(); +fory.register(Person.class, "example.Person"); +``` + +### Python + +```python +from dataclasses import dataclass +from typing import Optional, List +import pyfory + +@dataclass +class Person: + # Non-nullable by default + name: str # Must have a value + age: pyfory.int32 # Primitive + tags: List[str] # Must not be None + + # Optional makes it nullable + nickname: Optional[str] = None # Can be None + bio: Optional[str] = None # Can be None + +fory = pyfory.Fory(xlang=True) +fory.register_type(Person, typename="example.Person") +``` + +### Rust + +```rust +use fory::Fory; + +#[derive(Fory)] +#[tag("example.Person")] +struct Person { + // Non-nullable by default + name: String, + age: i32, + tags: Vec, + + // Option is nullable + nickname: Option, // Can be None + bio: Option, // Can be None +} +``` + +### Go + +```go +type Person struct { + // Non-nullable by default + Name string + Age int32 + Tags []string + + // Pointer types for nullable fields + Nickname *string // Can be nil + Bio *string // Can be nil +} + +fory := forygo.NewFory() +fory.RegisterTagType("example.Person", Person{}) +``` + +### C++ + +```cpp +struct Person { + // Non-nullable by default + std::string name; + int32_t age; + std::vector tags; + + // std::optional for nullable + std::optional nickname; + std::optional bio; +}; +FORY_STRUCT(Person, name, age, tags, nickname, bio); +``` + +## 自定义可空性 + +### Java:@ForyField 注解 + +```java +public class Config { + @ForyField(nullable = true) + String optionalSetting; // Explicitly nullable + + @ForyField(nullable = false) + String requiredSetting; // Explicitly non-nullable (default) +} +``` + +### C++:fory::field 包装器 + +```cpp +struct Config { + // Explicitly mark as nullable + fory::field> optional_setting; + + // Explicitly mark as non-nullable (default) + fory::field> required_setting; +}; +FORY_STRUCT(Config, optional_setting, required_setting); +``` + +## Null 值处理 + +当不可空字段收到 null 值时: + +| 语言 | 行为 | +| -------- | ---------------------------------------------------- | +| Java | 抛出 `NullPointerException` 或序列化错误 | +| Python | 抛出 `TypeError` 或序列化错误 | +| Rust | 编译期错误(非 Option 类型不能为 None) | +| Go | 使用零值(空字符串、0 等) | +| C++ | 默认构造值或未定义行为 | + +## Schema 兼容性 + +可空标记是结构体 schema 指纹的一部分。修改字段可空性是**破坏性变更**,会导致 schema 版本不匹配错误。 + +``` +Schema A: { name: String (non-nullable) } +Schema B: { name: String (nullable) } +// These have different fingerprints and are incompatible +``` + +## 最佳实践 + +1. **Use non-nullable by default**: Only make fields nullable when null is a valid semantic value +2. **Use Optional/Option wrappers**: Instead of raw types with nullable annotation +3. **Be consistent across languages**: Use the same nullability for corresponding fields +4. **Document nullable fields**: Make it clear which fields can be null in your API + +## See Also + +- [Reference Tracking](field-reference-tracking.md) - Shared and circular reference handling +- [Serialization](serialization.md) - Basic cross-language serialization +- [Type Mapping](https://fory.apache.org/docs/specification/xlang_type_mapping) - Cross-language type mapping reference +- [Xlang Specification](https://fory.apache.org/docs/specification/fory_xlang_serialization_spec) - Binary protocol details diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/xlang/field-reference-tracking.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/xlang/field-reference-tracking.md new file mode 100644 index 00000000000..1286d3fea53 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/xlang/field-reference-tracking.md @@ -0,0 +1,260 @@ +--- +title: 引用跟踪 +sidebar_position: 45 +id: reference_tracking +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. +--- + +本页说明 Fory 在跨语言序列化中如何处理共享引用与循环引用的引用跟踪。 + +## 概述 + +引用跟踪支持: + +- **共享引用**:同一对象被多次引用时只序列化一次 +- **循环引用**:对象引用自身或形成环 +- **内存效率**:重复对象不会产生重复数据 + +## 启用引用跟踪 + +### Java + +```java +Fory fory = Fory.builder() + .withLanguage(Language.XLANG) + .withRefTracking(true) + .build(); +``` + +### Python + +```python +fory = pyfory.Fory(xlang=True, ref_tracking=True) +``` + +### Go + +```go +fory := forygo.NewFory(true) // true enables ref tracking +``` + +### C++ + +```cpp +auto fory = fory::Fory::create(fory::Config{ + .ref_tracking = true +}); +``` + +### Rust + +```rust +let fory = Fory::builder() + .with_ref_tracking(true) + .build(); +``` + +## 编码格式 + +启用引用跟踪后,可空字段会在值之前写入 **ref 标记字节**: + +``` +[ref_flag] [value data if not null/ref] +``` + +其中 `ref_flag` 为: + +| 值 | 含义 | +| -------------------------- | ----------------------------------------------------- | +| `-1` (NULL_FLAG) | 值为 null | +| `-2` (NOT_NULL_VALUE_FLAG) | 值存在,且是首次出现 | +| `≥0` | 指向此前已序列化对象的引用 ID | + +## 引用跟踪与可空性 + +二者是**相互独立**的概念: + +| 概念 | 目的 | 控制方式 | +| ---------------------- | ------------------------------------------ | ---------------------------------------- | +| **可空性** | 字段是否可以保存 null 值 | 字段类型(`Optional`)或注解 | +| **引用跟踪** | 是否对重复对象去重 | 全局 `refTracking` 选项 | + +关键行为: + +- ref 标记字节**只会为可空字段写入** +- 即使 `refTracking=true`,不可空字段也完全跳过 ref 标记 +- 引用去重只适用于多次出现的对象 + +```java +// Reference tracking enabled, but non-nullable fields still skip ref flags +Fory fory = Fory.builder() + .withLanguage(Language.XLANG) + .withRefTracking(true) + .build(); +``` + +## 按字段配置引用跟踪 + +默认情况下,即使全局 `refTracking=true`,**大多数字段也不会跟踪引用**。只有特定指针/智能指针类型默认跟踪引用。 + +### 各语言默认行为 + +| 语言 | 默认引用跟踪 | 默认跟踪引用的类型 | +| -------- | -------------------- | --------------------------------- | +| Java | 否 | 无(使用注解启用) | +| Python | 否 | 无(使用注解启用) | +| Go | 否 | 无(使用 `fory:"ref"` 启用) | +| C++ | 否 | `std::shared_ptr` | +| Rust | 否 | `Rc`、`Arc`、`Weak` | + +### 自定义字段级引用跟踪 + +#### Java:@ForyField 注解 + +```java +public class Document { + // Default: no ref tracking + String title; + + // Enable ref tracking for this field + @ForyField(trackingRef = true) + Author author; + + // Shared across documents, track refs to avoid duplicates + @ForyField(trackingRef = true) + List tags; +} +``` + +#### C++:fory::field 包装器 + +```cpp +struct Document { + std::string title; + + // shared_ptr tracks refs by default + std::shared_ptr author; + + // Explicitly enable ref tracking + fory::field, 1, fory::track_ref> tags; + + // Explicitly disable ref tracking + fory::field, 2, fory::track_ref> data; +}; +FORY_STRUCT(Document, title, author, tags, data); +``` + +#### Rust:字段属性 + +```rust +#[derive(Fory)] +#[tag("example.Document")] +struct Document { + title: String, + + // Rc/Arc track refs by default + author: Rc, + + // Explicitly enable ref tracking + #[track_ref] + tags: Vec, +} +``` + +#### Go:结构体 tag + +```go +type Document struct { + Title string + + // Enable ref tracking for pointer to struct + Author *Author `fory:"ref"` + + // Enable ref tracking for slice + Tags []Tag `fory:"ref"` +} +``` + +### 何时启用字段级引用跟踪 + +以下字段应启用引用跟踪: + +- 可能多次包含同一个对象实例 +- 参与循环引用链 +- 持有可能被共享的大对象 + +以下字段应禁用或保持默认: + +- 始终包含唯一值 +- 是基本类型或简单值类型 +- 不参与对象共享 + +## 示例:共享引用 + +```java +public class Container { + List data; + List sameData; // Points to same list +} + +Container obj = new Container(); +obj.data = Arrays.asList("a", "b", "c"); +obj.sameData = obj.data; // Shared reference + +// With refTracking=true: data serialized once, sameData stores reference ID +// With refTracking=false: data serialized twice (duplicate) +``` + +## 示例:循环引用 + +```java +public class Node { + String value; + Node next; +} + +Node a = new Node("A"); +Node b = new Node("B"); +a.next = b; +b.next = a; // Circular reference + +// With refTracking=true: works correctly +// With refTracking=false: infinite recursion error +``` + +## 语言支持 + +| Language | Shared Refs | Circular Refs | +| ---------- | ----------- | -------------------- | +| Java | Yes | Yes | +| Python | Yes | Yes | +| Go | Yes | Yes | +| C++ | Yes | Yes | +| JavaScript | Yes | Yes | +| Rust | Yes | No (ownership rules) | + +## Performance Considerations + +- **Overhead**: Reference tracking adds a hash map lookup per object +- **When to enable**: Use when data has shared/circular references +- **When to disable**: Use for simple data structures without sharing + +## See Also + +- [Field Nullability](field-nullability.md) - How nullability affects serialization +- [Serialization](serialization.md) - Basic cross-language serialization examples +- [Xlang Specification](https://fory.apache.org/docs/specification/fory_xlang_serialization_spec) - Binary protocol details diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/xlang/field-type-meta.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/xlang/field-type-meta.md new file mode 100644 index 00000000000..5463f70f849 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/xlang/field-type-meta.md @@ -0,0 +1,301 @@ +--- +title: 字段类型元信息 +sidebar_position: 46 +id: field_type_meta +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. +--- + +字段类型元信息配置用于控制结构体字段序列化时是否写入类型信息。当实际运行时类型可能不同于声明字段类型时,它是支持多态的关键。 + +## 概述 + +序列化结构体字段时,Fory 需要判断是否写入类型元信息: + +- **静态类型**:直接使用声明字段类型的序列化器(不写入类型信息) +- **动态类型**:写入类型信息以支持运行时子类型 + +## 何时需要类型元信息 + +以下场景需要类型元信息: + +1. **接口/抽象字段**:声明类型是抽象类型,因此必须记录具体类型 +2. **多态字段**:运行时类型可能是声明类型的子类 +3. **跨语言兼容性**:接收端需要类型信息才能正确反序列化 + +以下场景不需要类型元信息: + +1. **final/具体类型**:声明类型是 final/sealed,不能被继承 +2. **基本类型**:编译期已知类型 +3. **性能优化**:明确知道运行时类型始终与声明类型一致 + +## 各语言配置 + +### Java + +Java 需要显式配置,因为具体类只要没有标记为 `final` 就可能被继承。 + +使用带 `dynamic` 参数的 `@ForyField` 注解: + +```java +import org.apache.fory.annotation.ForyField; +import org.apache.fory.annotation.ForyField.Dynamic; + +public class Container { + // AUTO (default): Interface types write type info, concrete types don't + @ForyField(id = 0) + private Shape shape; // Interface - type info written + + // FALSE: Never write type info (use declared type's serializer) + @ForyField(id = 1, dynamic = Dynamic.FALSE) + private Circle circle; // Always treated as Circle + + // TRUE: Always write type info (support runtime subtypes) + @ForyField(id = 2, dynamic = Dynamic.TRUE) + private Shape concreteShape; // Type info written even if concrete +} +``` + +**Dynamic 选项**: + +| 值 | 行为 | +| ------- | ------------------------------------------------------ | +| `AUTO` | 接口/抽象类型为动态类型,具体类型不是 | +| `FALSE` | 不写入类型信息,使用声明类型的序列化器 | +| `TRUE` | 始终写入类型信息以支持运行时子类型 | + +**使用场景**: + +- `AUTO`:默认行为,适合大多数场景 +- `FALSE`:明确知道精确类型时用于性能优化 +- `TRUE`:具体字段可能持有子类实例时使用 + +### C++ + +C++ 使用 `fory::dynamic` 模板标签或 `.dynamic(bool)` 构建方法: + +**使用 `fory::field<>` 模板**: + +```cpp +#include "fory/serialization/fory.h" + +// Abstract base class with pure virtual methods +struct Animal { + virtual ~Animal() = default; + virtual std::string speak() const = 0; +}; + +struct Zoo { + // Auto: type info written because Animal is polymorphic (std::is_polymorphic) + fory::field, 0, fory::nullable> animal; + + // Force non-dynamic: skip type info even though Animal is polymorphic + fory::field, 1, fory::nullable, fory::dynamic> fixed_animal; + + // Force dynamic: write type info even for non-polymorphic types + fory::field, 2, fory::dynamic> polymorphic_data; +}; +FORY_STRUCT(Zoo, animal, fixed_animal, polymorphic_data); +``` + +**使用 `FORY_FIELD_CONFIG` 宏**: + +```cpp +struct Zoo { + std::shared_ptr animal; + std::shared_ptr fixed_animal; + std::shared_ptr polymorphic_data; +}; + +FORY_STRUCT(Zoo, animal, fixed_animal, polymorphic_data); + +FORY_FIELD_CONFIG(Zoo, + (animal, fory::F(0).nullable()), // Auto-detect polymorphism + (fixed_animal, fory::F(1).nullable().dynamic(false)), // Skip type info + (polymorphic_data, fory::F(2).dynamic(true)) // Force type info +); +``` + +**默认行为**:Fory 通过 `std::is_polymorphic` 自动检测多态。带纯虚方法的类型默认按动态类型处理。 + +### Go and Rust + +Go 和 Rust **不需要**显式动态配置,原因是: + +- **Go**:接口类型天然是动态的,Fory 可以从类型判断它是否为接口 +- **Rust**:trait object(`dyn Trait`)在类型系统中有显式标记 + +这些语言的类型系统已经表明字段是否具备多态性: + +```go +// Go: interface types are automatically dynamic +type Container struct { + Shape Shape // Interface - type info written automatically + Circle Circle // Concrete struct - no type info needed +} +``` + +```rust +// Rust: trait objects are explicitly marked +struct Container { + shape: Box, // Trait object - type info written automatically + circle: Circle, // Concrete type - no type info needed +} +``` + +### Python + +使用带 `dynamic` 参数的 `pyfory.field()`: + +```python +from dataclasses import dataclass +from abc import ABC, abstractmethod +import pyfory + +class Shape(ABC): + @abstractmethod + def area(self) -> float: + pass + +@dataclass +class Circle(Shape): + radius: float = 0.0 + + def area(self) -> float: + return 3.14159 * self.radius * self.radius + +@dataclass +class Container: + # Abstract class: dynamic is always True (type info written) + shape: Shape = pyfory.field(id=0) + + # Concrete type with explicit dynamic=True (force type info) + circle: Circle = pyfory.field(id=1, dynamic=True) + + # Concrete type with explicit dynamic=False (skip type info) + fixed_circle: Circle = pyfory.field(id=2, dynamic=False) +``` + +**默认行为**: + +| 模式 | 抽象类 | 具体对象类型 | 数值/str/time 类型 | +| ----------- | -------------- | --------------------- | ---------------------- | +| Native 模式 | `True` | `True` | `False` | +| Xlang 模式 | `True` | `False` | `False` | + +- **抽象类**:`dynamic` 始终为 `True`(必须写入类型信息) +- **Native 模式**:对象类型的 `dynamic` 默认为 `True`,数值/str/time 类型为 `False` +- **Xlang 模式**:具体类型的 `dynamic` 默认为 `False` + +## 默认行为 + +| 语言 | 接口/抽象类型 | 具体类型 | +| -------- | ------------------------ | ---------------- | +| Java | 动态(写入类型) | 静态(不写入类型) | +| C++ | 动态(virtual) | 静态 | +| Go | 动态(interface) | 静态(struct) | +| Rust | 动态(dyn Trait) | 静态 | +| Python | 动态(所有对象) | 动态 | + +## 性能考虑 + +写入类型元信息会带来开销: + +- **空间**:类型信息会增加序列化输出的字节数 +- **时间**:序列化/反序列化期间需要解析类型 + +以下场景可使用 `dynamic = FALSE`(Java)或 `dynamic(false)`(C++): + +- 确定运行时类型与声明类型一致 +- 对性能要求很高且不需要多态 +- 字段类型实际上等同于 final + +## 跨语言兼容性 + +为跨语言消费序列化数据时: + +1. **使用一致的类型注册**:在各语言中用相同 ID 注册类型 +2. **优先显式配置**:不确定接收端预期时使用 `dynamic = TRUE` +3. **记录多态字段**:明确哪些字段可能包含子类型 + +## 示例:多态容器 + +### Java + +```java +public interface Animal { + String speak(); +} + +public class Dog implements Animal { + private String name; + + @Override + public String speak() { return "Woof!"; } +} + +public class Cat implements Animal { + private String name; + + @Override + public String speak() { return "Meow!"; } +} + +public class Zoo { + // Type info written because Animal is an interface + @ForyField(id = 0) + private Animal animal; + + // Force type info for concrete type that may hold subtypes + @ForyField(id = 1, dynamic = Dynamic.TRUE) + private Dog maybeMixedBreed; +} +``` + +### C++ + +```cpp +// Abstract base class with pure virtual methods +class Animal { +public: + virtual std::string speak() const = 0; + virtual ~Animal() = default; +}; + +class Dog : public Animal { +public: + std::string name; + std::string speak() const override { return "Woof!"; } +}; + +struct Zoo { + std::shared_ptr animal; + std::shared_ptr maybe_mixed_breed; +}; + +FORY_STRUCT(Zoo, animal, maybe_mixed_breed); + +FORY_FIELD_CONFIG(Zoo, + (animal, fory::F(0).nullable()), // Auto-detect (Animal is polymorphic) + (maybe_mixed_breed, fory::F(1).dynamic(true)) // Force dynamic for concrete type +); +``` + +## Related Topics + +- [Field Nullability](field_nullability) - Controlling null handling for fields +- [Field Reference Tracking](reference_tracking) - Managing shared/circular references +- [Type Mapping](../../specification/xlang_type_mapping) - Cross-language type compatibility diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/xlang/getting-started.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/xlang/getting-started.md new file mode 100644 index 00000000000..55a6900400e --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/xlang/getting-started.md @@ -0,0 +1,465 @@ +--- +title: 入门指南 +sidebar_position: 10 +id: getting_started +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. +--- + +本指南介绍所有受支持语言中跨语言序列化的安装与基础设置。 + +## 安装 + +### Java + +**Maven:** + +```xml + + org.apache.fory + fory-core + 1.3.0 + +``` + +**Gradle:** + +```gradle +implementation 'org.apache.fory:fory-core:1.3.0' +``` + +### Python + +```bash +pip install pyfory +``` + +### Go + +```bash +go get github.com/apache/fory/go/fory +``` + +### Rust + +```toml +[dependencies] +fory = "1.3.0" +``` + +### JavaScript/TypeScript + +```bash +npm install @apache-fory/core +``` + +对于可选的 Node.js 字符串快速路径: + +```bash +npm install @apache-fory/core @apache-fory/hps +``` + +### C\# + +```bash +dotnet add package Apache.Fory --version 1.3.0 +``` + +### Dart + +```bash +dart pub add fory:^1.3.0 +dart pub add dev:build_runner +``` + +### Swift + +将 Fory 添加到 `Package.swift`: + +```swift +dependencies: [ + .package(url: "https://github.com/apache/fory.git", exact: "1.3.0") +] +``` + +### Scala + +```scala +libraryDependencies += "org.apache.fory" %% "fory-scala" % "1.3.0" +``` + +### Kotlin + +```kotlin +implementation("org.apache.fory:fory-kotlin:1.3.0") +``` + +### C++ + +使用 Bazel 或 CMake 从源码构建。详见 [C++ 指南](../cpp/index.md)。 + +## 创建 Xlang 运行时 + +对于暴露模式开关的运行时,Xlang 模式是默认选项。Swift、C#、JavaScript/TypeScript 和 Dart +只暴露 xlang 编码格式。下面的示例将兼容的 Schema 演进保留在默认路径上,只展示会改变其他设置的选项。 + +### Java + +```java +import org.apache.fory.*; +import org.apache.fory.config.*; + +Fory fory = Fory.builder() + .withXlang(true) + .withRefTracking(true) // Optional: for circular references + .build(); +``` + +### Python + +```python +import pyfory + +fory = pyfory.Fory(xlang=True) + +# Enable reference tracking when needed +fory = pyfory.Fory(xlang=True, ref=True) +``` + +### Go + +```go +import forygo "github.com/apache/fory/go/fory" + +fory := forygo.NewFory(forygo.WithXlang(true)) +// Or with reference tracking +fory := forygo.NewFory(forygo.WithXlang(true), forygo.WithTrackRef(true)) +``` + +### Rust + +```rust +use fory::Fory; + +let fory = Fory::builder().xlang(true).build(); +``` + +### JavaScript/TypeScript + +```javascript +import Fory, { Type } from "@apache-fory/core"; + +const fory = new Fory(); +``` + +### C\# + +```csharp +using Apache.Fory; + +Fory fory = Fory.Builder().Build(); +``` + +### Dart + +```dart +import 'package:fory/fory.dart'; + +final fory = Fory(); +``` + +### Swift + +```swift +import Fory + +let fory = Fory() +``` + +### Scala + +```scala +import org.apache.fory.scala.ForyScala + +val fory = ForyScala.builder() + .withXlang(true) + .build() +``` + +### Kotlin + +```kotlin +import org.apache.fory.kotlin.ForyKotlin + +val fory = ForyKotlin.builder() + .withXlang(true) + .build() +``` + +### C++ + +```cpp +#include "fory/serialization/fory.h" + +using namespace fory::serialization; + +auto fory = Fory::builder().xlang(true).build(); +``` + +## 类型注册 + +自定义类型必须在所有语言中使用一致的名称或 ID 注册。 + +### 按名称注册(推荐) + +使用字符串名称更灵活,也更不容易发生冲突: + +**Java:** + +```java +fory.register(Person.class, "example.Person"); +``` + +**Python:** + +```python +fory.register_type(Person, typename="example.Person") +``` + +**Go:** + +```go +fory.RegisterStructByName(Person{}, "example.Person") +``` + +**Rust:** + +```rust +use fory::{Fory, ForyStruct}; + +#[derive(ForyStruct)] +struct Person { + name: String, + age: i32, +} + +let mut fory = Fory::builder().xlang(true).build(); +fory + .register_by_name::("example", "Person") + .expect("register Person"); +``` + +**JavaScript/TypeScript:** + +```javascript +const personType = Type.struct( + { typeName: "example.Person" }, + { + name: Type.string(), + age: Type.int32(), + }, +); +const { serialize, deserialize } = fory.register(personType); +``` + +**C++:** + +```cpp +fory.register_struct("example", "Person"); +// For enums, use register_enum: +// fory.register_enum("example", "Color"); +``` + +**C#:** + +```csharp +fory.Register("example", "Person"); +``` + +**Dart:** + +```dart +PersonForyModule.register( + fory, + Person, + namespace: 'example', + typeName: 'Person', +); +``` + +**Swift:** + +```swift +try fory.register(Person.self, namespace: "example", name: "Person") +``` + +**Scala:** + +```scala +fory.register(classOf[Person], "example.Person") +``` + +**Kotlin:** + +```kotlin +fory.register(Person::class.java, "example.Person") +``` + +### 按 ID 注册 + +使用数字 ID 速度更快,并且生成的二进制输出更小: + +**Java:** + +```java +fory.register(Person.class, 100); +``` + +**Python:** + +```python +fory.register_type(Person, type_id=100) +``` + +**Go:** + +```go +fory.RegisterStruct(Person{}, 100) +``` + +**Rust:** + +```rust +fory.register::(100)?; +``` + +**JavaScript/TypeScript:** + +```javascript +const personType = Type.struct( + { typeId: 100 }, + { + name: Type.string(), + age: Type.int32(), + }, +); +``` + +**C++:** + +```cpp +fory.register_struct(100); +// For enums, use register_enum: +// fory.register_enum(101); +``` + +**C#:** + +```csharp +fory.Register(100); +``` + +**Dart:** + +```dart +PersonForyModule.register(fory, Person, id: 100); +``` + +**Swift:** + +```swift +fory.register(Person.self, id: 100) +``` + +**Scala:** + +```scala +fory.register(classOf[Person], 100) +``` + +**Kotlin:** + +```kotlin +fory.register(Person::class.java, 100) +``` + +## Hello World 示例 + +下面给出一个完整示例,展示如何在 Java 中序列化、在 Python 中反序列化: + +### Java(序列化端) + +```java +import org.apache.fory.*; +import org.apache.fory.config.*; +import java.nio.file.*; + +public class Person { + public String name; + public int age; +} + +public class HelloWorld { + public static void main(String[] args) throws Exception { + Fory fory = Fory.builder().withXlang(true).build(); + fory.register(Person.class, "example.Person"); + + Person person = new Person(); + person.name = "Alice"; + person.age = 30; + + byte[] bytes = fory.serialize(person); + Files.write(Path.of("person.bin"), bytes); + System.out.println("Serialized to person.bin"); + } +} +``` + +### Python(反序列化端) + +```python +import pyfory +from dataclasses import dataclass + +@dataclass +class Person: + name: str + age: pyfory.Int32 + +fory = pyfory.Fory(xlang=True) +fory.register_type(Person, typename="example.Person") + +with open("person.bin", "rb") as f: + data = f.read() + +person = fory.deserialize(data) +print(f"Name: {person.name}, Age: {person.age}") +# Output: Name: Alice, Age: 30 +``` + +## 最佳实践 + +1. **使用一致的类型名**:确保所有语言使用相同的类型名或 ID。 +2. **启用引用跟踪**:如果你的数据包含循环引用或共享引用。 +3. **复用 Fory 实例**:创建 Fory 实例成本较高,应尽量复用。 +4. **使用类型注解**:在 Python 中使用 `pyfory.Int32` 等标记来获得精确的类型映射。 +5. **测试跨语言链路**:验证序列化在所有目标语言之间都能正确工作。 + +## 后续步骤 + +- [类型映射](../../specification/xlang_type_mapping.md) - 跨语言类型映射参考 +- [序列化](serialization.md) - 更详细的序列化示例 +- [故障排查](troubleshooting.md) - 常见问题与解决方案 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/xlang/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/xlang/index.md new file mode 100644 index 00000000000..24c3f98de50 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/xlang/index.md @@ -0,0 +1,172 @@ +--- +title: Xlang 序列化指南 +sidebar_position: 0 +id: serialization_index +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. +--- + +Apache Fory™ xlang 序列化是跨语言载荷的默认编码格式。你可以在一种语言中序列化数据,并在另一种语言中反序列化,无需手动转换。对于小型契约,可以直接使用语言模型类型;当更适合 schema-first 工作流时,也可以使用 Fory IDL 和代码生成。 + +## 特性 + +- **无需 IDL**:直接使用语言模型类型序列化对象。 +- **多语言支持**:Java、Python、C++、Go、Rust、JavaScript/TypeScript、C#、Swift、Dart、Scala 和 Kotlin 可通过同一 xlang 格式互操作。 +- **引用支持**:在各语言运行时启用引用跟踪后,共享引用和循环引用可以跨语言边界工作。 +- **Schema 演进**:兼容模式是 xlang 的默认设置,因此读取方可以容忍字段新增、删除或重排。 +- **带外缓冲区**:语言运行时可以为大型二进制数据暴露零拷贝缓冲区路径。 +- **高性能**:可用时,运行时会使用生成的序列化器、JIT 序列化器或优化代码路径。 + +## 支持的语言 + +| 语言 | 状态 | 包或目标 | +| --------------------- | ---- | -------------------------------- | +| Java | 支持 | `org.apache.fory:fory-core` | +| Python | 支持 | `pyfory` | +| C++ | 支持 | Bazel/CMake build | +| Go | 支持 | `github.com/apache/fory/go/fory` | +| Rust | 支持 | `fory` crate | +| JavaScript/TypeScript | 支持 | `@apache-fory/core` | +| C# | 支持 | `Apache.Fory` | +| Swift | 支持 | Swift Package Manager target | +| Dart | 支持 | `fory` package | +| Scala | 支持 | `org.apache.fory:fory-scala` | +| Kotlin | 支持 | `org.apache.fory:fory-kotlin` | + +## 何时使用 Xlang 模式 + +在以下场景使用 xlang 模式: + +- 构建多语言微服务 +- 创建多语言数据管道 +- 在前端 JavaScript/TypeScript 与 Java、Python、Go、C#、Scala 或 Kotlin 等后端运行时之间共享数据 + +对于 Java、Scala、Kotlin、Python、C++、Go 或 Rust 中的同语言流量,请使用原生模式: + +- 所有序列化/反序列化都发生在同一语言中 +- 需要 Python pickle 风格对象或 Java 序列化钩子等语言特定功能 +- 希望为同语言服务使用原生模式的 schema-consistent 载荷 + +## 快速示例 + +### Java(生产者) + +```java +import org.apache.fory.*; +import org.apache.fory.config.*; + +public class Person { + public String name; + public int age; +} + +Fory fory = Fory.builder().withXlang(true).build(); +fory.register(Person.class, "example.Person"); + +Person person = new Person(); +person.name = "Alice"; +person.age = 30; +byte[] bytes = fory.serialize(person); +// Send bytes to Python, Go, Rust, etc. +``` + +### Python(消费者) + +```python +import pyfory +from dataclasses import dataclass + +@dataclass +class Person: + name: str + age: pyfory.Int32 + +fory = pyfory.Fory(xlang=True) +fory.register_type(Person, typename="example.Person") + +# Receive bytes from Java +person = fory.deserialize(bytes_from_java) +print(f"{person.name}, {person.age}") # Alice, 30 +``` + +## Fory IDL + +对于 schema-first 项目,Fory 还提供 **Fory IDL** 和代码生成。 + +- 编译器文档:[Fory IDL Overview](../../compiler/index.md) +- 最适合大型多语言消息契约和长期维护的 schema + +### 最小 IDL 示例 + +创建 `person.fdl`: + +```protobuf +package example; + +message Person { + string name = 1; + int32 age = 2; + optional string email = 3; +} +``` + +生成代码: + +```bash +foryc person.fdl --lang java,python,cpp,go,rust,javascript,csharp,swift,dart,scala,kotlin --output ./generated +``` + +这会生成原生语言类型,并在所有目标语言之间保持一致的字段/类型映射。 + +## 何时使用 Fory IDL + +| 选项 | 适用场景 | 原因 | +| ---------------------------------- | ------------------------------------------ | ---------------------------------------------------------- | +| 原生 xlang 类型(无 IDL) | 只有少量消息类型,并且希望快速推进 | 避免引入和运维编译器带来的集成/设置成本 | +| Fory IDL(schema-first + codegen) | 有大量消息,跨多个语言/团队/服务使用 | 提供单一契约、更强一致性,并让长期演进更容易 | +| 混合方式(先原生,后迁移到 IDL) | 项目初期较小,但消息数量和跨团队依赖在增长 | 保持早期迭代速度,等 schema 复杂度上升后再标准化 | + +## 文档 + +| 主题 | 描述 | +| --------------------------------------------------------- | ---------------------------------------- | +| [入门指南](getting-started.md) | 所有语言的安装和基本设置 | +| [类型映射](../../specification/xlang_type_mapping.md) | Xlang 类型映射参考 | +| [序列化](serialization.md) | 内置类型、自定义类型、引用处理 | +| [零拷贝](zero-copy.md) | 大型数据的带外序列化 | +| [行格式](row_format.md) | 支持随机访问的缓存友好二进制格式 | +| [故障排查](troubleshooting.md) | 常见问题和解决方案 | + +## 特定语言指南 + +有关特定语言的详细信息和 API 参考: + +- [Java Xlang 序列化指南](../java/xlang-serialization.md) +- [Python Xlang 序列化指南](../python/xlang-serialization.md) +- [C++ Xlang 序列化指南](../cpp/xlang-serialization.md) +- [Go Xlang 序列化指南](../go/xlang-serialization.md) +- [Rust Xlang 序列化指南](../rust/xlang-serialization.md) +- [JavaScript/TypeScript Xlang 序列化指南](../javascript/xlang-serialization.md) +- [C# Xlang 序列化指南](../csharp/xlang-serialization.md) +- [Swift Xlang 序列化指南](../swift/xlang-serialization.md) +- [Dart Xlang 序列化指南](../dart/xlang-serialization.md) +- [Scala Schema IDL 与 Xlang 指南](../scala/schema-idl.md) +- [Kotlin 静态生成序列化器指南](../kotlin/static-generated-serializers.md) + +## 规范 + +- [Xlang 序列化规范](../../specification/xlang_serialization_spec.md) - 二进制协议细节 +- [类型映射规范](../../specification/xlang_type_mapping.md) - 完整类型映射参考 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/xlang/row_format.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/xlang/row_format.md new file mode 100644 index 00000000000..97f6ecd6b24 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/xlang/row_format.md @@ -0,0 +1,190 @@ +--- +title: 行格式 +sidebar_position: 5 +id: row_format +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. +--- + +Fory 行格式是一种缓存友好的二进制格式,专为高效随机访问和部分序列化而设计。与对象图序列化不同,行格式允许您在不反序列化整个对象的情况下读取单个字段。 + +## 特性 + +- **零拷贝随机访问**:直接从二进制数据中读取特定字段 +- **部分序列化**:在序列化期间跳过不必要的字段 +- **跨语言兼容**:行格式数据可以在 Java、Python 和 C++ 之间共享 +- **Apache Arrow 集成**:将行格式与 Arrow RecordBatch 相互转换以进行分析(Java/Python) + +## Java + +```java +public class Bar { + String f1; + List f2; +} + +public class Foo { + int f1; + List f2; + Map f3; + List f4; +} + +RowEncoder encoder = Encoders.bean(Foo.class); +Foo foo = new Foo(); +foo.f1 = 10; +foo.f2 = IntStream.range(0, 1000000).boxed().collect(Collectors.toList()); +foo.f3 = IntStream.range(0, 1000000).boxed().collect(Collectors.toMap(i -> "k"+i, i->i)); +List bars = new ArrayList<>(1000000); +for (int i = 0; i < 1000000; i++) { + Bar bar = new Bar(); + bar.f1 = "s"+i; + bar.f2 = LongStream.range(0, 10).boxed().collect(Collectors.toList()); + bars.add(bar); +} +foo.f4 = bars; +// 可以被 Python 零拷贝读取 +BinaryRow binaryRow = encoder.toRow(foo); +// 可以是来自 Python 的数据 +Foo newFoo = encoder.fromRow(binaryRow); +// 零拷贝读取 List f2 +BinaryArray binaryArray2 = binaryRow.getArray(1); +// 零拷贝读取 List f4 +BinaryArray binaryArray4 = binaryRow.getArray(3); +// 零拷贝读取 List f4 的第 11 个元素 +BinaryRow barStruct = binaryArray4.getStruct(10); + +// 零拷贝读取 List f4 的第 11 个元素的 f2 的第 6 个元素 +barStruct.getArray(1).getInt64(5); +RowEncoder barEncoder = Encoders.bean(Bar.class); +// 反序列化部分数据 +Bar newBar = barEncoder.fromRow(barStruct); +Bar newBar2 = barEncoder.fromRow(binaryArray4.getStruct(20)); +``` + +## Python + +```python +@dataclass +class Bar: + f1: str + f2: List[pa.int64] +@dataclass +class Foo: + f1: pa.int32 + f2: List[pa.int32] + f3: Dict[str, pa.int32] + f4: List[Bar] + +encoder = pyfory.encoder(Foo) +foo = Foo(f1=10, f2=list(range(1000_000)), + f3={f"k{i}": i for i in range(1000_000)}, + f4=[Bar(f1=f"s{i}", f2=list(range(10))) for i in range(1000_000)]) +binary: bytes = encoder.to_row(foo).to_bytes() +print(f"start: {datetime.datetime.now()}") +foo_row = pyfory.RowData(encoder.schema, binary) +print(foo_row.f2[100000], foo_row.f4[100000].f1, foo_row.f4[200000].f2[5]) +print(f"end: {datetime.datetime.now()}") + +binary = pickle.dumps(foo) +print(f"pickle start: {datetime.datetime.now()}") +new_foo = pickle.loads(binary) +print(new_foo.f2[100000], new_foo.f4[100000].f1, new_foo.f4[200000].f2[5]) +print(f"pickle end: {datetime.datetime.now()}") +``` + +## Apache Arrow 支持 + +Fory 行格式支持从/到 Arrow Table/RecordBatch 的自动转换,以用于分析工作负载。 + +### Java + +```java +Schema schema = TypeInference.inferSchema(BeanA.class); +ArrowWriter arrowWriter = ArrowUtils.createArrowWriter(schema); +Encoder encoder = Encoders.rowEncoder(BeanA.class); +for (int i = 0; i < 10; i++) { + BeanA beanA = BeanA.createBeanA(2); + arrowWriter.write(encoder.toRow(beanA)); +} +return arrowWriter.finishAsRecordBatch(); +``` + +### Python + +```python +import pyfory +encoder = pyfory.encoder(Foo) +encoder.to_arrow_record_batch([foo] * 10000) +encoder.to_arrow_table([foo] * 10000) +``` + +## 接口和扩展类型的支持 + +Fory 支持 Java `interface` 类型和子类(`extends`)类型的行格式映射,实现更动态和灵活的数据 schema。 + +这些增强功能在 [#2243](https://github.com/apache/fory/pull/2243)、[#2250](https://github.com/apache/fory/pull/2250) 和 [#2256](https://github.com/apache/fory/pull/2256) 中引入。 + +### 示例:使用 RowEncoder 的接口映射 + +```java +public interface Animal { + String speak(); +} + +public class Dog implements Animal { + public String name; + + @Override + public String speak() { + return "Woof"; + } +} + +// 使用接口类型的 RowEncoder 进行编码和解码 +RowEncoder encoder = Encoders.bean(Animal.class); +Dog dog = new Dog(); +dog.name = "Bingo"; +BinaryRow row = encoder.toRow(dog); +Animal decoded = encoder.fromRow(row); +System.out.println(decoded.speak()); // Woof +``` + +### 示例:使用 RowEncoder 的扩展类型 + +```java +public class Parent { + public String parentField; +} + +public class Child extends Parent { + public String childField; +} + +// 使用父类类型的 RowEncoder 进行编码和解码 +RowEncoder encoder = Encoders.bean(Parent.class); +Child child = new Child(); +child.parentField = "Hello"; +child.childField = "World"; +BinaryRow row = encoder.toRow(child); +Parent decoded = encoder.fromRow(row); +``` + +## 另请参阅 + +- [行格式规范](https://fory.apache.org/docs/next/specification/fory_row_format_spec) - 二进制格式详情 +- [Java 行格式指南](../java/row-format.md) - Java 特定的行格式文档 +- [Python 行格式指南](../python/row-format.md) - Python 特定的行格式文档 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/xlang/serialization.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/xlang/serialization.md new file mode 100644 index 00000000000..14cc126d8ab --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/xlang/serialization.md @@ -0,0 +1,563 @@ +--- +title: 序列化 +sidebar_position: 30 +id: serialization +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. +--- + +本页演示常见的跨语言序列化模式。当各端使用匹配的类型标识、字段 schema 和兼容性设置时,在一种受支持语言中序列化的数据可以在任何其他受支持语言中反序列化。 + +## 远端 Schema Metadata 限制 + +兼容模式可能为 reader 尚未知的类型接收远端 metadata(`TypeDef` 或 `TypeMeta`)。Fory 会限制可接受的不同远端 metadata 版本数量,并限制每个收到的 metadata body 大小: + +- `maxSchemaVersionsPerType`:一个逻辑类型可接受的最大远端 metadata 版本数,默认值为 `10`。 +- `maxAverageSchemaVersionsPerType`:所有已接受远端类型的平均 metadata 版本数,默认值为 `3`;有效全局下限为 `8192` 个 metadata entry。 +- `maxTypeFields`:一个收到的 struct metadata body 可声明的最大字段数,默认值为 `512`。 +- `maxTypeMetaBytes`:一个收到的 TypeDef 或 TypeMeta body 的最大编码 metadata body 字节数,不包含 8 字节 header 和扩展 size varint,默认值为 `4096`。 + +这些限制是资源保护。它们不会改变编码格式、注册要求、动态类型加载、unknown-type handling 或 Schema 演进兼容性。 + +仅当数据不是恶意输入,且可信 peer 会发送更大的 metadata 或大量 schema 版本时,才调高这些值。 + +| 语言 | 字段数选项 | Metadata 字节选项 | 单类型版本选项 | 平均版本选项 | +| --------------------- | ------------------- | --------------------- | ------------------------------ | ------------------------------------- | +| Java | `withMaxTypeFields` | `withMaxTypeMetaBytes` | `withMaxSchemaVersionsPerType` | `withMaxAverageSchemaVersionsPerType` | +| Scala | `withMaxTypeFields` | `withMaxTypeMetaBytes` | `withMaxSchemaVersionsPerType` | `withMaxAverageSchemaVersionsPerType` | +| Kotlin | `withMaxTypeFields` | `withMaxTypeMetaBytes` | `withMaxSchemaVersionsPerType` | `withMaxAverageSchemaVersionsPerType` | +| Python | `max_type_fields` | `max_type_meta_bytes` | `max_schema_versions_per_type` | `max_average_schema_versions_per_type` | +| JavaScript/TypeScript | `maxTypeFields` | `maxTypeMetaBytes` | `maxSchemaVersionsPerType` | `maxAverageSchemaVersionsPerType` | +| C++ | `max_type_fields` | `max_type_meta_bytes` | `max_schema_versions_per_type` | `max_average_schema_versions_per_type` | +| Go | `WithMaxTypeFields` | `WithMaxTypeMetaBytes` | `WithMaxSchemaVersionsPerType` | `WithMaxAverageSchemaVersionsPerType` | +| Rust | `max_type_fields` | `max_type_meta_bytes` | `max_schema_versions_per_type` | `max_average_schema_versions_per_type` | +| C# | `MaxTypeFields` | `MaxTypeMetaBytes` | `MaxSchemaVersionsPerType` | `MaxAverageSchemaVersionsPerType` | +| Swift | `maxTypeFields` | `maxTypeMetaBytes` | `maxSchemaVersionsPerType` | `maxAverageSchemaVersionsPerType` | +| Dart | `maxTypeFields` | `maxTypeMetaBytes` | `maxSchemaVersionsPerType` | `maxAverageSchemaVersionsPerType` | + +## 序列化内置类型 + +常见类型可以自动序列化,无需注册:原始数值类型、字符串、二进制、数组、列表、映射等。 + +低精度浮点值也是内置 xlang 类型系统的一部分: + +- `float16` 和 `array` +- `bfloat16` 和 `array` + +请使用类型映射参考中记录的特定语言承载类型。Python 使用 `pyfory.Float16` 和 `pyfory.BFloat16` 作为注解标记;标量值是原生 Python `float`,密集低精度数组使用 `pyfory.Float16Array` 和 `pyfory.BFloat16Array`。Go 对标量、切片和数组承载类型使用 `float16` 和 `bfloat16` 包;JavaScript 对标量 `float16` 和 `bfloat16` 使用 `number`,并对相应的 `array` schema 使用密集数组承载类型 `BoolArray`、`Float16Array` 和 `BFloat16Array`。Dart 对标量字段使用 `double` 加 `Float16Type` 或 `Bfloat16Type` 元数据,并对密集数组使用 `Float16List` / `Bfloat16List`。Java 在受支持的低精度承载类型上使用 `@ArrayType` 来表示 `array` / `array` schema,而普通对象数组仍走 `list` 路径;C++、Rust 和 C# 提供各自专用的标量和数组承载类型。 + +当 `compatible=true` 时,直接的 struct/class 字段可以在密集 bool/数值 `T` 的 `list` 与 `array` 之间演进。相同符号性和宽度域中的整数列表元素编码,与对应的密集数组元素域匹配。这只适用于直接匹配的字段 schema,不适用于嵌套 collection、map、array、union 或泛型位置。如果对端 `list` 载荷声明了可空或启用引用跟踪的元素,将其读入本地 `array` 字段会触发兼容读取错误。 + +### Java + +```java +import org.apache.fory.*; +import org.apache.fory.config.*; + +import java.util.*; + +public class Example1 { + public static void main(String[] args) { + Fory fory = Fory.builder().withXlang(true).build(); + List list = ofArrayList(true, false, "str", -1.1, 1, new int[100], new double[20]); + byte[] bytes = fory.serialize(list); + // bytes can be deserialized by other languages + fory.deserialize(bytes); + Map map = new HashMap<>(); + map.put("k1", "v1"); + map.put("k2", list); + map.put("k3", -1); + bytes = fory.serialize(map); + // bytes can be deserialized by other languages + fory.deserialize(bytes); + } +} +``` + +### Python + +```python +import pyfory +import numpy as np + +fory = pyfory.Fory(xlang=True) +object_list = [True, False, "str", -1.1, 1, + np.full(100, 0, dtype=np.int32), np.full(20, 0.0, dtype=np.double)] +data = fory.serialize(object_list) +# bytes can be deserialized by other languages +new_list = fory.deserialize(data) +object_map = {"k1": "v1", "k2": object_list, "k3": -1} +data = fory.serialize(object_map) +# bytes can be deserialized by other languages +new_map = fory.deserialize(data) +print(new_map) +``` + +### Go + +```go +package main + +import forygo "github.com/apache/fory/go/fory" +import "fmt" + +func main() { + list := []any{true, false, "str", -1.1, 1, make([]int32, 10), make([]float64, 20)} + fory := forygo.NewFory(forygo.WithXlang(true)) + bytes, err := fory.Marshal(list) + if err != nil { + panic(err) + } + var newValue any + // bytes can be deserialized by other languages + if err := fory.Unmarshal(bytes, &newValue); err != nil { + panic(err) + } + fmt.Println(newValue) + dict := map[string]any{ + "k1": "v1", + "k2": list, + "k3": -1, + } + bytes, err = fory.Marshal(dict) + if err != nil { + panic(err) + } + // bytes can be deserialized by other languages + if err := fory.Unmarshal(bytes, &newValue); err != nil { + panic(err) + } + fmt.Println(newValue) +} +``` + +### JavaScript + +```javascript +import Fory from "@apache-fory/core"; + +const fory = new Fory(); +const input = fory.serialize("hello fory"); +const result = fory.deserialize(input); +console.log(result); +``` + +### Rust + +```rust +use fory::Fory; + +fn run() { + let fory = Fory::builder().xlang(true).build(); + let bin = fory.serialize(&"hello".to_string()).expect("serialize success"); + let obj: String = fory.deserialize(&bin).expect("deserialize success"); + assert_eq!("hello".to_string(), obj); +} +``` + +## 序列化自定义类型 + +用户定义类型必须使用 register API 注册,以建立不同语言中类型之间的映射关系。请在所有语言中使用一致的类型名称。 + +### Java + +```java +import org.apache.fory.*; +import org.apache.fory.config.*; +import java.util.*; + +public class Example2 { + public static class SomeClass1 { + Object f1; + Map f2; + } + + public static class SomeClass2 { + Object f1; + String f2; + List f3; + Map f4; + Byte f5; + Short f6; + Integer f7; + Long f8; + Float f9; + Double f10; + short[] f11; + List f12; + } + + public static Object createObject() { + SomeClass1 obj1 = new SomeClass1(); + obj1.f1 = true; + obj1.f2 = ofHashMap((byte) -1, 2); + SomeClass2 obj = new SomeClass2(); + obj.f1 = obj1; + obj.f2 = "abc"; + obj.f3 = ofArrayList("abc", "abc"); + obj.f4 = ofHashMap((byte) 1, 2); + obj.f5 = Byte.MAX_VALUE; + obj.f6 = Short.MAX_VALUE; + obj.f7 = Integer.MAX_VALUE; + obj.f8 = Long.MAX_VALUE; + obj.f9 = 1.0f / 2; + obj.f10 = 1 / 3.0; + obj.f11 = new short[]{(short) 1, (short) 2}; + obj.f12 = ofArrayList((short) -1, (short) 4); + return obj; + } + + // mvn exec:java -Dexec.mainClass="org.apache.fory.examples.Example2" + public static void main(String[] args) { + Fory fory = Fory.builder().withXlang(true).build(); + fory.register(SomeClass1.class, "example.SomeClass1"); + fory.register(SomeClass2.class, "example.SomeClass2"); + byte[] bytes = fory.serialize(createObject()); + // bytes can be deserialized by other languages + System.out.println(fory.deserialize(bytes)); + } +} +``` + +### Python + +```python +from dataclasses import dataclass +from typing import List, Dict, Any +import pyfory, array + + +@dataclass +class SomeClass1: + f1: Any + f2: Dict[pyfory.Int8, pyfory.Int32] + + +@dataclass +class SomeClass2: + f1: Any = None + f2: str = None + f3: List[str] = None + f4: Dict[pyfory.Int8, pyfory.Int32] = None + f5: pyfory.Int8 = None + f6: pyfory.Int16 = None + f7: pyfory.Int32 = None + # int type will be taken as `pyfory.Int64`. + # use `pyfory.Int32` for type hint if peer uses more narrow type. + f8: int = None + f9: pyfory.Float32 = None + # float type will be taken as `pyfory.Float64` + f10: float = None + f11: pyfory.Array[pyfory.Int16] = None + f12: List[pyfory.Int16] = None + + +if __name__ == "__main__": + f = pyfory.Fory(xlang=True) + f.register_type(SomeClass1, typename="example.SomeClass1") + f.register_type(SomeClass2, typename="example.SomeClass2") + obj1 = SomeClass1(f1=True, f2={-1: 2}) + obj = SomeClass2( + f1=obj1, + f2="abc", + f3=["abc", "abc"], + f4={1: 2}, + f5=2 ** 7 - 1, + f6=2 ** 15 - 1, + f7=2 ** 31 - 1, + f8=2 ** 63 - 1, + f9=1.0 / 2, + f10=1 / 3.0, + f11=array.array("h", [1, 2]), + f12=[-1, 4], + ) + data = f.serialize(obj) + # bytes can be deserialized by other languages + print(f.deserialize(data)) +``` + +### Go + +```go +package main + +import forygo "github.com/apache/fory/go/fory" +import "fmt" + +func main() { + type SomeClass1 struct { + F1 any + F2 map[int8]int32 + } + + type SomeClass2 struct { + F1 any + F2 string + F3 []any + F4 map[int8]int32 + F5 int8 + F6 int16 + F7 int32 + F8 int64 + F9 float32 + F10 float64 + F11 []int16 + F12 []int16 + } + serializer := forygo.NewFory(forygo.WithXlang(true)) + if err := serializer.RegisterStructByName(SomeClass1{}, "example.SomeClass1"); err != nil { + panic(err) + } + if err := serializer.RegisterStructByName(SomeClass2{}, "example.SomeClass2"); err != nil { + panic(err) + } + obj1 := &SomeClass1{F1: true, F2: map[int8]int32{-1: 2}} + obj := &SomeClass2{ + F1: obj1, + F2: "abc", + F3: []any{"abc", "abc"}, + F4: map[int8]int32{1: 2}, + F5: 127, + F6: 32767, + F7: 2147483647, + F8: 9223372036854775807, + F9: 1.0 / 2, + F10: 1.0 / 3.0, + F11: []int16{1, 2}, + F12: []int16{-1, 4}, + } + bytes, err := serializer.Marshal(obj) + if err != nil { + panic(err) + } + var newValue any + // bytes can be deserialized by other languages + if err := serializer.Unmarshal(bytes, &newValue); err != nil { + panic(err) + } + fmt.Println(newValue) +} +``` + +### JavaScript + +```javascript +import Fory, { Type } from "@apache-fory/core"; + +// Describe data structures using JSON schema +const description = Type.struct( + { typeName: "example.foo" }, + { + foo: Type.string(), + }, +); +const fory = new Fory(); +const { serialize, deserialize } = fory.register(description); +const input = serialize({ foo: "hello fory" }); +const result = deserialize(input); +console.log(result); +``` + +### Rust + +```rust +use chrono::{NaiveDate, NaiveDateTime}; +use fory::{Fory, ForyStruct}; +use std::collections::HashMap; + +#[test] +fn complex_struct() { + #[derive(ForyStruct, Debug, PartialEq)] + struct Animal { + category: String, + } + + #[derive(ForyStruct, Debug, PartialEq)] + struct Person { + c1: Vec, // binary + c2: Vec, // primitive array + animal: Vec, + c3: Vec>, + name: String, + c4: HashMap, + age: u16, + op: Option, + op2: Option, + date: NaiveDate, + time: NaiveDateTime, + c5: f32, + c6: f64, + } + let person: Person = Person { + c1: vec![1, 2, 3], + c2: vec![5, 6, 7], + c3: vec![vec![1, 2], vec![1, 3]], + animal: vec![Animal { + category: "Dog".to_string(), + }], + c4: HashMap::from([ + ("hello1".to_string(), "hello2".to_string()), + ("hello2".to_string(), "hello3".to_string()), + ]), + age: 12, + name: "helo".to_string(), + op: Some("option".to_string()), + op2: None, + date: NaiveDate::from_ymd_opt(2025, 12, 12).unwrap(), + time: NaiveDateTime::from_timestamp_opt(1689912359, 0).unwrap(), + c5: 2.0, + c6: 4.0, + }; + + let mut fory = Fory::builder().xlang(true).build(); + fory + .register_by_name::("example", "foo2") + .expect("register Animal"); + fory + .register_by_name::("example", "foo") + .expect("register Person"); + let bin = fory.serialize(&person).expect("serialize success"); + let obj: Person = fory.deserialize(&bin).expect("deserialize success"); + assert_eq!(person, obj); +} +``` + +## 序列化共享引用和循环引用 + +共享引用和循环引用可以自动序列化,不会产生重复数据或递归错误。启用引用跟踪即可使用此功能。 + +### Java + +```java +import org.apache.fory.*; +import org.apache.fory.config.*; +import java.util.*; + +public class ReferenceExample { + public static class SomeClass { + SomeClass f1; + Map f2; + Map f3; + } + + public static Object createObject() { + SomeClass obj = new SomeClass(); + obj.f1 = obj; + obj.f2 = ofHashMap("k1", "v1", "k2", "v2"); + obj.f3 = obj.f2; + return obj; + } + + // mvn exec:java -Dexec.mainClass="org.apache.fory.examples.ReferenceExample" + public static void main(String[] args) { + Fory fory = Fory.builder() + .withXlang(true) + .withRefTracking(true) + .build(); + fory.register(SomeClass.class, "example.SomeClass"); + byte[] bytes = fory.serialize(createObject()); + // bytes can be deserialized by other languages + System.out.println(fory.deserialize(bytes)); + } +} +``` + +### Python + +```python +from typing import Dict +import pyfory + +class SomeClass: + f1: "SomeClass" + f2: Dict[str, str] + f3: Dict[str, str] + +fory = pyfory.Fory(xlang=True, ref=True) +fory.register_type(SomeClass, typename="example.SomeClass") +obj = SomeClass() +obj.f2 = {"k1": "v1", "k2": "v2"} +obj.f1, obj.f3 = obj, obj.f2 +data = fory.serialize(obj) +# bytes can be deserialized by other languages +print(fory.deserialize(data)) +``` + +### Go + +```go +package main + +import forygo "github.com/apache/fory/go/fory" +import "fmt" + +func main() { + type SomeClass struct { + F1 *SomeClass + F2 map[string]string + F3 map[string]string + } + fory := forygo.NewFory(forygo.WithXlang(true), forygo.WithTrackRef(true)) + if err := fory.RegisterStruct(SomeClass{}, 65); err != nil { + panic(err) + } + value := &SomeClass{F2: map[string]string{"k1": "v1", "k2": "v2"}} + value.F3 = value.F2 + value.F1 = value + bytes, err := fory.Marshal(value) + if err != nil { + panic(err) + } + var newValue any + // bytes can be deserialized by other languages + if err := fory.Unmarshal(bytes, &newValue); err != nil { + panic(err) + } + fmt.Println(newValue) +} +``` + +### JavaScript + +```javascript +import Fory, { Type } from "@apache-fory/core"; + +const description = Type.struct("example.foo", { + foo: Type.string(), + bar: Type.struct("example.foo").setTrackingRef(true), +}); + +const fory = new Fory({ ref: true }); +const { serialize, deserialize } = fory.register(description); +const data: any = { + foo: "hello fory", +}; +data.bar = data; +const input = serialize(data); +const result = deserialize(input); +console.log(result.bar.foo === result.foo); +``` + +### Rust + +由于所有权限制,Rust 中无法实现循环引用。 + +## 另请参阅 + +- [零拷贝序列化](zero-copy.md) - 大型数据的带外序列化 +- [类型映射](../../specification/xlang_type_mapping.md) - 跨语言类型映射参考 +- [入门指南](getting-started.md) - 安装和设置 +- [Xlang 序列化规范](../../specification/xlang_serialization_spec.md) - 二进制协议细节 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/xlang/troubleshooting.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/xlang/troubleshooting.md new file mode 100644 index 00000000000..d0c85ca753b --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/xlang/troubleshooting.md @@ -0,0 +1,321 @@ +--- +title: 故障排查 +sidebar_position: 6 +id: xlang_troubleshooting +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. +--- + +本页涵盖了使用跨语言序列化时的常见问题和解决方案。 + +## 类型注册错误 + +### "类型未注册"错误 + +**症状:** + +``` +Error: Type 'example.Person' is not registered +``` + +**原因:** 在反序列化之前未注册类型,或类型名称不匹配。 + +**解决方案:** + +1. 确保类型在双方使用相同的名称注册: + + ```java + // Java + fory.register(Person.class, "example.Person"); + ``` + + ```python + # Python + fory.register_type(Person, typename="example.Person") + ``` + +2. 检查类型名称中的拼写错误或大小写差异 + +3. 在任何序列化/反序列化调用之前注册类型 + +### "类型 ID 不匹配"错误 + +**症状:** + +``` +Error: Expected type ID 100, got 101 +``` + +**原因:** 跨语言使用了不同的类型 ID。 + +**解决方案:** 使用一致的类型 ID: + +```java +// Java +fory.register(Person.class, 100); +fory.register(Address.class, 101); +``` + +```python +# Python +fory.register_type(Person, type_id=100) +fory.register_type(Address, type_id=101) +``` + +## 类型映射问题 + +### 整数溢出 + +**症状:** 值被截断或意外包装。 + +**原因:** 跨语言使用了不同的整数大小。 + +**解决方案:** + +1. 在 Python 中,使用显式类型注解: + + ```python + @dataclass + class Data: + value: pyfory.Int32Type # 而不仅仅是 'int' + ``` + +2. 确保整数范围兼容: + - `int8`:-128 到 127 + - `int16`:-32,768 到 32,767 + - `int32`:-2,147,483,648 到 2,147,483,647 + +### 浮点精度损失 + +**症状:** 浮点值的精度出乎意料。 + +**原因:** 混合使用 `float32` 和 `float64` 类型。 + +**解决方案:** + +1. 使用一致的浮点类型: + + ```python + @dataclass + class Data: + value: pyfory.Float32Type # 显式 32 位浮点数 + ``` + +2. 请注意,Python 的 `float` 默认映射到 `float64` + +### 字符串编码错误 + +**症状:** + +``` +Error: Invalid UTF-8 sequence +``` + +**原因:** 非 UTF-8 编码的字符串。 + +**解决方案:** + +1. 确保所有字符串都是有效的 UTF-8 +2. 在 Python 中,在序列化之前解码字节: + + ```python + text = raw_bytes.decode('utf-8') + ``` + +## 字段顺序问题 + +### "字段不匹配"错误 + +**症状:** 反序列化的对象具有错误的字段值。 + +**原因:** 语言之间的字段顺序不同。 + +**解决方案:** Fory 按字段的 snake_cased 名称对字段进行排序。确保字段名称一致: + +```java +// Java - 字段将按以下顺序排序:age、email、name +public class Person { + public String name; + public int age; + public String email; +} +``` + +```python +# Python - 相同的字段顺序 +@dataclass +class Person: + name: str + age: pyfory.Int32Type + email: str +``` + +## 引用跟踪问题 + +### 循环引用导致栈溢出 + +**症状:** + +``` +StackOverflowError 或 RecursionError +``` + +**原因:** 引用跟踪被禁用,但数据具有循环引用。 + +**解决方案:** 启用引用跟踪: + +```java +// Java +Fory fory = Fory.builder() + .withLanguage(Language.XLANG) + .withRefTracking(true) + .build(); +``` + +```python +# Python +fory = pyfory.Fory(ref_tracking=True) +``` + +### 重复对象 + +**症状:** 共享对象在反序列化后被重复。 + +**原因:** 引用跟踪被禁用。 + +**解决方案:** 如果对象在图中共享,请启用引用跟踪。 + +## 语言模式问题 + +### "无效的魔术数字"错误 + +**症状:** + +``` +Error: Invalid magic number in header +``` + +**原因:** 一方使用 Java 原生模式而不是 xlang 模式。 + +**解决方案:** 确保双方都使用 xlang 模式: + +```java +// Java - 必须使用 Language.XLANG +Fory fory = Fory.builder() + .withLanguage(Language.XLANG) + .build(); +``` + +### Xlang 模式中的不兼容类型 + +**症状:** + +``` +Error: Type 'Optional' is not supported in xlang mode +``` + +**原因:** 使用了没有跨语言等效项的 Java 特定类型。 + +**解决方案:** 使用兼容的类型: + +```java +// 而不是 Optional +public String email; // nullable + +// 而不是 BigDecimal +public double amount; + +// 而不是 EnumSet +public Set statuses; +``` + +## 版本兼容性 + +### 序列化格式已更改 + +**症状:** 升级 Fory 后反序列化失败。 + +**原因:** 序列化格式中的破坏性更改。 + +**解决方案:** + +1. 确保所有服务使用兼容的 Fory 版本 +2. 检查发布说明中的破坏性更改 +3. 考虑使用 schema 演化(compatible 模式)进行渐进式升级 + +## 调试技巧 + +### 启用调试日志 + +**Java:** + +```java +// 添加到 JVM 选项 +-Dfory.debug=true +``` + +**Python:** + +```python +import logging +logging.getLogger('pyfory').setLevel(logging.DEBUG) +``` + +### 检查序列化数据 + +使用十六进制转储检查二进制格式: + +```python +data = fory.serialize(obj) +print(data.hex()) +``` + +### 测试往返 + +始终在每种语言中测试往返序列化: + +```java +byte[] bytes = fory.serialize(obj); +Object result = fory.deserialize(bytes); +assert obj.equals(result); +``` + +### 跨语言测试 + +在部署之前测试所有目标语言的序列化: + +```bash +# 在 Java 中序列化 +java -jar serializer.jar > data.bin + +# 在 Python 中反序列化 +python deserializer.py data.bin +``` + +## 常见错误 + +1. **未注册类型**:始终在使用前注册自定义类型 +2. **类型名称/ID 不一致**:在所有语言中使用相同的名称/ID +3. **忘记 xlang 模式**:在 Java 中使用 `Language.XLANG` +4. **错误的类型注解**:在 Python 中使用 `pyfory.Int32Type` 等 +5. **忽略引用跟踪**:为循环/共享引用启用 + +## 另请参阅 + +- [类型映射](https://fory.apache.org/docs/specification/xlang_type_mapping) - 跨语言类型映射参考 +- [入门指南](getting-started.md) - 设置指南 +- [Java 故障排查](../java/troubleshooting.md) - Java 特定问题 +- [Python 故障排查](../python/troubleshooting.md) - Python 特定问题 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/xlang/zero-copy.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/xlang/zero-copy.md new file mode 100644 index 00000000000..07657f19219 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/guide/xlang/zero-copy.md @@ -0,0 +1,207 @@ +--- +title: 零拷贝序列化 +sidebar_position: 4 +id: xlang_zero_copy +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. +--- + +零拷贝序列化允许大型二进制数据(字节数组、数值数组)以带外方式序列化,避免内存复制并减少序列化开销。 + +## 何时使用零拷贝 + +在以下情况下使用零拷贝序列化: + +- 序列化大型字节数组或二进制数据块 +- 处理数值数组(int[]、double[] 等) +- 通过高性能网络传输数据 +- 内存效率至关重要 + +## 工作原理 + +1. **序列化**:大型缓冲区被提取出来,并通过回调单独返回 +2. **传输**:主序列化数据和缓冲区对象分别传输 +3. **反序列化**:提供缓冲区以重建原始对象 + +这避免了将大型数据复制到主序列化缓冲区中。 + +## Java + +```java +import org.apache.fory.*; +import org.apache.fory.config.*; +import org.apache.fory.serializer.BufferObject; +import org.apache.fory.memory.MemoryBuffer; + +import java.util.*; +import java.util.stream.Collectors; + +public class ZeroCopyExample { + public static void main(String[] args) { + Fory fory = Fory.builder().withLanguage(Language.XLANG).build(); + + // 包含大型数组的数据 + List list = List.of( + "str", + new byte[1000], // 大型字节数组 + new int[100], // 大型 int 数组 + new double[100] // 大型 double 数组 + ); + + // 在序列化期间收集缓冲区对象 + Collection bufferObjects = new ArrayList<>(); + byte[] bytes = fory.serialize(list, e -> !bufferObjects.add(e)); + + // 转换为缓冲区以便传输 + List buffers = bufferObjects.stream() + .map(BufferObject::toBuffer) + .collect(Collectors.toList()); + + // 使用缓冲区反序列化 + Object result = fory.deserialize(bytes, buffers); + System.out.println(result); + } +} +``` + +## Python + +```python +import array +import pyfory +import numpy as np + +fory = pyfory.Fory() + +# 包含大型数组的数据 +data = [ + "str", + bytes(bytearray(1000)), # 大型字节数组 + array.array("i", range(100)), # 大型 int 数组 + np.full(100, 0.0, dtype=np.double) # 大型 numpy 数组 +] + +# 在序列化期间收集缓冲区对象 +serialized_objects = [] +serialized_data = fory.serialize(data, buffer_callback=serialized_objects.append) + +# 转换为缓冲区以便传输 +buffers = [obj.to_buffer() for obj in serialized_objects] + +# 使用缓冲区反序列化 +result = fory.deserialize(serialized_data, buffers=buffers) +print(result) +``` + +## Go + +```go +package main + +import forygo "github.com/apache/fory/go/fory" +import "fmt" + +func main() { + fory := forygo.NewFory() + + // 包含大型数组的数据 + list := []interface{}{ + "str", + make([]byte, 1000), // 大型字节数组 + } + + buf := fory.NewByteBuffer(nil) + var bufferObjects []fory.BufferObject + + // 在序列化期间收集缓冲区对象 + fory.Serialize(buf, list, func(o fory.BufferObject) bool { + bufferObjects = append(bufferObjects, o) + return false + }) + + // 转换为缓冲区以便传输 + var buffers []*fory.ByteBuffer + for _, o := range bufferObjects { + buffers = append(buffers, o.ToBuffer()) + } + + // 使用缓冲区反序列化 + var newList []interface{} + if err := fory.Deserialize(buf, &newList, buffers); err != nil { + panic(err) + } + fmt.Println(newList) +} +``` + +## JavaScript + +```javascript +// 零拷贝支持即将推出 +``` + +## 使用场景 + +### 高性能数据传输 + +在通过网络发送大型数据集时: + +```java +// 发送方 +Collection buffers = new ArrayList<>(); +byte[] metadata = fory.serialize(dataObject, e -> !buffers.add(e)); + +// 分别发送元数据和缓冲区 +network.sendMetadata(metadata); +for (BufferObject buf : buffers) { + network.sendBuffer(buf.toBuffer()); +} + +// 接收方 +byte[] metadata = network.receiveMetadata(); +List buffers = network.receiveBuffers(); +Object data = fory.deserialize(metadata, buffers); +``` + +### 内存映射文件 + +零拷贝与内存映射文件配合良好: + +```java +// 写入 +Collection buffers = new ArrayList<>(); +byte[] data = fory.serialize(largeObject, e -> !buffers.add(e)); +writeToFile("data.bin", data); +for (int i = 0; i < buffers.size(); i++) { + writeToFile("buffer" + i + ".bin", buffers.get(i).toBuffer()); +} + +// 读取 +byte[] data = readFromFile("data.bin"); +List buffers = readBufferFiles(); +Object result = fory.deserialize(data, buffers); +``` + +## 性能考虑 + +1. **阈值**:由于回调开销,小数组可能不会从零拷贝中受益 +2. **网络**:当缓冲区可以在不复制的情况下发送时,零拷贝最有益 +3. **内存**:通过避免缓冲区复制来减少峰值内存使用 + +## 另请参阅 + +- [序列化](serialization.md) - 标准序列化示例 +- [Python 带外指南](../python/out-of-band.md) - Python 特定的零拷贝详情 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/introduction/_category_.json b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/introduction/_category_.json new file mode 100644 index 00000000000..42026dcfccb --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/introduction/_category_.json @@ -0,0 +1 @@ +{"position": 1, "label": "Apache Fory 介绍"} diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/introduction/benchmark.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/introduction/benchmark.md new file mode 100644 index 00000000000..bffb25531c0 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/introduction/benchmark.md @@ -0,0 +1,90 @@ +--- +id: benchmark +title: 性能测试 +sidebar_position: 3 +--- + +> **说明**:不同的序列化框架在不同场景下各有优势。性能测试结果仅供参考。 +> 对于你的具体使用场景,请使用合适的配置和工作负载自行进行基准测试。 + +## Java 性能测试 + +Java 性能测试部分使用 `docs/benchmarks/java` 中的当前基准套件,对 Fory 与常见 Java 序列化框架进行对比。 + +**序列化吞吐**: + +![Java 序列化吞吐](../benchmarks/java/java_repo_serialization_throughput.png) + +**反序列化吞吐**: + +![Java 反序列化吞吐](../benchmarks/java/java_repo_deserialization_throughput.png) + +**跨语言吞吐**: + +![Java 跨语言吞吐](../benchmarks/java/throughput.png) + +**重要说明**:Fory 的运行时代码生成依赖充分预热后才能进行准确的性能测量。 + +更多性能测试说明、原始数据和完整 Java benchmark README 请参见 [Java Benchmarks](https://github.com/apache/fory/tree/main/docs/benchmarks/java)。 + +## Python 性能测试 + +Fory Python 在对象和列表两类工作负载下,相比 `pickle` 和 Protobuf 展现出较强的性能表现。 + +![Python 吞吐图](../benchmarks/python/throughput.png) + +性能测试配置、原始结果以及复现方式请参见 [Python 性能测试报告](../benchmarks/python/README.md)。 + +## Rust 性能测试 + +Fory Rust 相比其他 Rust 序列化框架展现出有竞争力的性能。 + +![Rust 吞吐图](../benchmarks/rust/throughput.png) + +注意:结果取决于硬件、数据集和实现版本。关于如何自行运行性能测试,请参见 Rust 指南:https://github.com/apache/fory/blob/main/benchmarks/rust_benchmark/README.md + +## C++ 性能测试 + +Fory C++ 相比 Protobuf C++ 序列化框架展现出有竞争力的性能。 + +![C++ 吞吐图](../benchmarks/cpp/throughput.png) + +## Go 性能测试 + +Fory Go 在单对象和列表两类工作负载下,相比 Protobuf 和 Msgpack 展现出较强的性能表现。 + +![Go 吞吐图](../benchmarks/go/throughput.png) + +注意:结果取决于硬件、数据集和实现版本。详细信息请参见 Go 性能测试报告:https://fory.apache.org/docs/benchmarks/go/ + +## C\# 性能测试 + +Fory C\# 在强类型对象的序列化和反序列化工作负载下,相比 Protobuf 和 Msgpack 展现出较强的性能表现。 + +![C# 吞吐图](../benchmarks/csharp/throughput.png) + +注意:结果取决于硬件和运行时版本。详细信息请参见 C\# 性能测试报告:https://fory.apache.org/docs/benchmarks/csharp/ + +## Swift 性能测试 + +Fory Swift 在标量对象和列表两类工作负载下,相比 Protobuf 和 Msgpack 展现出较强的性能表现。 + +![Swift 吞吐图](../benchmarks/swift/throughput.png) + +注意:结果取决于硬件和运行时版本。详细信息请参见 Swift 性能测试报告:https://fory.apache.org/docs/benchmarks/swift/ + +## JavaScript 性能测试 + +Fory JavaScript 在具有代表性的 Node.js 工作负载下,相比 Protocol Buffers 与 JSON 展现出较强性能表现。 + +![JavaScript 吞吐图](../benchmarks/javascript/throughput.png) + +注意:结果取决于硬件、数据集和运行时版本。详细信息请参见 [JavaScript 性能测试报告](../benchmarks/javascript/README.md)。 + +## Dart 性能测试 + +Fory Dart 在具有代表性的对象和列表工作负载下,相比 Protocol Buffers 展现出较强性能表现。 + +![Dart 吞吐图](../benchmarks/dart/throughput.png) + +注意:结果取决于硬件、数据集和运行时版本。详细信息请参见 [Dart 性能测试报告](../benchmarks/dart/README.md)。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/introduction/features.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/introduction/features.md new file mode 100644 index 00000000000..ce2907baf13 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/introduction/features.md @@ -0,0 +1,210 @@ +--- +id: features +title: 特性 +sidebar_position: 2 +--- + +## 核心能力 + +### 高性能序列化 + +Apache Fory™ 通过先进的优化技术提供卓越性能: + +- **JIT 编译**:Java 运行时代码生成可消除虚方法调用并内联热点路径 +- **静态代码生成**:Rust、C++ 和 Go 的编译时代码生成可在无运行时开销的情况下实现峰值性能 +- **零拷贝操作**:直接访问内存,无需中间缓冲区复制;行格式支持随机访问和部分序列化 +- **智能编码**:对整数和字符串采用变长压缩;对数组提供 SIMD 加速(Java 16+) +- **元数据共享**:类元数据打包可减少多次序列化中的冗余类型信息 + +### 跨语言序列化 + +**[xlang 序列化格式](../specification/xlang_serialization_spec.md)** 支持不同编程语言之间的无缝数据交换: + +- **自动类型映射**:在语言特定类型之间进行智能转换([类型映射](../specification/xlang_type_mapping.md)) +- **引用保持**:共享引用和循环引用在跨语言场景下也能正确工作 +- **多态**:对象会按其真实运行时类型进行序列化和反序列化 +- **Schema 演进**:可选的向前和向后兼容能力,支持 Schema 演进 +- **自动序列化**:无需 IDL 或 Schema 定义;无需代码生成即可直接序列化任意对象 + +### 行格式 + +面向分析类工作负载优化、对缓存友好的 **[行格式](../specification/row_format_spec.md)**: + +- **零拷贝随机访问**:无需反序列化整个对象即可读取单个字段 +- **部分操作**:按需序列化和反序列化部分字段以提升效率 +- **Apache Arrow 集成**:可无缝转换为列式格式,适用于分析流水线 +- **多语言支持**:支持 Java、Python、Rust 和 C++ + +### 安全性与生产就绪 + +企业级安全性与兼容性: + +- **类注册**:基于白名单的反序列化控制机制(默认启用) +- **深度限制**:防止递归对象图攻击 +- **可配置策略**:支持自定义类检查器和反序列化策略 +- **平台支持**:支持 Java 8-24、GraalVM 原生镜像及多种操作系统平台 + +## Java 特性 + +### 高性能 + +- **JIT 代码生成**:高度可扩展的 JIT 框架可通过异步多线程编译在运行时生成序列化器代码,并通过以下方式带来 20-170 倍加速: + - 内联变量以减少内存访问 + - 内联方法调用以消除虚分发开销 + - 尽量减少条件分支 + - 消除哈希查找 +- **零拷贝**:直接访问内存,无需中间缓冲区复制;行格式支持随机访问和部分序列化 +- **变长编码**:对整数和长整数进行优化压缩 +- **元数据共享**:缓存类元数据以减少冗余类型信息 +- **SIMD 加速**:支持 Java Vector API 进行数组操作(Java 16+) + +### 直接替换 + +- **100% 兼容 JDK 序列化**:支持 `writeObject`/`readObject`/`writeReplace`/`readResolve`/`readObjectNoData`/`Externalizable` +- **支持 Java 8-24**:兼容所有现代 Java 版本,包括 Java 17+ 的 record +- **GraalVM 原生镜像**:支持 AOT 编译,无需反射配置 + +### 高级特性 + +- **引用跟踪**:自动处理共享引用和循环引用 +- **Schema 演进**:支持类 Schema 的向前和向后兼容 +- **多态**:完整支持继承层次结构和接口 +- **深拷贝**:高效深拷贝复杂对象图并保留引用关系 +- **安全性**:支持类注册和可配置的反序列化策略 + +## Python 特性 + +### **灵活的序列化模式** + +- **Python 原生模式**:完全兼容 Python,可直接替换 pickle/cloudpickle +- **跨语言模式**:针对多语言数据交换进行优化 +- **行格式**:面向分析工作负载的零拷贝行格式 + +### 多样化的序列化特性 + +- **支持共享引用和循环引用**:适用于 Python 原生模式和跨语言模式下的复杂对象图 +- **支持多态**:对自定义类型进行自动类型分发 +- **支持 Schema 演进**:在跨语言模式下,对 dataclass 提供向前和向后兼容能力 +- **支持带外缓冲区**:可对 NumPy 数组、Pandas DataFrame 等大型数据结构进行零拷贝序列化,并兼容 pickle protocol 5 + +### **极速性能** + +- **性能极快**:相比其他序列化框架具有明显性能优势 +- **运行时代码生成** 与 **Cython 加速** 的核心实现,可提供最佳性能 + +### 紧凑的数据大小 + +- **紧凑的对象图协议**:空间开销极低,相比 pickle/cloudpickle 最多可减少 3 倍数据大小 +- **元数据打包与共享**:可减少类型向前和向后兼容带来的额外空间开销 + +### **安全与可靠性** + +- **严格模式**:通过类型注册和校验,防止反序列化不受信任的类型 +- **引用跟踪**:可安全处理循环引用 + +## Rust 特性 + +### 为什么选择 Apache Fory™ Rust? + +- **极致性能**:支持零拷贝反序列化和优化的二进制协议 +- **跨语言**:可在 Java、Python、C++、Go、JavaScript 和 Rust 之间无缝序列化和反序列化数据 +- **类型安全**:通过 derive macro 在编译期进行类型检查 +- **循环引用**:自动跟踪共享引用和循环引用,支持 `Rc`/`Arc` 与弱引用 +- **多态**:支持对 `Box`、`Rc` 和 `Arc` 等 trait 对象进行序列化 +- **Schema 演进**:兼容模式支持类型定义独立演进 +- **双模式**:同时支持对象图序列化和零拷贝行格式 + +### 对象图序列化 + +自动序列化复杂对象图,并保留对象之间的结构和关系。`#[derive(ForyObject)]` macro 会在编译期生成高效的序列化代码,从而消除运行时开销: + +- 支持任意深度的嵌套 struct 序列化 +- 支持集合类型(Vec、HashMap、HashSet、BTreeMap) +- 支持枚举类型 +- 通过 `Option` 支持可选字段 +- 自动处理基础类型和字符串 +- 使用变长整数实现高效二进制编码 + +### 共享引用与循环引用 + +通过 `Rc` 和 `Arc` 自动跟踪并保留共享对象的引用标识。当同一个对象被多次引用时,Fory 只会序列化一次,并在后续位置使用引用 ID。这带来以下好处: + +- **空间效率**:序列化结果中不会重复存储相同数据 +- **引用标识保持**:反序列化后的对象会保留相同的共享关系 +- **支持循环引用**:可使用 `RcWeak` 和 `ArcWeak` 断开循环 + +### Trait 对象序列化 + +通过 trait 对象实现多态序列化,从而支持动态分发和类型灵活性。这对插件系统、异构集合和可扩展架构非常重要。支持的 trait 对象类型包括: + +- `Box` - 拥有所有权的 trait 对象 +- `Rc` - 引用计数 trait 对象 +- `Arc` - 线程安全的引用计数 trait 对象 +- `Vec>`、`HashMap>` - trait 对象集合 + +### Schema 演进 + +在 **兼容模式** 下支持 Schema 演进,允许序列化端和反序列化端使用不同的类型定义。这使分布式系统中的服务可以独立演进而不破坏兼容性: + +- 新增带默认值的字段 +- 删除废弃字段(反序列化时跳过) +- 修改字段可空性(`T` ↔ `Option`) +- 调整字段顺序(按名称匹配,而非按位置匹配) +- 对缺失字段进行类型安全的默认值回退 + +### 自定义序列化器 + +对于不支持 `#[derive(ForyObject)]` 的类型,可以手动实现 `Serializer` trait。这适用于以下场景: + +- 其他 crate 中的外部类型 +- 有特殊序列化需求的类型 +- 与旧数据格式兼容 +- 对性能要求极高的自定义编码 + +### 基于行的序列化 + +高性能 **行格式** 支持零拷贝反序列化。与传统对象序列化需要在内存中重建整个对象不同,行格式支持直接从二进制数据中 **随机访问** 字段,而无需完整反序列化。 + +- **零拷贝访问**:读取字段时无需分配或复制数据 +- **部分反序列化**:只访问需要的字段 +- **内存映射文件**:可处理大于 RAM 的数据 +- **缓存友好**:顺序内存布局可更高效利用 CPU 缓存 +- **惰性求值**:将高成本操作延迟到字段访问时再执行 + +## Scala 特性 + +### 支持的类型 + +Apache Fory™ 支持所有 Scala 对象序列化: + +- 支持 `case class` 序列化 +- 支持 `pojo/bean` 类序列化 +- 支持 `object` 单例序列化 +- 支持 `collection` 序列化 +- 其他类型如 `tuple/either` 以及基础类型也都支持 + +同时支持 Scala 2 和 Scala 3。 + +### Scala 类默认值支持 + +Fory 在兼容模式下支持 Scala 类在反序列化时使用默认值。该特性使 case class 或普通 Scala 类在带有默认参数时也能实现向前和向后兼容。 + +当 Scala 类带有默认参数时,Scala 编译器会在伴生对象中(针对 case class)或类自身中(针对普通 Scala 类)生成类似 `apply$default$1`、`apply$default$2` 的方法,用于返回默认值。Fory 可以识别这些方法,并在反序列化时为缺失字段应用默认值。 + +## Kotlin 特性 + +Kotlin 支持建立在默认的 Fory Java 实现之上,并增加了对 Kotlin 特有类型和 Schema 演进的处理能力。 + +### 支持的类型 + +- 基础类型:`Byte`、`Boolean`、`Int`、`Short`、`Long`、`Char`、`Float`、`Double` +- 无符号类型:`UByte`、`UShort`、`UInt`、`ULong` +- 通过默认 Java 实现支持字符串、标准数组和标准集合 +- Kotlin 集合和数组,例如 `ArrayDeque`、`Array`、`BooleanArray`、`ByteArray`、`IntArray`、`LongArray`、`UByteArray`、`UIntArray` 和 `ULongArray` +- 常见标准库类型,包括 `Pair`、`Triple`、`Result`、`Regex`、`Random`、`Duration` 和 `Uuid` +- Kotlin range 和 progression 类型,例如 `CharRange`、`IntRange`、`LongRange`、`UIntRange`、`ULongRange` 及其对应的 progression 类型 +- 空集合,如 `emptyList`、`emptyMap` 和 `emptySet` + +### 数据类默认值支持 + +在兼容模式下,Fory 可以识别 Kotlin 数据类的默认值,并在字段缺失时于反序列化阶段自动应用这些默认值。这使 Schema 演进更加安全,例如在新增带默认值字段时无需破坏兼容性。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/introduction/overview.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/introduction/overview.md new file mode 100644 index 00000000000..929aa34ed47 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/introduction/overview.md @@ -0,0 +1,102 @@ +--- +id: overview +title: 概述 +sidebar_position: 1 +--- + +**Apache Fory™** 是一个高性能多语言序列化框架,面向原生对象序列化和跨语言二进制编解码,支持 Schema IDL、对象引用、Schema 演进、Row Format 随机访问与零拷贝读取。 + +Fory 面向跨语言、跨运行时的紧凑高吞吐序列化而构建。它可以直接处理应用中的对象;当需要稳定契约时,也可以使用共享 Schema;同时保留对象图中的共享引用、循环引用和多态运行时类型等语义。 + +## 快速示例 + +跨语言序列化,在 Rust 中序列化,在 Python 中反序列化: + +**Rust** + +```rust +use fory::{Fory, ForyObject}; + +#[derive(ForyObject, Debug, PartialEq)] +struct User { + name: String, + age: i32, +} + +fn main() { + let mut fory = Fory::default().xlang(true); + fory.register::(1); + + let user = User { name: "Alice".to_string(), age: 30 }; + let bytes = fory.serialize(&user).unwrap(); + let decoded: User = fory.deserialize(&bytes).unwrap(); + println!("{:?}", decoded); // User { name: "Alice", age: 30 } +} +``` + +**Python** + +```python +import pyfory +from dataclasses import dataclass + +@dataclass +class User: + name: str + age: pyfory.int32 + +fory = pyfory.Fory(xlang=True) +fory.register(User, type_id=1) + +user = User(name="Alice", age=30) +data = fory.serialize(user) +decoded = fory.deserialize(data) +print(decoded) # User(name='Alice', age=30) +``` + +## 核心特性 + +### 高效跨语言编码 + +**[xlang 序列化格式](../specification/xlang_serialization_spec.md)** 可以在支持的语言之间交换紧凑二进制载荷: + +- **紧凑元数据**:打包类型元信息和字段信息,降低载荷体积。 +- **Schema 演进**:兼容模式支持应用 Schema 的向前和向后演进。 +- **对象图语义**:跨运行时保留共享引用、循环引用和多态运行时类型。 +- **类型映射**:语言特定值通过共享的[类型映射](../specification/xlang_type_mapping.md)进行转换。 + +### 领域对象优先 + +Fory 直接序列化原生领域对象,而不是要求应用引入包装类型: + +- Java 类、Scala/Kotlin 类型,以及 GraalVM native image 工作负载。 +- Python dataclass 和 Python 原生对象图。 +- Go struct、Rust struct、C++ struct、C# 模型、Swift 类型、Dart 模型,以及 JavaScript/TypeScript 值。 +- 当需要共享契约时,也支持生成或注解过的类型。 + +### 原生支持引用的 Schema IDL + +**[Fory IDL 和编译器](../compiler/index.md)** 允许团队一次定义 Schema,并为每种目标语言生成原生领域对象: + +- 建模数字、字符串、list、map、array、enum、struct 和 union。 +- 在 Schema 中直接表达共享引用和循环引用。 +- 生成惯用的宿主语言代码,不把传输专用包装类型引入用户代码。 +- 当服务需要在独立维护的运行时之间共享稳定契约时,可以使用 Schema IDL。 + +### 行格式随机访问 + +缓存友好的 **[行格式](../specification/row_format_spec.md)** 面向分析和部分读取工作负载优化: + +- **零拷贝随机访问**:无需重建完整对象即可读取字段、数组和嵌套值。 +- **部分操作**:只读取查询或流水线阶段需要的值。 +- **Apache Arrow 集成**:转换为列式数据,用于分析流水线。 +- **多语言支持**:可在 Java、Python、Rust 和 C++ 中使用行格式。 + +### 优化运行时 + +Fory 让热点路径保持高速,同时不要求所有运行时采用相同实现策略: + +- **Java JIT 序列化器**:运行时代码生成消除反射开销,并内联热点路径。 +- **生成式和静态序列化器**:其他运行时在合适场景下使用生成式或静态序列化器。 +- **零拷贝路径**:行格式和带外 buffer 可避免大值的不必要复制。 +- **元数据共享**:复用或打包重复类型信息,降低序列化开销。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/start/_category_.json b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/start/_category_.json new file mode 100644 index 00000000000..876bbf155e3 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/start/_category_.json @@ -0,0 +1 @@ +{"position": 2, "label": "快速开始"} diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/start/install.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/start/install.md new file mode 100644 index 00000000000..509dcf52dba --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/start/install.md @@ -0,0 +1,177 @@ +--- +id: install +title: 安装 +sidebar_position: 0 +--- + +Apache Fory™ 同时提供源码发布物和各语言对应的软件包。 + +源码下载请参见 Apache Fory™ [download](https://fory.apache.org/download) 页面。 + +## Java + +使用 Maven 添加 Apache Fory™: + +```xml + + org.apache.fory + fory-core + 1.3.0 + + + + + +``` + +## Scala + +Scala 2.13 的 Maven 依赖: + +```xml + + org.apache.fory + fory-scala_2.13 + 1.3.0 + +``` + +Scala 3 的 Maven 依赖: + +```xml + + org.apache.fory + fory-scala_3 + 1.3.0 + +``` + +Scala 2.13 的 sbt 依赖: + +```sbt +libraryDependencies += "org.apache.fory" % "fory-scala_2.13" % "1.3.0" +``` + +Scala 3 的 sbt 依赖: + +```sbt +libraryDependencies += "org.apache.fory" % "fory-scala_3" % "1.3.0" +``` + +## Kotlin + +使用 Maven 添加 Apache Fory™ Kotlin: + +```xml + + org.apache.fory + fory-kotlin + 1.3.0 + +``` + +## Python + +```bash +python -m pip install --upgrade pip +pip install pyfory==1.3.0 +``` + +## Go + +请使用完整的 Go 模块路径 `github.com/apache/fory/go/fory`: + +```bash +go get github.com/apache/fory/go/fory@v1.3.0 +``` + +如果你的 Go proxy 还没有同步新的子模块 tag,请稍后重试,或者临时使用 `GOPROXY=direct`。 + +## Rust + +```toml +[dependencies] +fory = "1.3.0" +``` + +或者使用 `cargo add`: + +```bash +cargo add fory@1.3.0 +``` + +## JavaScript / TypeScript + +从 npm 安装已发布的 JavaScript 包: + +```bash +npm install @apache-fory/core +``` + +可选的原生加速需要 Node.js 20+: + +```bash +npm install @apache-fory/hps +``` + +## Dart + +在 `pubspec.yaml` 中添加 Apache Fory™ Dart: + +```yaml +dependencies: + fory: ^1.3.0 + +dev_dependencies: + build_runner: ^2.4.13 +``` + +定义带注解的类型后生成序列化器: + +```bash +dart run build_runner build --delete-conflicting-outputs +``` + +## C\# + +安装 `Apache.Fory` NuGet 包。它同时包含运行时以及 `[ForyObject]` 类型所需的源代码生成器。 + +```bash +dotnet add package Apache.Fory --version 1.3.0 +``` + +```xml + + + +``` + +## Swift + +使用 Swift Package Manager 从 GitHub 仓库引入 Apache Fory™: + +```swift +dependencies: [ + .package(url: "https://github.com/apache/fory.git", exact: "1.3.0") +], +targets: [ + .target( + name: "MyApp", + dependencies: [ + .product(name: "Fory", package: "fory") + ] + ) +] +``` diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/start/usage.md b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/start/usage.md new file mode 100644 index 00000000000..5c600cd61f6 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.3.0/start/usage.md @@ -0,0 +1,486 @@ +--- +id: usage +title: 使用 +sidebar_position: 1 +--- + +本章节提供 Apache Fory™ 的快速入门示例。 + +## 选择模式 + +Apache Fory™ 有两种线格式模式: + +- **xlang 模式**是默认模式,也是跨语言共享载荷时使用的可移植格式。跨语言服务应使用 xlang 模式;Dart、JavaScript/TypeScript、C# 和 Swift 也只暴露 xlang 模式。 +- **原生模式**通过 `xlang=false` 或对应的 builder 选项启用,适用于 Java、Scala、Kotlin、Python、C++、Go 和 Rust。只有读写双方都属于同一运行时家族时才使用原生模式,因为它遵循该运行时的原生类型系统,支持更广的语言特有对象面,并针对该运行时优化。 + +xlang/default 用法默认使用 schema-compatible 模式。原生模式默认使用 schema-consistent 载荷,只有显式启用 compatible 模式时才改变。 + +## xlang 模式 + +当字节需要跨运行时边界传输时使用 xlang 模式。自定义类型需要在每个对端使用相同的数字 ID 或 namespace/type name 注册。 + +下面的示例中,支持双模式的运行时都会显式设置 xlang 选项。Dart、JavaScript/TypeScript、C# 和 Swift 只支持 xlang 模式,因此示例不会展示 xlang 开关。 + +### Java + +```java +import org.apache.fory.Fory; + +public class XlangExample { + public record Person(String name, int age) {} + + public static void main(String[] args) { + Fory fory = Fory.builder() + .withXlang(true) + .build(); + fory.register(Person.class, "example", "Person"); + + Person person = new Person("chaokunyang", 28); + byte[] bytes = fory.serialize(person); + Person result = (Person) fory.deserialize(bytes); + System.out.println(result.name() + " " + result.age()); + } +} +``` + +### Python + +```python +from dataclasses import dataclass +import pyfory + +@dataclass +class Person: + name: str + age: pyfory.Int32 + +fory = pyfory.Fory(xlang=True) +fory.register(Person, typename="example.Person") + +person = Person(name="chaokunyang", age=28) +data = fory.serialize(person) +result = fory.deserialize(data) +print(result.name, result.age) +``` + +### Dart + +```dart +import 'package:fory/fory.dart'; + +part 'person.fory.dart'; + +@ForyStruct() +class Person { + Person(); + + String name = ''; + + @ForyField(type: Int32Type()) + int age = 0; +} + +void main() { + final fory = Fory(); + PersonFory.register( + fory, + Person, + namespace: 'example', + typeName: 'Person', + ); + + final person = Person() + ..name = 'chaokunyang' + ..age = 28; + + final bytes = fory.serialize(person); + final result = fory.deserialize(bytes); + print('${result.name} ${result.age}'); +} +``` + +### Go + +```go +package main + +import ( + "fmt" + + "github.com/apache/fory/go/fory" +) + +type Person struct { + Name string + Age int32 +} + +func main() { + f := fory.New(fory.WithXlang(true)) + if err := f.RegisterStruct(Person{}, 1); err != nil { + panic(err) + } + + person := &Person{Name: "chaokunyang", Age: 28} + data, err := f.Serialize(person) + if err != nil { + panic(err) + } + + var result Person + if err := f.Deserialize(data, &result); err != nil { + panic(err) + } + + fmt.Printf("%s %d\n", result.Name, result.Age) +} +``` + +### Rust + +```rust +use fory::{Error, Fory, ForyObject}; + +#[derive(ForyObject, Debug, PartialEq)] +struct Person { + name: String, + age: i32, +} + +fn main() -> Result<(), Error> { + let mut fory = Fory::builder().xlang(true).build(); + fory.register_by_name::("example", "Person")?; + + let person = Person { + name: "chaokunyang".to_string(), + age: 28, + }; + + let bytes = fory.serialize(&person)?; + let result: Person = fory.deserialize(&bytes)?; + assert_eq!(person, result); + Ok(()) +} +``` + +### C++ + +```cpp +#include +#include + +#include "fory/serialization/fory.h" + +using namespace fory::serialization; + +struct Person { + std::string name; + int32_t age; + + bool operator==(const Person &other) const { + return name == other.name && age == other.age; + } + + FORY_STRUCT(Person, name, age); +}; + +int main() { + auto fory = Fory::builder().xlang(true).build(); + fory.register_struct(1); + + Person person{"chaokunyang", 28}; + auto bytes = fory.serialize(person).value(); + auto result = fory.deserialize(bytes).value(); + assert(person == result); + return 0; +} +``` + +### Scala + +```scala +import org.apache.fory.Fory +import org.apache.fory.scala.ForyScala + +case class Person(name: String, age: Int) + +object Example { + def main(args: Array[String]): Unit = { + val fory: Fory = ForyScala.builder() + .withXlang(true) + .build() + fory.register(classOf[Person]) + + val bytes = fory.serialize(Person("chaokunyang", 28)) + val result = fory.deserialize(bytes).asInstanceOf[Person] + println(s"${result.name} ${result.age}") + } +} +``` + +### Kotlin + +```kotlin +import org.apache.fory.ThreadSafeFory +import org.apache.fory.kotlin.ForyKotlin + +data class Person(val name: String, val age: Int) + +fun main() { + val fory: ThreadSafeFory = ForyKotlin.builder() + .withXlang(true) + .requireClassRegistration(true) + .buildThreadSafeFory() + fory.register(Person::class.java) + + val bytes = fory.serialize(Person("chaokunyang", 28)) + val result = fory.deserialize(bytes) as Person + println("${result.name} ${result.age}") +} +``` + +### JavaScript / TypeScript + +```typescript +import Fory, { Type } from "@apache-fory/core"; + +const personType = Type.struct( + { typeName: "example.Person" }, + { + name: Type.string(), + age: Type.int32(), + }, +); + +const fory = new Fory(); +const { serialize, deserialize } = fory.register(personType); + +const payload = serialize({ name: "chaokunyang", age: 28 }); +const result = deserialize(payload); +console.log(result); +``` + +### C\# + +```csharp +using Apache.Fory; + +[ForyObject] +public sealed class Person +{ + public string Name { get; set; } = string.Empty; + public int Age { get; set; } +} + +Fory fory = Fory.Builder().Build(); +fory.Register(1); + +Person person = new() { Name = "chaokunyang", Age = 28 }; +byte[] data = fory.Serialize(person); +Person result = fory.Deserialize(data); + +Console.WriteLine($"{result.Name} {result.Age}"); +``` + +### Swift + +```swift +import Fory + +@ForyStruct +struct Person: Equatable { + var name: String = "" + var age: Int32 = 0 +} + +let fory = Fory() +fory.register(Person.self, id: 1) + +let person = Person(name: "chaokunyang", age: 28) +let data = try fory.serialize(person) +let result: Person = try fory.deserialize(data) + +print("\(result.name) \(result.age)") +``` + +更多跨语言规则和示例请参见: + +- [跨语言序列化指南](../guide/xlang/index.md) +- [Java 指南](../guide/java/index.md) +- [Python 指南](../guide/python/index.md) +- [Dart 指南](../guide/dart/index.md) +- [Go 指南](../guide/go/index.md) +- [Rust 指南](../guide/rust/index.md) +- [C++ 指南](../guide/cpp/index.md) +- [C# 指南](../guide/csharp/index.md) +- [Swift 指南](../guide/swift/index.md) + +## 原生模式 + +只有在每个读写方都属于同一运行时家族时才使用原生模式。原生模式支持比可移植 xlang 映射更广的语言特有对象模型,并针对所属运行时优化。 + +Java 和 Python 的原生模式都是同语言场景的一等入口。当你要替换 JDK serialization、Kryo、FST、Hessian 或 Java-only Protocol Buffers 载荷时,Java 应从原生模式开始。当你要替换 `pickle` 或 `cloudpickle` 并且载荷只在 Python 内流转时,Python 应使用原生模式。 + +Dart、JavaScript/TypeScript、C# 和 Swift 不暴露原生模式。 + +### Java + +```java +Fory fory = Fory.builder() + .withXlang(false) + .requireClassRegistration(true) + .build(); +``` + +注册 Java 类后照常使用 `serialize` / `deserialize`。Java 对象钩子、`Externalizable`、动态对象图、对象拷贝和 Java 原生模式 zero-copy buffer 参见 [Java 指南](../guide/java/index.md)。 + +### Python + +```python +import pyfory + +fory = pyfory.Fory(xlang=False, ref=False, strict=True) +``` + +注册 Python 类后照常使用 `serialize` / `deserialize`。原生模式的 pickle 替代行为和安全设置参见 [Python 指南](../guide/python/index.md)。 + +### Go + +```go +f := fory.New(fory.WithXlang(false)) +``` + +Go-only 结构体、指针、接口和 Go 特有类型行为可使用原生模式。struct tag 和原生模式配置参见 [Go 指南](../guide/go/index.md)。 + +### Rust + +```rust +let mut fory = Fory::builder().xlang(false).build(); +``` + +依赖 Rust 特有对象行为的 Rust-only 载荷可使用原生模式。derive、引用和支持类型参见 [Rust 指南](../guide/rust/index.md)。 + +### C++ + +```cpp +auto fory = Fory::builder().xlang(false).build(); +``` + +不需要可移植 xlang 类型映射的 C++-only 流量可使用原生模式。`FORY_STRUCT`、配置和 schema metadata 参见 [C++ 指南](../guide/cpp/index.md)。 + +### Scala + +```scala +val fory = ForyScala.builder() + .withXlang(false) + .build() +``` + +需要 Scala case class、集合、tuple、option 或 enum 并且只在 Scala/JVM 内流转的载荷可使用原生模式。参见 [Scala 指南](../guide/scala/index.md)。 + +### Kotlin + +```kotlin +val fory = ForyKotlin.builder() + .withXlang(false) + .requireClassRegistration(true) + .buildThreadSafeFory() +``` + +需要 Kotlin data class、可空类型、range、unsigned value 或 Kotlin 集合并且只在 Kotlin/JVM 内流转的载荷可使用原生模式。参见 [Kotlin 指南](../guide/kotlin/index.md)。 + +## Row Format 编码 + +Row format 提供对序列化数据的零拷贝随机访问,适合分析型工作负载和数据处理管线。 + +### Java + +```java +import org.apache.fory.format.*; +import java.util.*; +import java.util.stream.*; + +public class Bar { + String f1; + List f2; +} + +public class Foo { + int f1; + List f2; + Map f3; + List f4; +} + +RowEncoder encoder = Encoders.bean(Foo.class); +Foo foo = new Foo(); +foo.f1 = 10; +foo.f2 = IntStream.range(0, 1000000).boxed().collect(Collectors.toList()); +foo.f3 = IntStream.range(0, 1000000).boxed().collect(Collectors.toMap(i -> "k"+i, i -> i)); + +List bars = new ArrayList<>(1000000); +for (int i = 0; i < 1000000; i++) { + Bar bar = new Bar(); + bar.f1 = "s" + i; + bar.f2 = LongStream.range(0, 10).boxed().collect(Collectors.toList()); + bars.add(bar); +} +foo.f4 = bars; + +// Serialize to row format (can be zero-copy read by Python) +BinaryRow binaryRow = encoder.toRow(foo); + +// Deserialize entire object +Foo newFoo = encoder.fromRow(binaryRow); + +// Zero-copy access to nested fields without full deserialization +BinaryArray binaryArray2 = binaryRow.getArray(1); // Access f2 field +BinaryArray binaryArray4 = binaryRow.getArray(3); // Access f4 field +BinaryRow barStruct = binaryArray4.getStruct(10); // Access 11th Bar element +long value = barStruct.getArray(1).getInt64(5); // Access nested value + +// Partial deserialization +RowEncoder barEncoder = Encoders.bean(Bar.class); +Bar newBar = barEncoder.fromRow(barStruct); +Bar newBar2 = barEncoder.fromRow(binaryArray4.getStruct(20)); +``` + +### Python + +```python +from dataclasses import dataclass +from typing import List, Dict +import pyarrow as pa +import pyfory + +@dataclass +class Bar: + f1: str + f2: List[pa.int64] + +@dataclass +class Foo: + f1: pa.int32 + f2: List[pa.int32] + f3: Dict[str, pa.int32] + f4: List[Bar] + +encoder = pyfory.encoder(Foo) +foo = Foo( + f1=10, + f2=list(range(1000_000)), + f3={f"k{i}": i for i in range(1000_000)}, + f4=[Bar(f1=f"s{i}", f2=list(range(10))) for i in range(1000_000)] +) + +# Serialize to row format +binary: bytes = encoder.to_row(foo).to_bytes() + +# Zero-copy random access without full deserialization +foo_row = pyfory.RowData(encoder.schema, binary) +print(foo_row.f2[100000]) # Access element directly +print(foo_row.f4[100000].f1) # Access nested field +print(foo_row.f4[200000].f2[5]) # Access deeply nested field +``` + +更多 row format 细节请参见 [Java Row Format 指南](../guide/java/row-format.md) 或 [Python Row Format 指南](../guide/python/row-format.md)。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-pages/download/index.md b/i18n/zh-CN/docusaurus-plugin-content-pages/download/index.md index 9cebfa3847e..0257e242ae0 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-pages/download/index.md +++ b/i18n/zh-CN/docusaurus-plugin-content-pages/download/index.md @@ -9,11 +9,11 @@ Apache Fory™ 的官方发布以源码制品形式提供。 ## 最新版本 -当前最新源码版本为 1.2.0: +当前最新源码版本为 1.3.0: | 版本 | 日期 | 源码 | 发布说明 | | ------ | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------- | -| 1.2.0 | 2026-06-16 | [source](https://www.apache.org/dyn/closer.lua/fory/1.2.0/apache-fory-1.2.0-src.tar.gz?action=download) [asc](https://downloads.apache.org/fory/1.2.0/apache-fory-1.2.0-src.tar.gz.asc) [sha512](https://downloads.apache.org/fory/1.2.0/apache-fory-1.2.0-src.tar.gz.sha512) | [release notes](https://github.com/apache/fory/releases/tag/v1.2.0) | +| 1.3.0 | 2026-06-25 | [source](https://www.apache.org/dyn/closer.lua/fory/1.3.0/apache-fory-1.3.0-src.tar.gz?action=download) [asc](https://downloads.apache.org/fory/1.3.0/apache-fory-1.3.0-src.tar.gz.asc) [sha512](https://downloads.apache.org/fory/1.3.0/apache-fory-1.3.0-src.tar.gz.sha512) | [release notes](https://github.com/apache/fory/releases/tag/v1.3.0) | ## 所有归档版本 @@ -31,13 +31,13 @@ Fory 为下载站点上的所有文件提供 SHA 摘要和 PGP 签名文件。 要验证 SHA 摘要,你需要 `.tar.gz` 文件及其对应的 `.tar.gz.sha512` 文件。示例命令如下: ```bash -sha512sum --check apache-fory-1.2.0-src.tar.gz.sha512 +sha512sum --check apache-fory-1.3.0-src.tar.gz.sha512 ``` 输出类似下面这样即表示校验通过: ```bash -apache-fory-1.2.0-src.tar.gz: OK +apache-fory-1.3.0-src.tar.gz: OK ``` ### 校验签名 @@ -53,7 +53,7 @@ gpg --import KEYS 之后可以校验签名: ```bash -gpg --verify apache-fory-1.2.0-src.tar.gz.asc apache-fory-1.2.0-src.tar.gz +gpg --verify apache-fory-1.3.0-src.tar.gz.asc apache-fory-1.3.0-src.tar.gz ``` 如果出现如下输出,即表示签名正确: diff --git a/src/components/home/HomepageLanding.tsx b/src/components/home/HomepageLanding.tsx index 7ddd8c60b17..887b30722ce 100644 --- a/src/components/home/HomepageLanding.tsx +++ b/src/components/home/HomepageLanding.tsx @@ -138,7 +138,7 @@ const runtimeExamples: RuntimeExample[] = [ install: ` org.apache.fory fory-core - 1.2.0 + 1.3.0 `, codeLanguage: "java", guide: "/docs/guide/java/", @@ -181,7 +181,7 @@ out = fory.deserialize(data)`, id: "rust", label: "Rust", installLanguage: "bash", - install: `cargo add fory@1.2.0`, + install: `cargo add fory@1.3.0`, codeLanguage: "rust", guide: "/docs/guide/rust/", summary: "Rust uses derive macros for type-safe structs and supports both xlang and native payloads.", @@ -229,7 +229,7 @@ _ = f.Deserialize(payload, &out)`, install: `FetchContent_Declare( fory GIT_REPOSITORY https://github.com/apache/fory.git - GIT_TAG v1.2.0 + GIT_TAG v1.3.0 SOURCE_SUBDIR cpp )`, codeLanguage: "cpp", @@ -276,7 +276,7 @@ const out = deserialize(payload);`, id: "csharp", label: "C#", installLanguage: "bash", - install: `dotnet add package Apache.Fory --version 1.2.0`, + install: `dotnet add package Apache.Fory --version 1.3.0`, codeLanguage: "csharp", guide: "/docs/guide/csharp/", summary: ".NET support uses source-generated serializers for Fory structs, enums, and unions.", @@ -299,7 +299,7 @@ Person out = fory.Deserialize(payload);`, id: "swift", label: "Swift", installLanguage: "swift", - install: `.package(url: "https://github.com/apache/fory.git", exact: "1.2.0")`, + install: `.package(url: "https://github.com/apache/fory.git", exact: "1.3.0")`, codeLanguage: "swift", guide: "/docs/guide/swift/", summary: "Swift uses @ForyStruct, @ForyEnum, and @ForyUnion macros for xlang-compatible models.", @@ -322,7 +322,7 @@ let out: Person = try fory.deserialize(payload)`, label: "Dart", installLanguage: "yaml", install: `dependencies: - fory: ^1.2.0 + fory: ^1.3.0 dev_dependencies: build_runner: ^2.4.13`, @@ -357,7 +357,7 @@ final out = fory.deserialize(payload);`, id: "scala", label: "Scala", installLanguage: "sbt", - install: `libraryDependencies += "org.apache.fory" %% "fory-scala" % "1.2.0"`, + install: `libraryDependencies += "org.apache.fory" %% "fory-scala" % "1.3.0"`, codeLanguage: "scala", guide: "/docs/guide/scala/", summary: "Scala builds on Fory Java with optimized serializers for case classes, collections, tuples, and Option.", @@ -377,8 +377,8 @@ val out = fory.deserialize(payload).asInstanceOf[Person]`, id: "kotlin", label: "Kotlin", installLanguage: "kotlin", - install: `implementation("org.apache.fory:fory-kotlin:1.2.0") -ksp("org.apache.fory:fory-kotlin-ksp:1.2.0")`, + install: `implementation("org.apache.fory:fory-kotlin:1.3.0") +ksp("org.apache.fory:fory-kotlin-ksp:1.3.0")`, codeLanguage: "kotlin", guide: "/docs/guide/kotlin/", summary: "Kotlin adds data-class support, Android guidance, and KSP static serializers for xlang/schema mode.", diff --git a/src/pages/download/index.md b/src/pages/download/index.md index 66b2b099015..0de8daf3570 100644 --- a/src/pages/download/index.md +++ b/src/pages/download/index.md @@ -9,11 +9,11 @@ For binary install, please see Apache Fory™ [install](/docs/start/install/) do ## The latest release -The latest source release is 1.2.0: +The latest source release is 1.3.0: | Version | Date | Source | Release Notes | | ------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | -| 1.2.0 | 2026-06-16 | [source](https://www.apache.org/dyn/closer.lua/fory/1.2.0/apache-fory-1.2.0-src.tar.gz?action=download) [asc](https://downloads.apache.org/fory/1.2.0/apache-fory-1.2.0-src.tar.gz.asc) [sha512](https://downloads.apache.org/fory/1.2.0/apache-fory-1.2.0-src.tar.gz.sha512) | [release notes](https://github.com/apache/fory/releases/tag/v1.2.0) | +| 1.3.0 | 2026-06-25 | [source](https://www.apache.org/dyn/closer.lua/fory/1.3.0/apache-fory-1.3.0-src.tar.gz?action=download) [asc](https://downloads.apache.org/fory/1.3.0/apache-fory-1.3.0-src.tar.gz.asc) [sha512](https://downloads.apache.org/fory/1.3.0/apache-fory-1.3.0-src.tar.gz.sha512) | [release notes](https://github.com/apache/fory/releases/tag/v1.3.0) | ## All archived releases @@ -31,13 +31,13 @@ These files are named after the files they relate to but have `.sha512/.asc` ext To verify the SHA digests, you need the `.tar.gz` file and its associated `.tar.gz.sha512` file. An example command: ```bash -sha512sum --check apache-fory-1.2.0-src.tar.gz.sha512 +sha512sum --check apache-fory-1.3.0-src.tar.gz.sha512 ``` It should output something like: ```bash -apache-fory-1.2.0-src.tar.gz: OK +apache-fory-1.3.0-src.tar.gz: OK ``` ### Verifying Signatures @@ -54,7 +54,7 @@ gpg --import KEYS Then you can verify signature: ```bash -gpg --verify apache-fory-1.2.0-src.tar.gz.asc apache-fory-1.2.0-src.tar.gz +gpg --verify apache-fory-1.3.0-src.tar.gz.asc apache-fory-1.3.0-src.tar.gz ``` If something like the following appears, it means the signature is correct: diff --git a/versioned_docs/version-1.3.0/benchmarks/cpp/README.md b/versioned_docs/version-1.3.0/benchmarks/cpp/README.md new file mode 100644 index 00000000000..8b201fb7753 --- /dev/null +++ b/versioned_docs/version-1.3.0/benchmarks/cpp/README.md @@ -0,0 +1,78 @@ +# C++ Benchmark Performance Report + +_Generated on 2026-06-12 16:14:04_ + +## How to Generate This Report + +```bash +cd benchmarks/cpp/build +./fory_benchmark --benchmark_format=json --benchmark_out=benchmark_results.json +cd .. +python benchmark_report.py --json-file build/benchmark_results.json --output-dir report +``` + +## Benchmark Plot + +The plot shows throughput (ops/sec); higher is better. + +![Throughput](throughput.png) + +## Hardware & OS Info + +| Key | Value | +| -------------------------- | ------------------------- | +| OS | Darwin 24.6.0 | +| Machine | arm64 | +| Processor | arm | +| CPU Cores (Physical) | 12 | +| CPU Cores (Logical) | 12 | +| Total RAM (GB) | 48.0 | +| Benchmark Date | 2026-06-12T16:13:27+08:00 | +| CPU Cores (from benchmark) | 12 | + +## Benchmark Results + +### Timing Results (nanoseconds) + +| Datatype | Operation | fory (ns) | protobuf (ns) | msgpack (ns) | Fastest | +| ----------------- | ----------- | --------- | ------------- | ------------ | ------- | +| NumericStruct | Serialize | 25.7 | 48.2 | 85.4 | fory | +| NumericStruct | Deserialize | 25.1 | 31.8 | 887.2 | fory | +| Sample | Serialize | 60.6 | 96.4 | 361.6 | fory | +| Sample | Deserialize | 176.7 | 397.0 | 2031.6 | fory | +| MediaContent | Serialize | 113.5 | 471.0 | 290.4 | fory | +| MediaContent | Deserialize | 247.3 | 641.9 | 2015.4 | fory | +| NumericStructList | Serialize | 83.3 | 372.9 | 446.9 | fory | +| NumericStructList | Deserialize | 158.1 | 268.2 | 4342.4 | fory | +| SampleList | Serialize | 258.8 | 2829.7 | 2602.3 | fory | +| SampleList | Deserialize | 1001.7 | 2794.4 | 12220.7 | fory | +| MediaContentList | Serialize | 504.2 | 2589.8 | 1549.5 | fory | +| MediaContentList | Deserialize | 1258.6 | 3620.3 | 10263.4 | fory | + +### Throughput Results (ops/sec) + +| Datatype | Operation | fory TPS | protobuf TPS | msgpack TPS | Fastest | +| ----------------- | ----------- | ---------- | ------------ | ----------- | ------- | +| NumericStruct | Serialize | 38,845,461 | 20,734,963 | 11,707,994 | fory | +| NumericStruct | Deserialize | 39,872,217 | 31,443,829 | 1,127,092 | fory | +| Sample | Serialize | 16,496,488 | 10,372,657 | 2,765,312 | fory | +| Sample | Deserialize | 5,660,852 | 2,518,926 | 492,232 | fory | +| MediaContent | Serialize | 8,808,084 | 2,122,926 | 3,443,519 | fory | +| MediaContent | Deserialize | 4,043,028 | 1,557,819 | 496,175 | fory | +| NumericStructList | Serialize | 11,999,598 | 2,681,661 | 2,237,536 | fory | +| NumericStructList | Deserialize | 6,323,730 | 3,728,133 | 230,285 | fory | +| SampleList | Serialize | 3,864,068 | 353,391 | 384,276 | fory | +| SampleList | Deserialize | 998,326 | 357,854 | 81,828 | fory | +| MediaContentList | Serialize | 1,983,502 | 386,135 | 645,372 | fory | +| MediaContentList | Deserialize | 794,544 | 276,221 | 97,434 | fory | + +### Serialized Data Sizes (bytes) + +| Datatype | fory | protobuf | msgpack | +| ----------------- | ---- | -------- | ------- | +| NumericStruct | 78 | 93 | 87 | +| Sample | 445 | 375 | 530 | +| MediaContent | 362 | 301 | 480 | +| NumericStructList | 255 | 475 | 449 | +| SampleList | 1978 | 1890 | 2664 | +| MediaContentList | 1531 | 1520 | 2421 | diff --git a/versioned_docs/version-1.3.0/benchmarks/cpp/throughput.png b/versioned_docs/version-1.3.0/benchmarks/cpp/throughput.png new file mode 100644 index 00000000000..b2d6bfffb93 Binary files /dev/null and b/versioned_docs/version-1.3.0/benchmarks/cpp/throughput.png differ diff --git a/versioned_docs/version-1.3.0/benchmarks/csharp/README.md b/versioned_docs/version-1.3.0/benchmarks/csharp/README.md new file mode 100644 index 00000000000..7dcdb79a774 --- /dev/null +++ b/versioned_docs/version-1.3.0/benchmarks/csharp/README.md @@ -0,0 +1,89 @@ +# C# Benchmark Performance Report + +_Generated on 2026-05-08 17:54:45_ + +## How to Generate This Report + +```bash +cd benchmarks/csharp +dotnet run -c Release --project ./Fory.CSharpBenchmark.csproj -- --output build/benchmark_results.json +python3 benchmark_report.py --json-file build/benchmark_results.json --output-dir report +``` + +## Benchmark Plot + +The plot shows throughput (ops/sec); higher is better. + +![Throughput](throughput.png) + +## Hardware & OS Info + +| Key | Value | +| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | +| OS | Darwin 24.6.0 Darwin Kernel Version 24.6.0: Wed Oct 15 21:12:15 PDT 2025; root:xnu-11417.140.69.703.14~1/RELEASE_ARM64_T6041 | +| OS Architecture | Arm64 | +| Machine | Arm64 | +| Runtime Version | 8.0.24 | +| Benchmark Date (UTC) | 2026-05-08T08:17:48.7871870Z | +| Warmup Seconds | 1 | +| Duration Seconds | 3 | +| CPU Logical Cores (from benchmark) | 12 | +| CPU Cores (Physical) | 12 | +| CPU Cores (Logical) | 12 | +| Total RAM (GB) | 48.0 | + +## Benchmark Coverage + +| Key | Value | +| ------------------- | ---------------------------------------------------------------------- | +| Cases in input JSON | 36 / 36 | +| Serializers | fory, msgpack, protobuf | +| Datatypes | struct, sample, mediacontent, structlist, samplelist, mediacontentlist | +| Operations | serialize, deserialize | + +## Benchmark Results + +### Timing Results (nanoseconds) + +| Datatype | Operation | fory (ns) | protobuf (ns) | msgpack (ns) | Fastest | +| ----------------- | ----------- | --------- | ------------- | ------------ | ------- | +| NumericStruct | Serialize | 50.3 | 170.8 | 107.8 | fory | +| NumericStruct | Deserialize | 82.4 | 252.0 | 143.4 | fory | +| Sample | Serialize | 263.2 | 607.1 | 377.1 | fory | +| Sample | Deserialize | 199.4 | 1191.7 | 785.6 | fory | +| MediaContent | Serialize | 379.7 | 509.6 | 417.6 | fory | +| MediaContent | Deserialize | 450.3 | 846.6 | 791.4 | fory | +| NumericStructList | Serialize | 183.7 | 641.8 | 447.8 | fory | +| NumericStructList | Deserialize | 288.3 | 974.3 | 702.1 | fory | +| SampleList | Serialize | 1205.7 | 3559.1 | 1864.1 | fory | +| SampleList | Deserialize | 895.1 | 5710.3 | 2757.4 | fory | +| MediaContentList | Serialize | 1495.4 | 2473.6 | 1812.4 | fory | +| MediaContentList | Deserialize | 1946.7 | 3789.3 | 3778.4 | fory | + +### Throughput Results (ops/sec) + +| Datatype | Operation | fory TPS | protobuf TPS | msgpack TPS | Fastest | +| ----------------- | ----------- | ---------- | ------------ | ----------- | ------- | +| NumericStruct | Serialize | 19,881,457 | 5,853,473 | 9,276,378 | fory | +| NumericStruct | Deserialize | 12,137,374 | 3,968,585 | 6,973,504 | fory | +| Sample | Serialize | 3,799,418 | 1,647,119 | 2,652,142 | fory | +| Sample | Deserialize | 5,016,006 | 839,129 | 1,272,975 | fory | +| MediaContent | Serialize | 2,633,704 | 1,962,428 | 2,394,549 | fory | +| MediaContent | Deserialize | 2,220,537 | 1,181,222 | 1,263,568 | fory | +| NumericStructList | Serialize | 5,445,002 | 1,558,156 | 2,232,996 | fory | +| NumericStructList | Deserialize | 3,469,207 | 1,026,402 | 1,424,322 | fory | +| SampleList | Serialize | 829,415 | 280,973 | 536,448 | fory | +| SampleList | Deserialize | 1,117,133 | 175,122 | 362,663 | fory | +| MediaContentList | Serialize | 668,732 | 404,272 | 551,755 | fory | +| MediaContentList | Deserialize | 513,699 | 263,899 | 264,664 | fory | + +### Serialized Data Sizes (bytes) + +| Datatype | fory | protobuf | msgpack | +| ----------------- | ---- | -------- | ------- | +| NumericStruct | 78 | 93 | 87 | +| Sample | 445 | 460 | 562 | +| MediaContent | 362 | 307 | 479 | +| NumericStructList | 255 | 475 | 444 | +| SampleList | 1978 | 2315 | 2819 | +| MediaContentList | 1531 | 1550 | 2404 | diff --git a/versioned_docs/version-1.3.0/benchmarks/csharp/throughput.png b/versioned_docs/version-1.3.0/benchmarks/csharp/throughput.png new file mode 100644 index 00000000000..2c6c7bdd8ce Binary files /dev/null and b/versioned_docs/version-1.3.0/benchmarks/csharp/throughput.png differ diff --git a/versioned_docs/version-1.3.0/benchmarks/dart/README.md b/versioned_docs/version-1.3.0/benchmarks/dart/README.md new file mode 100644 index 00000000000..b42cedf85c6 --- /dev/null +++ b/versioned_docs/version-1.3.0/benchmarks/dart/README.md @@ -0,0 +1,49 @@ +# Fory Dart Benchmark + +This benchmark compares serialization and deserialization throughput for Apache Fory, Protocol Buffers, and JSON in Dart. + +## Throughput Plot + +![Throughput](throughput.png) + +## Hardware and Runtime Info + +| Key | Value | +| --------------------- | ----------------------------------------------------------------- | +| Timestamp | 2026-05-08T08:23:10.201764Z | +| OS | Version 15.7.2 (Build 24G325) | +| Host | MacBook-Pro.local | +| CPU Cores (Logical) | 12 | +| Memory (GB) | 48.00 | +| Dart | 3.10.7 (stable) (Tue Dec 23 00:01:57 2025 -0800) on "macos_arm64" | +| Samples per case | 5 | +| Warmup per case (s) | 1.0 | +| Duration per case (s) | 1.5 | + +## Throughput Results + +| Datatype | Operation | Fory TPS | Protobuf TPS | JSON TPS | Fastest | +| ----------------- | ----------- | --------: | -----------: | --------: | ------------- | +| NumericStruct | Serialize | 9,007,809 | 1,582,003 | 774,574 | fory (5.69x) | +| NumericStruct | Deserialize | 9,039,403 | 3,343,459 | 1,391,036 | fory (2.70x) | +| Sample | Serialize | 2,434,800 | 538,385 | 133,800 | fory (4.52x) | +| Sample | Deserialize | 2,362,665 | 909,410 | 239,924 | fory (2.60x) | +| MediaContent | Serialize | 1,167,225 | 423,564 | 223,387 | fory (2.76x) | +| MediaContent | Deserialize | 1,987,141 | 770,107 | 254,156 | fory (2.58x) | +| NumericStructList | Serialize | 2,551,102 | 283,827 | 139,615 | fory (8.99x) | +| NumericStructList | Deserialize | 3,028,068 | 530,360 | 265,058 | fory (5.71x) | +| SampleList | Serialize | 568,937 | 47,426 | 25,386 | fory (12.00x) | +| SampleList | Deserialize | 542,871 | 108,349 | 48,058 | fory (5.01x) | +| MediaContentList | Serialize | 226,507 | 81,828 | 41,780 | fory (2.77x) | +| MediaContentList | Deserialize | 458,667 | 139,395 | 50,183 | fory (3.29x) | + +## Serialized Size (bytes) + +| Datatype | Fory | Protobuf | JSON | +| ----------------- | ---: | -------: | ---: | +| NumericStruct | 78 | 93 | 159 | +| Sample | 445 | 377 | 791 | +| MediaContent | 362 | 307 | 619 | +| NumericStructList | 255 | 475 | 816 | +| SampleList | 1978 | 1900 | 3976 | +| MediaContentList | 1531 | 1550 | 3122 | diff --git a/versioned_docs/version-1.3.0/benchmarks/dart/throughput.png b/versioned_docs/version-1.3.0/benchmarks/dart/throughput.png new file mode 100644 index 00000000000..ee3833d8bc9 Binary files /dev/null and b/versioned_docs/version-1.3.0/benchmarks/dart/throughput.png differ diff --git a/versioned_docs/version-1.3.0/benchmarks/go/README.md b/versioned_docs/version-1.3.0/benchmarks/go/README.md new file mode 100644 index 00000000000..582f1220479 --- /dev/null +++ b/versioned_docs/version-1.3.0/benchmarks/go/README.md @@ -0,0 +1,58 @@ +# Go Serialization Benchmark Report + +Generated: 2026-05-08 17:55:12 + +## Performance Chart + +![Throughput](throughput.png) + +## System Information + +- **OS**: Darwin 24.6.0 +- **Architecture**: arm64 +- **Python**: 3.10.8 + +## Performance Summary + +| Data Type | Operation | Fory (ops/s) | Protobuf (ops/s) | Msgpack (ops/s) | Fory vs PB | Fory vs MP | +| ----------------- | ----------- | ------------ | ---------------- | --------------- | ---------- | ---------- | +| NumericStruct | Serialize | 12.74M | 7.16M | 3.63M | 1.78x | 3.51x | +| NumericStruct | Deserialize | 10.63M | 8.40M | 1.78M | 1.27x | 5.98x | +| Sample | Serialize | 7.16M | 2.53M | 646K | 2.84x | 11.10x | +| Sample | Deserialize | 3.27M | 2.10M | 343K | 1.56x | 9.54x | +| MediaContent | Serialize | 3.74M | 1.75M | 1.14M | 2.14x | 3.27x | +| MediaContent | Deserialize | 2.03M | 1.23M | 646K | 1.66x | 3.15x | +| NumericStructList | Serialize | 1.10M | 386K | 201K | 2.84x | 5.44x | +| NumericStructList | Deserialize | 1.09M | 368K | 103K | 2.96x | 10.54x | +| SampleList | Serialize | 496K | 126K | 36K | 3.93x | 13.83x | +| SampleList | Deserialize | 195K | 96K | 17K | 2.04x | 11.73x | +| MediaContentList | Serialize | 250K | 91K | 57K | 2.73x | 4.38x | +| MediaContentList | Deserialize | 112K | 74K | 31K | 1.53x | 3.65x | + +## Detailed Timing (ns/op) + +| Data Type | Operation | Fory | Protobuf | Msgpack | +| ----------------- | ----------- | ------ | -------- | ------- | +| NumericStruct | Serialize | 78.5 | 139.6 | 275.5 | +| NumericStruct | Deserialize | 94.0 | 119.0 | 562.5 | +| Sample | Serialize | 139.6 | 395.9 | 1549.0 | +| Sample | Deserialize | 306.0 | 475.9 | 2919.0 | +| MediaContent | Serialize | 267.3 | 571.6 | 875.1 | +| MediaContent | Deserialize | 492.4 | 815.8 | 1549.0 | +| NumericStructList | Serialize | 912.8 | 2594.0 | 4970.0 | +| NumericStructList | Deserialize | 919.9 | 2721.0 | 9698.0 | +| SampleList | Serialize | 2018.0 | 7927.0 | 27909.0 | +| SampleList | Deserialize | 5126.0 | 10460.0 | 60118.0 | +| MediaContentList | Serialize | 4006.0 | 10939.0 | 17553.0 | +| MediaContentList | Deserialize | 8893.0 | 13588.0 | 32439.0 | + +### Serialized Data Sizes (bytes) + +| Data Type | Fory | Protobuf | Msgpack | +| ----------------- | ---- | -------- | ------- | +| NumericStruct | 78 | 93 | 88 | +| Sample | 445 | 375 | 524 | +| MediaContent | 340 | 301 | 400 | +| NumericStructList | 819 | 1900 | 1766 | +| SampleList | 7599 | 7560 | 10486 | +| MediaContentList | 5774 | 6080 | 8006 | diff --git a/versioned_docs/version-1.3.0/benchmarks/go/benchmark_results.txt b/versioned_docs/version-1.3.0/benchmarks/go/benchmark_results.txt new file mode 100644 index 00000000000..9f471bc01fb --- /dev/null +++ b/versioned_docs/version-1.3.0/benchmarks/go/benchmark_results.txt @@ -0,0 +1,326 @@ +============================================ +Serialized Sizes (bytes): +============================================ +NumericStruct: + Fory: 78 bytes + Protobuf: 93 bytes + Msgpack: 88 bytes +Sample: + Fory: 445 bytes + Protobuf: 375 bytes + Msgpack: 524 bytes +MediaContent: + Fory: 340 bytes + Protobuf: 301 bytes + Msgpack: 400 bytes +NumericStructList: + Fory: 819 bytes + Protobuf: 1900 bytes + Msgpack: 1766 bytes +SampleList: + Fory: 7599 bytes + Protobuf: 7560 bytes + Msgpack: 10486 bytes +MediaContentList: + Fory: 5774 bytes + Protobuf: 6080 bytes + Msgpack: 8006 bytes +============================================ +============================================ +Serialized Sizes (bytes): +============================================ +NumericStruct: + Fory: 78 bytes + Protobuf: 93 bytes + Msgpack: 88 bytes +Sample: + Fory: 445 bytes + Protobuf: 375 bytes + Msgpack: 524 bytes +MediaContent: + Fory: 340 bytes + Protobuf: 301 bytes + Msgpack: 400 bytes +NumericStructList: + Fory: 819 bytes + Protobuf: 1900 bytes + Msgpack: 1766 bytes +SampleList: + Fory: 7599 bytes + Protobuf: 7560 bytes + Msgpack: 10486 bytes +MediaContentList: + Fory: 5774 bytes + Protobuf: 6080 bytes + Msgpack: 8006 bytes +============================================ +============================================ +Serialized Sizes (bytes): +============================================ +NumericStruct: + Fory: 78 bytes + Protobuf: 93 bytes + Msgpack: 88 bytes +Sample: + Fory: 445 bytes + Protobuf: 375 bytes + Msgpack: 524 bytes +MediaContent: + Fory: 340 bytes + Protobuf: 301 bytes + Msgpack: 400 bytes +NumericStructList: + Fory: 819 bytes + Protobuf: 1900 bytes + Msgpack: 1766 bytes +SampleList: + Fory: 7599 bytes + Protobuf: 7560 bytes + Msgpack: 10486 bytes +MediaContentList: + Fory: 5774 bytes + Protobuf: 6080 bytes + Msgpack: 8006 bytes +============================================ +============================================ +Serialized Sizes (bytes): +============================================ +NumericStruct: + Fory: 78 bytes + Protobuf: 93 bytes + Msgpack: 88 bytes +Sample: + Fory: 445 bytes + Protobuf: 375 bytes + Msgpack: 524 bytes +MediaContent: + Fory: 340 bytes + Protobuf: 301 bytes + Msgpack: 400 bytes +NumericStructList: + Fory: 819 bytes + Protobuf: 1900 bytes + Msgpack: 1766 bytes +SampleList: + Fory: 7599 bytes + Protobuf: 7560 bytes + Msgpack: 10486 bytes +MediaContentList: + Fory: 5774 bytes + Protobuf: 6080 bytes + Msgpack: 8006 bytes +============================================ +============================================ +Serialized Sizes (bytes): +============================================ +NumericStruct: + Fory: 78 bytes + Protobuf: 93 bytes + Msgpack: 88 bytes +Sample: + Fory: 445 bytes + Protobuf: 375 bytes + Msgpack: 524 bytes +MediaContent: + Fory: 340 bytes + Protobuf: 301 bytes + Msgpack: 400 bytes +NumericStructList: + Fory: 819 bytes + Protobuf: 1900 bytes + Msgpack: 1766 bytes +SampleList: + Fory: 7599 bytes + Protobuf: 7560 bytes + Msgpack: 10486 bytes +MediaContentList: + Fory: 5774 bytes + Protobuf: 6080 bytes + Msgpack: 8006 bytes +============================================ +goos: darwin +goarch: arm64 +pkg: github.com/apache/fory/benchmarks/go +cpu: Apple M4 Pro +BenchmarkFory_NumericStruct_Serialize-12 15385461 78.49 ns/op 0 B/op 0 allocs/op +BenchmarkFory_NumericStruct_Serialize-12 14953550 78.78 ns/op 0 B/op 0 allocs/op +BenchmarkFory_NumericStruct_Serialize-12 15520209 76.33 ns/op 0 B/op 0 allocs/op +BenchmarkFory_NumericStruct_Serialize-12 15806354 76.43 ns/op 0 B/op 0 allocs/op +BenchmarkFory_NumericStruct_Serialize-12 15314667 78.51 ns/op 0 B/op 0 allocs/op +BenchmarkProtobuf_NumericStruct_Serialize-12 8704411 138.0 ns/op 192 B/op 2 allocs/op +BenchmarkProtobuf_NumericStruct_Serialize-12 8522941 138.8 ns/op 192 B/op 2 allocs/op +BenchmarkProtobuf_NumericStruct_Serialize-12 8285118 138.9 ns/op 192 B/op 2 allocs/op +BenchmarkProtobuf_NumericStruct_Serialize-12 8818959 139.6 ns/op 192 B/op 2 allocs/op +BenchmarkProtobuf_NumericStruct_Serialize-12 8463876 139.6 ns/op 192 B/op 2 allocs/op +BenchmarkMsgpack_NumericStruct_Serialize-12 4396588 274.9 ns/op 240 B/op 3 allocs/op +BenchmarkMsgpack_NumericStruct_Serialize-12 4438035 272.7 ns/op 240 B/op 3 allocs/op +BenchmarkMsgpack_NumericStruct_Serialize-12 4352302 272.0 ns/op 240 B/op 3 allocs/op +BenchmarkMsgpack_NumericStruct_Serialize-12 4346308 277.3 ns/op 240 B/op 3 allocs/op +BenchmarkMsgpack_NumericStruct_Serialize-12 4361384 275.5 ns/op 240 B/op 3 allocs/op +BenchmarkFory_NumericStruct_Deserialize-12 12784374 95.10 ns/op 48 B/op 1 allocs/op +BenchmarkFory_NumericStruct_Deserialize-12 12615007 94.35 ns/op 48 B/op 1 allocs/op +BenchmarkFory_NumericStruct_Deserialize-12 12597256 94.07 ns/op 48 B/op 1 allocs/op +BenchmarkFory_NumericStruct_Deserialize-12 12663748 93.35 ns/op 48 B/op 1 allocs/op +BenchmarkFory_NumericStruct_Deserialize-12 12668444 94.05 ns/op 48 B/op 1 allocs/op +BenchmarkProtobuf_NumericStruct_Deserialize-12 9892239 157.3 ns/op 96 B/op 1 allocs/op +BenchmarkProtobuf_NumericStruct_Deserialize-12 6950502 152.1 ns/op 96 B/op 1 allocs/op +BenchmarkProtobuf_NumericStruct_Deserialize-12 7466115 210.2 ns/op 96 B/op 1 allocs/op +BenchmarkProtobuf_NumericStruct_Deserialize-12 6987369 156.1 ns/op 96 B/op 1 allocs/op +BenchmarkProtobuf_NumericStruct_Deserialize-12 10052640 119.0 ns/op 96 B/op 1 allocs/op +BenchmarkMsgpack_NumericStruct_Deserialize-12 2397518 502.5 ns/op 96 B/op 2 allocs/op +BenchmarkMsgpack_NumericStruct_Deserialize-12 2379376 501.1 ns/op 96 B/op 2 allocs/op +BenchmarkMsgpack_NumericStruct_Deserialize-12 2390596 500.8 ns/op 96 B/op 2 allocs/op +BenchmarkMsgpack_NumericStruct_Deserialize-12 2387790 504.9 ns/op 96 B/op 2 allocs/op +BenchmarkMsgpack_NumericStruct_Deserialize-12 2404051 562.5 ns/op 96 B/op 2 allocs/op +BenchmarkFory_NumericStructList_Serialize-12 1000000 1081 ns/op 0 B/op 0 allocs/op +BenchmarkFory_NumericStructList_Serialize-12 1000000 1021 ns/op 0 B/op 0 allocs/op +BenchmarkFory_NumericStructList_Serialize-12 1293205 947.2 ns/op 0 B/op 0 allocs/op +BenchmarkFory_NumericStructList_Serialize-12 1278226 929.6 ns/op 0 B/op 0 allocs/op +BenchmarkFory_NumericStructList_Serialize-12 1316396 912.8 ns/op 0 B/op 0 allocs/op +BenchmarkProtobuf_NumericStructList_Serialize-12 467838 2594 ns/op 4192 B/op 23 allocs/op +BenchmarkProtobuf_NumericStructList_Serialize-12 462572 2743 ns/op 4192 B/op 23 allocs/op +BenchmarkProtobuf_NumericStructList_Serialize-12 453700 2589 ns/op 4192 B/op 23 allocs/op +BenchmarkProtobuf_NumericStructList_Serialize-12 460408 2607 ns/op 4192 B/op 23 allocs/op +BenchmarkProtobuf_NumericStructList_Serialize-12 452139 2594 ns/op 4192 B/op 23 allocs/op +BenchmarkMsgpack_NumericStructList_Serialize-12 247603 4879 ns/op 4106 B/op 8 allocs/op +BenchmarkMsgpack_NumericStructList_Serialize-12 249087 4844 ns/op 4106 B/op 8 allocs/op +BenchmarkMsgpack_NumericStructList_Serialize-12 248340 5142 ns/op 4106 B/op 8 allocs/op +BenchmarkMsgpack_NumericStructList_Serialize-12 206158 5846 ns/op 4106 B/op 8 allocs/op +BenchmarkMsgpack_NumericStructList_Serialize-12 238311 4970 ns/op 4106 B/op 8 allocs/op +BenchmarkFory_NumericStructList_Deserialize-12 1311939 932.1 ns/op 1072 B/op 3 allocs/op +BenchmarkFory_NumericStructList_Deserialize-12 1313912 937.1 ns/op 1072 B/op 3 allocs/op +BenchmarkFory_NumericStructList_Deserialize-12 1315593 915.4 ns/op 1072 B/op 3 allocs/op +BenchmarkFory_NumericStructList_Deserialize-12 1311540 927.6 ns/op 1072 B/op 3 allocs/op +BenchmarkFory_NumericStructList_Deserialize-12 1292565 919.9 ns/op 1072 B/op 3 allocs/op +BenchmarkProtobuf_NumericStructList_Deserialize-12 447052 2745 ns/op 3512 B/op 28 allocs/op +BenchmarkProtobuf_NumericStructList_Deserialize-12 429406 2743 ns/op 3512 B/op 28 allocs/op +BenchmarkProtobuf_NumericStructList_Deserialize-12 438819 2706 ns/op 3512 B/op 28 allocs/op +BenchmarkProtobuf_NumericStructList_Deserialize-12 447928 2785 ns/op 3512 B/op 28 allocs/op +BenchmarkProtobuf_NumericStructList_Deserialize-12 447502 2721 ns/op 3512 B/op 28 allocs/op +BenchmarkMsgpack_NumericStructList_Deserialize-12 122101 9764 ns/op 2193 B/op 7 allocs/op +BenchmarkMsgpack_NumericStructList_Deserialize-12 123258 9896 ns/op 2193 B/op 7 allocs/op +BenchmarkMsgpack_NumericStructList_Deserialize-12 121612 9738 ns/op 2193 B/op 7 allocs/op +BenchmarkMsgpack_NumericStructList_Deserialize-12 124760 9670 ns/op 2193 B/op 7 allocs/op +BenchmarkMsgpack_NumericStructList_Deserialize-12 125086 9698 ns/op 2193 B/op 7 allocs/op +BenchmarkFory_Sample_Serialize-12 8696760 147.5 ns/op 0 B/op 0 allocs/op +BenchmarkFory_Sample_Serialize-12 8567470 181.8 ns/op 0 B/op 0 allocs/op +BenchmarkFory_Sample_Serialize-12 8293867 139.5 ns/op 0 B/op 0 allocs/op +BenchmarkFory_Sample_Serialize-12 8486005 140.6 ns/op 0 B/op 0 allocs/op +BenchmarkFory_Sample_Serialize-12 8506568 139.6 ns/op 0 B/op 0 allocs/op +BenchmarkProtobuf_Sample_Serialize-12 3011733 387.9 ns/op 704 B/op 2 allocs/op +BenchmarkProtobuf_Sample_Serialize-12 3108796 390.8 ns/op 704 B/op 2 allocs/op +BenchmarkProtobuf_Sample_Serialize-12 2547630 393.6 ns/op 704 B/op 2 allocs/op +BenchmarkProtobuf_Sample_Serialize-12 3026661 392.1 ns/op 704 B/op 2 allocs/op +BenchmarkProtobuf_Sample_Serialize-12 3128582 395.9 ns/op 704 B/op 2 allocs/op +BenchmarkMsgpack_Sample_Serialize-12 777014 1553 ns/op 2321 B/op 7 allocs/op +BenchmarkMsgpack_Sample_Serialize-12 775314 1520 ns/op 2321 B/op 7 allocs/op +BenchmarkMsgpack_Sample_Serialize-12 757838 1525 ns/op 2321 B/op 7 allocs/op +BenchmarkMsgpack_Sample_Serialize-12 802612 1757 ns/op 2321 B/op 7 allocs/op +BenchmarkMsgpack_Sample_Serialize-12 790094 1549 ns/op 2321 B/op 7 allocs/op +BenchmarkFory_Sample_Deserialize-12 4167534 327.9 ns/op 676 B/op 9 allocs/op +BenchmarkFory_Sample_Deserialize-12 3184602 338.0 ns/op 676 B/op 9 allocs/op +BenchmarkFory_Sample_Deserialize-12 3629605 301.8 ns/op 676 B/op 9 allocs/op +BenchmarkFory_Sample_Deserialize-12 4008093 302.2 ns/op 676 B/op 9 allocs/op +BenchmarkFory_Sample_Deserialize-12 3992972 306.0 ns/op 676 B/op 9 allocs/op +BenchmarkProtobuf_Sample_Deserialize-12 2527548 477.1 ns/op 708 B/op 9 allocs/op +BenchmarkProtobuf_Sample_Deserialize-12 2476074 474.8 ns/op 708 B/op 9 allocs/op +BenchmarkProtobuf_Sample_Deserialize-12 2532358 475.8 ns/op 708 B/op 9 allocs/op +BenchmarkProtobuf_Sample_Deserialize-12 2408528 473.0 ns/op 708 B/op 9 allocs/op +BenchmarkProtobuf_Sample_Deserialize-12 2482132 475.9 ns/op 708 B/op 9 allocs/op +BenchmarkMsgpack_Sample_Deserialize-12 415698 2893 ns/op 1576 B/op 38 allocs/op +BenchmarkMsgpack_Sample_Deserialize-12 417212 2950 ns/op 1576 B/op 38 allocs/op +BenchmarkMsgpack_Sample_Deserialize-12 424497 2879 ns/op 1576 B/op 38 allocs/op +BenchmarkMsgpack_Sample_Deserialize-12 417026 2953 ns/op 1576 B/op 38 allocs/op +BenchmarkMsgpack_Sample_Deserialize-12 418338 2919 ns/op 1576 B/op 38 allocs/op +BenchmarkFory_SampleList_Serialize-12 599605 2041 ns/op 0 B/op 0 allocs/op +BenchmarkFory_SampleList_Serialize-12 582406 2046 ns/op 0 B/op 0 allocs/op +BenchmarkFory_SampleList_Serialize-12 563160 2029 ns/op 0 B/op 0 allocs/op +BenchmarkFory_SampleList_Serialize-12 607074 2067 ns/op 0 B/op 0 allocs/op +BenchmarkFory_SampleList_Serialize-12 613364 2018 ns/op 0 B/op 0 allocs/op +BenchmarkProtobuf_SampleList_Serialize-12 145861 8291 ns/op 14816 B/op 23 allocs/op +BenchmarkProtobuf_SampleList_Serialize-12 150979 7938 ns/op 14816 B/op 23 allocs/op +BenchmarkProtobuf_SampleList_Serialize-12 144135 8049 ns/op 14816 B/op 23 allocs/op +BenchmarkProtobuf_SampleList_Serialize-12 149202 8469 ns/op 14816 B/op 23 allocs/op +BenchmarkProtobuf_SampleList_Serialize-12 152338 7927 ns/op 14816 B/op 23 allocs/op +BenchmarkMsgpack_SampleList_Serialize-12 43825 27705 ns/op 32793 B/op 11 allocs/op +BenchmarkMsgpack_SampleList_Serialize-12 43051 29707 ns/op 32793 B/op 11 allocs/op +BenchmarkMsgpack_SampleList_Serialize-12 42046 27699 ns/op 32793 B/op 11 allocs/op +BenchmarkMsgpack_SampleList_Serialize-12 42596 37501 ns/op 32793 B/op 11 allocs/op +BenchmarkMsgpack_SampleList_Serialize-12 42518 27909 ns/op 32793 B/op 11 allocs/op +BenchmarkFory_SampleList_Deserialize-12 249300 4817 ns/op 13952 B/op 163 allocs/op +BenchmarkFory_SampleList_Deserialize-12 245127 5156 ns/op 13952 B/op 163 allocs/op +BenchmarkFory_SampleList_Deserialize-12 247790 4943 ns/op 13952 B/op 163 allocs/op +BenchmarkFory_SampleList_Deserialize-12 249534 4934 ns/op 13952 B/op 163 allocs/op +BenchmarkFory_SampleList_Deserialize-12 239244 5126 ns/op 13952 B/op 163 allocs/op +BenchmarkProtobuf_SampleList_Deserialize-12 111327 10797 ns/op 20872 B/op 188 allocs/op +BenchmarkProtobuf_SampleList_Deserialize-12 109178 10919 ns/op 20872 B/op 188 allocs/op +BenchmarkProtobuf_SampleList_Deserialize-12 92876 12674 ns/op 20872 B/op 188 allocs/op +BenchmarkProtobuf_SampleList_Deserialize-12 108414 10816 ns/op 20872 B/op 188 allocs/op +BenchmarkProtobuf_SampleList_Deserialize-12 114196 10460 ns/op 20872 B/op 188 allocs/op +BenchmarkMsgpack_SampleList_Deserialize-12 20566 58400 ns/op 37251 B/op 727 allocs/op +BenchmarkMsgpack_SampleList_Deserialize-12 20526 65792 ns/op 37251 B/op 727 allocs/op +BenchmarkMsgpack_SampleList_Deserialize-12 20550 58213 ns/op 37251 B/op 727 allocs/op +BenchmarkMsgpack_SampleList_Deserialize-12 19180 58170 ns/op 37251 B/op 727 allocs/op +BenchmarkMsgpack_SampleList_Deserialize-12 20426 60118 ns/op 37251 B/op 727 allocs/op +BenchmarkFory_MediaContent_Serialize-12 4481811 268.2 ns/op 0 B/op 0 allocs/op +BenchmarkFory_MediaContent_Serialize-12 4470292 269.4 ns/op 0 B/op 0 allocs/op +BenchmarkFory_MediaContent_Serialize-12 4524715 269.0 ns/op 0 B/op 0 allocs/op +BenchmarkFory_MediaContent_Serialize-12 4516789 270.0 ns/op 0 B/op 0 allocs/op +BenchmarkFory_MediaContent_Serialize-12 4391221 267.3 ns/op 0 B/op 0 allocs/op +BenchmarkProtobuf_MediaContent_Serialize-12 2135143 544.6 ns/op 1144 B/op 11 allocs/op +BenchmarkProtobuf_MediaContent_Serialize-12 2160157 561.0 ns/op 1144 B/op 11 allocs/op +BenchmarkProtobuf_MediaContent_Serialize-12 2221641 545.3 ns/op 1144 B/op 11 allocs/op +BenchmarkProtobuf_MediaContent_Serialize-12 2214582 565.7 ns/op 1144 B/op 11 allocs/op +BenchmarkProtobuf_MediaContent_Serialize-12 2115079 571.6 ns/op 1144 B/op 11 allocs/op +BenchmarkMsgpack_MediaContent_Serialize-12 1380163 869.0 ns/op 1168 B/op 6 allocs/op +BenchmarkMsgpack_MediaContent_Serialize-12 1380481 874.4 ns/op 1168 B/op 6 allocs/op +BenchmarkMsgpack_MediaContent_Serialize-12 1369536 873.7 ns/op 1168 B/op 6 allocs/op +BenchmarkMsgpack_MediaContent_Serialize-12 1376877 881.8 ns/op 1168 B/op 6 allocs/op +BenchmarkMsgpack_MediaContent_Serialize-12 1381296 875.1 ns/op 1168 B/op 6 allocs/op +BenchmarkFory_MediaContent_Deserialize-12 2537758 474.9 ns/op 656 B/op 13 allocs/op +BenchmarkFory_MediaContent_Deserialize-12 2491048 478.2 ns/op 656 B/op 13 allocs/op +BenchmarkFory_MediaContent_Deserialize-12 2500486 520.6 ns/op 656 B/op 13 allocs/op +BenchmarkFory_MediaContent_Deserialize-12 2419058 495.7 ns/op 656 B/op 13 allocs/op +BenchmarkFory_MediaContent_Deserialize-12 2404918 492.4 ns/op 656 B/op 13 allocs/op +BenchmarkProtobuf_MediaContent_Deserialize-12 1852849 647.5 ns/op 1088 B/op 21 allocs/op +BenchmarkProtobuf_MediaContent_Deserialize-12 1865282 638.1 ns/op 1088 B/op 21 allocs/op +BenchmarkProtobuf_MediaContent_Deserialize-12 1873513 649.3 ns/op 1088 B/op 21 allocs/op +BenchmarkProtobuf_MediaContent_Deserialize-12 1853527 635.6 ns/op 1088 B/op 21 allocs/op +BenchmarkProtobuf_MediaContent_Deserialize-12 1729706 815.8 ns/op 1088 B/op 21 allocs/op +BenchmarkMsgpack_MediaContent_Deserialize-12 801156 1553 ns/op 896 B/op 17 allocs/op +BenchmarkMsgpack_MediaContent_Deserialize-12 787426 1520 ns/op 896 B/op 17 allocs/op +BenchmarkMsgpack_MediaContent_Deserialize-12 750510 1538 ns/op 896 B/op 17 allocs/op +BenchmarkMsgpack_MediaContent_Deserialize-12 803448 1543 ns/op 896 B/op 17 allocs/op +BenchmarkMsgpack_MediaContent_Deserialize-12 780190 1549 ns/op 896 B/op 17 allocs/op +BenchmarkFory_MediaContentList_Serialize-12 304236 4158 ns/op 0 B/op 0 allocs/op +BenchmarkFory_MediaContentList_Serialize-12 300002 4008 ns/op 0 B/op 0 allocs/op +BenchmarkFory_MediaContentList_Serialize-12 301590 4026 ns/op 0 B/op 0 allocs/op +BenchmarkFory_MediaContentList_Serialize-12 304356 3955 ns/op 0 B/op 0 allocs/op +BenchmarkFory_MediaContentList_Serialize-12 305937 4006 ns/op 0 B/op 0 allocs/op +BenchmarkProtobuf_MediaContentList_Serialize-12 113460 10919 ns/op 22848 B/op 203 allocs/op +BenchmarkProtobuf_MediaContentList_Serialize-12 105204 12060 ns/op 22848 B/op 203 allocs/op +BenchmarkProtobuf_MediaContentList_Serialize-12 112503 10890 ns/op 22848 B/op 203 allocs/op +BenchmarkProtobuf_MediaContentList_Serialize-12 110593 10468 ns/op 22848 B/op 203 allocs/op +BenchmarkProtobuf_MediaContentList_Serialize-12 112423 10939 ns/op 22848 B/op 203 allocs/op +BenchmarkMsgpack_MediaContentList_Serialize-12 77541 15641 ns/op 16881 B/op 30 allocs/op +BenchmarkMsgpack_MediaContentList_Serialize-12 77178 16293 ns/op 16880 B/op 30 allocs/op +BenchmarkMsgpack_MediaContentList_Serialize-12 73228 16168 ns/op 16880 B/op 30 allocs/op +BenchmarkMsgpack_MediaContentList_Serialize-12 71780 16275 ns/op 16880 B/op 30 allocs/op +BenchmarkMsgpack_MediaContentList_Serialize-12 77145 17553 ns/op 16881 B/op 30 allocs/op +BenchmarkFory_MediaContentList_Deserialize-12 146653 8254 ns/op 13040 B/op 243 allocs/op +BenchmarkFory_MediaContentList_Deserialize-12 145420 8133 ns/op 13040 B/op 243 allocs/op +BenchmarkFory_MediaContentList_Deserialize-12 148918 8145 ns/op 13040 B/op 243 allocs/op +BenchmarkFory_MediaContentList_Deserialize-12 141998 8498 ns/op 13040 B/op 243 allocs/op +BenchmarkFory_MediaContentList_Deserialize-12 138174 8893 ns/op 13040 B/op 243 allocs/op +BenchmarkProtobuf_MediaContentList_Deserialize-12 90056 13521 ns/op 25400 B/op 428 allocs/op +BenchmarkProtobuf_MediaContentList_Deserialize-12 82902 13638 ns/op 25400 B/op 428 allocs/op +BenchmarkProtobuf_MediaContentList_Deserialize-12 91242 14083 ns/op 25400 B/op 428 allocs/op +BenchmarkProtobuf_MediaContentList_Deserialize-12 85706 13089 ns/op 25400 B/op 428 allocs/op +BenchmarkProtobuf_MediaContentList_Deserialize-12 91342 13588 ns/op 25400 B/op 428 allocs/op +BenchmarkMsgpack_MediaContentList_Deserialize-12 33295 31198 ns/op 20058 B/op 307 allocs/op +BenchmarkMsgpack_MediaContentList_Deserialize-12 40077 31423 ns/op 20058 B/op 307 allocs/op +BenchmarkMsgpack_MediaContentList_Deserialize-12 39219 30604 ns/op 20058 B/op 307 allocs/op +BenchmarkMsgpack_MediaContentList_Deserialize-12 39799 30377 ns/op 20058 B/op 307 allocs/op +BenchmarkMsgpack_MediaContentList_Deserialize-12 39267 32439 ns/op 20058 B/op 307 allocs/op +PASS +ok github.com/apache/fory/benchmarks/go 268.327s diff --git a/versioned_docs/version-1.3.0/benchmarks/go/serialized_sizes.txt b/versioned_docs/version-1.3.0/benchmarks/go/serialized_sizes.txt new file mode 100644 index 00000000000..a64d3e1c05c --- /dev/null +++ b/versioned_docs/version-1.3.0/benchmarks/go/serialized_sizes.txt @@ -0,0 +1,27 @@ +Serialized Sizes (bytes): +============================================ +NumericStruct: + Fory: 78 bytes + Protobuf: 93 bytes + Msgpack: 88 bytes +Sample: + Fory: 445 bytes + Protobuf: 375 bytes + Msgpack: 524 bytes +MediaContent: + Fory: 340 bytes + Protobuf: 301 bytes + Msgpack: 400 bytes +NumericStructList: + Fory: 819 bytes + Protobuf: 1900 bytes + Msgpack: 1766 bytes +SampleList: + Fory: 7599 bytes + Protobuf: 7560 bytes + Msgpack: 10486 bytes +MediaContentList: + Fory: 5774 bytes + Protobuf: 6080 bytes + Msgpack: 8006 bytes +============================================ diff --git a/versioned_docs/version-1.3.0/benchmarks/go/throughput.png b/versioned_docs/version-1.3.0/benchmarks/go/throughput.png new file mode 100644 index 00000000000..086d8b5533a Binary files /dev/null and b/versioned_docs/version-1.3.0/benchmarks/go/throughput.png differ diff --git a/versioned_docs/version-1.3.0/benchmarks/java/README.md b/versioned_docs/version-1.3.0/benchmarks/java/README.md new file mode 100644 index 00000000000..0f7dc210c36 --- /dev/null +++ b/versioned_docs/version-1.3.0/benchmarks/java/README.md @@ -0,0 +1,267 @@ +# Java Benchmarks + +## System Environment + +- Operation System:4.9.151-015.x86_64 +- CPU:Intel(R) Xeon(R) Platinum 8163 CPU @ 2.50GHz +- Byte Order:Little Endian +- L1d cache: 32K +- L1i cache:32K +- L2 cache: 1024K +- L3 cache: 33792K + +## JMH params + +Don't skip **warm up**, otherwise the results aren't accurate. + +```bash + -f 1 -wi 3 -i 3 -t 1 -w 2s -r 2s -rf cs +``` + +## Benchmark Data + +### Struct + +Struct is a class with 100 primitive fields: + +```java +public class Struct { + public int f1; + public long f2; + public float f3; + public double f4; + // ... + public double f99; +} +``` + +### Struct2 + +Struct2 is a class with 100 boxed fields: + +```java +public class Struct { + public Integer f1; + public Long f2; + public Float f3; + public Double f4; + // ... + public Double f99; +} +``` + +### MediaContent + +MEDIA_CONTENT is a class from [jvm-serializers](https://github.com/eishay/jvm-serializers/blob/master/tpc/src/data/media/MediaContent.java). + +### Sample + +SAMPLE is a class from [kryo benchmark](https://github.com/EsotericSoftware/kryo/blob/master/benchmarks/src/main/java/com/esotericsoftware/kryo/benchmarks/data/Sample.java) + +## Java Benchmark + +### Serialize to heap buffer + +Serialize data java byte array. + +#### Java same-schema serialization + +The reader and writer use the same class schema. Class forward/backward compatibility is not used in +this mode. + +![Java Heap Same-Schema Serialization](java_heap_serialize_consistent.png) + +#### Java compatible serialization + +The reader and writer can use different class schemas. Class forward/backward compatibility is +supported in this mode. + +![Java Heap Schema Compatible Serialization](java_heap_serialize_compatible.png) + +#### Java same-schema deserialization + +The reader and writer use the same class schema. Class forward/backward compatibility is not used in +this mode. + +![Java Heap Same-Schema Deserialization](java_heap_deserialize_consistent.png) + +#### Java compatible deserialization + +The reader and writer can use different class schemas. Class forward/backward compatibility is +supported in this mode. + +![Java Heap Schema Compatible Deserialization](java_heap_deserialize_compatible.png) + +### Off-heap serialization + +Serialize data off-heap memory. + +#### Java same-schema serialization + +The reader and writer use the same class schema. Class forward/backward compatibility is not used in +this mode. + +![Java Off Heap Same-Schema Serialization](java_offheap_serialize_consistent.png) + +#### Java compatible serialization + +The reader and writer can use different class schemas. Class forward/backward compatibility is +supported in this mode. + +![Java Off Heap Schema Compatible Serialization](java_offheap_serialize_compatible.png) + +#### Java same-schema deserialization + +The reader and writer use the same class schema. Class forward/backward compatibility is not used in +this mode. + +![Java Off Heap Same-Schema Deserialization](java_offheap_deserialize_consistent.png) + +#### Java compatible deserialization + +The reader and writer can use different class schemas. Class forward/backward compatibility is +supported in this mode. + +![Java Off Heap Schema Compatible Deserialization](java_offheap_deserialize_compatible.png) + +### Zero-copy serialization + +Note that zero-copy serialization just avoid the copy in serialization, if you send data to other machine, there may be copies. + +But if you serialize data between processes on same node and use shared-memory, if the data are in off-heap before serialization, then other processes can read this buffer without any copies. + +#### Java zero-copy serialize to heap buffer + +![Java Zero Copy Serialization](java_zero_copy_serialize.png) + +#### Java zero-copy serialize to direct buffer + +![Java Zero Copy Deserialization](java_zero_copy_deserialize.png) + +### Benchmark Data + +#### Java Serialization + +| Benchmark | objectType | bufferType | references | Fory | ForyMetaShared | Kryo | Fst | Hession | Jdk | Protostuff | +| ---------------------- | ------------- | ------------ | ---------- | -------------- | --------------- | -------------- | ------------- | ------------- | ------------- | ------------- | +| serialize | STRUCT | array | False | 7501415.567260 | | 558194.100861 | 882178.995727 | 258233.998931 | 155908.244240 | 330975.350403 | +| serialize | STRUCT | array | True | 6264439.154428 | | 557542.628765 | 757753.756691 | 260845.209485 | 151258.539369 | | +| serialize | STRUCT | directBuffer | False | 9834223.243204 | | 1078046.011115 | 807847.663261 | 266481.009225 | 154875.908438 | 340262.650047 | +| serialize | STRUCT | directBuffer | True | 7551780.823133 | | 853350.408656 | 762088.935404 | 261762.594966 | 156404.686214 | | +| serialize | STRUCT2 | array | False | 3586126.623874 | | 325172.969175 | 371762.982661 | 56056.080075 | 36846.049162 | 322563.440433 | +| serialize | STRUCT2 | array | True | 3306474.506382 | | 259863.332448 | 380638.700267 | 60038.879790 | 38183.705811 | | +| serialize | STRUCT2 | directBuffer | False | 2643155.135327 | | 355688.882786 | 365317.705376 | 55924.319442 | 37444.967981 | 325093.716261 | +| serialize | STRUCT2 | directBuffer | True | 2391110.083108 | | 338960.426033 | 370851.880711 | 56674.065604 | 35798.679246 | | +| serialize | MEDIA_CONTENT | array | False | 3031642.924542 | | 730792.521676 | 751892.023189 | 367782.358049 | 137989.198821 | 780618.761219 | +| serialize | MEDIA_CONTENT | array | True | 2250384.600246 | | 445251.084327 | 583859.907758 | 329427.470680 | 140260.668888 | | +| serialize | MEDIA_CONTENT | directBuffer | False | 2479862.129632 | | 608972.517580 | 728001.080250 | 372477.138150 | 138567.623369 | 805941.345157 | +| serialize | MEDIA_CONTENT | directBuffer | True | 1938527.588331 | | 359875.473951 | 595679.580108 | 353376.085025 | 140158.673910 | | +| serialize | SAMPLE | array | False | 3570966.469087 | | 1105365.931217 | 915907.574306 | 220386.502846 | 118374.836631 | 663272.710783 | +| serialize | SAMPLE | array | True | 1767693.835090 | | 734215.482291 | 731869.156376 | 192414.014211 | 119858.140625 | | +| serialize | SAMPLE | directBuffer | False | 3684487.760591 | | 1376560.302168 | 902302.261168 | 220981.308085 | 118273.584257 | 693641.589806 | +| serialize | SAMPLE | directBuffer | True | 1826456.709478 | | 932887.968348 | 723614.066770 | 211949.960255 | 108263.040839 | | +| serialize_compatible | STRUCT | array | False | 3530406.108869 | 9204444.777172 | 145964.199559 | | 258650.663523 | | | +| serialize_compatible | STRUCT | array | True | 3293059.098127 | 7064625.291374 | 136180.832879 | | 263564.913879 | | | +| serialize_compatible | STRUCT | directBuffer | False | 2653169.568374 | 11650229.648715 | 106695.800225 | | 249221.452137 | | | +| serialize_compatible | STRUCT | directBuffer | True | 2393817.762938 | 8702412.752357 | 106458.212005 | | 263623.143601 | | | +| serialize_compatible | STRUCT2 | array | False | 2773368.997680 | 2575824.143864 | 125807.748004 | | 58509.125342 | | | +| serialize_compatible | STRUCT2 | array | True | 2564174.550276 | 3543082.528217 | 114983.546343 | | 55552.977735 | | | +| serialize_compatible | STRUCT2 | directBuffer | False | 1912402.937879 | 2714748.572248 | 92130.672361 | | 58908.567439 | | | +| serialize_compatible | STRUCT2 | directBuffer | True | 1848338.968058 | 1866073.031851 | 88989.724768 | | 55524.373547 | | | +| serialize_compatible | MEDIA_CONTENT | array | False | 1679272.036223 | 2992288.235281 | 188911.259146 | | 377195.903772 | | | +| serialize_compatible | MEDIA_CONTENT | array | True | 1406736.538716 | 2058738.716953 | 145782.916427 | | 351657.879556 | | | +| serialize_compatible | MEDIA_CONTENT | directBuffer | False | 1710680.937387 | 2291443.556971 | 185363.714829 | | 371729.727192 | | | +| serialize_compatible | MEDIA_CONTENT | directBuffer | True | 1149999.473994 | 1804349.244125 | 142836.961878 | | 343834.954942 | | | +| serialize_compatible | SAMPLE | array | False | 3604596.465625 | 4409055.687063 | 378907.663184 | | 234454.975158 | | | +| serialize_compatible | SAMPLE | array | True | 1619648.337293 | 1840705.439334 | 320815.567701 | | 206174.173039 | | | +| serialize_compatible | SAMPLE | directBuffer | False | 3484533.218305 | 5043538.364886 | 296102.615094 | | 194761.224263 | | | +| serialize_compatible | SAMPLE | directBuffer | True | 1730822.630648 | 1859289.705838 | 276757.392449 | | 212840.483308 | | | +| deserialize | STRUCT | array | False | 4595230.434552 | | 607750.343557 | 357887.235311 | 84709.108821 | 29603.066599 | 517381.168594 | +| deserialize | STRUCT | array | True | 4634753.596131 | | 552802.227807 | 353480.554035 | 91050.370224 | 29727.744196 | | +| deserialize | STRUCT | directBuffer | False | 5012002.859236 | | 910534.169114 | 352441.597147 | 91151.633583 | 28717.004518 | 538922.947147 | +| deserialize | STRUCT | directBuffer | True | 4864329.316938 | | 914404.107564 | 334574.303484 | 91037.205901 | 29549.998286 | | +| deserialize | STRUCT2 | array | False | 1126298.359550 | | 275984.042401 | 280131.091068 | 69758.767783 | 14888.805111 | 416212.973861 | +| deserialize | STRUCT2 | array | True | 1046649.083082 | | 222710.554833 | 260649.308016 | 68616.029248 | 14034.100664 | | +| deserialize | STRUCT2 | directBuffer | False | 1117586.457565 | | 319247.256793 | 262519.858810 | 66866.108653 | 14652.043788 | 425523.315814 | +| deserialize | STRUCT2 | directBuffer | True | 1018277.848128 | | 249105.828416 | 234973.637096 | 65338.345185 | 14425.886048 | | +| deserialize | MEDIA_CONTENT | array | False | 2054066.903469 | | 577631.234369 | 363455.785182 | 118156.072284 | 38536.250402 | 951662.019963 | +| deserialize | MEDIA_CONTENT | array | True | 1507767.206603 | | 365530.417232 | 304371.728638 | 120016.594171 | 38957.191090 | | +| deserialize | MEDIA_CONTENT | directBuffer | False | 1502746.028159 | | 389473.174523 | 311691.658687 | 111067.942626 | 40512.632076 | 964664.641598 | +| deserialize | MEDIA_CONTENT | directBuffer | True | 1290593.975753 | | 306995.220799 | 251820.171513 | 121820.821260 | 37030.594632 | | +| deserialize | SAMPLE | array | False | 2069988.624415 | | 979173.981159 | 473409.796491 | 119471.518388 | 29309.573998 | 619338.385412 | +| deserialize | SAMPLE | array | True | 1797942.442313 | | 716438.884369 | 428315.502365 | 121106.002978 | 27466.003923 | | +| deserialize | SAMPLE | directBuffer | False | 2229791.078395 | | 983538.936801 | 441027.550809 | 117806.916589 | 28128.457935 | 624804.978534 | +| deserialize | SAMPLE | directBuffer | True | 1958815.397807 | | 762889.302732 | 420523.770904 | 121940.783597 | 28221.014735 | | +| deserialize_compatible | STRUCT | array | False | 2110335.039275 | 4978833.206806 | 78771.635309 | | 88617.486795 | | | +| deserialize_compatible | STRUCT | array | True | 2135681.982674 | 4807963.882520 | 72805.937649 | | 90206.654212 | | | +| deserialize_compatible | STRUCT | directBuffer | False | 1596464.248141 | 5149070.657830 | 58574.904225 | | 89580.561575 | | | +| deserialize_compatible | STRUCT | directBuffer | True | 1684681.074242 | 5137500.621288 | 60685.320299 | | 84407.472531 | | | +| deserialize_compatible | STRUCT2 | array | False | 849507.176263 | 1201998.142474 | 60602.285743 | | 63703.763814 | | | +| deserialize_compatible | STRUCT2 | array | True | 815120.319155 | 1058423.614156 | 62729.908347 | | 69521.573119 | | | +| deserialize_compatible | STRUCT2 | directBuffer | False | 784036.589363 | 1131212.586953 | 54637.329134 | | 69342.030965 | | | +| deserialize_compatible | STRUCT2 | directBuffer | True | 782679.662083 | 1089162.408165 | 51761.569591 | | 68542.055543 | | | +| deserialize_compatible | MEDIA_CONTENT | array | False | 1441671.706320 | 2279742.810882 | 180882.860363 | | 121619.090797 | | | +| deserialize_compatible | MEDIA_CONTENT | array | True | 1121136.039627 | 1623938.202345 | 154311.211540 | | 119994.104050 | | | +| deserialize_compatible | MEDIA_CONTENT | directBuffer | False | 1256034.732514 | 1718098.363961 | 134485.160300 | | 107594.474890 | | | +| deserialize_compatible | MEDIA_CONTENT | directBuffer | True | 1054942.751816 | 1333345.536684 | 119311.787329 | | 116531.023438 | | | +| deserialize_compatible | SAMPLE | array | False | 2296046.895861 | 2485564.396196 | 255086.928308 | | 121898.105768 | | | +| deserialize_compatible | SAMPLE | array | True | 1834139.395757 | 2002938.794909 | 238811.995510 | | 121297.485903 | | | +| deserialize_compatible | SAMPLE | directBuffer | False | 2308111.633661 | 2289261.533644 | 201993.787890 | | 124044.417439 | | | +| deserialize_compatible | SAMPLE | directBuffer | True | 1820490.585648 | 1927548.827586 | 174534.710870 | | 120276.449497 | | | + +#### Java Zero-copy + +| Benchmark | array_size | bufferType | dataType | Fory | Kryo | Fst | +| ----------- | ---------- | ------------ | --------------- | -------------- | -------------- | -------------- | +| serialize | 200 | array | BUFFER | 5123572.914045 | 1985187.977633 | 2400193.220466 | +| serialize | 200 | array | PRIMITIVE_ARRAY | 8297232.942927 | 147342.606262 | 313986.053417 | +| serialize | 200 | directBuffer | BUFFER | 5400346.890126 | 1739454.519770 | 2282550.111756 | +| serialize | 200 | directBuffer | PRIMITIVE_ARRAY | 8335248.350301 | 972683.763633 | 294132.218623 | +| serialize | 1000 | array | BUFFER | 4979590.929127 | 1616159.671230 | 1805557.477810 | +| serialize | 1000 | array | PRIMITIVE_ARRAY | 8772856.921028 | 31395.721514 | 67209.107012 | +| serialize | 1000 | directBuffer | BUFFER | 5376191.775007 | 1377272.568510 | 1644789.427010 | +| serialize | 1000 | directBuffer | PRIMITIVE_ARRAY | 8207563.785251 | 209183.090868 | 66108.014322 | +| serialize | 5000 | array | BUFFER | 5018916.322770 | 711287.533377 | 811029.402136 | +| serialize | 5000 | array | PRIMITIVE_ARRAY | 8027439.580226 | 6248.006967 | 14997.400124 | +| serialize | 5000 | directBuffer | BUFFER | 5330897.682960 | 707092.956534 | 477148.540850 | +| serialize | 5000 | directBuffer | PRIMITIVE_ARRAY | 7695981.988316 | 43565.678616 | 15000.378818 | +| deserialize | 200 | array | BUFFER | 3302149.383135 | 1296284.787720 | 657754.887247 | +| deserialize | 200 | array | PRIMITIVE_ARRAY | 986136.067809 | 146675.360652 | 219333.990504 | +| deserialize | 200 | directBuffer | BUFFER | 3113115.471758 | 1004844.498712 | 598421.278941 | +| deserialize | 200 | directBuffer | PRIMITIVE_ARRAY | 991807.969328 | 518713.299422 | 179604.045774 | +| deserialize | 1000 | array | BUFFER | 2831942.848999 | 721266.541130 | 422147.154601 | +| deserialize | 1000 | array | PRIMITIVE_ARRAY | 205671.992736 | 30409.835023 | 53100.903684 | +| deserialize | 1000 | directBuffer | BUFFER | 3397690.327371 | 592972.713203 | 298929.116572 | +| deserialize | 1000 | directBuffer | PRIMITIVE_ARRAY | 202275.242341 | 112132.004609 | 38572.001768 | +| deserialize | 5000 | array | BUFFER | 3296658.120035 | 147251.846111 | 136934.604328 | +| deserialize | 5000 | array | PRIMITIVE_ARRAY | 40312.590172 | 6122.351228 | 10672.872798 | +| deserialize | 5000 | directBuffer | BUFFER | 3284441.570594 | 148614.476829 | 77950.612503 | +| deserialize | 5000 | directBuffer | PRIMITIVE_ARRAY | 40413.743717 | 21826.040410 | 8561.694533 | + +## Xlang Benchmark + +Run from `benchmarks/java/run.sh`. Raw JMH JSON stays under the ignored local `benchmarks/java/reports/` directory; `throughput.png` and this xlang section are synced into `docs/benchmarks/java/`. + +```bash +cd benchmarks/java +./run.sh +``` + +JMH parameters: `-f 1 -wi 3 -i 3 -t 1 -w 3s -r 3s -bm thrpt -tu s`. Higher throughput is better. + +![Java Xlang Serialization Throughput](throughput.png) + +| Data type | Operation | Fory ops/sec | Protobuf ops/sec | Flatbuffer ops/sec | Fastest | +| ----------------- | ----------- | ------------ | ---------------- | ------------------ | ------- | +| NumericStruct | Serialize | 46,787,647 | 33,024,161 | 9,612,018 | Fory | +| NumericStruct | Deserialize | 71,683,707 | 29,837,931 | 40,514,436 | Fory | +| Sample | Serialize | 17,406,902 | 2,071,963 | 3,153,672 | Fory | +| Sample | Deserialize | 17,772,123 | 1,867,967 | 4,179,494 | Fory | +| MediaContent | Serialize | 10,783,325 | 1,781,338 | 1,444,737 | Fory | +| MediaContent | Deserialize | 7,950,203 | 2,184,597 | 3,453,985 | Fory | +| NumericStructList | Serialize | 21,263,673 | 2,511,081 | 3,047,836 | Fory | +| NumericStructList | Deserialize | 19,249,877 | 2,067,204 | 8,168,569 | Fory | +| SampleList | Serialize | 4,580,165 | 401,280 | 696,268 | Fory | +| SampleList | Deserialize | 3,811,985 | 344,945 | 773,625 | Fory | +| MediaContentList | Serialize | 1,657,717 | 353,717 | 296,868 | Fory | +| MediaContentList | Deserialize | 1,111,043 | 435,956 | 516,192 | Fory | diff --git a/versioned_docs/version-1.3.0/benchmarks/java/data/jmh-jdk-11-deserialization.csv b/versioned_docs/version-1.3.0/benchmarks/java/data/jmh-jdk-11-deserialization.csv new file mode 100644 index 00000000000..98760a4721c --- /dev/null +++ b/versioned_docs/version-1.3.0/benchmarks/java/data/jmh-jdk-11-deserialization.csv @@ -0,0 +1,153 @@ +"Benchmark","Mode","Threads","Samples","Score","Score Error (99.9%)","Unit","Param: bufferType","Param: objectType","Param: references" +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fst_deserialize","thrpt",1,3,473409.796491,250793.361170,"ops/s",array,SAMPLE,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fst_deserialize","thrpt",1,3,428315.502365,191770.682411,"ops/s",array,SAMPLE,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fst_deserialize","thrpt",1,3,363455.785182,44717.666948,"ops/s",array,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fst_deserialize","thrpt",1,3,304371.728638,74831.791688,"ops/s",array,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fst_deserialize","thrpt",1,3,357887.235311,218092.594151,"ops/s",array,STRUCT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fst_deserialize","thrpt",1,3,353480.554035,229239.505256,"ops/s",array,STRUCT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fst_deserialize","thrpt",1,3,280131.091068,89472.347221,"ops/s",array,STRUCT2,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fst_deserialize","thrpt",1,3,260649.308016,165597.762734,"ops/s",array,STRUCT2,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fst_deserialize","thrpt",1,3,441027.550809,321712.847983,"ops/s",directBuffer,SAMPLE,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fst_deserialize","thrpt",1,3,420523.770904,122531.658151,"ops/s",directBuffer,SAMPLE,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fst_deserialize","thrpt",1,3,311691.658687,185300.664992,"ops/s",directBuffer,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fst_deserialize","thrpt",1,3,251820.171513,894743.730727,"ops/s",directBuffer,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fst_deserialize","thrpt",1,3,352441.597147,109488.413174,"ops/s",directBuffer,STRUCT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fst_deserialize","thrpt",1,3,334574.303484,89858.329407,"ops/s",directBuffer,STRUCT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fst_deserialize","thrpt",1,3,262519.858810,138990.948859,"ops/s",directBuffer,STRUCT2,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fst_deserialize","thrpt",1,3,234973.637096,568162.582389,"ops/s",directBuffer,STRUCT2,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize","thrpt",1,3,2069988.624415,1501625.089719,"ops/s",array,SAMPLE,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize","thrpt",1,3,1797942.442313,3541577.357052,"ops/s",array,SAMPLE,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize","thrpt",1,3,2054066.903469,3314161.094630,"ops/s",array,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize","thrpt",1,3,1507767.206603,1662320.958961,"ops/s",array,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize","thrpt",1,3,4595230.434552,678411.427346,"ops/s",array,STRUCT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize","thrpt",1,3,4634753.596131,582102.492158,"ops/s",array,STRUCT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize","thrpt",1,3,1126298.359550,856691.704987,"ops/s",array,STRUCT2,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize","thrpt",1,3,1046649.083082,1816153.563648,"ops/s",array,STRUCT2,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize","thrpt",1,3,2229791.078395,2950833.975767,"ops/s",directBuffer,SAMPLE,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize","thrpt",1,3,1958815.397807,381903.427430,"ops/s",directBuffer,SAMPLE,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize","thrpt",1,3,1502746.028159,1759759.267183,"ops/s",directBuffer,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize","thrpt",1,3,1290593.975753,246089.810346,"ops/s",directBuffer,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize","thrpt",1,3,5012002.859236,3360944.705659,"ops/s",directBuffer,STRUCT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize","thrpt",1,3,4864329.316938,4357833.300692,"ops/s",directBuffer,STRUCT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize","thrpt",1,3,1117586.457565,323438.118804,"ops/s",directBuffer,STRUCT2,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize","thrpt",1,3,1018277.848128,1141314.435153,"ops/s",directBuffer,STRUCT2,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize_compatible","thrpt",1,3,2296046.895861,2532372.600048,"ops/s",array,SAMPLE,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize_compatible","thrpt",1,3,1834139.395757,1021897.505849,"ops/s",array,SAMPLE,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize_compatible","thrpt",1,3,1441671.706320,317052.609754,"ops/s",array,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize_compatible","thrpt",1,3,1121136.039627,979316.642796,"ops/s",array,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize_compatible","thrpt",1,3,2110335.039275,389404.889706,"ops/s",array,STRUCT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize_compatible","thrpt",1,3,2135681.982674,1140328.709799,"ops/s",array,STRUCT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize_compatible","thrpt",1,3,849507.176263,77466.989680,"ops/s",array,STRUCT2,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize_compatible","thrpt",1,3,815120.319155,110547.743648,"ops/s",array,STRUCT2,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize_compatible","thrpt",1,3,2308111.633661,1699117.497565,"ops/s",directBuffer,SAMPLE,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize_compatible","thrpt",1,3,1820490.585648,244290.391445,"ops/s",directBuffer,SAMPLE,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize_compatible","thrpt",1,3,1256034.732514,811833.294307,"ops/s",directBuffer,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize_compatible","thrpt",1,3,1054942.751816,180122.424477,"ops/s",directBuffer,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize_compatible","thrpt",1,3,1596464.248141,1624412.248391,"ops/s",directBuffer,STRUCT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize_compatible","thrpt",1,3,1684681.074242,865262.572050,"ops/s",directBuffer,STRUCT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize_compatible","thrpt",1,3,784036.589363,189745.805570,"ops/s",directBuffer,STRUCT2,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.fory_deserialize_compatible","thrpt",1,3,782679.662083,232459.929272,"ops/s",directBuffer,STRUCT2,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.forymetashared_deserialize_compatible","thrpt",1,3,2485564.396196,404678.689647,"ops/s",array,SAMPLE,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.forymetashared_deserialize_compatible","thrpt",1,3,2002938.794909,254819.284313,"ops/s",array,SAMPLE,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.forymetashared_deserialize_compatible","thrpt",1,3,2279742.810882,1984221.778254,"ops/s",array,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.forymetashared_deserialize_compatible","thrpt",1,3,1623938.202345,902985.831204,"ops/s",array,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.forymetashared_deserialize_compatible","thrpt",1,3,4978833.206806,1201415.339844,"ops/s",array,STRUCT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.forymetashared_deserialize_compatible","thrpt",1,3,4807963.882520,981981.561987,"ops/s",array,STRUCT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.forymetashared_deserialize_compatible","thrpt",1,3,1201998.142474,446807.356524,"ops/s",array,STRUCT2,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.forymetashared_deserialize_compatible","thrpt",1,3,1058423.614156,2254684.330438,"ops/s",array,STRUCT2,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.forymetashared_deserialize_compatible","thrpt",1,3,2289261.533644,330913.304260,"ops/s",directBuffer,SAMPLE,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.forymetashared_deserialize_compatible","thrpt",1,3,1927548.827586,2169684.221478,"ops/s",directBuffer,SAMPLE,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.forymetashared_deserialize_compatible","thrpt",1,3,1718098.363961,1096276.962216,"ops/s",directBuffer,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.forymetashared_deserialize_compatible","thrpt",1,3,1333345.536684,1110700.750020,"ops/s",directBuffer,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.forymetashared_deserialize_compatible","thrpt",1,3,5149070.657830,3249519.880480,"ops/s",directBuffer,STRUCT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.forymetashared_deserialize_compatible","thrpt",1,3,5137500.621288,3988987.937014,"ops/s",directBuffer,STRUCT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.forymetashared_deserialize_compatible","thrpt",1,3,1131212.586953,240674.468318,"ops/s",directBuffer,STRUCT2,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.forymetashared_deserialize_compatible","thrpt",1,3,1089162.408165,294119.059967,"ops/s",directBuffer,STRUCT2,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize","thrpt",1,3,119471.518388,29846.632785,"ops/s",array,SAMPLE,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize","thrpt",1,3,121106.002978,27710.578092,"ops/s",array,SAMPLE,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize","thrpt",1,3,118156.072284,4767.841380,"ops/s",array,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize","thrpt",1,3,120016.594171,41255.986380,"ops/s",array,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize","thrpt",1,3,84709.108821,132894.355932,"ops/s",array,STRUCT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize","thrpt",1,3,91050.370224,17939.461828,"ops/s",array,STRUCT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize","thrpt",1,3,69758.767783,10886.276794,"ops/s",array,STRUCT2,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize","thrpt",1,3,68616.029248,13625.851120,"ops/s",array,STRUCT2,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize","thrpt",1,3,117806.916589,35449.535259,"ops/s",directBuffer,SAMPLE,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize","thrpt",1,3,121940.783597,54381.355274,"ops/s",directBuffer,SAMPLE,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize","thrpt",1,3,111067.942626,136178.034974,"ops/s",directBuffer,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize","thrpt",1,3,121820.821260,36972.450957,"ops/s",directBuffer,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize","thrpt",1,3,91151.633583,18401.579776,"ops/s",directBuffer,STRUCT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize","thrpt",1,3,91037.205901,58868.732797,"ops/s",directBuffer,STRUCT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize","thrpt",1,3,66866.108653,38522.707093,"ops/s",directBuffer,STRUCT2,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize","thrpt",1,3,65338.345185,56556.914062,"ops/s",directBuffer,STRUCT2,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize_compatible","thrpt",1,3,121898.105768,55415.594537,"ops/s",array,SAMPLE,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize_compatible","thrpt",1,3,121297.485903,33990.621079,"ops/s",array,SAMPLE,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize_compatible","thrpt",1,3,121619.090797,6094.683470,"ops/s",array,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize_compatible","thrpt",1,3,119994.104050,24538.128667,"ops/s",array,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize_compatible","thrpt",1,3,88617.486795,40076.835119,"ops/s",array,STRUCT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize_compatible","thrpt",1,3,90206.654212,19313.248466,"ops/s",array,STRUCT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize_compatible","thrpt",1,3,63703.763814,132184.298414,"ops/s",array,STRUCT2,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize_compatible","thrpt",1,3,69521.573119,8875.645591,"ops/s",array,STRUCT2,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize_compatible","thrpt",1,3,124044.417439,41135.181705,"ops/s",directBuffer,SAMPLE,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize_compatible","thrpt",1,3,120276.449497,87827.771405,"ops/s",directBuffer,SAMPLE,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize_compatible","thrpt",1,3,107594.474890,81084.454215,"ops/s",directBuffer,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize_compatible","thrpt",1,3,116531.023438,141591.979641,"ops/s",directBuffer,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize_compatible","thrpt",1,3,89580.561575,23464.095972,"ops/s",directBuffer,STRUCT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize_compatible","thrpt",1,3,84407.472531,46007.731889,"ops/s",directBuffer,STRUCT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize_compatible","thrpt",1,3,69342.030965,17706.784671,"ops/s",directBuffer,STRUCT2,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.hession_deserialize_compatible","thrpt",1,3,68542.055543,31793.805138,"ops/s",directBuffer,STRUCT2,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.jdk_deserialize","thrpt",1,3,29309.573998,765.998843,"ops/s",array,SAMPLE,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.jdk_deserialize","thrpt",1,3,27466.003923,14896.452629,"ops/s",array,SAMPLE,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.jdk_deserialize","thrpt",1,3,38536.250402,28217.590983,"ops/s",array,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.jdk_deserialize","thrpt",1,3,38957.191090,23931.545367,"ops/s",array,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.jdk_deserialize","thrpt",1,3,29603.066599,10373.666720,"ops/s",array,STRUCT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.jdk_deserialize","thrpt",1,3,29727.744196,15820.774882,"ops/s",array,STRUCT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.jdk_deserialize","thrpt",1,3,14888.805111,842.165916,"ops/s",array,STRUCT2,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.jdk_deserialize","thrpt",1,3,14034.100664,13936.787605,"ops/s",array,STRUCT2,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.jdk_deserialize","thrpt",1,3,28128.457935,5385.055284,"ops/s",directBuffer,SAMPLE,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.jdk_deserialize","thrpt",1,3,28221.014735,15281.277719,"ops/s",directBuffer,SAMPLE,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.jdk_deserialize","thrpt",1,3,40512.632076,8592.454839,"ops/s",directBuffer,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.jdk_deserialize","thrpt",1,3,37030.594632,9683.316145,"ops/s",directBuffer,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.jdk_deserialize","thrpt",1,3,28717.004518,17023.663871,"ops/s",directBuffer,STRUCT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.jdk_deserialize","thrpt",1,3,29549.998286,13823.854047,"ops/s",directBuffer,STRUCT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.jdk_deserialize","thrpt",1,3,14652.043788,2419.900104,"ops/s",directBuffer,STRUCT2,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.jdk_deserialize","thrpt",1,3,14425.886048,8723.095052,"ops/s",directBuffer,STRUCT2,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize","thrpt",1,3,979173.981159,178146.966897,"ops/s",array,SAMPLE,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize","thrpt",1,3,716438.884369,623966.984928,"ops/s",array,SAMPLE,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize","thrpt",1,3,577631.234369,221011.018380,"ops/s",array,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize","thrpt",1,3,365530.417232,214946.853015,"ops/s",array,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize","thrpt",1,3,607750.343557,99638.164976,"ops/s",array,STRUCT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize","thrpt",1,3,552802.227807,126286.013177,"ops/s",array,STRUCT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize","thrpt",1,3,275984.042401,9405.143983,"ops/s",array,STRUCT2,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize","thrpt",1,3,222710.554833,45168.519253,"ops/s",array,STRUCT2,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize","thrpt",1,3,983538.936801,1112062.949472,"ops/s",directBuffer,SAMPLE,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize","thrpt",1,3,762889.302732,279678.610328,"ops/s",directBuffer,SAMPLE,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize","thrpt",1,3,389473.174523,43058.395501,"ops/s",directBuffer,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize","thrpt",1,3,306995.220799,57411.579647,"ops/s",directBuffer,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize","thrpt",1,3,910534.169114,1888677.766640,"ops/s",directBuffer,STRUCT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize","thrpt",1,3,914404.107564,357717.816934,"ops/s",directBuffer,STRUCT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize","thrpt",1,3,319247.256793,217674.935649,"ops/s",directBuffer,STRUCT2,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize","thrpt",1,3,249105.828416,131690.474985,"ops/s",directBuffer,STRUCT2,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize_compatible","thrpt",1,3,255086.928308,23672.392381,"ops/s",array,SAMPLE,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize_compatible","thrpt",1,3,238811.995510,18059.118666,"ops/s",array,SAMPLE,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize_compatible","thrpt",1,3,180882.860363,69339.581545,"ops/s",array,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize_compatible","thrpt",1,3,154311.211540,23065.912064,"ops/s",array,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize_compatible","thrpt",1,3,78771.635309,45155.641241,"ops/s",array,STRUCT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize_compatible","thrpt",1,3,72805.937649,16849.888437,"ops/s",array,STRUCT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize_compatible","thrpt",1,3,60602.285743,61404.274046,"ops/s",array,STRUCT2,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize_compatible","thrpt",1,3,62729.908347,32805.651539,"ops/s",array,STRUCT2,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize_compatible","thrpt",1,3,201993.787890,41567.224827,"ops/s",directBuffer,SAMPLE,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize_compatible","thrpt",1,3,174534.710870,96357.498949,"ops/s",directBuffer,SAMPLE,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize_compatible","thrpt",1,3,134485.160300,104898.991148,"ops/s",directBuffer,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize_compatible","thrpt",1,3,119311.787329,66580.856795,"ops/s",directBuffer,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize_compatible","thrpt",1,3,58574.904225,140682.248378,"ops/s",directBuffer,STRUCT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize_compatible","thrpt",1,3,60685.320299,24159.853451,"ops/s",directBuffer,STRUCT,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize_compatible","thrpt",1,3,54637.329134,14011.283798,"ops/s",directBuffer,STRUCT2,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.kryo_deserialize_compatible","thrpt",1,3,51761.569591,61030.640534,"ops/s",directBuffer,STRUCT2,true +"org.apache.fory.benchmark.UserTypeDeserializeSuite.protostuff_deserialize","thrpt",1,3,619338.385412,275705.847169,"ops/s",array,SAMPLE,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.protostuff_deserialize","thrpt",1,3,951662.019963,165160.853007,"ops/s",array,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.protostuff_deserialize","thrpt",1,3,517381.168594,313746.662696,"ops/s",array,STRUCT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.protostuff_deserialize","thrpt",1,3,416212.973861,119393.345217,"ops/s",array,STRUCT2,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.protostuff_deserialize","thrpt",1,3,624804.978534,147685.682690,"ops/s",directBuffer,SAMPLE,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.protostuff_deserialize","thrpt",1,3,964664.641598,218386.902856,"ops/s",directBuffer,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.protostuff_deserialize","thrpt",1,3,538922.947147,59168.303230,"ops/s",directBuffer,STRUCT,false +"org.apache.fory.benchmark.UserTypeDeserializeSuite.protostuff_deserialize","thrpt",1,3,425523.315814,179264.359952,"ops/s",directBuffer,STRUCT2,false diff --git a/versioned_docs/version-1.3.0/benchmarks/java/data/jmh-jdk-11-serialization.csv b/versioned_docs/version-1.3.0/benchmarks/java/data/jmh-jdk-11-serialization.csv new file mode 100644 index 00000000000..2e985751a77 --- /dev/null +++ b/versioned_docs/version-1.3.0/benchmarks/java/data/jmh-jdk-11-serialization.csv @@ -0,0 +1,153 @@ +"Benchmark","Mode","Threads","Samples","Score","Score Error (99.9%)","Unit","Param: bufferType","Param: objectType","Param: references" +"org.apache.fory.benchmark.UserTypeSerializeSuite.fst_serialize","thrpt",1,3,915907.574306,87627.907529,"ops/s",array,SAMPLE,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.fst_serialize","thrpt",1,3,731869.156376,37845.555465,"ops/s",array,SAMPLE,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.fst_serialize","thrpt",1,3,751892.023189,174327.010719,"ops/s",array,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.fst_serialize","thrpt",1,3,583859.907758,320047.618377,"ops/s",array,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.fst_serialize","thrpt",1,3,882178.995727,294884.724923,"ops/s",array,STRUCT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.fst_serialize","thrpt",1,3,757753.756691,605180.489112,"ops/s",array,STRUCT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.fst_serialize","thrpt",1,3,371762.982661,80513.482138,"ops/s",array,STRUCT2,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.fst_serialize","thrpt",1,3,380638.700267,101974.434105,"ops/s",array,STRUCT2,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.fst_serialize","thrpt",1,3,902302.261168,474054.277143,"ops/s",directBuffer,SAMPLE,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.fst_serialize","thrpt",1,3,723614.066770,77429.645491,"ops/s",directBuffer,SAMPLE,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.fst_serialize","thrpt",1,3,728001.080250,75699.111305,"ops/s",directBuffer,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.fst_serialize","thrpt",1,3,595679.580108,117269.848918,"ops/s",directBuffer,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.fst_serialize","thrpt",1,3,807847.663261,67842.801069,"ops/s",directBuffer,STRUCT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.fst_serialize","thrpt",1,3,762088.935404,575559.070335,"ops/s",directBuffer,STRUCT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.fst_serialize","thrpt",1,3,365317.705376,138313.634773,"ops/s",directBuffer,STRUCT2,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.fst_serialize","thrpt",1,3,370851.880711,66834.323719,"ops/s",directBuffer,STRUCT2,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize","thrpt",1,3,3570966.469087,152949.902180,"ops/s",array,SAMPLE,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize","thrpt",1,3,1767693.835090,263146.035836,"ops/s",array,SAMPLE,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize","thrpt",1,3,3031642.924542,567213.986117,"ops/s",array,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize","thrpt",1,3,2250384.600246,529709.299207,"ops/s",array,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize","thrpt",1,3,7501415.567260,6672023.542025,"ops/s",array,STRUCT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize","thrpt",1,3,6264439.154428,976363.317001,"ops/s",array,STRUCT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize","thrpt",1,3,3586126.623874,1966282.728305,"ops/s",array,STRUCT2,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize","thrpt",1,3,3306474.506382,869558.338568,"ops/s",array,STRUCT2,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize","thrpt",1,3,3684487.760591,882227.920611,"ops/s",directBuffer,SAMPLE,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize","thrpt",1,3,1826456.709478,1377648.673630,"ops/s",directBuffer,SAMPLE,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize","thrpt",1,3,2479862.129632,1498299.302699,"ops/s",directBuffer,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize","thrpt",1,3,1938527.588331,148125.034055,"ops/s",directBuffer,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize","thrpt",1,3,9834223.243204,5284494.290641,"ops/s",directBuffer,STRUCT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize","thrpt",1,3,7551780.823133,744016.111639,"ops/s",directBuffer,STRUCT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize","thrpt",1,3,2643155.135327,200764.008652,"ops/s",directBuffer,STRUCT2,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize","thrpt",1,3,2391110.083108,2608567.411194,"ops/s",directBuffer,STRUCT2,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize_compatible","thrpt",1,3,3604596.465625,1232634.245435,"ops/s",array,SAMPLE,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize_compatible","thrpt",1,3,1619648.337293,212055.067245,"ops/s",array,SAMPLE,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize_compatible","thrpt",1,3,1679272.036223,788041.322785,"ops/s",array,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize_compatible","thrpt",1,3,1406736.538716,263608.222325,"ops/s",array,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize_compatible","thrpt",1,3,3530406.108869,3125642.741982,"ops/s",array,STRUCT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize_compatible","thrpt",1,3,3293059.098127,96940.669016,"ops/s",array,STRUCT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize_compatible","thrpt",1,3,2773368.997680,503239.905176,"ops/s",array,STRUCT2,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize_compatible","thrpt",1,3,2564174.550276,978139.792610,"ops/s",array,STRUCT2,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize_compatible","thrpt",1,3,3484533.218305,476583.192148,"ops/s",directBuffer,SAMPLE,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize_compatible","thrpt",1,3,1730822.630648,410016.597939,"ops/s",directBuffer,SAMPLE,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize_compatible","thrpt",1,3,1710680.937387,307207.222026,"ops/s",directBuffer,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize_compatible","thrpt",1,3,1149999.473994,140915.968294,"ops/s",directBuffer,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize_compatible","thrpt",1,3,2653169.568374,535312.987476,"ops/s",directBuffer,STRUCT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize_compatible","thrpt",1,3,2393817.762938,380997.375838,"ops/s",directBuffer,STRUCT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize_compatible","thrpt",1,3,1912402.937879,284090.793301,"ops/s",directBuffer,STRUCT2,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.fory_serialize_compatible","thrpt",1,3,1848338.968058,108311.846780,"ops/s",directBuffer,STRUCT2,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.forymetashared_serialize_compatible","thrpt",1,3,4409055.687063,112740.443049,"ops/s",array,SAMPLE,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.forymetashared_serialize_compatible","thrpt",1,3,1840705.439334,516764.580627,"ops/s",array,SAMPLE,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.forymetashared_serialize_compatible","thrpt",1,3,2992288.235281,867622.163323,"ops/s",array,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.forymetashared_serialize_compatible","thrpt",1,3,2058738.716953,2388681.798594,"ops/s",array,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.forymetashared_serialize_compatible","thrpt",1,3,9204444.777172,1526628.258403,"ops/s",array,STRUCT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.forymetashared_serialize_compatible","thrpt",1,3,7064625.291374,2655795.498346,"ops/s",array,STRUCT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.forymetashared_serialize_compatible","thrpt",1,3,2575824.143864,2216634.295140,"ops/s",array,STRUCT2,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.forymetashared_serialize_compatible","thrpt",1,3,3543082.528217,393858.603613,"ops/s",array,STRUCT2,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.forymetashared_serialize_compatible","thrpt",1,3,5043538.364886,2797987.191909,"ops/s",directBuffer,SAMPLE,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.forymetashared_serialize_compatible","thrpt",1,3,1859289.705838,366893.710607,"ops/s",directBuffer,SAMPLE,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.forymetashared_serialize_compatible","thrpt",1,3,2291443.556971,3167882.958411,"ops/s",directBuffer,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.forymetashared_serialize_compatible","thrpt",1,3,1804349.244125,481995.792041,"ops/s",directBuffer,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.forymetashared_serialize_compatible","thrpt",1,3,11650229.648715,1101289.207239,"ops/s",directBuffer,STRUCT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.forymetashared_serialize_compatible","thrpt",1,3,8702412.752357,1116617.972427,"ops/s",directBuffer,STRUCT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.forymetashared_serialize_compatible","thrpt",1,3,2714748.572248,284462.787825,"ops/s",directBuffer,STRUCT2,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.forymetashared_serialize_compatible","thrpt",1,3,1866073.031851,1521156.537508,"ops/s",directBuffer,STRUCT2,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize","thrpt",1,3,220386.502846,60892.430853,"ops/s",array,SAMPLE,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize","thrpt",1,3,192414.014211,34217.359956,"ops/s",array,SAMPLE,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize","thrpt",1,3,367782.358049,54538.846197,"ops/s",array,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize","thrpt",1,3,329427.470680,530871.650379,"ops/s",array,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize","thrpt",1,3,258233.998931,145299.453488,"ops/s",array,STRUCT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize","thrpt",1,3,260845.209485,90601.426407,"ops/s",array,STRUCT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize","thrpt",1,3,56056.080075,3919.171009,"ops/s",array,STRUCT2,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize","thrpt",1,3,60038.879790,6808.083631,"ops/s",array,STRUCT2,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize","thrpt",1,3,220981.308085,153633.380796,"ops/s",directBuffer,SAMPLE,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize","thrpt",1,3,211949.960255,231322.136517,"ops/s",directBuffer,SAMPLE,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize","thrpt",1,3,372477.138150,39349.725456,"ops/s",directBuffer,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize","thrpt",1,3,353376.085025,11334.885856,"ops/s",directBuffer,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize","thrpt",1,3,266481.009225,113518.076189,"ops/s",directBuffer,STRUCT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize","thrpt",1,3,261762.594966,64652.028148,"ops/s",directBuffer,STRUCT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize","thrpt",1,3,55924.319442,28173.043405,"ops/s",directBuffer,STRUCT2,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize","thrpt",1,3,56674.065604,2667.957614,"ops/s",directBuffer,STRUCT2,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize_compatible","thrpt",1,3,234454.975158,5232.236462,"ops/s",array,SAMPLE,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize_compatible","thrpt",1,3,206174.173039,54217.176504,"ops/s",array,SAMPLE,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize_compatible","thrpt",1,3,377195.903772,33297.468886,"ops/s",array,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize_compatible","thrpt",1,3,351657.879556,10762.766962,"ops/s",array,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize_compatible","thrpt",1,3,258650.663523,64158.329095,"ops/s",array,STRUCT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize_compatible","thrpt",1,3,263564.913879,101476.568014,"ops/s",array,STRUCT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize_compatible","thrpt",1,3,58509.125342,10836.872797,"ops/s",array,STRUCT2,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize_compatible","thrpt",1,3,55552.977735,37802.538138,"ops/s",array,STRUCT2,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize_compatible","thrpt",1,3,194761.224263,8828.274695,"ops/s",directBuffer,SAMPLE,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize_compatible","thrpt",1,3,212840.483308,50730.019909,"ops/s",directBuffer,SAMPLE,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize_compatible","thrpt",1,3,371729.727192,136063.629978,"ops/s",directBuffer,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize_compatible","thrpt",1,3,343834.954942,13355.085558,"ops/s",directBuffer,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize_compatible","thrpt",1,3,249221.452137,261034.656030,"ops/s",directBuffer,STRUCT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize_compatible","thrpt",1,3,263623.143601,32854.227410,"ops/s",directBuffer,STRUCT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize_compatible","thrpt",1,3,58908.567439,4137.470432,"ops/s",directBuffer,STRUCT2,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.hession_serialize_compatible","thrpt",1,3,55524.373547,668.406873,"ops/s",directBuffer,STRUCT2,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.jdk_serialize","thrpt",1,3,118374.836631,34704.407553,"ops/s",array,SAMPLE,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.jdk_serialize","thrpt",1,3,119858.140625,49308.850176,"ops/s",array,SAMPLE,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.jdk_serialize","thrpt",1,3,137989.198821,40017.148924,"ops/s",array,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.jdk_serialize","thrpt",1,3,140260.668888,57308.595910,"ops/s",array,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.jdk_serialize","thrpt",1,3,155908.244240,20385.278504,"ops/s",array,STRUCT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.jdk_serialize","thrpt",1,3,151258.539369,12676.385578,"ops/s",array,STRUCT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.jdk_serialize","thrpt",1,3,36846.049162,5491.880967,"ops/s",array,STRUCT2,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.jdk_serialize","thrpt",1,3,38183.705811,14778.659439,"ops/s",array,STRUCT2,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.jdk_serialize","thrpt",1,3,118273.584257,18950.629048,"ops/s",directBuffer,SAMPLE,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.jdk_serialize","thrpt",1,3,108263.040839,14982.910444,"ops/s",directBuffer,SAMPLE,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.jdk_serialize","thrpt",1,3,138567.623369,24365.255940,"ops/s",directBuffer,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.jdk_serialize","thrpt",1,3,140158.673910,54416.908878,"ops/s",directBuffer,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.jdk_serialize","thrpt",1,3,154875.908438,11984.781345,"ops/s",directBuffer,STRUCT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.jdk_serialize","thrpt",1,3,156404.686214,58131.602098,"ops/s",directBuffer,STRUCT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.jdk_serialize","thrpt",1,3,37444.967981,14948.012339,"ops/s",directBuffer,STRUCT2,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.jdk_serialize","thrpt",1,3,35798.679246,36641.428549,"ops/s",directBuffer,STRUCT2,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize","thrpt",1,3,1105365.931217,162251.803619,"ops/s",array,SAMPLE,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize","thrpt",1,3,734215.482291,79297.387490,"ops/s",array,SAMPLE,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize","thrpt",1,3,730792.521676,678674.266715,"ops/s",array,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize","thrpt",1,3,445251.084327,85646.179264,"ops/s",array,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize","thrpt",1,3,558194.100861,257156.321315,"ops/s",array,STRUCT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize","thrpt",1,3,557542.628765,63084.390134,"ops/s",array,STRUCT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize","thrpt",1,3,325172.969175,20774.334333,"ops/s",array,STRUCT2,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize","thrpt",1,3,259863.332448,86373.633851,"ops/s",array,STRUCT2,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize","thrpt",1,3,1376560.302168,147424.342310,"ops/s",directBuffer,SAMPLE,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize","thrpt",1,3,932887.968348,249800.928765,"ops/s",directBuffer,SAMPLE,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize","thrpt",1,3,608972.517580,249598.971835,"ops/s",directBuffer,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize","thrpt",1,3,359875.473951,274870.064607,"ops/s",directBuffer,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize","thrpt",1,3,1078046.011115,249527.480472,"ops/s",directBuffer,STRUCT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize","thrpt",1,3,853350.408656,126642.692106,"ops/s",directBuffer,STRUCT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize","thrpt",1,3,355688.882786,37587.645927,"ops/s",directBuffer,STRUCT2,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize","thrpt",1,3,338960.426033,36185.446014,"ops/s",directBuffer,STRUCT2,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize_compatible","thrpt",1,3,378907.663184,280309.649766,"ops/s",array,SAMPLE,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize_compatible","thrpt",1,3,320815.567701,455179.720989,"ops/s",array,SAMPLE,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize_compatible","thrpt",1,3,188911.259146,97827.317807,"ops/s",array,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize_compatible","thrpt",1,3,145782.916427,81270.905462,"ops/s",array,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize_compatible","thrpt",1,3,145964.199559,88578.667153,"ops/s",array,STRUCT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize_compatible","thrpt",1,3,136180.832879,167433.069232,"ops/s",array,STRUCT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize_compatible","thrpt",1,3,125807.748004,23178.451287,"ops/s",array,STRUCT2,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize_compatible","thrpt",1,3,114983.546343,7341.380140,"ops/s",array,STRUCT2,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize_compatible","thrpt",1,3,296102.615094,18485.738321,"ops/s",directBuffer,SAMPLE,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize_compatible","thrpt",1,3,276757.392449,61173.636852,"ops/s",directBuffer,SAMPLE,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize_compatible","thrpt",1,3,185363.714829,121440.885632,"ops/s",directBuffer,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize_compatible","thrpt",1,3,142836.961878,12450.918452,"ops/s",directBuffer,MEDIA_CONTENT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize_compatible","thrpt",1,3,106695.800225,13463.570576,"ops/s",directBuffer,STRUCT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize_compatible","thrpt",1,3,106458.212005,2994.065295,"ops/s",directBuffer,STRUCT,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize_compatible","thrpt",1,3,92130.672361,9515.661170,"ops/s",directBuffer,STRUCT2,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.kryo_serialize_compatible","thrpt",1,3,88989.724768,9379.338279,"ops/s",directBuffer,STRUCT2,true +"org.apache.fory.benchmark.UserTypeSerializeSuite.protostuff_serialize","thrpt",1,3,663272.710783,522116.492895,"ops/s",array,SAMPLE,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.protostuff_serialize","thrpt",1,3,780618.761219,406063.945348,"ops/s",array,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.protostuff_serialize","thrpt",1,3,330975.350403,196475.862643,"ops/s",array,STRUCT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.protostuff_serialize","thrpt",1,3,322563.440433,201276.713653,"ops/s",array,STRUCT2,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.protostuff_serialize","thrpt",1,3,693641.589806,43883.803566,"ops/s",directBuffer,SAMPLE,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.protostuff_serialize","thrpt",1,3,805941.345157,91281.699006,"ops/s",directBuffer,MEDIA_CONTENT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.protostuff_serialize","thrpt",1,3,340262.650047,152117.387028,"ops/s",directBuffer,STRUCT,false +"org.apache.fory.benchmark.UserTypeSerializeSuite.protostuff_serialize","thrpt",1,3,325093.716261,56485.972228,"ops/s",directBuffer,STRUCT2,false diff --git a/versioned_docs/version-1.3.0/benchmarks/java/data/jmh-jdk-11-zerocopy.csv b/versioned_docs/version-1.3.0/benchmarks/java/data/jmh-jdk-11-zerocopy.csv new file mode 100644 index 00000000000..503298291f9 --- /dev/null +++ b/versioned_docs/version-1.3.0/benchmarks/java/data/jmh-jdk-11-zerocopy.csv @@ -0,0 +1,73 @@ +"Benchmark","Mode","Threads","Samples","Score","Score Error (99.9%)","Unit","Param: array_size","Param: bufferType","Param: dataType","Param: references" +"org.apache.fory.benchmark.ZeroCopySuite.fst_deserialize","thrpt",1,3,219333.990504,40075.312705,"ops/s",200,array,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.fst_deserialize","thrpt",1,3,657754.887247,408901.031753,"ops/s",200,array,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.fst_deserialize","thrpt",1,3,179604.045774,45387.509931,"ops/s",200,directBuffer,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.fst_deserialize","thrpt",1,3,598421.278941,135411.381215,"ops/s",200,directBuffer,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.fst_deserialize","thrpt",1,3,53100.903684,17709.358858,"ops/s",1000,array,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.fst_deserialize","thrpt",1,3,422147.154601,528539.529166,"ops/s",1000,array,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.fst_deserialize","thrpt",1,3,38572.001768,24685.893533,"ops/s",1000,directBuffer,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.fst_deserialize","thrpt",1,3,298929.116572,135040.133488,"ops/s",1000,directBuffer,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.fst_deserialize","thrpt",1,3,10672.872798,11519.184075,"ops/s",5000,array,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.fst_deserialize","thrpt",1,3,136934.604328,19269.032565,"ops/s",5000,array,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.fst_deserialize","thrpt",1,3,8561.694533,2137.867210,"ops/s",5000,directBuffer,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.fst_deserialize","thrpt",1,3,77950.612503,83587.484192,"ops/s",5000,directBuffer,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.fst_serialize","thrpt",1,3,313986.053417,29311.641179,"ops/s",200,array,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.fst_serialize","thrpt",1,3,2400193.220466,242732.420524,"ops/s",200,array,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.fst_serialize","thrpt",1,3,294132.218623,60640.778775,"ops/s",200,directBuffer,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.fst_serialize","thrpt",1,3,2282550.111756,93004.652618,"ops/s",200,directBuffer,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.fst_serialize","thrpt",1,3,67209.107012,5947.935958,"ops/s",1000,array,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.fst_serialize","thrpt",1,3,1805557.477810,5232839.112456,"ops/s",1000,array,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.fst_serialize","thrpt",1,3,66108.014322,16869.542536,"ops/s",1000,directBuffer,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.fst_serialize","thrpt",1,3,1644789.427010,54907.475118,"ops/s",1000,directBuffer,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.fst_serialize","thrpt",1,3,14997.400124,12425.308762,"ops/s",5000,array,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.fst_serialize","thrpt",1,3,811029.402136,39593.863849,"ops/s",5000,array,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.fst_serialize","thrpt",1,3,15000.378818,2219.583522,"ops/s",5000,directBuffer,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.fst_serialize","thrpt",1,3,477148.540850,34970.176073,"ops/s",5000,directBuffer,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.fory_deserialize","thrpt",1,3,986136.067809,274606.792449,"ops/s",200,array,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.fory_deserialize","thrpt",1,3,3302149.383135,3166455.521704,"ops/s",200,array,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.fory_deserialize","thrpt",1,3,991807.969328,296490.837932,"ops/s",200,directBuffer,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.fory_deserialize","thrpt",1,3,3113115.471758,4861106.592529,"ops/s",200,directBuffer,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.fory_deserialize","thrpt",1,3,205671.992736,127494.951753,"ops/s",1000,array,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.fory_deserialize","thrpt",1,3,2831942.848999,1720214.698856,"ops/s",1000,array,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.fory_deserialize","thrpt",1,3,202275.242341,56270.238293,"ops/s",1000,directBuffer,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.fory_deserialize","thrpt",1,3,3397690.327371,146058.097383,"ops/s",1000,directBuffer,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.fory_deserialize","thrpt",1,3,40312.590172,11836.614114,"ops/s",5000,array,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.fory_deserialize","thrpt",1,3,3296658.120035,2199676.238015,"ops/s",5000,array,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.fory_deserialize","thrpt",1,3,40413.743717,9893.735875,"ops/s",5000,directBuffer,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.fory_deserialize","thrpt",1,3,3284441.570594,998247.999163,"ops/s",5000,directBuffer,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.fory_serialize","thrpt",1,3,8297232.942927,6613235.386482,"ops/s",200,array,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.fory_serialize","thrpt",1,3,5123572.914045,9342104.794019,"ops/s",200,array,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.fory_serialize","thrpt",1,3,8335248.350301,2796789.944593,"ops/s",200,directBuffer,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.fory_serialize","thrpt",1,3,5400346.890126,7654935.504676,"ops/s",200,directBuffer,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.fory_serialize","thrpt",1,3,8772856.921028,1984208.178343,"ops/s",1000,array,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.fory_serialize","thrpt",1,3,4979590.929127,5568756.926202,"ops/s",1000,array,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.fory_serialize","thrpt",1,3,8207563.785251,4153794.222735,"ops/s",1000,directBuffer,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.fory_serialize","thrpt",1,3,5376191.775007,9167580.616022,"ops/s",1000,directBuffer,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.fory_serialize","thrpt",1,3,8027439.580226,6148996.477681,"ops/s",5000,array,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.fory_serialize","thrpt",1,3,5018916.322770,1424045.598203,"ops/s",5000,array,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.fory_serialize","thrpt",1,3,7695981.988316,578662.201123,"ops/s",5000,directBuffer,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.fory_serialize","thrpt",1,3,5330897.682960,8055585.038901,"ops/s",5000,directBuffer,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.kryo_deserialize","thrpt",1,3,146675.360652,23502.675233,"ops/s",200,array,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.kryo_deserialize","thrpt",1,3,1296284.787720,297204.673569,"ops/s",200,array,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.kryo_deserialize","thrpt",1,3,518713.299422,600548.228171,"ops/s",200,directBuffer,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.kryo_deserialize","thrpt",1,3,1004844.498712,552313.083272,"ops/s",200,directBuffer,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.kryo_deserialize","thrpt",1,3,30409.835023,9562.322209,"ops/s",1000,array,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.kryo_deserialize","thrpt",1,3,721266.541130,62426.822891,"ops/s",1000,array,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.kryo_deserialize","thrpt",1,3,112132.004609,6625.327605,"ops/s",1000,directBuffer,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.kryo_deserialize","thrpt",1,3,592972.713203,511777.226637,"ops/s",1000,directBuffer,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.kryo_deserialize","thrpt",1,3,6122.351228,3021.683209,"ops/s",5000,array,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.kryo_deserialize","thrpt",1,3,147251.846111,278375.379465,"ops/s",5000,array,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.kryo_deserialize","thrpt",1,3,21826.040410,2246.004993,"ops/s",5000,directBuffer,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.kryo_deserialize","thrpt",1,3,148614.476829,87146.409451,"ops/s",5000,directBuffer,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.kryo_serialize","thrpt",1,3,147342.606262,138864.952435,"ops/s",200,array,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.kryo_serialize","thrpt",1,3,1985187.977633,70426.638387,"ops/s",200,array,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.kryo_serialize","thrpt",1,3,972683.763633,604464.052899,"ops/s",200,directBuffer,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.kryo_serialize","thrpt",1,3,1739454.519770,1053963.541481,"ops/s",200,directBuffer,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.kryo_serialize","thrpt",1,3,31395.721514,3652.506399,"ops/s",1000,array,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.kryo_serialize","thrpt",1,3,1616159.671230,2722436.839807,"ops/s",1000,array,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.kryo_serialize","thrpt",1,3,209183.090868,25323.702489,"ops/s",1000,directBuffer,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.kryo_serialize","thrpt",1,3,1377272.568510,203974.331559,"ops/s",1000,directBuffer,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.kryo_serialize","thrpt",1,3,6248.006967,783.226602,"ops/s",5000,array,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.kryo_serialize","thrpt",1,3,711287.533377,86363.201941,"ops/s",5000,array,BUFFER,false +"org.apache.fory.benchmark.ZeroCopySuite.kryo_serialize","thrpt",1,3,43565.678616,4221.267495,"ops/s",5000,directBuffer,PRIMITIVE_ARRAY,false +"org.apache.fory.benchmark.ZeroCopySuite.kryo_serialize","thrpt",1,3,707092.956534,298737.951680,"ops/s",5000,directBuffer,BUFFER,false diff --git a/versioned_docs/version-1.3.0/benchmarks/java/java_heap_deserialize_compatible.png b/versioned_docs/version-1.3.0/benchmarks/java/java_heap_deserialize_compatible.png new file mode 100644 index 00000000000..52dd292a3c0 Binary files /dev/null and b/versioned_docs/version-1.3.0/benchmarks/java/java_heap_deserialize_compatible.png differ diff --git a/versioned_docs/version-1.3.0/benchmarks/java/java_heap_deserialize_consistent.png b/versioned_docs/version-1.3.0/benchmarks/java/java_heap_deserialize_consistent.png new file mode 100644 index 00000000000..a21445421a7 Binary files /dev/null and b/versioned_docs/version-1.3.0/benchmarks/java/java_heap_deserialize_consistent.png differ diff --git a/versioned_docs/version-1.3.0/benchmarks/java/java_heap_serialize_compatible.png b/versioned_docs/version-1.3.0/benchmarks/java/java_heap_serialize_compatible.png new file mode 100644 index 00000000000..2142dfdbab0 Binary files /dev/null and b/versioned_docs/version-1.3.0/benchmarks/java/java_heap_serialize_compatible.png differ diff --git a/versioned_docs/version-1.3.0/benchmarks/java/java_heap_serialize_consistent.png b/versioned_docs/version-1.3.0/benchmarks/java/java_heap_serialize_consistent.png new file mode 100644 index 00000000000..5703f724733 Binary files /dev/null and b/versioned_docs/version-1.3.0/benchmarks/java/java_heap_serialize_consistent.png differ diff --git a/versioned_docs/version-1.3.0/benchmarks/java/java_offheap_deserialize_compatible.png b/versioned_docs/version-1.3.0/benchmarks/java/java_offheap_deserialize_compatible.png new file mode 100644 index 00000000000..ce77bf6fb7c Binary files /dev/null and b/versioned_docs/version-1.3.0/benchmarks/java/java_offheap_deserialize_compatible.png differ diff --git a/versioned_docs/version-1.3.0/benchmarks/java/java_offheap_deserialize_consistent.png b/versioned_docs/version-1.3.0/benchmarks/java/java_offheap_deserialize_consistent.png new file mode 100644 index 00000000000..04c3e3d04aa Binary files /dev/null and b/versioned_docs/version-1.3.0/benchmarks/java/java_offheap_deserialize_consistent.png differ diff --git a/versioned_docs/version-1.3.0/benchmarks/java/java_offheap_serialize_compatible.png b/versioned_docs/version-1.3.0/benchmarks/java/java_offheap_serialize_compatible.png new file mode 100644 index 00000000000..8d9197121c8 Binary files /dev/null and b/versioned_docs/version-1.3.0/benchmarks/java/java_offheap_serialize_compatible.png differ diff --git a/versioned_docs/version-1.3.0/benchmarks/java/java_offheap_serialize_consistent.png b/versioned_docs/version-1.3.0/benchmarks/java/java_offheap_serialize_consistent.png new file mode 100644 index 00000000000..8bdea8f5598 Binary files /dev/null and b/versioned_docs/version-1.3.0/benchmarks/java/java_offheap_serialize_consistent.png differ diff --git a/versioned_docs/version-1.3.0/benchmarks/java/java_repo_deserialization_throughput.png b/versioned_docs/version-1.3.0/benchmarks/java/java_repo_deserialization_throughput.png new file mode 100644 index 00000000000..d64f5e77a19 Binary files /dev/null and b/versioned_docs/version-1.3.0/benchmarks/java/java_repo_deserialization_throughput.png differ diff --git a/versioned_docs/version-1.3.0/benchmarks/java/java_repo_serialization_throughput.png b/versioned_docs/version-1.3.0/benchmarks/java/java_repo_serialization_throughput.png new file mode 100644 index 00000000000..8f9824ffe1a Binary files /dev/null and b/versioned_docs/version-1.3.0/benchmarks/java/java_repo_serialization_throughput.png differ diff --git a/versioned_docs/version-1.3.0/benchmarks/java/java_zero_copy_deserialize.png b/versioned_docs/version-1.3.0/benchmarks/java/java_zero_copy_deserialize.png new file mode 100644 index 00000000000..a91315eb345 Binary files /dev/null and b/versioned_docs/version-1.3.0/benchmarks/java/java_zero_copy_deserialize.png differ diff --git a/versioned_docs/version-1.3.0/benchmarks/java/java_zero_copy_serialize.png b/versioned_docs/version-1.3.0/benchmarks/java/java_zero_copy_serialize.png new file mode 100644 index 00000000000..a3f1b983464 Binary files /dev/null and b/versioned_docs/version-1.3.0/benchmarks/java/java_zero_copy_serialize.png differ diff --git a/versioned_docs/version-1.3.0/benchmarks/java/throughput.png b/versioned_docs/version-1.3.0/benchmarks/java/throughput.png new file mode 100644 index 00000000000..025f8b4329d Binary files /dev/null and b/versioned_docs/version-1.3.0/benchmarks/java/throughput.png differ diff --git a/versioned_docs/version-1.3.0/benchmarks/javascript/README.md b/versioned_docs/version-1.3.0/benchmarks/javascript/README.md new file mode 100644 index 00000000000..58322311522 --- /dev/null +++ b/versioned_docs/version-1.3.0/benchmarks/javascript/README.md @@ -0,0 +1,82 @@ +# JavaScript Benchmark Performance Report + +_Generated on 2026-05-08 17:55:12_ + +## How to Generate This Report + +```bash +cd benchmarks/javascript +./run.sh +``` + +## Benchmark Semantics + +The timed serializer loops use serializer-native typed values. Fory receives the pre-normalized Fory value used by its schema, protobuf receives the prebuilt protobuf-shaped value, and JSON receives the benchmark JavaScript object. Protobuf timings do not include `toProto`, `fromProto`, `protobufjs.create`, or `toObject` conversion work. + +## Benchmark Plot + +The plot shows throughput (ops/sec); higher is better. + +![Throughput](throughput.png) + +## Hardware & OS Info + +| Key | Value | +| -------------------------- | ------------------------ | +| OS | Darwin 24.6.0 | +| Machine | arm64 | +| Processor | arm | +| CPU Cores (Physical) | 12 | +| CPU Cores (Logical) | 12 | +| Total RAM (GB) | 48.0 | +| Benchmark Date | 2026-05-08T08:07:36.073Z | +| CPU Cores (from benchmark) | 12 | +| Node.js | v22.20.0 | +| V8 | 12.4.254.21-node.33 | + +## Benchmark Results + +### Timing Results (nanoseconds) + +| Datatype | Operation | fory (ns) | protobuf (ns) | json (ns) | Fastest | +| ----------------- | ----------- | --------- | ------------- | --------- | -------- | +| NumericStruct | Serialize | 76.0 | 613.0 | 496.0 | fory | +| NumericStruct | Deserialize | 56.9 | 94.8 | 333.0 | fory | +| Sample | Serialize | 318.0 | 2016.6 | 1409.3 | fory | +| Sample | Deserialize | 496.0 | 902.5 | 1609.6 | fory | +| MediaContent | Serialize | 494.1 | 1358.5 | 803.5 | fory | +| MediaContent | Deserialize | 539.3 | 628.3 | 1134.3 | fory | +| NumericStructList | Serialize | 195.3 | 3019.3 | 2013.5 | fory | +| NumericStructList | Deserialize | 183.7 | 606.9 | 1944.0 | fory | +| SampleList | Serialize | 1681.9 | 19346.7 | 11870.3 | fory | +| SampleList | Deserialize | 2571.9 | 5730.6 | 9074.5 | fory | +| MediaContentList | Serialize | 2785.9 | 7616.6 | 3611.5 | fory | +| MediaContentList | Deserialize | 3709.7 | 3018.6 | 5294.5 | protobuf | + +### Throughput Results (ops/sec) + +| Datatype | Operation | fory TPS | protobuf TPS | json TPS | Fastest | +| ----------------- | ----------- | ---------- | ------------ | --------- | -------- | +| NumericStruct | Serialize | 13,162,466 | 1,631,271 | 2,016,097 | fory | +| NumericStruct | Deserialize | 17,568,418 | 10,543,763 | 3,002,971 | fory | +| Sample | Serialize | 3,144,194 | 495,893 | 709,593 | fory | +| Sample | Deserialize | 2,015,942 | 1,108,010 | 621,285 | fory | +| MediaContent | Serialize | 2,023,719 | 736,097 | 1,244,512 | fory | +| MediaContent | Deserialize | 1,854,348 | 1,591,617 | 881,572 | fory | +| NumericStructList | Serialize | 5,121,376 | 331,201 | 496,645 | fory | +| NumericStructList | Deserialize | 5,444,504 | 1,647,728 | 514,414 | fory | +| SampleList | Serialize | 594,551 | 51,688 | 84,244 | fory | +| SampleList | Deserialize | 388,820 | 174,503 | 110,199 | fory | +| MediaContentList | Serialize | 358,954 | 131,293 | 276,891 | fory | +| MediaContentList | Deserialize | 269,561 | 331,275 | 188,876 | protobuf | + +### Serialized Data Sizes (bytes) + +| Datatype | fory | protobuf | json | +| ----------------- | ---- | -------- | ---- | +| NumericStruct | 78 | 93 | 159 | +| Sample | 445 | 377 | 724 | +| MediaContent | 388 | 307 | 596 | +| NumericStructList | 255 | 475 | 817 | +| SampleList | 1978 | 1900 | 3642 | +| MediaContentList | 1661 | 1550 | 3009 | diff --git a/versioned_docs/version-1.3.0/benchmarks/javascript/throughput.png b/versioned_docs/version-1.3.0/benchmarks/javascript/throughput.png new file mode 100644 index 00000000000..54c24051d66 Binary files /dev/null and b/versioned_docs/version-1.3.0/benchmarks/javascript/throughput.png differ diff --git a/versioned_docs/version-1.3.0/benchmarks/python/README.md b/versioned_docs/version-1.3.0/benchmarks/python/README.md new file mode 100644 index 00000000000..d0c51f33d99 --- /dev/null +++ b/versioned_docs/version-1.3.0/benchmarks/python/README.md @@ -0,0 +1,87 @@ +# Python Benchmark Performance Report + +_Generated on 2026-05-08 17:54:45_ + +## How to Generate This Report + +```bash +cd benchmarks/python +./run.sh +``` + +## Benchmark Plot + +The plot shows throughput (ops/sec); higher is better. + +![Throughput](throughput.png) + +## Hardware & OS Info + +| Key | Value | +| --------------------- | ---------------------------- | +| OS | Darwin 24.6.0 | +| Machine | arm64 | +| Processor | arm | +| Python | 3.10.8 | +| CPU Cores (Physical) | 12 | +| CPU Cores (Logical) | 12 | +| Total RAM (GB) | 48.0 | +| Python Implementation | CPython | +| Benchmark Platform | macOS-15.7.2-arm64-arm-64bit | + +## Benchmark Configuration + +| Key | Value | +| ---------- | ----- | +| warmup | 3 | +| iterations | 15 | +| repeat | 5 | +| number | 1000 | +| list_size | 5 | + +## Benchmark Results + +### Timing Results (nanoseconds) + +| Datatype | Operation | fory (ns) | protobuf (ns) | pickle (ns) | Fastest | +| ----------------- | ----------- | --------- | ------------- | ----------- | ------- | +| NumericStruct | Serialize | 491.4 | 802.3 | 1119.8 | fory | +| NumericStruct | Deserialize | 522.2 | 1211.6 | 1788.7 | fory | +| Sample | Serialize | 1096.4 | 3315.8 | 10185.2 | fory | +| Sample | Deserialize | 2772.0 | 6659.7 | 7061.9 | fory | +| MediaContent | Serialize | 989.2 | 3433.2 | 4392.7 | fory | +| MediaContent | Deserialize | 1518.7 | 4381.2 | 4305.1 | fory | +| NumericStructList | Serialize | 1111.2 | 4707.9 | 3235.8 | fory | +| NumericStructList | Deserialize | 1891.7 | 6891.0 | 3974.9 | fory | +| SampleList | Serialize | 3447.2 | 18719.1 | 32125.7 | fory | +| SampleList | Deserialize | 13131.6 | 35264.2 | 24154.4 | fory | +| MediaContentList | Serialize | 2996.5 | 17597.4 | 11087.8 | fory | +| MediaContentList | Deserialize | 6228.7 | 21562.0 | 10459.3 | fory | + +### Throughput Results (ops/sec) + +| Datatype | Operation | fory TPS | protobuf TPS | pickle TPS | Fastest | +| ----------------- | ----------- | --------- | ------------ | ---------- | ------- | +| NumericStruct | Serialize | 2,035,025 | 1,246,379 | 893,009 | fory | +| NumericStruct | Deserialize | 1,915,112 | 825,344 | 559,055 | fory | +| Sample | Serialize | 912,072 | 301,590 | 98,182 | fory | +| Sample | Deserialize | 360,751 | 150,158 | 141,605 | fory | +| MediaContent | Serialize | 1,010,939 | 291,275 | 227,652 | fory | +| MediaContent | Deserialize | 658,462 | 228,247 | 232,281 | fory | +| NumericStructList | Serialize | 899,960 | 212,407 | 309,040 | fory | +| NumericStructList | Deserialize | 528,636 | 145,116 | 251,580 | fory | +| SampleList | Serialize | 290,092 | 53,421 | 31,128 | fory | +| SampleList | Deserialize | 76,152 | 28,357 | 41,400 | fory | +| MediaContentList | Serialize | 333,720 | 56,826 | 90,189 | fory | +| MediaContentList | Deserialize | 160,547 | 46,378 | 95,609 | fory | + +### Serialized Data Sizes (bytes) + +| Datatype | fory | protobuf | pickle | +| ----------------- | ---- | -------- | ------ | +| NumericStruct | 78 | 93 | 169 | +| Sample | 445 | 375 | 1176 | +| MediaContent | 366 | 301 | 624 | +| NumericStructList | 219 | 475 | 582 | +| SampleList | 1914 | 1890 | 3546 | +| MediaContentList | 1614 | 1520 | 1415 | diff --git a/versioned_docs/version-1.3.0/benchmarks/python/throughput.png b/versioned_docs/version-1.3.0/benchmarks/python/throughput.png new file mode 100644 index 00000000000..7bd99a82e6d Binary files /dev/null and b/versioned_docs/version-1.3.0/benchmarks/python/throughput.png differ diff --git a/versioned_docs/version-1.3.0/benchmarks/rust/README.md b/versioned_docs/version-1.3.0/benchmarks/rust/README.md new file mode 100644 index 00000000000..0bd26916f24 --- /dev/null +++ b/versioned_docs/version-1.3.0/benchmarks/rust/README.md @@ -0,0 +1,77 @@ +# Rust Benchmark Performance Report + +_Generated on 2026-05-08 17:55:12_ + +## How to Generate This Report + +```bash +cd benchmarks/rust +cargo bench --bench serialization_bench 2>&1 | tee results/cargo_bench.log +cargo run --release --bin fory_profiler -- --print-all-serialized-sizes | tee results/serialized_sizes.txt +python benchmark_report.py --log-file results/cargo_bench.log --size-file results/serialized_sizes.txt --output-dir results +``` + +## Benchmark Plot + +The plot shows throughput (ops/sec); higher is better. + +![Throughput](throughput.png) + +## Hardware & OS Info + +| Key | Value | +| -------------------- | ------------------- | +| OS | Darwin 24.6.0 | +| Machine | arm64 | +| Processor | arm | +| CPU Cores (Physical) | 12 | +| CPU Cores (Logical) | 12 | +| Total RAM (GB) | 48.0 | +| Benchmark Date | 2026-05-08T16:47:49 | + +## Benchmark Results + +### Timing Results (nanoseconds) + +| Datatype | Operation | fory (ns) | protobuf (ns) | msgpack (ns) | Fastest | +| ----------------- | ----------- | --------- | ------------- | ------------ | ------- | +| NumericStruct | Serialize | 38.1 | 94.6 | 239.5 | fory | +| NumericStruct | Deserialize | 32.6 | 62.4 | 107.3 | fory | +| Sample | Serialize | 95.3 | 591.8 | 601.1 | fory | +| Sample | Deserialize | 410.1 | 925.8 | 805.9 | fory | +| MediaContent | Serialize | 120.0 | 553.9 | 446.9 | fory | +| MediaContent | Deserialize | 566.7 | 713.0 | 902.6 | fory | +| NumericStructList | Serialize | 121.5 | 512.0 | 618.0 | fory | +| NumericStructList | Deserialize | 137.9 | 404.9 | 615.9 | fory | +| SampleList | Serialize | 267.7 | 2920.2 | 2011.1 | fory | +| SampleList | Deserialize | 1831.9 | 4636.4 | 4141.4 | fory | +| MediaContentList | Serialize | 367.1 | 2835.6 | 1441.7 | fory | +| MediaContentList | Deserialize | 2703.8 | 3622.3 | 4832.3 | fory | + +### Throughput Results (ops/sec) + +| Datatype | Operation | fory TPS | protobuf TPS | msgpack TPS | Fastest | +| ----------------- | ----------- | ---------- | ------------ | ----------- | ------- | +| NumericStruct | Serialize | 26,237,767 | 10,572,613 | 4,174,668 | fory | +| NumericStruct | Deserialize | 30,720,079 | 16,035,920 | 9,322,271 | fory | +| Sample | Serialize | 10,494,611 | 1,689,874 | 1,663,700 | fory | +| Sample | Deserialize | 2,438,311 | 1,080,170 | 1,240,895 | fory | +| MediaContent | Serialize | 8,331,945 | 1,805,445 | 2,237,687 | fory | +| MediaContent | Deserialize | 1,764,633 | 1,402,426 | 1,107,960 | fory | +| NumericStructList | Serialize | 8,232,485 | 1,953,125 | 1,618,071 | fory | +| NumericStructList | Deserialize | 7,250,580 | 2,469,563 | 1,623,535 | fory | +| SampleList | Serialize | 3,735,664 | 342,442 | 497,240 | fory | +| SampleList | Deserialize | 545,881 | 215,685 | 241,464 | fory | +| MediaContentList | Serialize | 2,724,350 | 352,659 | 693,626 | fory | +| MediaContentList | Deserialize | 369,850 | 276,068 | 206,941 | fory | + +### Serialized Data Sizes (bytes) + +| Datatype | fory | protobuf | msgpack | +| ----------------- | ---- | -------- | ------- | +| NumericStruct | 78 | 93 | 87 | +| Sample | 445 | 375 | 590 | +| MediaContent | 362 | 301 | 500 | +| NumericStructList | 255 | 475 | 449 | +| SampleList | 1978 | 1890 | 2964 | +| MediaContentList | 1531 | 1520 | 2521 | diff --git a/versioned_docs/version-1.3.0/benchmarks/rust/throughput.png b/versioned_docs/version-1.3.0/benchmarks/rust/throughput.png new file mode 100644 index 00000000000..5640e734e40 Binary files /dev/null and b/versioned_docs/version-1.3.0/benchmarks/rust/throughput.png differ diff --git a/versioned_docs/version-1.3.0/benchmarks/swift/README.md b/versioned_docs/version-1.3.0/benchmarks/swift/README.md new file mode 100644 index 00000000000..ffb824b12a7 --- /dev/null +++ b/versioned_docs/version-1.3.0/benchmarks/swift/README.md @@ -0,0 +1,46 @@ +# Fory Swift Benchmark + +This benchmark compares serialization and deserialization throughput for Apache Fory, Protocol Buffers, and JSON in Swift. + +## Throughput Plot + +![Throughput](throughput.png) + +## Hardware and Runtime Info + +| Key | Value | +| --------------------- | ----------------------------- | +| Timestamp | 2026-05-08T09:05:32Z | +| OS | Version 15.7.2 (Build 24G325) | +| Host | macbook-pro.local | +| CPU Cores (Logical) | 12 | +| Memory (GB) | 48.00 | +| Duration per case (s) | 3 | + +## Throughput Results + +| Datatype | Operation | Fory TPS | Protobuf TPS | JSON TPS | Fastest | +| ----------------- | ----------- | ---------: | -----------: | -------: | ------------ | +| NumericStruct | Serialize | 9,435,623 | 6,175,939 | 408,960 | fory (1.53x) | +| NumericStruct | Deserialize | 11,037,225 | 6,842,676 | 328,302 | fory (1.61x) | +| Sample | Serialize | 3,596,835 | 1,257,100 | 79,781 | fory (2.86x) | +| Sample | Deserialize | 982,255 | 733,588 | 41,274 | fory (1.34x) | +| MediaContent | Serialize | 1,561,376 | 609,896 | 98,677 | fory (2.56x) | +| MediaContent | Deserialize | 523,836 | 395,202 | 70,528 | fory (1.33x) | +| NumericStructList | Serialize | 2,910,846 | 918,363 | 82,965 | fory (3.17x) | +| NumericStructList | Deserialize | 2,436,636 | 701,656 | 69,353 | fory (3.47x) | +| SampleList | Serialize | 694,557 | 202,040 | 16,679 | fory (3.44x) | +| SampleList | Deserialize | 187,109 | 131,947 | 8,236 | fory (1.42x) | +| MediaContentList | Serialize | 348,238 | 98,007 | 18,698 | fory (3.55x) | +| MediaContentList | Deserialize | 104,990 | 74,422 | 16,298 | fory (1.41x) | + +## Serialized Size (bytes) + +| Datatype | Fory | Protobuf | JSON | +| ----------------- | ---: | -------: | ---: | +| NumericStruct | 78 | 93 | 159 | +| Sample | 445 | 375 | 696 | +| MediaContent | 362 | 301 | 608 | +| NumericStructList | 255 | 475 | 816 | +| SampleList | 1978 | 1890 | 3501 | +| MediaContentList | 1531 | 1520 | 3067 | diff --git a/versioned_docs/version-1.3.0/benchmarks/swift/throughput.png b/versioned_docs/version-1.3.0/benchmarks/swift/throughput.png new file mode 100644 index 00000000000..c8d7f75e350 Binary files /dev/null and b/versioned_docs/version-1.3.0/benchmarks/swift/throughput.png differ diff --git a/versioned_docs/version-1.3.0/community/DEVELOPMENT.md b/versioned_docs/version-1.3.0/community/DEVELOPMENT.md new file mode 100644 index 00000000000..411255a4a8e --- /dev/null +++ b/versioned_docs/version-1.3.0/community/DEVELOPMENT.md @@ -0,0 +1,135 @@ +--- +title: Development +sidebar_position: 20 +id: development +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. +--- + +## How to build Apache Fory™ + +Clone the source tree from https://github.com/apache/fory. + +### Build Apache Fory™ Java + +```bash +cd java +mvn -T16 package +``` + +#### Environment Requirements + +- JDK 17+ +- Maven 3.6.3+ + +### Build Apache Fory™ Python + +```bash +cd python +pip install -v -e . + +# Optional: build Cython extension (replace X.Y with your Python version) +bazel build //:cp_fory_so --@rules_python//python/config_settings:python_version=X.Y +``` + +#### Environment Requirements + +- CPython 3.8+ +- Bazel 8+ (required when building Cython extensions) + +### Build Apache Fory™ C++ + +```bash +cd cpp +bazel build //cpp/... +``` + +#### Environment Requirements + +- C++17 compiler +- Bazel 8+ + +### Build Apache Fory™ Go + +```bash +cd go/fory +go test -v ./... +``` + +Run Go xlang tests from Java test module: + +```bash +cd java +mvn -T16 install -DskipTests +cd fory-core +FORY_GO_JAVA_CI=1 ENABLE_FORY_DEBUG_OUTPUT=1 mvn test -Dtest=org.apache.fory.xlang.GoXlangTest +``` + +#### Environment Requirements + +- Go 1.24+ + +### Build Apache Fory™ Rust + +```bash +cd rust +cargo build +cargo test --features tests + +# Debug a specific test +RUST_BACKTRACE=1 FORY_PANIC_ON_ERROR=1 ENABLE_FORY_DEBUG_OUTPUT=1 \ + cargo test --test mod $dir$::$test_file::$test_method -- --nocapture +``` + +#### Environment Requirements + +- Rust toolchain via rustup +- `cargo-expand` (optional, for macro expansion debugging) + +### Build Apache Fory™ JavaScript + +```bash +cd javascript +npm install + +npm run build +node ./node_modules/.bin/jest --ci --reporters=default --reporters=jest-junit +``` + +#### Environment Requirements + +- Node.js (LTS) +- npm + +### Lint Markdown Docs + +```bash +cd docs +npx prettier --write "**/*.md" +``` + +#### Environment Requirements + +- Node.js (LTS) +- npm + +## Contributing + +For contribution details, see [How to contribute to Apache Fory™](https://github.com/apache/fory/blob/main/CONTRIBUTING.md). +For AI-assisted contributions, follow the +[AI Contribution Policy](https://github.com/apache/fory/blob/main/AI_POLICY.md), including the +required self-review, two-reviewer AI review loop, disclosure, and verification evidence for +substantial AI assistance. diff --git a/versioned_docs/version-1.3.0/community/community.md b/versioned_docs/version-1.3.0/community/community.md new file mode 100644 index 00000000000..a866cc6a2fe --- /dev/null +++ b/versioned_docs/version-1.3.0/community/community.md @@ -0,0 +1,90 @@ +--- +title: Community +sidebar_position: 0 +id: community +--- + +Apache Fory™ is a volunteer project and it thrives on the contributions of its community. +We invite you to participate as much or as little as you wish. Here are several ways to contribute: + +- Use our project and share feedback. +- Provide use-cases for the project. +- Report bugs and contribute fixes. +- Contribute code and documentation improvements. + +## Mailing list + +| Name | Desc | Subscribe | Unsubscribe | Post | Archive | +| ----------------------- | ------------------------------- | ----------------------------------------------------- | --------------------------------------------------------- | ---------------------------------- | --------------------------------------------------------------------- | +| dev@fory.apache.org | Development related discussions | [Subscribe](mailto:dev-subscribe@fory.apache.org) | [Unsubscribe](mailto:dev-unsubscribe@fory.apache.org) | [Post](mailto:dev@fory.apache.org) | [Archive](https://lists.apache.org/list.html?dev@fory.apache.org) | +| commits@fory.apache.org | All commits to our repositories | [Subscribe](mailto:commits-subscribe@fory.apache.org) | [Unsubscribe](mailto:commits-unsubscribe@fory.apache.org) | Read only list | [Archive](https://lists.apache.org/list.html?commits@fory.apache.org) | + +Please make sure subscribe to any list before attempting to post. + +If you are not subscribed to the mailing list, your message will either be rejected or you won't receive the response. + +### How to subscribe to a mailing list + +To post messages, subscribe first by: + +1. Sending an email to listname-subscribe@fory.apache.org with `listname` replaced accordingly. +2. Replying to the confirmation email you'll receive, keeping the subject line intact. +3. You'll then get a welcome email, and the subscription succeeds. + +When discussing code snippets in emails, ensure: + +- You do not link to files in external services, as such files can change, get deleted or the link might break and thus + make an archived email thread useless. +- You paste text instead of screenshots of text. +- You keep formatting when pasting code in order to keep the code readable. +- There are enough import statements to avoid ambiguities. + +## Slack + +You can join +the [Apache Fory™ community on Slack](https://join.slack.com/t/fory-project/shared_invite/zt-1u8soj4qc-ieYEu7ciHOqA2mo47llS8A). + +There are a couple of community rules: + +- Be respectful and nice. +- All important decisions and conclusions must be reflected back to the mailing lists. "If it didn't happen on a mailing + list, it didn't happen." - The [Apache Mottos](https://theapacheway.com/on-list/). +- Use Slack threads to keep parallel conversations from overwhelming a channel. +- Please do not direct message people for troubleshooting, issue assigning and PR review. These should be picked-up + voluntarily. + +## Issue tracker + +We use GitHub Issues to track all issues: + +- code related issues: https://github.com/apache/fory/issues +- website related issues: https://github.com/apache/fory-site/issues + +You need to have a [GitHub account](https://github.com/signup) in order to create issues. +If you don't have a [GitHub account](https://github.com/signup), you can post an email to dev@fory.apache.org. + +### Bug reports + +To report a bug: + +- Verify that the bug does in fact exist. +- Search the [issue tracker](https://github.com/apache/fory/issues) to verify there is no existing issue reporting the bug you've found. +- Create a [bug report](https://github.com/apache/fory/issues/new?assignees=&labels=bug&projects=&template=bug_report.yml) on issue tracker. +- If possible, dive into the source code of fory, and submit a patch for the bug you reported, this helps ensure the bug + will be fixed quickly. + +### Reporting a Vulnerability + +Apache Fory™ is a project of the [Apache Software Foundation](https://apache.org/) and follows the [ASF vulnerability handling process](https://apache.org/security/#vulnerability-handling). + +To report a new vulnerability you have discovered please follow the [ASF vulnerability reporting process](https://apache.org/security/#reporting-a-vulnerability), which explains how to send us details privately. + +### Enhancement + +Enhancements or new feature proposals are also welcome. The more concrete and rationale the proposal is, the greater the +chance it will be incorporated into future releases. + +## Source code + +- fory core repository: https://github.com/apache/fory +- fory website repository: https://github.com/apache/fory-site diff --git a/versioned_docs/version-1.3.0/community/how_to_join_community.md b/versioned_docs/version-1.3.0/community/how_to_join_community.md new file mode 100644 index 00000000000..08afa8f35bf --- /dev/null +++ b/versioned_docs/version-1.3.0/community/how_to_join_community.md @@ -0,0 +1,105 @@ +--- +title: How to join Apache Fory™ +sidebar_position: 0 +id: how_to_join_community +--- + +First of all, kudos to you for choosing to join the open source contribution ranks. Secondly, we are very grateful that you have chosen to participate in the Fory community and contribute to this open source project. + +## Fory Contribution Guide + +The Fory team usually conducts development and issue maintenance on GitHub. Please open the [GitHub website](https://github.com/), click the `Sign up` button in the upper right corner, register your own account, and take the first step of your open source journey. + +In the [Fory repository](https://github.com/apache/fory), we have a [guide](https://fory.apache.org/zh-CN/docs/community/) for all open source contributors, introducing contents such as version management and branch management. **Please take a few minutes to read and understand it**. + +## Your First Pull Request + +### Step 0: Install Git + +Git is a version control system used to track and manage code changes in software development projects. It helps developers record and manage the history of the code, facilitating team collaboration, code version control, code merging, and other operations. With Git, you can track each version of each file and easily switch and compare between different versions. Git also provides branch management functionality, allowing multiple concurrent development tasks to be carried out simultaneously. + +- Visit the official Git website: [https://git-scm.com/] (https://git-scm.com/) +- Download the latest version of the Git installer. +- Run the downloaded installer and follow the prompts of the installation wizard to install. +- After the installation is complete, you can use the `git version` command in the command line to confirm the successful installation. + +### Step 1: Fork the Project + +- First, you need to fork this project. Enter the [Fory project page](https://github.com/apache/fory), and click the Fork button in the upper right corner. +- In your GitHub account, the project xxxx (your GitHub username)/fory will appear. +- On your local computer, use the following commands to obtain a fory folder: + +``` +// ssh +git clone git@github.com:xxxx (your GitHub username)/fory.git +// https +git clone https://github.com/xxxx (your GitHub username)/fory.git +``` + +### Step 2: Obtain the Project Code + +- Enter the fory folder and add the remote address of fory: + +``` +git remote add upstream https://github.com/apache/fory.git +``` + +### Step 3: Create a Branch + +- Alright, now you can start contributing our code. The default branch of Fory is the main branch. Whether it is for function development, bug fixes, or documentation writing, please create a new branch and then merge it to the main branch. Use the following code to create a branch: + +``` +// Create a function development branch +git checkout -b feat/xxxx + +// Create a problem-fixing development branch +git checkout -b fix/xxxx + +// Create a documentation, demo branch +git checkout -b docs/add-java-demo +``` + +Suppose we have created the documentation modification branch `docs/add-java-demo` and we have added some code and submitted it to the code repository. + +- `git add .` +- `git commit -a -m "docs: add java demo and related docs"` + +### Step 4: Merge the Modifications + +- Switch back to your development branch: + +``` +git checkout docs/add-java-demo +``` + +- Submit the updated code to your branch: + +``` +git push origin docs/add-java-demo +``` + +### Step 5: Submit a Pull Request + +You can click the `Compare & pull request` button on your GitHub code repository page. Or create it through the `contribute` button. + +- Fill in what type of modification this is. +- Fill in the associated issue. +- If there are complex changes, please explain the background and solution. + +After filling in the relevant information, click Create pull request to submit. + +## **Easily Step into the Fory Open Source Contribution Journey** + +"**good first issue**" is a common label in the open source community, and the purpose of this label is to help new contributors find entry-level issues that are suitable for them. + +The entry-level issues of Fory can be viewed through the [issue list](https://github.com/apache/fory/issues). + +If you currently **have the time and willingness** to participate in community contributions, you can take a look at **good first issue** in the issues and select one that interests you and is suitable for you to claim. + +## Embrace the Apache Fory™ Community + +While you contribute code to Fory, we encourage you to participate in other things that make the community more prosperous, such as: + +- Offer suggestions for the project's development, functional planning, etc. +- Create articles, videos, and hold lectures to promote Fory. +- Write promotion plans and execute them together with the team. diff --git a/versioned_docs/version-1.3.0/community/how_to_release.md b/versioned_docs/version-1.3.0/community/how_to_release.md new file mode 100644 index 00000000000..6e36dd4a79b --- /dev/null +++ b/versioned_docs/version-1.3.0/community/how_to_release.md @@ -0,0 +1,669 @@ +--- +title: How to release +sidebar_position: 0 +id: how_to_release +--- + +This document mainly introduces how the release manager releases a new version of Apache Fory™. + +## Introduction + +Source Release is the most important part which Apache values. + +Please pay more attention to license and signing issues. +Publishing software is a serious thing and has legal consequences. + +## First-time as a release manager + +### Environmental requirements + +This release process is operated in the Ubuntu OS, and the following tools are required: + +- OpenJDK 25+ +- Apache Maven 3.6.3+ +- Python 3.8+ +- GnuPG 2.x +- Git +- SVN (apache uses svn to host project releases) +- Optional package release and verification tools: Node.js LTS and npm, Rust via rustup, Go 1.24+, Dart, .NET SDK 8.0+, and sbt +- Pay attention to setting environment variables: if you configure gpg keys under a different directory, + please `export GNUPGHOME=$(xxx)` + +### Prepare GPG Key + +If you are the first to become a release manager, you need to prepare a gpg key. + +Following is a quick setup, you can refer to [Apache openpgp doc](https://infra.apache.org/openpgp.html) for further +details. + +#### Install GPG + +```bash +sudo apt install gnupg2 +``` + +#### Generate GPG Key + +Please use your apache name and email for generate key + +```bash +$ gpg --full-gen-key +gpg (GnuPG) 2.2.20; Copyright (C) 2020 Free Software Foundation, Inc. +This is free software: you are free to change and redistribute it. +There is NO WARRANTY, to the extent permitted by law. + +Please select what kind of key you want: + (1) RSA and RSA (default) + (2) DSA and Elgamal + (3) DSA (sign only) + (4) RSA (sign only) + (14) Existing key from card +Your selection? 1 # input 1 +RSA keys may be between 1024 and 4096 bits long. +What keysize do you want? (2048) 4096 # input 4096 +Requested keysize is 4096 bits +Please specify how long the key should be valid. + 0 = key does not expire + = key expires in n days + w = key expires in n weeks + m = key expires in n months + y = key expires in n years +Key is valid for? (0) 0 # input 0 +Key does not expire at all +Is this correct? (y/N) y # input y + +GnuPG needs to construct a user ID to identify your key. + +Real name: Chaokun Yang # input your name +Email address: chaokunyang@apache.org # input your email +Comment: CODE SIGNING KEY # input some annotations, optional +You selected this USER-ID: + "Chaokun " + +Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O # input O +We need to generate a lot of random bytes. It is a good idea to perform +some other action (type on the keyboard, move the mouse, utilize the +disks) during the prime generation; this gives the random number +generator a better chance to gain enough entropy. +We need to generate a lot of random bytes. It is a good idea to perform +some other action (type on the keyboard, move the mouse, utilize the +disks) during the prime generation; this gives the random number +generator a better chance to gain enough entropy. + +# Input the security key +┌──────────────────────────────────────────────────────┐ +│ Please enter this passphrase │ +│ │ +│ Passphrase: _______________________________ │ +│ │ +│ │ +└──────────────────────────────────────────────────────┘ +# key generation will be done after your inputting the key with the following output +gpg: key E49B00F626B marked as ultimately trusted +gpg: revocation certificate stored as '/Users/chaokunyang/.gnupg/openpgp-revocs.d/1E2CDAE4C08AD7D694D1CB139D7BE8E45E580BA4.rev' +public and secret key created and signed. + +pub rsa4096 2022-07-12 [SC] + 1E2CDAE4C08AD7D694D1CB139D7BE8E45E580BA4 +uid [ultimate] Chaokun +sub rsa4096 2022-07-12 [E] +``` + +#### Upload your public key to public GPG keyserver + +Firstly, list your key: + +```bash +gpg --list-keys +``` + +The output is like: + +```bash +-------------------------------------------------- +pub rsa4096 2024-03-27 [SC] + 1E2CDAE4C08AD7D694D1CB139D7BE8E45E580BA4 +uid [ultimate] chaokunyang (CODE SIGNING KEY) +sub rsa4096 2024-03-27 [E] +``` + +Then, send your key id to key server: + +```bash +gpg --keyserver keys.openpgp.org --send-key # e.g., 1E2CDAE4C08AD7D694D1CB139D7BE8E45E580BA4 +``` + +Among them, `keys.openpgp.org` is a randomly selected keyserver, you can use keyserver.ubuntu.com or any other +full-featured keyserver. + +#### Check whether the key is created successfully + +Uploading takes about one minute; after that, you can check by email at the corresponding keyserver. + +Uploading keys to the keyserver is mainly for joining +a [Web of Trust](https://infra.apache.org/release-signing.html#web-of-trust). + +#### Add your GPG public key to the project KEYS file + +The svn repository of the release branch is: https://dist.apache.org/repos/dist/release/fory + +Please add the public key to KEYS in the release branch: + +```bash +svn co --depth=files https://dist.apache.org/repos/dist/release/fory fory-dist +cd fory-dist +(gpg --list-sigs YOUR_NAME@apache.org && gpg --export --armor YOUR_NAME@apache.org) >> KEYS # Append your key to the KEYS file +svn add . # It is not needed if the KEYS document exists before. +svn ci -m "add gpg key for YOUR_NAME" # Later on, if you are asked to enter a username and password, just use your apache username and password. +``` + +#### Upload the GPG public key to your GitHub account + +- Enter https://github.com/settings/keys to add your GPG key. +- Please remember to bind the email address used in the GPG key to your GitHub + account (https://github.com/settings/emails) if you find "unverified" after adding it. + +### Further reading + +It's recommended but not mandatory to read following documents before making a release to know more details about apache +release: + +- Release policy: https://www.apache.org/legal/release-policy.html +- TLP release: https://infra.apache.org/release-distribution +- Release sign: https://infra.apache.org/release-signing.html +- Release publish: https://infra.apache.org/release-publishing.html +- Release download pages: https://infra.apache.org/release-download-pages.html +- Publishing maven artifacts: https://infra.apache.org/publishing-maven-artifacts.html + +## Start discussion about the release + +Start a discussion about the next release via sending email to: dev@fory.apache.org: + +Title: + +``` +[DISCUSS] Release Apache Fory ${release_version} +``` + +Content: + +``` +Hello, Apache Fory Community, + +This is a call for a discussion to release Apache Fory version ${release_version}. + +The planned change list for this release: + +https://github.com/apache/fory/compare/v${previous_release_version}...main + +Please leave your comments here about this release plan. We will bump the version in repo and start the release process after the discussion. + +Thanks, + +${name} +``` + +## Preparing for release + +If the discussion goes positive, you will need to prepare the release artifacts. + +### GitHub branch and tag + +- Create a new branch named `releases-${release_version}`. You can also run `python ci/release.py prepare -v ${release_version}` to create the branch, bump all versions, and create the preparation commit. +- Bump version to `${release_version}` by executing command `python ci/release.py bump_version -l all -version ${release_version}` if you do not use `prepare` +- Make a git commit and push the branch to `git@github.com:apache/fory.git` +- Create a new release-candidate tag by `git tag v${release_version}-${rc_version}`, then push it to `git@github.com:apache/fory.git` +- If the Go module under `go/fory` is part of this release, create and push the Go submodule tag after the vote passes. For example, for the final `${release_version}` release: + +```bash +git remote add apache git@github.com:apache/fory.git +git tag go/fory/v${release_version} +git push apache go/fory/v${release_version} +``` + +### Build and upload artifacts to SVN dist/dev repo + +First you need to build source release artifacts by `python ci/release.py build -v ${release_version}`. + +Then you need to upload it to svn dist repo. The dist repo of the dev branch +is: https://dist.apache.org/repos/dist/dev/fory + +```bash +# As this step will copy all the versions, it will take some time. If the network is broken, please use svn cleanup to delete the lock before re-execute it. +svn co https://dist.apache.org/repos/dist/dev/fory fory-dist-dev +``` + +Then, upload the artifacts: + +```bash +cd fory-dist-dev +# create a directory named by version +mkdir ${release_version}-${rc_version} +# copy source code and signature package to the versioned directory +cp ${repo_dir}/dist/* ${release_version}-${rc_version} +# check svn status +svn status +# add to svn +svn add ${release_version}-${rc_version} +# check svn status +svn status +# commit to SVN remote server +svn commit -m "Prepare for fory ${release_version}-${rc_version}" +``` + +Visit https://dist.apache.org/repos/dist/dev/fory/ to check the artifacts are uploaded correctly. + +### What to do if something goes wrong + +If some files are unexpected, you need to remove by `svn delete` and repeat the above upload process. + +## Voting + +### check version + +Fory requires votes from the Fory Community. + +- release_version: the version for fory, like 1.0.0. +- release_candidate_version: the version for voting, like 1.0.0-rc1. +- maven_artifact_number: the number for Maven staging artifacts, like 1001. Specifically, the maven_artifact_number can + be found by searching "fory" on https://repository.apache.org/#stagingRepositories. + +### Build the source code of fory and release it to nexus + +#### Configure Apache Account Passwords + +Before publishing Fory to Nexus, you need to securely configure your Apache account credentials. This step is critical +as passwords must be encrypted. + +First, open your Maven global settings file `settings.xml`, typically located at `~/.m2/settings.xml`. Add or modify the +following section: + +```xml + + + + apache.snapshots.https + your-apache-username + {your-encrypted-password} + + + apache.releases.https + your-apache-username + {your-encrypted-password} + + +``` + +**Important Notes:** + +- Replace `your-apache-username` with your Apache LDAP username +- Passwords must be encrypted using Maven's password encryption tool +- Encrypted passwords should be enclosed in curly braces `{}` + +Refer to the official documentation for detailed encryption +instructions: [Publishing Maven Artifacts](https://infra.apache.org/publishing-maven-artifacts.html) + +Steps to encrypt your password: + +1. Generate a master password (if you haven't already): + +2. ```sh + + mvn --encrypt-master-password your-master-password + + ``` + + Save the output to `~/.m2/settings-security.xml`: + +3. ```xml + + + {your-encrypted-master-password} + + + ``` + +4. Encrypt your Apache account password: + + ```sh + + mvn --encrypt-password your-apache-password + + ``` + + Place the encrypted output into the `password` field in `settings.xml` + +#### Build and Publish Java Module + +```sh + +# Navigate to the Java module directory +cd java + +# Execute Maven build and deploy to Nexus +# -T10: Use 10 threads for parallel build, improving speed +# clean: Clean the project +# deploy: Deploy to remote repository +# -Papache-release: Activate the release profile +# -DskipTests: Skip tests +mvn -T10 clean deploy --no-transfer-progress -DskipTests -Papache-release + +``` + +#### Build and Publish Kotlin Module + +```sh + +# Return to project root and navigate to Kotlin module +cd ../kotlin + +# Execute the same Maven command as Java module +# Configuration parameters are identical to Java module +mvn -T10 clean deploy --no-transfer-progress -DskipTests -Papache-release + +``` + +#### Build and Publish Scala Module + +```sh + +# Return to project root and navigate to Scala module +cd ../scala + +# Build and sign JARs for all Scala versions +# +publishSigned: Execute publishSigned for all configured Scala versions +echo "Starting to build Scala JARs..." +sbt +publishSigned + +# Prepare for upload to Sonatype (Nexus) +# sonatypePrepare: Prepare for Maven Central Repository release +echo "Starting upload preparation..." +sbt sonatypePrepare + +# Upload packaged JARs to Sonatype +# sonatypeBundleUpload: Upload prepared bundles +echo "Starting JAR upload..." +sbt sonatypeBundleUpload + +echo "Scala JAR deployment succeeded!" + +``` + +#### Close the Maven staging repository in Nexus + +After completing the publication of all modules, perform the following steps in Nexus: + +1. Log in to the Apache Nexus repository management interface +2. Navigate to the staging repositories page +3. Locate the latest Fory staging repository, such as `orgapachefory-1001` +4. Execute the "Close" operation to validate all uploaded artifacts +5. Record the staging repository ID for the vote email +6. Do not execute the "Release" operation until the vote passes + +These steps ensure all staged artifacts are verified before the community vote. + +### Build a pre-release + +You need to build a Pre-release before voting, such as: +https://github.com/apache/fory/releases/tag/v${release_version}-${rc_version} + +Pushing a `v*` tag triggers the tag-based package release workflows for Python, compiler, JavaScript, Rust, Dart, and C#. +For release-candidate tags that contain `-`, workflows publish prerelease or staging artifacts where the ecosystem supports +it, such as TestPyPI for Python packages and the `next` tag for npm packages. Monitor all triggered workflows before +starting the vote. + +### Fory Community Vote + +you need send a email to Fory Community: dev@fory.apache.org: + +Title: + +``` +[VOTE] Release Apache Fory v${release_version}-${rc_version} +``` + +Content: + +``` +Hello, Apache Fory Community: + +This is a call for vote to release Apache Fory +v${release_version}-${rc_version}. + +Apache Fory is a blazingly fast multi-language serialization framework +for idiomatic domain objects, schema IDL, and cross-language data +exchange. + +The discussion thread: +https://lists.apache.org/thread/xxr3od301g6v3ndj14zqc05byp9qvclh + +The change lists about this release: +https://github.com/apache/fory/compare/v${previous_release_version}...v${release_version}-${rc_version} + +The release candidates: +https://dist.apache.org/repos/dist/dev/fory/${release_version}-${rc_version}/ + +The maven staging for this release: +https://repository.apache.org/content/repositories/orgapachefory-${maven_artifact_number} + +Git tag for the release: +https://github.com/apache/fory/releases/tag/v${release_version}-${rc_version} + +If this release also publishes the Go module, include the Go submodule tag too: +https://github.com/apache/fory/releases/tag/go/fory/v${release_version} + +Git commit for the release: +https://github.com/apache/fory/commit/${release_commit} + +The artifacts signed with PGP key [${gpg_key_id}], corresponding to +[${apache_email}], that can be found in keys file: +https://downloads.apache.org/fory/KEYS + +The vote will be open for at least 72 hours until the necessary number of votes are reached. + +Please vote accordingly: + +[ ] +1 approve +[ ] +0 no opinion +[ ] -1 disapprove with the reason + +To learn more about Fory, please see https://fory.apache.org/ + +*Valid check is a requirement for a vote. *Checklist for reference: + +[ ] Download Fory is valid. +[ ] Checksums and PGP signatures are valid. +[ ] Source code distributions have correct names matching the current release. +[ ] LICENSE and NOTICE files are correct. +[ ] All files have license headers if necessary. +[ ] No compiled archives bundled in source archive. +[ ] Can compile from source. + +How to Build and Test, please refer to: https://github.com/apache/fory/blob/main/docs/DEVELOPMENT.md + + +Thanks, +${name} +``` + +After at least 3 +1 binding votes from Apache Fory PMC members and no veto, +first, reply to the above voting thread to notify that the voting has ended. + +``` +Hi all, + +The vote for Release Apache Fory v${release_version}-${rc_version} is closed now. + +Thanks to everyone for helping checking and voting for the release. + +I will close the vote later in another thread. + +Best, +${name} +``` + +Immediately afterward, launch a new voting thread to claim the voting results. + +Title: + +``` +[RESULT][VOTE] Release Apache Fory v${release_version}-${rc_version} +``` + +Content: + +``` +Hello, Apache Fory Community, + +The vote to release Apache Fory v${release_version}-${rc_version} has passed. + +The vote PASSED with 3 binding +1 votes and 0 -1 votes: + +Binding votes: + +- xxx +- yyy +- zzz + +Vote thread: ${vote_thread_url} + +Thanks, + +${name} +``` + +### What if vote fails + +If the vote failed, click "Drop" to drop the staging Maven artifacts. + +Address the raised issues, then bump `rc_version` and file a new vote again. + +## Official Release + +### Publish artifacts to SVN Release Directory + +- release_version: the release version for fory, like 1.0.0 +- release_candidate_version: the version for voting, like 1.0.0-rc1 + +```bash +svn mv https://dist.apache.org/repos/dist/dev/fory/${release_version}-${rc_version} https://dist.apache.org/repos/dist/release/fory/${release_version} -m "Release fory ${release_version}" +``` + +In the repository at https://dist.apache.org/repos/dist/dev/fory/, if any +outdated release_candidate_version are left behind when releasing the release_version, +please clear them to keep the dev repository tidy. + +When `https://archive.apache.org/dist/fory/${release_version}/` is +accessible (confirming that the release_version has been successfully released +and archived), we may clean up the previous release version in the release repository, +leaving only the current version. + +### Update Fory&Fory-Site content + +Submit a PR to https://github.com/apache/fory-site to update Fory-site. +Reference implementation: [#283](https://github.com/apache/fory-site/pull/283) +and [#285](https://github.com/apache/fory-site/pull/285). + +#### Update Fory-Site + +In general, the following key areas need to be modified: + +1. Write a new announcement, for example: + Add a new markdown file under the blog folder: + +``` +The Apache Fory team is pleased to announce the [?] release. This is a major release that includes [? PR](https://github.com/apache/fory/compare/v[?]...v[?]) from ? distinct contributors. See the [Install](https://fory.apache.org/docs/start/install) Page to learn how to get the libraries for your platform. +``` + +2. Replace versions by upgrading old versions to new ones. + For example, in [install](https://fory.apache.org/docs/start/install/#java) section, it is necessary to update the documentation for both the development branch and the latest release branch:: + +``` + + org.apache.fory + fory-core + 0.11.2 + +``` + +3. Update the download page, checksum and signature examples, release notes link, current docs, zh-CN translations, versioned docs snapshot, `versions.json`, and `docusaurus.config.ts` default docs version. + +#### Update Fory + +Submit a PR to https://github.com/apache/fory to update [README](https://github.com/apache/fory/blob/main/README.md), +package metadata for the next development version, and user-facing install snippets that should point at the latest +released version. + +### GitHub officially released + +You need to officially release this version in the Fory project +Reference implementation: https://github.com/apache/fory/releases/tag/v${release_version} + +Create and push the final `v${release_version}` tag from the voted commit after the vote passes. This tag triggers the +final package publishing workflows for Python, compiler, JavaScript, Rust, Dart, and C#. Monitor every workflow to +completion before sending the announcement. + +### Release Maven artifacts + +- maven_artifact_number: the number for Maven staging artifacts, like 1001. +- Open https://repository.apache.org/#stagingRepositories. +- Find the artifact `orgapachefory-${maven_artifact_number}`, click "Release". + +### Send the announcement + +Send the release announcement to dev@fory.apache.org and CC announce@apache.org. + +Title: + +``` +[ANNOUNCE] Apache Fory ${release_version} released +``` + +Content: + +``` +Hi all, + +The Apache Fory community is pleased to announce +that Apache Fory ${release_version} is now available. + +Apache Fory is a blazingly fast multi-language serialization framework +for idiomatic domain objects, schema IDL, and cross-language data +exchange. + +This release includes ${pr_count} PRs from ${contributor_count} contributors. + +Highlights in ${release_version} include: + +- ... + +Release blog, with details and examples: +https://fory.apache.org/blog/fory_${release_version_with_underscores}_release + +The release notes are available here: +https://github.com/apache/fory/releases/tag/v${release_version} + +For the complete list of changes: +https://github.com/apache/fory/compare/v${previous_release_version}...v${release_version} + +Apache Fory website: https://fory.apache.org/ + +Download Links: https://fory.apache.org/download + +Fory Resources: +- Fory github repo: https://github.com/apache/fory +- Issue: https://github.com/apache/fory/issues +- Mailing list: dev@fory.apache.org + +We are looking to grow our community and welcome new contributors. If +you are interested in contributing to Fory, please contact us on the +mailing list or on GitHub. We will be happy to help you get started. + +------------------ +Best Regards, +${your_name} +``` + +Remember to use plain text instead of rich text format, or you may be rejected when CC announce@apache.org + +After completing the above steps, the Fory release process comes to an end. diff --git a/versioned_docs/version-1.3.0/community/how_to_verify.md b/versioned_docs/version-1.3.0/community/how_to_verify.md new file mode 100644 index 00000000000..d4a871c6afb --- /dev/null +++ b/versioned_docs/version-1.3.0/community/how_to_verify.md @@ -0,0 +1,125 @@ +--- +title: How to verify +sidebar_position: 0 +id: how_to_verify +--- + +## Download the candidate version + +```bash +#If there is svn locally, you can clone to the local +svn co https://dist.apache.org/repos/dist/dev/fory/${release_version}-${rc_version}/ +# You can download the material file directly +wget https://dist.apache.org/repos/dist/dev/fory/${release_version}-${rc_version}/xxx.xxx +``` + +## Verify checksums and signatures + +First you need to install gpg: + +```bash +apt-get install gnupg +# or +yum install gnupg +# or +brew install gnupg +``` + +Then import the Fory release manager's public key: + +```bash +curl https://downloads.apache.org/fory/KEYS > KEYS # Download KEYS +gpg --import KEYS # Import KEYS to local +# Then, trust the public key: +gpg --edit-key # Edit the key(mentioned in vote email) +# It will enter the interactive mode, use the following command to trust the key: +gpg (GnuPG) 2.0.22; Copyright (C) 2013 Free Software Foundation, Inc. +This is free software: you are free to change and redistribute it. +There is NO WARRANTY, to the extent permitted by law. + + +pub 4096R/5E580BA4 created: 2024-03-27 expires: never usage: SC + trust: unknown validity: unknown +sub 4096R/A31EF728 created: 2024-03-27 expires: never usage: E +[ unknown] (1). chaokunyang (CODE SIGNING KEY) + +gpg> trust +pub 4096R/5E580BA4 created: 2024-03-27 expires: never usage: SC + trust: unknown validity: unknown +sub 4096R/A31EF728 created: 2024-03-27 expires: never usage: E +[ unknown] (1). chaokunyang (CODE SIGNING KEY) + +Please decide how far you trust this user to correctly verify other users' keys +(by looking at passports, checking fingerprints from different sources, etc.) + + 1 = I don't know or won't say + 2 = I do NOT trust + 3 = I trust marginally + 4 = I trust fully + 5 = I trust ultimately + m = back to the main menu + +Your decision? 5 +Do you really want to set this key to ultimate trust? (y/N) y + +pub 4096R/5E580BA4 created: 2024-03-27 expires: never usage: SC + trust: ultimate validity: unknown +sub 4096R/A31EF728 created: 2024-03-27 expires: never usage: E +[ unknown] (1). chaokunyang (CODE SIGNING KEY) +Please note that the shown key validity is not necessarily correct +unless you restart the program. +``` + +Next verify signature: + +```bash +for i in *.tar.gz; do echo $i; gpg --verify $i.asc $i; done +``` + +If something like the following appears, it means the signature is correct: + +```bash +apache-fory-0.12.0-src.tar.gz +gpg: Signature made Wed 17 Apr 2024 11:49:45 PM CST using RSA key ID 5E580BA4 +gpg: checking the trustdb +gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model +gpg: depth: 0 valid: 1 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 1u +gpg: Good signature from "chaokunyang (CODE SIGNING KEY) " +``` + +Then verify checksum: + +```bash +for i in *.tar.gz; do echo $i; sha512sum --check $i.sha512*; done +``` + +It should output something like: + +```bash +apache-fory-0.12.0-src.tar.gz +apache-fory-0.12.0-src.tar.gz: OK +``` + +A quick way to verify above is: + +```bash +curl -s https://raw.githubusercontent.com/apache/fory/main/ci/release.py | python3 - verify -v 0.5.0 +``` + +## Check the file content of the source package + +Unzip `apache-fory-${release_version}-${rc_version}-src.tar.gz` and check the follows: + +- LICENSE and NOTICE files are correct for the repository. +- All files have ASF license headers if necessary. +- Building is OK. + +## Check the Maven artifacts of fory-java + +Download the artifacts from https://repository.apache.org/content/repositories/orgapachefory-${maven_artifact_number}/. + +You can check the follows: + +- Checksum of JARs match the bundled checksum file. +- Signature of JARs match the bundled signature file. +- JARs is reproducible locally. This means you can build the JARs on your machine and verify the checksum is the same with the bundled one. diff --git a/versioned_docs/version-1.3.0/compiler/_category_.json b/versioned_docs/version-1.3.0/compiler/_category_.json new file mode 100644 index 00000000000..f835a100030 --- /dev/null +++ b/versioned_docs/version-1.3.0/compiler/_category_.json @@ -0,0 +1,6 @@ +{ + "position": 3, + "label": "Schema IDL & Compiler", + "collapsible": true, + "collapsed": true +} diff --git a/versioned_docs/version-1.3.0/compiler/compiler-guide.md b/versioned_docs/version-1.3.0/compiler/compiler-guide.md new file mode 100644 index 00000000000..184b48cc4cf --- /dev/null +++ b/versioned_docs/version-1.3.0/compiler/compiler-guide.md @@ -0,0 +1,915 @@ +--- +title: Compiler Guide +sidebar_position: 3 +id: compiler_guide +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 guide covers installation, usage, and integration of the Fory IDL compiler. + +## Installation + +### From Source + +```bash +cd compiler +pip install -e . +``` + +### Verify Installation + +```bash +foryc --help +``` + +## Command Line Interface + +### Basic Usage + +```bash +foryc [OPTIONS] FILES... +``` + +```bash +foryc --scan-generated [OPTIONS] +``` + +### Options + +Compile options: + +| Option | Description | Default | +| ------------------------------------- | ------------------------------------------------------ | ------------- | +| `--lang` | Comma-separated target languages | `all` | +| `--output`, `-o` | Output directory | `./generated` | +| `-I`, `--proto_path`, `--import_path` | Add directory to import search path (can be repeated) | (none) | +| `--java_out=DST_DIR` | Generate Java code in DST_DIR | (none) | +| `--python_out=DST_DIR` | Generate Python code in DST_DIR | (none) | +| `--cpp_out=DST_DIR` | Generate C++ code in DST_DIR | (none) | +| `--go_out=DST_DIR` | Generate Go code in DST_DIR | (none) | +| `--rust_out=DST_DIR` | Generate Rust code in DST_DIR | (none) | +| `--csharp_out=DST_DIR` | Generate C# code in DST_DIR | (none) | +| `--javascript_out=DST_DIR` | Generate JavaScript/TypeScript code in DST_DIR | (none) | +| `--swift_out=DST_DIR` | Generate Swift code in DST_DIR | (none) | +| `--dart_out=DST_DIR` | Generate Dart code in DST_DIR | (none) | +| `--scala_out=DST_DIR` | Generate Scala 3 code in DST_DIR | (none) | +| `--kotlin_out=DST_DIR` | Generate Kotlin code in DST_DIR | (none) | +| `--go_nested_type_style` | Go nested type naming: `camelcase` or `underscore` | `underscore` | +| `--swift_namespace_style` | Swift namespace style: `enum` or `flatten` | `enum` | +| `--emit-fdl` | Emit translated FDL (for non-FDL inputs) | `false` | +| `--emit-fdl-path` | Write translated FDL to this path (file or directory) | (stdout) | +| `--grpc` | Generate gRPC service companions for supported outputs | `false` | +| `--grpc-python-mode=MODE` | Python gRPC mode: `async` or `sync` | `async` | +| `--grpc-web` | Generate JavaScript gRPC-Web client companions | `false` | + +Schema-level file options are supported for language-specific generation choices. +For `go_nested_type_style` and `swift_namespace_style`, the CLI flag overrides +the schema option when both are present. Rust temporal codegen has no CLI flag: +set `option rust_use_chrono_temporal_types = true;` in the schema to generate +`chrono::NaiveDate`, `chrono::NaiveDateTime`, and `chrono::Duration` instead of +the default `fory::Date`, `fory::Timestamp`, and `fory::Duration`. Crates that +compile generated chrono-based Rust code must depend on `chrono` and enable +Fory's `chrono` feature. + +Scan options (with `--scan-generated`): + +| Option | Description | Default | +| ------------ | ------------------------------ | ------- | +| `--root` | Root directory to scan | `.` | +| `--relative` | Print paths relative to root | `false` | +| `--delete` | Delete matched generated files | `false` | +| `--dry-run` | Scan/print only, do not delete | `false` | + +### Scan Generated Files + +Use `--scan-generated` to find files produced by `foryc`. The scanner walks +the tree recursively, skips `build/`, `target/`, and hidden directories, and prints +each generated file as it is found. + +```bash +# Scan current directory +foryc --scan-generated + +# Scan a specific root +foryc --scan-generated --root ./src + +# Print paths relative to the scan root +foryc --scan-generated --root ./src --relative + +# Delete scanned generated files +foryc --scan-generated --root ./src --delete + +# Dry-run (scan and print only) +foryc --scan-generated --root ./src --dry-run +``` + +### Examples + +**Compile for all languages:** + +```bash +foryc schema.fdl +``` + +**Compile for a selected subset of languages:** + +```bash +foryc schema.fdl --lang java,python,csharp,javascript,swift,dart,kotlin +``` + +**Specify output directory:** + +```bash +foryc schema.fdl --output ./src/generated +``` + +**Compile multiple files:** + +```bash +foryc user.fdl order.fdl product.fdl --output ./generated +``` + +**Compile a simple schema containing service definitions (Java + Python + Go + Rust + C# + Dart + Scala + Kotlin + JavaScript models):** + +```bash +foryc compiler/examples/service.fdl --java_out=./generated/java --python_out=./generated/python --go_out=./generated/go --rust_out=./generated/rust --csharp_out=./generated/csharp --dart_out=./generated/dart --scala_out=./generated/scala --kotlin_out=./generated/kotlin --javascript_out=./generated/javascript +``` + +**Generate Java, Python, Go, Rust, C#, Dart, Scala, Kotlin, and Node.js JavaScript gRPC service companions:** + +```bash +foryc compiler/examples/service.fdl --java_out=./generated/java --python_out=./generated/python --go_out=./generated/go --rust_out=./generated/rust --csharp_out=./generated/csharp --dart_out=./generated/dart --scala_out=./generated/scala --kotlin_out=./generated/kotlin --javascript_out=./generated/javascript --grpc +``` + +The generated gRPC service code uses Fory to serialize request and response +payloads. Java output imports grpc-java APIs, Python output defaults to +`grpc.aio`, Go output imports grpc-go, Rust output imports `tonic` and `bytes`, +Scala output imports grpc-java APIs, and Kotlin output imports grpc-java and +grpc-kotlin APIs and uses coroutine stubs. C# output imports `Grpc.Core.Api` +types and can be hosted with normal .NET gRPC packages such as `Grpc.AspNetCore` +or called through `Grpc.Net.Client`. Dart output imports `package:grpc`. +JavaScript output imports `@grpc/grpc-js`. +Applications that compile or run those generated service files must provide +their own gRPC dependencies. Fory packages do not add a hard gRPC dependency for +this feature. + +Generate synchronous Python gRPC companions for existing sync `grpcio` +applications with: + +```bash +foryc compiler/examples/service.fdl --python_out=./generated/python --grpc --grpc-python-mode=sync +``` + +**Generate JavaScript gRPC-Web browser clients:** + +```bash +foryc compiler/examples/service.fdl --javascript_out=./generated/javascript --grpc-web +``` + +Use `--grpc` and `--grpc-web` together when the same JavaScript output should +include both Node.js and browser companions. The browser companion imports +`grpc-web`; the application must provide the package and a gRPC-Web compatible +server or proxy. + +**Use import search paths:** + +```bash +# Add a single import path +foryc src/main.fdl -I libs/common + +# Add multiple import paths (repeated option) +foryc src/main.fdl -I libs/common -I libs/types + +# Add multiple import paths (comma-separated) +foryc src/main.fdl -I libs/common,libs/types,third_party/ + +# Using --proto_path (protoc-compatible alias) +foryc src/main.fdl --proto_path=libs/common + +# Mix all styles +foryc src/main.fdl -I libs/common,libs/types --proto_path third_party/ +``` + +**Language-specific output directories (protoc-style):** + +```bash +# Generate only Java code to a specific directory +foryc schema.fdl --java_out=./src/main/java + +# Generate multiple languages to different directories +foryc schema.fdl --java_out=./java/gen --python_out=./python/src --cpp_out=./cpp/gen --go_out=./go/gen --rust_out=./rust/gen --csharp_out=./csharp/gen --javascript_out=./javascript/src --swift_out=./swift/gen --dart_out=./dart/gen --scala_out=./scala/gen --kotlin_out=./kotlin/gen + +# Combine with import paths +foryc schema.fdl --java_out=./gen/java -I proto/ -I common/ + +# Generate Scala 3 code to a specific directory +foryc schema.fdl --scala_out=./src/main/scala + +# Generate Scala 3 models and gRPC service companions +foryc service.fdl --scala_out=./src/main/scala --grpc + +# Generate Kotlin code to a specific directory +foryc schema.fdl --kotlin_out=./src/main/kotlin +``` + +When using `--{lang}_out` options: + +- Only the specified languages are generated (not all languages) +- The compiler writes under the specified directory (language-specific generators may still create package/module subdirectories) +- This is compatible with protoc-style workflows + +**Inspect translated Fory IDL from proto/fbs input:** + +```bash +# Print translated Fory IDL to stdout +foryc schema.proto --emit-fdl + +# Write translated Fory IDL to a directory +foryc schema.fbs --emit-fdl --emit-fdl-path ./translated +``` + +## Import Path Resolution + +When compiling Fory IDL files with imports, the compiler searches for imported files in this order: + +1. **Relative to the importing file (default)** - The directory containing the file with the import statement is always searched first, automatically. No `-I` flag needed for same-directory imports. +2. **Each `-I` path in order** - Additional search paths specified on the command line + +**Same-directory imports work automatically:** + +```protobuf +// main.fdl +import "common.fdl"; // Found if common.fdl is in the same directory +``` + +```bash +# No -I needed for same-directory imports +foryc main.fdl +``` + +**Example project structure:** + +``` +project/ +├── src/ +│ └── main.fdl # import "common.fdl"; +└── libs/ + └── common.fdl +``` + +**Without `-I` (fails):** + +```bash +$ foryc src/main.fdl +Import error: Import not found: common.fdl + Searched in: /project/src +``` + +**With `-I` (succeeds):** + +```bash +$ foryc src/main.fdl -I libs/ +Compiling src/main.fdl... + Resolved 1 import(s) +``` + +## Supported Languages + +| Language | Flag | Output Extension | Description | +| --------------------- | ------------ | ---------------- | -------------------------------------- | +| Java | `java` | `.java` | POJOs with Fory annotations | +| Python | `python` | `.py` | Dataclasses with type hints | +| Go | `go` | `.go` | Structs with struct tags | +| Rust | `rust` | `.rs` | Structs with derive macros | +| C++ | `cpp` | `.h` | Structs with FORY macros | +| C# | `csharp` | `.cs` | Classes with Fory attributes | +| JavaScript/TypeScript | `javascript` | `.ts` | Interfaces with schema module helpers | +| Swift | `swift` | `.swift` | Fory Swift model macros | +| Dart | `dart` | `.dart` | `@ForyStruct` classes with annotations | +| Scala | `scala` | `.scala` | Scala 3 models with macro derivation | +| Kotlin | `kotlin` | `.kt` | Kotlin models with KSP serializers | + +## Output Structure + +### Java + +``` +generated/ +└── java/ + └── com/ + └── example/ + ├── User.java + ├── Order.java + ├── Status.java + └── ExampleForyModule.java +``` + +- One file per type (enum or message) +- Package structure matches Fory IDL package +- Schema module class generated + +### Python + +``` +generated/ +└── python/ + └── example.py +``` + +- Single module with all types +- Module name derived from package +- Registration function included + +### Go + +``` +generated/ +└── go/ + └── example/ + └── example.go +``` + +- Single file with all types +- Directory and package name are derived from `go_package` or the Fory IDL package +- Registration function included + +### Rust + +``` +generated/ +└── rust/ + └── example.rs +``` + +- Single module with all types +- Module name derived from package +- Registration function included + +### C++ + +``` +generated/ +└── cpp/ + └── example.h +``` + +- Single header file +- Namespace matches package (dots to `::`) +- Header guards and forward declarations + +### JavaScript/TypeScript + +``` +generated/ +└── javascript/ + ├── example.ts + ├── example_grpc.ts # with --grpc + └── example_grpc_web.ts # with --grpc-web +``` + +- Single `.ts` file per schema +- `export interface` declarations for messages +- `export enum` declarations for enums +- Discriminated unions with case enums +- Schema helpers `registerXxxTypes(fory)` plus default `serializeX` and + `deserializeX` helpers included +- `--grpc` emits a Node.js companion using `@grpc/grpc-js` +- `--grpc-web` emits a browser client companion using `grpc-web` + +### C\# + +``` +generated/ +└── csharp/ + └── example/ + └── Example.cs +``` + +- Single `.cs` file per schema named from the normalized PascalCase source file + stem +- Namespace uses `csharp_namespace` (if set) or Fory IDL package +- Includes source-file-prefixed `XXXForyModule` installation helper and + `ToBytes`/`FromBytes` methods +- Imported schemas are installed transitively (for example `root.idl` importing + `addressbook.fdl` and `tree.fdl`) +- With `--grpc`, one `Grpc.cs` companion per service next to the + schema file output + +### Swift + +``` +generated/ +└── swift/ + └── addressbook/ + └── addressbook.swift +``` + +- Single `.swift` file per schema +- Package segments are mapped to nested Swift enums (for example `addressbook.*` -> `Addressbook.*`) +- Generated messages use `@ForyStruct`, enums use `@ForyEnum`, and unions use `@ForyUnion`/`@ForyCase` +- Union types are generated as tagged enums with associated payload values +- Each schema includes a schema-file module owner and `toBytes`/`fromBytes` + helpers +- Imported schemas are installed transitively by generated module helpers + +### Dart + +``` +generated/ +└── dart/ + └── package/ + ├── package.dart + └── package.fory.dart +``` + +- Two files per schema: a main `.dart` file with annotated types, and a `.fory.dart` part file with generated serializers +- Package segments map to directories (e.g., `demo.foo` → `demo/foo/`) +- IDL module class included in the main file; generated serializer metadata is + included in the part file +- Typed arrays used for non-optional, non-ref primitive lists (e.g., `Int32List`) +- With `--grpc`, one `_grpc.dart` companion per schema is generated next to + the model file, containing each service's `Client` and `ServiceBase`; it + imports `package:grpc` + +### Scala + +``` +generated/ +└── scala/ + └── example/ + ├── User.scala + ├── Status.scala + ├── Animal.scala + ├── ExampleServiceGrpc.scala + └── ExampleForyModule.scala +``` + +- One Scala 3 source file per generated type +- Package structure matches the Fory IDL package +- Messages derive `org.apache.fory.scala.ForySerializer` +- `optional T` fields use `Option[T]` +- Enums use Scala 3 `enum` +- Unions use Scala 3 ADT `enum` with `@ForyUnion`, `@ForyCase`, and an `Unknown` +- Schema module object included +- With `--grpc`, one `Grpc.scala` service companion is generated + per local service definition + +### Kotlin + +``` +generated/ +└── kotlin/ + └── example/ + ├── User.kt + ├── Status.kt + ├── Animal.kt + └── ExampleForyModule.kt +``` + +- One Kotlin source file per generated type +- Package structure uses `kotlin_package` when set, otherwise the Fory IDL package +- Messages use `@ForyStruct` and KSP-generated serializers +- Enums use stable Fory enum IDs +- Unions use sealed classes with `@ForyUnion`, `@ForyCase`, and an unknown-case carrier +- Schema module object included +- With `--grpc`, one `GrpcKt.kt` coroutine service companion per + service + +### C# IDL Matrix Verification + +Run the end-to-end C# IDL matrix (FDL/IDL/Proto/FBS generation plus roundtrip tests): + +```bash +cd integration_tests/idl_tests +./run_csharp_tests.sh +``` + +This runner executes same-schema and compatible roundtrips across: + +- `addressbook`, `auto_id`, `complex_pb` primitives +- `collection` and union/list variants +- `optional_types` +- `any_example` (`.fdl`) and `any_example` (`.proto`) +- `tree` and `graph` reference-tracking cases +- `monster.fbs` and `complex_fbs.fbs` +- `root.idl` cross-package import coverage +- evolving schema compatibility cases + +### Swift IDL Matrix Verification + +Run the end-to-end Swift IDL matrix (FDL/IDL/Proto/FBS generation plus roundtrip tests): + +```bash +cd integration_tests/idl_tests +./run_swift_tests.sh +``` + +This runs: + +- local Swift IDL roundtrip tests in both compatible and same-schema modes +- Java-driven peer roundtrip validation with `IDL_PEER_LANG=swift` + +The script also sets `DATA_FILE*` variables so file-based roundtrip paths are exercised. + +## Build Integration + +### Maven (Java) + +Add to your `pom.xml`: + +```xml + + + + org.codehaus.mojo + exec-maven-plugin + 3.1.0 + + + generate-fory-types + generate-sources + + exec + + + foryc + + ${project.basedir}/src/main/fdl/schema.fdl + --java_out + ${project.build.directory}/generated-sources/fory + + + + + + + +``` + +Add generated sources: + +```xml + + + + org.codehaus.mojo + build-helper-maven-plugin + 3.4.0 + + + generate-sources + + add-source + + + + ${project.build.directory}/generated-sources/fory + + + + + + + +``` + +### Gradle (Java/Kotlin) + +Add to `build.gradle`: + +```groovy +task generateForyTypes(type: Exec) { + commandLine 'foryc', + "${projectDir}/src/main/fdl/schema.fdl", + '--java_out', "${buildDir}/generated/sources/fory" +} + +compileJava.dependsOn generateForyTypes + +sourceSets { + main { + java { + srcDir "${buildDir}/generated/sources/fory" + } + } +} +``` + +### Python (setuptools) + +Add to `setup.py` or `pyproject.toml`: + +```python +# setup.py +from setuptools import setup +from setuptools.command.build_py import build_py +import subprocess + +class BuildWithForyIdl(build_py): + def run(self): + subprocess.run([ + 'foryc', + 'schema.fdl', + '--python_out', 'src/generated' + ], check=True) + super().run() + +setup( + cmdclass={'build_py': BuildWithForyIdl}, + # ... +) +``` + +### Go (go generate) + +Add to your Go file: + +```go +//go:generate foryc ../schema.fdl --lang go --output . +package models +``` + +Run: + +```bash +go generate ./... +``` + +### Rust (build.rs) + +Add to `build.rs`: + +```rust +use std::process::Command; + +fn main() { + println!("cargo:rerun-if-changed=schema.fdl"); + + let status = Command::new("foryc") + .args(&["schema.fdl", "--rust_out", "src/generated"]) + .status() + .expect("Failed to run foryc"); + + if !status.success() { + panic!("Fory IDL compilation failed"); + } +} +``` + +### CMake (C++) + +Add to `CMakeLists.txt`: + +```cmake +find_program(FORY_COMPILER foryc) + +add_custom_command( + OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/generated/example.h + COMMAND ${FORY_COMPILER} + ${CMAKE_CURRENT_SOURCE_DIR}/schema.fdl + --cpp_out ${CMAKE_CURRENT_SOURCE_DIR}/generated + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/schema.fdl + COMMENT "Generating Fory IDL types" +) + +add_custom_target(generate_fory_idl DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/generated/example.h) + +add_library(mylib ...) +add_dependencies(mylib generate_fory_idl) +target_include_directories(mylib PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/generated) +``` + +### Bazel + +Create a rule in `BUILD`: + +```python +genrule( + name = "generate_fdl", + srcs = ["schema.fdl"], + outs = ["generated/example.h"], + cmd = "$(location //:fory_compiler) $(SRCS) --cpp_out $(RULEDIR)/generated", + tools = ["//:fory_compiler"], +) + +cc_library( + name = "models", + hdrs = [":generate_fdl"], + # ... +) +``` + +### Dart / Flutter + +Add the Fory dependency to `pubspec.yaml`: + +```yaml +dependencies: + fory: ^1.3.0 + +dev_dependencies: + build_runner: ^2.4.0 +``` + +Generate schema types with the compiler: + +```bash +foryc schema.fdl --dart_out=lib/generated +``` + +Then run `build_runner` to generate the serializers: + +```bash +dart run build_runner build +``` + +## Error Handling + +### Syntax Errors + +``` +Error: Line 5, Column 12: Expected ';' after field declaration +``` + +Fix: Check the indicated line for missing semicolons or syntax issues. + +### Duplicate Type Names + +``` +Error: Duplicate type name: User +``` + +Fix: Ensure each enum and message has a unique name within the file. + +### Duplicate Type IDs + +``` +Error: Duplicate type ID 100: User and Order +``` + +Fix: Assign unique type IDs to each type. + +### Unknown Type References + +``` +Error: Unknown type 'Address' in Customer.address +``` + +Fix: Define the referenced type before using it, or check for typos. + +Service RPC request and response types are validated in the same way: an RPC such as +`rpc SayHello (HelloRequest) returns (HelloReply);` must reference defined message +types, otherwise the validator reports an `Unknown type '...'` error on the RPC line. + +### Duplicate Field Numbers + +``` +Error: Duplicate field number 1 in User: name and id +``` + +Fix: Assign unique field numbers within each message. + +## Best Practices + +### Project Structure + +``` +project/ +├── fdl/ +│ ├── common.fdl # Shared types +│ ├── user.fdl # User domain +│ └── order.fdl # Order domain +├── src/ +│ └── generated/ # Generated code (git-ignored) +└── build.gradle +``` + +### Version Control + +- **Track**: Fory IDL schema files +- **Ignore**: Generated code (can be regenerated) + +Add to `.gitignore`: + +``` +# Generated Fory IDL code +src/generated/ +generated/ +``` + +### CI/CD Integration + +Always regenerate during builds: + +```yaml +# GitHub Actions example +steps: + - name: Install Fory IDL Compiler + run: pip install ./compiler + + - name: Generate Types + run: foryc fdl/*.fdl --output src/generated + + - name: Build + run: ./gradlew build +``` + +### Schema Evolution + +When modifying schemas: + +1. **Never reuse field numbers** - Mark as reserved instead +2. **Never change type IDs** - They're part of the binary format +3. **Add new fields** - Use new field numbers +4. **Use `optional`** - For backward compatibility + +```protobuf +message User [id=100] { + string id = 1; + string name = 2; + // Field 3 was removed, don't reuse + optional string email = 4; // New field +} +``` + +## Troubleshooting + +### Command Not Found + +``` +foryc: command not found +``` + +**Solution:** Ensure the compiler is installed and in your PATH: + +```bash +pip install -e ./compiler +# Or add to PATH +export PATH=$PATH:~/.local/bin +``` + +### Permission Denied + +``` +Permission denied: ./generated +``` + +**Solution:** Ensure write permissions on the output directory: + +```bash +chmod -R u+w ./generated +``` + +### Import Errors in Generated Code + +**Java:** Ensure Fory dependency is in your project: + +```xml + + org.apache.fory + fory-core + ${fory.version} + +``` + +**Python:** Ensure pyfory is installed: + +```bash +pip install pyfory +``` + +**Go:** Ensure fory module is available: + +```bash +go get github.com/apache/fory/go/fory +``` + +**Rust:** Ensure fory crate is in `Cargo.toml`: + +```toml +[dependencies] +fory = "x.y.z" +``` + +**C++:** Ensure Fory headers are in include path. + +**Dart:** Ensure the fory package is in `pubspec.yaml`: + +```yaml +dependencies: + fory: ^1.3.0 +``` diff --git a/versioned_docs/version-1.3.0/compiler/flatbuffers-idl.md b/versioned_docs/version-1.3.0/compiler/flatbuffers-idl.md new file mode 100644 index 00000000000..0ad11a64874 --- /dev/null +++ b/versioned_docs/version-1.3.0/compiler/flatbuffers-idl.md @@ -0,0 +1,232 @@ +--- +title: FlatBuffers IDL Support +sidebar_position: 7 +id: flatbuffers_idl +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 page explains how Apache Fory consumes FlatBuffers schemas (`.fbs`) and +translates them into Fory IR for code generation. + +## What This Page Covers + +- When to use FlatBuffers input with Fory +- Exact FlatBuffers to Fory mapping behavior +- Supported Fory-specific attributes in `.fbs` +- Adoption notes and generated-code differences + +## Why Use Apache Fory + +- Idiomatic generated code: Fory generates language-idiomatic classes/structs + that can be used directly as domain objects. +- Java performance: In Java object-serialization workloads, Fory is faster than + FlatBuffers in Fory benchmarks. +- Other languages: serialization performance is generally in a similar range. +- Deserialization in practice: FlatBuffers can be faster when callers read + directly from its buffer, but applications that need native objects still + require conversion, and that conversion step can dominate read cost. In those + cases, Fory deserialization is often faster end-to-end. +- Easier APIs: Fory uses direct native objects, so you do not need to + reverse-build tables or manually manage offsets. +- Better graph modeling: Shared and circular references are first-class features + in Fory. + +## Quick Decision Guide + +| Situation | Recommended Path | +| ------------------------------------------------------------------ | ---------------------- | +| You already have `.fbs` schemas and want Fory APIs/codegen | Use FlatBuffers input | +| You are starting new schema work and want full Fory syntax control | Use native Fory IDL | +| You need FlatBuffers wire compatibility | Keep FlatBuffers stack | +| You need Fory object-graph semantics (`ref`, weak refs, etc.) | Use Fory | + +## FlatBuffers to Fory Mapping + +### Schema-Level Rules + +- `namespace` maps to Fory package namespace. +- `include` entries map to Fory imports. +- `table` is translated as `evolving=true`. +- `struct` is translated as `evolving=false`. +- `root_type` is parsed but ignored by Fory codegen. +- `file_identifier` and `file_extension` are parsed but not used by Fory codegen. + +### Field Numbering + +FlatBuffers fields do not have explicit field IDs. Fory assigns field numbers by +source declaration order, starting at `1`. + +### Scalar Type Mapping + +| FlatBuffers | Fory Type | +| ----------- | --------- | +| `byte` | `int8` | +| `ubyte` | `uint8` | +| `short` | `int16` | +| `ushort` | `uint16` | +| `int` | `int32` | +| `uint` | `uint32` | +| `long` | `int64` | +| `ulong` | `uint64` | +| `float` | `float32` | +| `double` | `float64` | +| `bool` | `bool` | +| `string` | `string` | + +Vectors (`[T]`) map to Fory lists. + +### Unions + +FlatBuffers unions map to Fory unions. + +- Case IDs are assigned by declaration order, starting at `1`. +- Case names are derived from type names using snake_case field naming. + +**FlatBuffers** + +```fbs +union Payload { + Note, + Metric +} + +table Container { + payload: Payload; +} +``` + +**Fory shape after translation** + +```protobuf +union Payload { + Note note = 1; + Metric metric = 2; +} + +message Container { + Payload payload = 1; +} +``` + +### Services + +FlatBuffers `rpc_service` definitions are translated to Fory services. With +`--grpc`, the compiler emits gRPC service companions for supported outputs such +as Java, Python, Go, Rust, C#, Dart, Scala, Kotlin, and JavaScript. JavaScript +browser clients are generated with `--grpc-web`. These companions use Fory +serialization for request and response payloads. + +```fbs +rpc_service SearchService { + Lookup(SearchRequest):SearchResponse; + StreamLookup(SearchRequest):SearchResponse (streaming: "server"); +} +``` + +```bash +foryc api.fbs --java_out=./generated/java --python_out=./generated/python --go_out=./generated/go --rust_out=./generated/rust --csharp_out=./generated/csharp --dart_out=./generated/dart --scala_out=./generated/scala --kotlin_out=./generated/kotlin --javascript_out=./generated/javascript --grpc +``` + +Generated service code imports grpc APIs, so applications must provide grpc-java, +grpc-kotlin, Scala grpc-java APIs, `grpcio`, grpc-go, Rust `tonic` and `bytes`, +`@grpc/grpc-js`, C# `Grpc.Core.Api` plus server/client dependencies, or Dart +`package:grpc` when they compile or run those files. Python companions use +`grpc.aio` by default and can be generated in sync mode with +`--grpc-python-mode=sync`. Fory packages do not add gRPC as a hard dependency. +Use `--grpc-web` with JavaScript output to generate browser clients that import +`grpc-web`. + +### Defaults and Metadata + +- FlatBuffers default values are parsed but not applied as Fory defaults. +- Non-Fory metadata attributes are preserved as generic options in IR and may be + consumed by downstream tooling. + +## Fory-Specific Attributes in FlatBuffers + +FlatBuffers metadata attributes use `key:value`. For Fory-specific options, use +`fory_` (or `fory.`) prefix in `.fbs`; the prefix is removed during parsing. + +### Supported Field Attributes + +| FlatBuffers Attribute | Effect in Fory | +| -------------------------------- | -------------------------------------------------------------------------------- | +| `fory_ref:true` | Enable reference tracking for the field | +| `fory_nullable:true` | Mark field optional/nullable | +| `fory_weak_ref:true` | Enable weak reference semantics and implies `ref` | +| `fory_thread_safe_pointer:false` | For ref fields, select Rust `Rc`/`RcWeak` instead of the default `Arc`/`ArcWeak` | + +Semantics: + +- `fory_weak_ref:true` implies `ref`. +- `fory_thread_safe_pointer` defaults to `true`, only takes effect when the field + is ref-tracked, and does not change the wire format. +- In Rust codegen, `fory_weak_ref:true` uses `ArcWeak` by default and switches to + `RcWeak` only when `fory_thread_safe_pointer:false` is set. +- For list fields, `fory_ref:true` applies to list elements. + +Example: + +```fbs +table Node { + parent: Node (fory_weak_ref: true); + children: [Node] (fory_ref: true); + local: Node (fory_ref: true, fory_thread_safe_pointer: false); +} +``` + +## Generated Code Differences + +Using `.fbs` as input to Fory still produces normal Fory-generated code, not +FlatBuffers `ByteBuffer`-style APIs. + +- Java, Scala, and Kotlin: JVM model types with Fory metadata and registration helpers +- Python: dataclasses plus registration helpers +- C++, Go, and Rust: native structs and Fory metadata +- JavaScript/TypeScript: TypeScript interfaces and schema helpers +- C#, Swift, and Dart: annotated or macro-based model types with registration helpers + +The serialization format is Fory binary protocol, not FlatBuffers wire format. + +## Usage + +Compile a FlatBuffers schema directly: + +```bash +foryc schema.fbs --lang java,python --output ./generated +``` + +Inspect translated schema syntax for debugging: + +```bash +foryc schema.fbs --emit-fdl --emit-fdl-path ./translated +``` + +## Adoption Notes + +1. Keep existing `namespace` values stable to keep type registration stable. +2. Review fields that relied on FlatBuffers default literals and set explicit + defaults in application code if needed. +3. Add `fory_ref`/`fory_weak_ref` where object-graph semantics are required. +4. Validate generated model behavior with roundtrip tests before replacing + existing serialization paths. + +## Summary + +FlatBuffers input lets you reuse existing `.fbs` schemas while moving to Fory's +serialization and code generation model. This is useful for incremental adoption +while preserving schema investment and using Fory-native object APIs. diff --git a/versioned_docs/version-1.3.0/compiler/generated-code.md b/versioned_docs/version-1.3.0/compiler/generated-code.md new file mode 100644 index 00000000000..b3e5ebc91f0 --- /dev/null +++ b/versioned_docs/version-1.3.0/compiler/generated-code.md @@ -0,0 +1,1900 @@ +--- +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 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 schema modules or registration helpers, depending on the target language. + +Generated schema modules are named from the schema source file, not from the +package or namespace. In targets that expose the module directly in a language +package or namespace, names such as `AddressbookForyModule` or +`ComplexPbForyModule` let multiple IDL files target the same package or +namespace without producing colliding `ForyModule` types. + +## 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) + +```protobuf +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 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 people = 1; + map people_by_name = 2; +} +``` + +### `auto_id.fdl` (excerpt) + +```protobuf +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: + +- `/addressbook/` +- Type files: `AddressBook.java`, `Person.java`, `Dog.java`, `Cat.java`, `Animal.java` +- Schema module: `AddressbookForyModule.java` + +For schemas without a Java package, the schema module name is derived from the +source file stem, for example `main.fdl` generates `MainForyModule.java`. +Java import graphs cannot mix default-package schemas with named Java packages. + +### Type Generation + +Messages generate Java classes with `@ForyField`, default constructors, getters/setters, and byte helpers: + +```java +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) { ... } +} +``` + +Messages with `evolving=false` are generated with Java fixed-schema struct encoding. + +Unions generate classes extending `org.apache.fory.type.union.Union`: + +```java +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) { ... } +} +``` + +### Schema Module + +Each JVM schema generates a `ForyModule`. Imported schema modules are installed +through `fory.register(...)`, so shared imports are deduplicated by the Fory instance. + +```java +public final class AddressbookForyModule implements org.apache.fory.ForyModule { + public static final AddressbookForyModule INSTANCE = new AddressbookForyModule(); + + static ThreadSafeFory getFory() { ... } + + @Override + public void install(Fory fory) { + org.apache.fory.resolver.TypeResolver resolver = fory.getTypeResolver(); + resolver.registerUnion(Animal.class, 106L, new org.apache.fory.serializer.UnionSerializer(resolver, 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`): + +```java +resolver.register(Status.class, 1124725126L); +resolver.registerUnion(Wrapper.class, 1471345060L, new org.apache.fory.serializer.UnionSerializer(resolver, Wrapper.class)); +resolver.register(Envelope.class, 3022445236L); +resolver.registerUnion(Envelope.Detail.class, 1609214087L, new org.apache.fory.serializer.UnionSerializer(resolver, Envelope.Detail.class)); +resolver.register(Envelope.Payload.class, 2862577837L); +``` + +If `option enable_auto_type_id = false;` is set, registration uses symbolic names: + +```java +resolver.register(Config.class, "myapp.models", "Config"); +resolver.registerUnion( + Holder.class, + "myapp.models", + "Holder", + new org.apache.fory.serializer.UnionSerializer(resolver, Holder.class)); +``` + +### Usage + +```java +Person person = new Person(); +person.setName("Alice"); +person.setPet(Animal.ofDog(new Dog())); + +byte[] data = person.toBytes(); +Person restored = Person.fromBytes(data); +``` + +### gRPC Service Companions + +When a schema contains services and the compiler is run with `--grpc`, Java +generation emits one `Grpc.java` file per service next to the model +types. + +```java +public final class AddressBookServiceGrpc { + public static final String SERVICE_NAME = "addressbook.AddressBookService"; + + public static AddressBookServiceStub newStub(io.grpc.Channel channel) { ... } + public static AddressBookServiceBlockingStub newBlockingStub(io.grpc.Channel channel) { ... } + public static AddressBookServiceFutureStub newFutureStub(io.grpc.Channel channel) { ... } + + public abstract static class AddressBookServiceImplBase + implements io.grpc.BindableService { + public void lookup(Person request, io.grpc.stub.StreamObserver responseObserver) { ... } + } +} +``` + +The generated marshaller serializes each request or response with the schema +module's `ThreadSafeFory`. It uses grpc-java's `MethodDescriptor.Marshaller` +API, so applications compiling these files must provide grpc-java dependencies. +Those dependencies are not added to Fory Java artifacts. + +## Python + +### Output Layout + +Python output is one module per schema file, for example: + +- `/addressbook.py` + +### Type Generation + +Unions generate a case enum plus a `Union` subclass with typed helpers: + +```python +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: + +```python +@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: + +```python +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: + +```python +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: + +```python +fory.register_type(Config, name="myapp.models.Config") +fory.register_union( + Holder, + name="myapp.models.Holder", + serializer=HolderSerializer(fory), +) +``` + +### Usage + +```python +person = Person(name="Alice", pet=Animal.dog(Dog(name="Rex", bark_volume=10))) + +data = person.to_bytes() +restored = Person.from_bytes(data) +``` + +### gRPC Service Companions + +When a schema contains services and the compiler is run with `--grpc`, Python +generation emits a companion module named `_grpc.py`. The module name is +derived from the Fory package by replacing dots with underscores, or `generated` +when the schema has no package. Python gRPC output defaults to `grpc.aio` +AsyncIO APIs. + +```python +import grpc +import grpc.aio + + +class AddressBookServiceStub: + def __init__(self, channel): + self.lookup = channel.unary_unary( + "/addressbook.AddressBookService/Lookup", + request_serializer=_serialize, + response_deserializer=_deserialize, + ) + + +class AddressBookServiceServicer: + async def lookup(self, request, context): + await context.abort(grpc.StatusCode.UNIMPLEMENTED, "Method not implemented!") + + +def add_servicer(servicer, server): ... +``` + +Python gRPC serializers receive and return complete `bytes` payloads, so the +generated callbacks call the model module's `_get_fory().serialize(...)` and +`_get_fory().deserialize(...)` directly. Applications using the generated +companion module must install `grpcio`; `pyfory` does not add a hard gRPC +dependency. The Python API uses snake_case method names while preserving the +original IDL method names in the gRPC wire paths. + +Generate synchronous Python `grpcio` companions with +`--grpc --grpc-python-mode=sync`. Sync mode keeps the same generated filename +and public names, but servicer methods use regular `def` methods and sync +`grpc.Channel` and `grpc.Server` instances. + +## Rust + +### Output Layout + +Rust output is one module file per schema, for example: + +- `/addressbook.rs` + +When `--grpc` is used and the schema contains services, Rust also emits: + +- `/addressbook_service.rs` +- `/addressbook_service_grpc.rs` + +### Type Generation + +Unions map to Rust enums with `#[fory(id = ...)]` schema case attributes. +`#[fory(unknown)] Unknown(::fory::UnknownCase)` marks the Fory-provided +forward-compatibility carrier. The marker only selects the carrier and does not +add an entry to the schema case table; schema cases still use the full `0..N` +ID range. A generated typed union must have at least one non-`Unknown` case. The +compiler marks the first declared non-`Unknown` case as `#[fory(default)]` and +emits `Default` from that case: + +```rust +#[derive(::fory::ForyUnion, Clone, Debug, PartialEq, Eq, Hash)] +pub enum Animal { + #[fory(unknown)] + Unknown(::fory::UnknownCase), + #[fory(id = 0, default)] + Dog(self::Dog), + #[fory(id = 1)] + Cat(self::Cat), +} + +impl ::std::default::Default for Animal { + fn default() -> Self { + Self::Dog(::fory_default()) + } +} +``` + +Nested types generate nested modules: + +```rust +pub mod person { + #[derive(ForyEnum, Debug, Clone, PartialEq, Default)] + #[repr(i32)] + pub enum PhoneType { + #[default] + Mobile = 0, + Home = 1, + Work = 2, + } + + #[derive(ForyStruct, Debug, Clone, PartialEq, Default)] + pub struct PhoneNumber { + #[fory(id = 1)] + pub number: String, + #[fory(id = 2)] + pub phone_type: PhoneType, + } +} +``` + +Messages derive `ForyStruct` and include `to_bytes`/`from_bytes` helpers: + +```rust +#[derive(ForyStruct, Debug, Clone, PartialEq, Default)] +pub struct Person { + #[fory(id = 1)] + pub name: String, + #[fory(id = 7)] + pub phones: Vec, + #[fory(id = 8)] + pub pet: Animal, +} +``` + +### Registration + +Generated registration function: + +```rust +pub fn register_types(fory: &mut Fory) -> Result<(), fory::Error> { + fory.register_union::(106)?; + fory.register::(101)?; + fory.register::(102)?; + fory.register::(100)?; + fory.register::(104)?; + fory.register::(105)?; + fory.register::(103)?; + Ok(()) +} +``` + +For schemas without explicit `[id=...]`, generated registration uses computed numeric IDs: + +```rust +fory.register::(1124725126)?; +fory.register_union::(1471345060)?; +fory.register::(3022445236)?; +fory.register_union::(1609214087)?; +fory.register::(2862577837)?; +``` + +If `option enable_auto_type_id = false;` is set: + +```rust +fory.register_by_name::("myapp.models.Config")?; +fory.register_union_by_name::("myapp.models.Holder")?; +``` + +### Usage + +```rust +let person = Person { + name: "Alice".into(), + pet: Animal::Dog(self::Dog::default()), + ..Default::default() +}; + +let bytes = person.to_bytes()?; +let restored = Person::from_bytes(&bytes)?; +``` + +### gRPC Service Companions + +When a schema contains services and the compiler is run with `--grpc`, Rust +generation emits a service API module and a tonic binding module. For a schema +module named `addressbook`, those files are `addressbook_service.rs` and +`addressbook_service_grpc.rs`. + +The service API module contains the async trait and gRPC path constants: + +```rust +#[::tonic::async_trait] +pub trait AddressBookService: ::std::marker::Send + ::std::marker::Sync + 'static { + async fn lookup( + &self, + request: ::tonic::Request, + ) -> ::std::result::Result< + ::tonic::Response, + ::tonic::Status, + >; +} + +pub const ADDRESS_BOOK_SERVICE_SERVICE_NAME: &str = "addressbook.AddressBookService"; +pub const ADDRESS_BOOK_SERVICE_LOOKUP_PATH: &str = "/addressbook.AddressBookService/Lookup"; +``` + +The tonic binding module contains Fory-backed codecs, payload implementations, +and client/server wrappers. It serializes each request or response with the +generated model type's `to_bytes` and `from_bytes` helpers: + +```rust +impl codec::ForyGrpcPayload for crate::addressbook::Person { + fn encode_fory_payload(&self) -> ::std::result::Result<::std::vec::Vec, ::fory::Error> { + self.to_bytes() + } + + fn decode_fory_payload(payload: &[u8]) -> ::std::result::Result { + Self::from_bytes(payload) + } +} +``` + +Applications compiling the generated Rust service files must provide `tonic` and +`bytes` dependencies; Fory's Rust crate does not add those gRPC dependencies as +hard dependencies. + +## C++ + +### Output Layout + +C++ output is one header per schema file, for example: + +- `/addressbook.h` + +### Type Generation + +Messages generate `final` classes with typed accessors and byte helpers: + +```cpp +class Person final { + public: + class PhoneNumber final { + public: + const std::string& number() const; + std::string* mutable_number(); + template + void set_number(Arg&& arg, Args&&... args); + + fory::Result, fory::Error> to_bytes() const; + static fory::Result from_bytes(const std::vector& data); + }; + + const std::string& name() const; + std::string* mutable_name(); + template + 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: + +```cpp +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(); + } + return payload_.get(); + } + void clear_payload() { payload_.reset(); } + + private: + std::unique_ptr payload_; +}; +``` + +Unions generate `std::variant` wrappers: + +```cpp +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 + decltype(auto) visit(Visitor&& vis) const; + + private: + std::variant value_; +}; +``` + +Generated headers include `FORY_UNION`, `FORY_ENUM`, and `FORY_STRUCT` macros +for serialization metadata. Field and payload configuration is embedded in the +generated `FORY_STRUCT`/`FORY_UNION` entries. + +### Registration + +Generated registration function: + +```cpp +inline void register_types(fory::serialization::BaseFory& fory) { + fory.register_union(106); + fory.register_enum(101); + fory.register_struct(102); + fory.register_struct(100); + fory.register_struct(104); + fory.register_struct(105); + fory.register_struct(103); +} +``` + +For schemas without explicit `[id=...]`, generated registration uses computed numeric IDs: + +```cpp +fory.register_enum(1124725126); +fory.register_union(1471345060); +fory.register_struct(3022445236); +fory.register_union(1609214087); +fory.register_struct(2862577837); +``` + +If `option enable_auto_type_id = false;` is set: + +```cpp +fory.register_struct("myapp.models.Config"); +fory.register_union("myapp.models.Holder"); +``` + +### Usage + +```cpp +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`): + +```go +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: + +```go +type Person struct { + Name string `fory:"id=1"` + Id int32 `fory:"id=2"` + Phones []Person_PhoneNumber `fory:"id=7,type=list"` + 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: + +```go +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: + +```go +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: + +```go +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: + +```go +if err := f.RegisterStructByName(Config{}, "myapp.models.Config"); err != nil { ... } +if err := f.RegisterUnionByName(Holder{}, "myapp.models.Holder", fory.NewUnionSerializer(...)); err != nil { ... } +``` + +`go_nested_type_style` controls nested type naming: + +```protobuf +option go_nested_type_style = "camelcase"; +``` + +The CLI flag `--go_nested_type_style` overrides this schema option when both are set. + +### Usage + +```go +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) +} +``` + +### gRPC Service Companions + +When a schema contains services and the compiler is run with `--grpc`, Go +generation emits one `_grpc.go` file next to the model file. The +companion contains grpc-go client and server interfaces plus a Fory-backed +`CodecV2`. + +```go +type AddressBookServiceClient interface { + Lookup(ctx context.Context, in *Person, opts ...grpc.CallOption) (*AddressBook, error) +} + +func NewAddressBookServiceClient(cc grpc.ClientConnInterface) AddressBookServiceClient { ... } + +type CodecV2 struct{} +``` + +The generated codec uses the same package-level thread-safe Fory runtime as the +generated `ToBytes` and `FromBytes` helpers. Applications should pass +`CodecV2{}` to grpc-go server options, and generated clients force the same +codec on each call: + +```go +server := grpc.NewServer(grpc.ForceServerCodecV2(addressbook.CodecV2{})) +addressbook.RegisterAddressBookServiceServer(server, service) + +client := addressbook.NewAddressBookServiceClient(conn) +``` + +Go method names are exported as PascalCase identifiers, while the gRPC method +path keeps the exact service and method names from the schema. Regenerate both +peers after changing service or method names. + +Applications compiling these files must provide grpc-go dependencies; Fory Go +packages do not add gRPC as a hard dependency. + +## C\# + +### Output Layout + +C# output is one `.cs` file per schema, for example: + +- `/addressbook/Addressbook.cs` + +The C# model file name uses the normalized PascalCase source file stem. For +example, `service.fdl` generates `Service.cs`, `order-events.fdl` generates +`OrderEvents.cs`, and `123-schema.fdl` generates `Schema123Schema.cs`. + +### Type Generation + +Messages generate `[ForyStruct]` classes with C# properties and byte helpers: + +```csharp +[ForyStruct] +public sealed partial class Person +{ + public string Name { get; set; } = string.Empty; + public int Id { get; set; } + public List Phones { get; set; } = new(); + public Animal Pet { get; set; } = null!; + + public byte[] ToBytes() { ... } + public static Person FromBytes(byte[] data) { ... } +} +``` + +Unions generate `[ForyUnion]` ADTs. `Unknown(UnknownCase)` is the +Fory-provided forward-compatibility carrier marked with `[ForyUnknownCase]`. +The marker only selects the carrier and does not add an entry to the schema case +table. Schema-defined cases use non-negative `[ForyCase]` IDs. If a case needs +non-default schema encoding, the generated `[ForyCase]` carries `Type`. Known +case record names are PascalCase FDL case names; payload types are emitted as +qualified references when needed to avoid name conflicts. A typed union must +have at least one non-`Unknown` case. + +```csharp +[ForyUnion] +public abstract partial record Animal +{ + private Animal() {} + + [ForyUnknownCase] + public sealed partial record Unknown(UnknownCase Value) : Animal; + + [ForyCase(0)] + public sealed partial record Dog(global::addressbook.Dog Value) : Animal; + + [ForyCase(1)] + public sealed partial record Cat(global::addressbook.Cat Value) : Animal; +} +``` + +### Module Installation + +Each schema generates a module class that installs imported modules first and +then registers the local schema types: + +```csharp +public static class AddressbookForyModule +{ + public static void Install(Fory fory) + { + fory.Register((uint)106); + fory.Register((uint)100); + // ... + } +} +``` + +The C# model file basename and module class both use the normalized source file +stem. They do not use `csharp_namespace` and they do not use gRPC service names. +For example, `service.fdl` generates `Service.cs` and `ServiceForyModule`, +while `order-events.fdl` generates `OrderEvents.cs` and +`OrderEventsForyModule`. A gRPC service named `Greeter` generates the service +companion `GreeterGrpc.cs`; it does not change the schema module name. To get +`GreeterForyModule`, name the schema file `greeter.fdl` or `Greeter.fdl`. + +This source-file rule lets several schemas target the same C# namespace without +colliding. No namespace-derived or service-derived module alias is generated. + +When explicit type IDs are not provided, generated installation uses computed +numeric IDs (same behavior as other targets). + +### gRPC Service Companions + +When a schema contains services and the compiler is run with `--grpc`, C# +generation emits one `Grpc.cs` file per service next to the schema +model file. + +```csharp +public static partial class AddressBookService +{ + public abstract partial class AddressBookServiceBase + { + public virtual Task Lookup( + Person request, + grpc::ServerCallContext context) { ... } + } + + public partial class AddressBookServiceClient + : grpc::ClientBase + { + public virtual AddressBook Lookup(Person request, grpc::CallOptions options) { ... } + public virtual grpc::AsyncUnaryCall LookupAsync( + Person request, + grpc::CallOptions options) { ... } + } + + public static grpc::ServerServiceDefinition BindService( + AddressBookServiceBase serviceImpl) { ... } + + public static void BindService( + grpc::ServiceBinderBase serviceBinder, + AddressBookServiceBase? serviceImpl) { ... } +} +``` + +Each generated method descriptor uses a static Fory-backed +`Grpc.Core.Marshaller` that reuses the schema module's `ThreadSafeFory`. +Deserialization reads the gRPC body through `PayloadAsReadOnlySequence()` and +rejects trailing bytes after the single Fory frame. Generated service companions +do not use protobuf parsers and do not create Fory instances per RPC call. + +Streaming RPCs map to standard gRPC C# APIs: + +| IDL shape | Server method | Client method | +| ----------------------------------------- | ----------------------------------------------------------------------------- | ------------------------------------------- | +| `rpc A (Req) returns (Res)` | `Task A(Req request, ServerCallContext context)` | `A(...)` and `AAsync(...)` | +| `rpc A (Req) returns (stream Res)` | `Task A(Req request, IServerStreamWriter responseStream, ...)` | `AsyncServerStreamingCall A(...)` | +| `rpc A (stream Req) returns (Res)` | `Task A(IAsyncStreamReader requestStream, ...)` | `AsyncClientStreamingCall A(...)` | +| `rpc A (stream Req) returns (stream Res)` | `Task A(IAsyncStreamReader requestStream, IServerStreamWriter ...)` | `AsyncDuplexStreamingCall A(...)` | + +Applications compiling generated C# service files must provide `Grpc.Core.Api` +and their chosen .NET gRPC hosting or client package, such as `Grpc.AspNetCore` +or `Grpc.Net.Client`. The `Apache.Fory` package does not add gRPC dependencies +as hard dependencies. + +## JavaScript/TypeScript + +### Output Layout + +JavaScript/TypeScript output is one `.ts` file per schema, for example: + +- `/addressbook.ts` + +When the schema contains services, JavaScript can also emit service companions: + +- `/addressbook_grpc.ts` with `--grpc` +- `/addressbook_grpc_web.ts` with `--grpc-web` + +### Type Generation + +Messages generate `export interface` declarations with camelCase field names: + +```typescript +export interface Person { + name: string; + id: number; + phones: PhoneNumber[]; + pet?: Animal | null; +} +``` + +Enums generate `export enum` declarations: + +```typescript +export enum PhoneType { + MOBILE = 0, + HOME = 1, + WORK = 2, +} +``` + +Unions generate a discriminated union with a case enum: + +```typescript +export enum AnimalCase { + DOG = 1, + CAT = 2, +} + +export type Animal = + | { case: AnimalCase.DOG; value: Dog } + | { case: AnimalCase.CAT; value: Cat }; +``` + +### Schema Helpers + +Each generated model file exports a registration helper for custom `Fory` +instances and root serialization helpers. The public API looks like: + +```typescript +import type Fory, { Serializer } from "@apache-fory/core"; + +export function registerAddressbookTypes(fory: Fory): { + person: { + serialize: (value: Person | null) => Uint8Array; + deserialize: (bytes: Uint8Array) => Person; + serializer: Serializer; + }; +}; +export const serializePerson: (value: Person | null) => Uint8Array; +export const deserializePerson: (bytes: Uint8Array) => Person; +``` + +Imported schema modules are registered automatically by `registerXxxTypes(fory)`. +Use `serializeX` and `deserializeX` for the generated default serialization +path. Call `registerXxxTypes(fory)` when the application manages its own `Fory` +instance. Generated gRPC companions import the generated helpers automatically. + +### gRPC Service Companions + +When a schema contains services and the compiler is run with `--grpc`, +JavaScript generation emits a Node.js companion named `_grpc.ts`. +The file contains service descriptors, handler interfaces, a client class, and a +server registration helper for `@grpc/grpc-js`. + +```typescript +export interface GreeterHandlers extends grpc.UntypedServiceImplementation { + sayHello: grpc.handleUnaryCall; +} + +export function addGreeterService( + server: grpc.Server, + handlers: GreeterHandlers, +): void { ... } + +export class GreeterClient extends grpc.Client { ... } +``` + +When the compiler is run with `--grpc-web`, JavaScript generation emits a +browser companion named `_grpc_web.ts`. It contains callback clients for +`grpc-web`; services with unary RPCs also get promise clients: + +```typescript +export class GreeterWebClient { ... } + +export class GreeterWebPromiseClient { ... } +``` + +Node.js service companions import `@grpc/grpc-js`; browser companions import +`grpc-web`. Add those packages to the application that compiles or runs the +generated files. + +## Swift + +### Output Layout + +Swift output is one `.swift` file per schema, for example: + +- `/addressbook/addressbook.swift` + +### Type Generation + +The generator creates Swift models with split model macros and stable field/case IDs. +A typed union must include `@ForyUnknownCase case unknown(UnknownCase)` and at +least one non-`unknown` case; `unknown(UnknownCase)` is only the +Fory-provided forward-compatibility carrier. The marker only selects the carrier +and does not add an entry to the schema case table. + +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: + +```swift +public enum Addressbook { + @ForyUnion + public enum Animal { + @ForyUnknownCase + case unknown(UnknownCase) + @ForyCase(id: 0) + case dog(Addressbook.Dog) + @ForyCase(id: 1) + case cat(Addressbook.Cat) + } + + @ForyStruct + 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: + +```swift +@ForyStruct +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. +Fixed or tagged integer encodings inside list/map fields are emitted as Swift +field type hints, for example `@ListField(element: .encoding(.fixed))` or +`@MapField(value: .encoding(.tagged))`. +For non-null fixed-width integer list elements, Swift classifies the field as +the corresponding Fory primitive packed-array type; fixed-width integer sets +remain Fory sets. + +### Module Installation + +Each schema includes a `ForyModule` owner with transitive import installation: + +```swift +public enum ForyModule { + public static func install(_ fory: Fory) throws { + try ComplexPb.ForyModule.install(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_ForyModule`). + +For schemas without explicit `[id=...]`, installation uses computed numeric IDs. +If `option enable_auto_type_id = false;` is set, generated code uses name-based registration APIs. + +## Dart + +### Output Layout + +Dart output is two files per schema: a main `.dart` file with annotated types and the IDL module owner, and a `.fory.dart` part file with generated serializers and metadata. + +- `/package/package.dart` +- `/package/package.fory.dart` + +### Type Generation + +Messages generate `@ForyStruct` annotated `final class` declarations with `@ForyField` on each field: + +```dart +@ForyStruct() +final class Person { + Person(); + + @ForyField(id: 1) + String name = ''; + + @ForyField(id: 2, type: Int32Type()) + int id = 0; + + @ForyField(id: 7) + List phones = []; + + @ForyField(id: 8) + Animal pet = Animal._empty(); +} +``` + +Enums generate Dart `enum` declarations with a `rawValue` getter and `fromRawValue` factory: + +```dart +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: + +```dart +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`). + +`list` fields generate ordered collection carriers and use the Fory list +protocol. `array` fields generate dense one-dimensional bool or numeric +carriers and use the specialized dense-array protocol. Generated code must not +choose `array` only because a language has an optimized list-like carrier; +the schema kind comes from the IDL. + +| IDL schema | Dart generated carrier | Notes | +| ------------------- | ---------------------- | ------------------------------------------ | +| `list` | `List` | List protocol, varint element encoding | +| `list` | `List` | List protocol, fixed-width element segment | +| `array` | `BoolList` | One byte per bool | +| `array` | `Int8List` | Dense signed bytes | +| `array` | `Int16List` | Dense little-endian int16 | +| `array` | `Int32List` | Dense little-endian int32 | +| `array` | `Int64List` | Dense little-endian int64 | +| `array` | `Uint8List` | Dense unsigned bytes | +| `array` | `Uint16List` | Dense little-endian uint16 | +| `array` | `Uint32List` | Dense little-endian uint32 | +| `array` | `Uint64List` | Dense little-endian uint64 | +| `array` | `Float16List` | Dense binary16 storage | +| `array` | `Bfloat16List` | Dense bfloat16 storage | +| `array` | `Float32List` | Dense little-endian float32 | +| `array` | `Float64List` | Dense little-endian float64 | + +Generated Dart fields that use `ArrayType(element: BoolType())` must use +`BoolList`; plain `List` remains the generated and handwritten carrier +for `list`. + +Reference tracking on list elements or map values uses the container sugar annotations: + +```dart +@ListField(element: DeclaredType(ref: true)) +@ForyField(id: 3) +List children = []; + +@MapField(value: DeclaredType(ref: true)) +@ForyField(id: 2) +Map byName = {}; +``` + +### Module Installation + +Each generated Dart IDL library includes a module owner named after the input +file, such as `AddressbookForyModule` for `addressbook.dart`. The module +installs imported modules first and then registers every local schema type with +its default IDL identity: + +```dart +abstract final class AddressbookForyModule { + static void install(Fory fory) { + complex_pb.ComplexPbForyModule.install(fory); + _registerType(fory, Person); + _registerType(fory, Dog); + } + + static Fory getFory() { ... } + + static void _registerType(Fory fory, Type type) { + if (type == Person) { + registerGeneratedStruct(fory, _personForySchema, id: 100, namespace: null, typeName: null); + return; + } + // ... other types + } +} +``` + +### Usage + +```dart +import 'package:fory/fory.dart'; +import 'generated/addressbook/addressbook.dart'; + +void main() { + final fory = Fory(); + AddressbookForyModule.install(fory); + + final person = Person() + ..name = 'Alice' + ..id = 1; + + final bytes = fory.serialize(person); + final roundTrip = fory.deserialize(bytes); +} +``` + +### gRPC Service Companions + +When a schema contains services and the compiler is run with `--grpc`, Dart +generation emits one `_grpc.dart` file per schema next to the model +types. It targets `package:grpc`. Request and response serialization uses a Fory +runtime the companion obtains automatically and that registers the schema's +types on first use, so no manual registration is required; an application may +optionally inject a custom `Fory` via the schema module's `install(...)` before +the first call. + +All four RPC modes are generated: unary, server-streaming, client-streaming, and +bidirectional. The client class extends `Client`; the service base class extends +`Service` and self-registers each method with `$addMethod`. + +```dart +class GreeterClient extends Client { + // Single response: ResponseFuture. Streaming response: ResponseStream. + ResponseFuture sayHello(HelloRequest request, {CallOptions? options}) { ... } + ResponseStream sayHellos(HelloRequest request, {CallOptions? options}) { ... } + ResponseFuture collectHellos(Stream request, {CallOptions? options}) { ... } + ResponseStream chatHellos(Stream request, {CallOptions? options}) { ... } +} + +abstract class GreeterServiceBase extends Service { + Future sayHello(ServiceCall call, HelloRequest request); + Stream sayHellos(ServiceCall call, HelloRequest request); + Future collectHellos(ServiceCall call, Stream request); + Stream chatHellos(ServiceCall call, Stream request); +} +``` + +A single-response client method returns `ResponseFuture` (client-streaming +adapts the streaming call with `.single`); a streaming-response method returns +`ResponseStream`. On the server, implementations override the abstract methods, +which receive a single request as `Q` and a client-streaming request as +`Stream`, and return a `Future` for single responses or a `Stream` for +streaming responses. +Applications compiling these files must provide a `grpc` dependency; the Fory Dart +runtime does not add one. The original IDL method names are used in the gRPC wire +paths. + +## Kotlin + +The Kotlin target emits Kotlin source only. The compiler does not generate Java +files. + +### Output Layout + +For source file `addressbook.fdl` with `package addressbook`, Kotlin output is +generated under: + +- `/addressbook/` +- Type files: `AddressBook.kt`, `Person.kt`, `Dog.kt`, `Cat.kt`, `Animal.kt` +- Schema module: `AddressbookForyModule.kt` + +The schema module name is derived from the source file stem. Schemas in the same +Kotlin package need distinct generated file names; duplicate generated Kotlin +file paths are rejected before files are written. + +If `option kotlin_package = "...";` is present, the output path and Kotlin +package use that option. Otherwise Kotlin uses the FDL package. A Kotlin import +graph cannot mix default-package schemas with named Kotlin packages. +Registration still uses the FDL package so cross-language type names stay +stable. + +### Type Generation + +Messages generate Kotlin `data class` declarations by default: + +```kotlin +@ForyStruct +public data class Person( + @field:ForyField(id = 1) + public val name: String, + + @field:ForyField(id = 7) + public val phones: List, + + @field:ForyField(id = 8) + public val pet: Animal, +) { + public fun toBytes(): ByteArray = AddressbookForyModule.getFory().serialize(this) + + public companion object { + public fun fromBytes(bytes: ByteArray): Person = + AddressbookForyModule.getFory().deserialize(bytes, Person::class.java) + } +} +``` + +Messages that participate in compiler-detected construction cycles generate +normal mutable classes so the generated serializer can publish the instance +before reading back-references: + +```kotlin +@ForyStruct +public class Node() { + @ForyField(id = 1) + public var id: String = "" + + @Ref + @ForyField(id = 2) + public var parent: Node? = null +} +``` + +Generated Kotlin IDL sources express nullability with Kotlin `?`, not Fory +`@Nullable`, including mutable classes emitted for compiler-detected +construction cycles. + +Enums generate Kotlin enum classes with stable Fory enum IDs. Unions generate +sealed classes with `@ForyUnion`; the Fory-provided `Unknown(UnknownCase)` +carrier is marked with `@ForyUnknownCase`. The marker only selects the carrier +and does not add an entry to the schema case table. Schema-defined cases may use +case IDs `0..N` and hold a single `value` property. A typed union must have at +least one non-`Unknown` case. + +```kotlin +package addressbook + +import org.apache.fory.annotation.ForyCase +import org.apache.fory.annotation.ForyUnion +import org.apache.fory.annotation.ForyUnknownCase +import org.apache.fory.type.union.UnknownCase + +@ForyUnion +public sealed class Animal { + @ForyUnknownCase + public data class Unknown(public val value: UnknownCase) : Animal() + + @ForyCase(id = 0) + public data class Dog(public val value: addressbook.Dog) : Animal() +} +``` + +Packaged Kotlin output keeps the schema case name and qualifies the payload +type when both have the same simple name. If a target output mode cannot express +a legal qualifier for a conflict, the compiler appends `Case` to the generated +case class name. + +Kotlin `int32`, `int64`, `uint32`, and `uint64` fields use xlang varint +encoding by default, so generated Kotlin does not emit `@VarInt` for the +default case. It emits `@Fixed` or `@Tagged` only when the schema requests that +non-default encoding. `duration` maps to `kotlin.time.Duration`, and infinite +durations are rejected when encoded. Dense `array` and +`array` use the Java core `Float16Array` and `BFloat16Array` +carriers. Generated Kotlin IDL uses `@ArrayType ByteArray` for `array`, +including nested positions. + +### Schema Module + +Generated schema modules register schema types and resolve KSP-generated +serializers from the target class name. The package-owned helper Fory instance uses +`ForyKotlin.builder().withXlang(true)` with the schema module installed, so message +`toBytes`/`fromBytes` helpers work without caller-managed Fory setup. For +`addressbook.fdl`: + +```kotlin +public object AddressbookForyModule : ForyModule { + private val fory: ThreadSafeFory by lazy { + ForyKotlin.builder() + .withXlang(true) + .withRefTracking(true) + .withModule(this) + .buildThreadSafeFory() + } + + internal fun getFory(): ThreadSafeFory = fory + + override fun install(fory: Fory) { + KotlinSerializers.registerType(fory, Person::class.java, 100L) + KotlinSerializers.registerSerializer(fory, Person::class.java) + KotlinSerializers.registerUnion(fory, Animal::class.java, 106L) + } +} +``` + +`registerUnion` discovers the generated `_ForySerializer`; callers do +not pass a serializer instance. + +### gRPC Service Companions + +When a schema contains services and the compiler is run with `--grpc`, Kotlin +generation emits one `GrpcKt.kt` file per service next to the model +types. The file contains a grpc-kotlin coroutine companion object, not Java +`*Grpc.java` source. + +```kotlin +public object AddressBookServiceGrpcKt { + public const val SERVICE_NAME: String = "addressbook.AddressBookService" + + @JvmStatic + public val serviceDescriptor: io.grpc.ServiceDescriptor + get() = serviceDescriptorValue + + @JvmStatic + public val lookupMethod: io.grpc.MethodDescriptor + get() = lookupMethodValue + + public abstract class AddressBookServiceCoroutineImplBase( + coroutineContext: kotlin.coroutines.CoroutineContext = + kotlin.coroutines.EmptyCoroutineContext, + ) : io.grpc.kotlin.AbstractCoroutineServerImpl(coroutineContext) { + public open suspend fun lookup(request: Person): AddressBook = + throw io.grpc.StatusException( + io.grpc.Status.UNIMPLEMENTED.withDescription( + "Method addressbook.AddressBookService/Lookup is unimplemented", + ), + ) + } + + public class AddressBookServiceCoroutineStub @JvmOverloads constructor( + channel: io.grpc.Channel, + callOptions: io.grpc.CallOptions = io.grpc.CallOptions.DEFAULT, + ) : io.grpc.kotlin.AbstractCoroutineStub( + channel, + callOptions, + ) { + public suspend fun lookup( + request: Person, + headers: io.grpc.Metadata = io.grpc.Metadata(), + ): AddressBook = + io.grpc.kotlin.ClientCalls.unaryRpc( + channel, + lookupMethod, + request, + callOptions, + headers, + ) + } +} +``` + +Streaming RPCs use `kotlinx.coroutines.flow.Flow`: + +| IDL shape | Server method | Client method | +| ----------------------------------------- | ----------------------------------------- | ----------------------------------------- | +| `rpc A (Req) returns (Res)` | `suspend fun a(request: Req): Res` | `suspend fun a(request: Req): Res` | +| `rpc A (Req) returns (stream Res)` | `fun a(request: Req): Flow` | `fun a(request: Req): Flow` | +| `rpc A (stream Req) returns (Res)` | `suspend fun a(requests: Flow): Res` | `suspend fun a(requests: Flow): Res` | +| `rpc A (stream Req) returns (stream Res)` | `fun a(requests: Flow): Flow` | `fun a(requests: Flow): Flow` | + +Each method descriptor uses a Fory-backed `io.grpc.MethodDescriptor.Marshaller` +that reuses the generated schema module's `ThreadSafeFory`. Generated service +companions do not call protobuf parsers, do not expose KSP serializer class +names, and do not create Fory instances per call. + +Applications compiling the generated Kotlin service files must provide +grpc-java, grpc-kotlin, and `kotlinx-coroutines-core` dependencies. Fory Kotlin +artifacts do not add those gRPC dependencies as hard dependencies. + +## Scala + +The Scala target emits Scala 3 source only. The `fory-scala` artifact still +supports Scala 2.13 and Scala 3, but generated IDL source and macro derivation +require Scala 3. + +### Output Layout + +For `package addressbook`, Scala output is generated under: + +- `/addressbook/` +- Type files: `AddressBook.scala`, `Person.scala`, `Dog.scala`, `Cat.scala`, `Animal.scala` +- Schema module: `AddressbookForyModule.scala` + +For schemas without a Scala package, the schema module name is derived from the +source file stem, for example `main.fdl` generates `MainForyModule.scala`. +Scala import graphs cannot mix default-package schemas with named Scala +packages. + +### Type Generation + +Messages outside compiler-detected construction cycles generate case classes: + +```scala +import org.apache.fory.annotation.{ForyField, ForyStruct} +import org.apache.fory.scala.ForySerializer + +@ForyStruct +final case class Person( + @ForyField(id = 1) name: String, + @ForyField(id = 3) email: Option[String], + @ForyField(id = 7) phones: List[Person.PhoneNumber], + @ForyField(id = 8) pet: Animal +) derives ForySerializer { + def toBytes(): Array[Byte] = + AddressbookForyModule.getFory.serialize(this) +} + +object Person { + def fromBytes(bytes: Array[Byte]): Person = + AddressbookForyModule.getFory.deserialize(bytes).asInstanceOf[Person] +} +``` + +Messages in circular construction cycles generate normal classes with mutable +serialized fields so reads can register the object before reading back-references: + +```scala +import org.apache.fory.annotation.{ForyField, ForyStruct, Ref} +import org.apache.fory.scala.ForySerializer + +@ForyStruct +final class Node() derives ForySerializer { + @ForyField(id = 1) + var id: String = "" + + @Ref + @ForyField(id = 2) + var parent: Option[Node] = None +} +``` + +Enums generate Scala 3 enums with stable Fory IDs: + +```scala +import org.apache.fory.annotation.ForyEnumId + +enum PhoneType { + @ForyEnumId(0) + case Mobile + + @ForyEnumId(1) + case Home + + @ForyEnumId(2) + case Work +} +``` + +Unions generate Scala 3 ADT enums. `Unknown(UnknownCase)` is the Fory-provided +forward-compatibility carrier marked with `@ForyUnknownCase`. It is omitted +from the schema case table because the marker only selects the carrier and does +not add a schema entry. Schema-defined cases use non-negative `@ForyCase` IDs. +A typed union must have at least one +non-`Unknown` case. + +```scala +package addressbook + +import org.apache.fory.annotation.{ForyCase, ForyUnion, ForyUnknownCase} +import org.apache.fory.scala.ForySerializer +import org.apache.fory.`type`.union.UnknownCase + +@ForyUnion +enum Animal derives ForySerializer { + @ForyUnknownCase + case Unknown(value: UnknownCase) + + @ForyCase(id = 0) + case Dog(value: _root_.addressbook.Dog) + + @ForyCase(id = 1) + case Cat(value: _root_.addressbook.Cat) +} +``` + +Packaged Scala output keeps the schema case name and qualifies the payload type +when both have the same simple name. If a target output mode cannot express a +legal qualifier for a conflict, the compiler appends `Case` to the generated +case name. + +`optional T` fields generate `Option[T]`. Top-level message references use +`@Ref` on the field or constructor parameter. Nested element/value references +use type-use annotations such as `List[Node @Ref]`. + +### Schema Module + +Generated schema modules register schema serializers, enums, structs, and +unions. The package-owned helper Fory instance uses +`ForyScala.builder().withXlang(true)` with the schema module installed, so +message `toBytes`/`fromBytes` helpers work without caller-managed Fory setup: + +```scala +object AddressbookForyModule extends org.apache.fory.ForyModule { + private lazy val fory: ThreadSafeFory = + ForyScala.builder() + .withXlang(true) + .withRefTracking(true) + .withModule(this) + .buildThreadSafeFory() + + private[addressbook] def getFory: ThreadSafeFory = fory + + override def install(fory: Fory): Unit = { + ScalaSerializers.registerEnum(fory, classOf[Person.PhoneType], 101L) + ForySerializer.register(fory, classOf[Person.PhoneNumber], 102L) + ForySerializer.register(fory, classOf[Person], 100L) + ForySerializer.register(fory, classOf[Animal], 106L) + } +} +``` + +### gRPC Service Companions + +When a schema contains services and the compiler is run with `--grpc`, Scala +generation emits one `Grpc.scala` companion per local service +definition. The companion lives in the same Scala package as the generated +models and schema module. + +For a service such as: + +```protobuf +service AddressBookService { + rpc Lookup (Person) returns (AddressBook); + rpc Watch (Person) returns (stream AddressBook); + rpc Upload (stream Person) returns (AddressBook); + rpc Chat (stream Person) returns (stream AddressBook); +} +``` + +the generated companion contains: + +- `SERVICE_NAME` and grpc-java method descriptors +- `AddressBookServiceImplBase` for server implementations +- `AddressBookServiceClient` for client calls +- Fory-backed grpc-java marshallers for request and response payloads + +The generated Scala client keeps grpc-java available per method while adding +Scala-friendly convenience methods for the shapes where a direct Scala handle +can preserve the needed lifecycle controls: + +| RPC shape | Scala convenience method | grpc-java-style method | +| ------------------------------------------------------- | ----------------------------------- | ------------------------------------------------ | +| `rpc Lookup (Person) returns (AddressBook)` | `lookup(request): RpcFuture[Resp]` | async observer, blocking, and `ListenableFuture` | +| `rpc Watch (Person) returns (stream AddressBook)` | `watch(request): RpcIterator[Resp]` | async observer and blocking iterator | +| `rpc Upload (stream Person) returns (AddressBook)` | None | request `StreamObserver` | +| `rpc Chat (stream Person) returns (stream AddressBook)` | None | request and response `StreamObserver` | + +Unary client convenience methods return `org.apache.fory.scala.rpc.RpcFuture`: + +```scala +val client = AddressBookServiceGrpc.newClient(channel) +val call = client.lookup(person) +call.asFuture.foreach(handleAddressBook)(scala.concurrent.ExecutionContext.global) +``` + +Server-streaming client convenience methods return +`org.apache.fory.scala.rpc.RpcIterator`: + +```scala +val stream = client.watch(person) +try { + while (stream.hasNext) { + handleAddressBook(stream.next()) + } +} finally { + stream.close() +} +``` + +Close or cancel the `RpcIterator` when the client stops before consuming the +whole stream. The generated adapter cancels the underlying gRPC call so the +server is not left writing a response stream the client no longer reads. + +Client-streaming and bidirectional methods use grpc-java `StreamObserver` APIs: + +```scala +val requestStream = client.upload( + new io.grpc.stub.StreamObserver[AddressBook] { + override def onNext(value: AddressBook): Unit = handleAddressBook(value) + override def onError(t: Throwable): Unit = handleError(t) + override def onCompleted(): Unit = () + } +) +requestStream.onNext(person) +requestStream.onCompleted() +``` + +Server implementations mirror grpc-java. Unary methods can override the direct +request-to-response method generated by Scala, but streaming methods override +observer-based methods and must call `onNext`, `onError`, and `onCompleted` +according to grpc-java lifecycle rules. + +Applications compiling generated Scala gRPC companions must provide grpc-java +dependencies such as `grpc-api`, `grpc-stub`, and a transport like +`grpc-netty-shaded`. The `fory-scala` artifact does not add grpc-java as a hard +dependency. + +## Cross-Language Notes + +### Type ID Behavior + +- Explicit `[id=...]` values are used directly by generated module installation or registration helpers. +- 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 module installation or registration helpers use name-based APIs instead of numeric IDs. + +### Nested Type Shape + +| Language | Nested type form | +| --------------------- | ------------------------------ | +| Java | `Person.PhoneNumber` | +| Python | `Person.PhoneNumber` | +| Rust | `person::PhoneNumber` | +| C++ | `Person::PhoneNumber` | +| Go | `Person_PhoneNumber` (default) | +| C# | `Person.PhoneNumber` | +| JavaScript/TypeScript | `Person.PhoneNumber` | +| Swift | `Person.PhoneNumber` | +| Dart | `Person_PhoneNumber` | +| Kotlin | `PersonPhoneNumber` | +| Scala | `Person.PhoneNumber` | + +### Byte Helper Naming + +| Language | Helpers | +| --------------------- | ------------------------- | +| Java | `toBytes` / `fromBytes` | +| Kotlin | `toBytes` / `fromBytes` | +| Scala | `toBytes` / `fromBytes` | +| Python | `to_bytes` / `from_bytes` | +| Rust | `to_bytes` / `from_bytes` | +| C++ | `to_bytes` / `from_bytes` | +| Go | `ToBytes` / `FromBytes` | +| C# | `ToBytes` / `FromBytes` | +| JavaScript/TypeScript | (via `fory.serialize()`) | +| Swift | `toBytes` / `fromBytes` | +| Dart | (via `fory.serialize()`) | diff --git a/versioned_docs/version-1.3.0/compiler/index.md b/versioned_docs/version-1.3.0/compiler/index.md new file mode 100644 index 00000000000..caa69a6fc9f --- /dev/null +++ b/versioned_docs/version-1.3.0/compiler/index.md @@ -0,0 +1,263 @@ +--- +title: Overview +sidebar_position: 1 +id: index +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. +--- + +Fory IDL is a schema definition language for Apache Fory that enables type-safe +cross-language serialization. Define your data structures once and generate +native data structure code for Java, Python, C++, Go, Rust, +JavaScript/TypeScript, C#, Swift, Dart, Scala, and Kotlin. Fory IDL can also +describe RPC services; for Java, Python, Go, Rust, C#, Dart, Scala, Kotlin, and +JavaScript, the compiler can generate gRPC service companions that use Fory +serialization for request and response payloads. + +## Example Schema + +Fory IDL provides a simple, intuitive syntax for defining cross-language data structures: + +```protobuf +package example; + +enum Status { + PENDING = 0; + ACTIVE = 1; + COMPLETED = 2; +} + +message User { + string name = 1; + int32 age = 2; + optional string email = 3; + list tags = 4; +} + +message Item { + string sku = 1; + int32 quantity = 2; +} + +message Order { + ref User customer = 1; + list items = 2; + Status status = 3; + map metadata = 4; +} + +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 LookupRequest [id=107] { + string name = 1; +} + +message LookupResponse [id=108] { + Animal animal = 1; +} + +service AnimalService { + rpc Lookup (LookupRequest) returns (LookupResponse); + rpc Classify (Animal) returns (Animal); +} +``` + +Generate Java, Python, Go, Rust, C#, Dart, Scala, Kotlin, and JavaScript models +plus gRPC service companions with: + +```bash +foryc animals.fdl --java_out=./generated/java --python_out=./generated/python --go_out=./generated/go --rust_out=./generated/rust --csharp_out=./generated/csharp --dart_out=./generated/dart --scala_out=./generated/scala --kotlin_out=./generated/kotlin --javascript_out=./generated/javascript --grpc +``` + +The generated service code uses normal gRPC APIs, but request and response +objects are serialized with Fory. Applications provide their own grpc-java, +grpc-kotlin, Scala grpc-java APIs, `grpcio`, grpc-go, Rust `tonic` and `bytes`, +C# `Grpc.Core.Api` and hosting/client dependencies, or Dart `package:grpc`; Fory +packages do not add gRPC as a hard dependency. Python companions use `grpc.aio` +by default and can be generated in sync mode with `--grpc-python-mode=sync`. +JavaScript Node.js companions use `@grpc/grpc-js`; browser clients are generated +separately with `--grpc-web` and use `grpc-web`. + +## Why Fory IDL? + +### Schema-First Development + +Define your data model once in Fory IDL and generate consistent, type-safe code across all languages. This ensures: + +- **Type Safety**: Catch type errors at compile time, not when the code runs +- **Consistency**: All languages use the same field names, types, and structures +- **Documentation**: Schema serves as living documentation +- **Evolution**: Managed schema changes across all implementations + +### Fory-Native Features + +Unlike generic IDLs, Fory IDL is designed specifically for Fory serialization: + +- **Reference Tracking**: First-class support for shared and circular references via `ref` +- **Nullable Fields**: Explicit `optional` modifier for nullable types +- **Type Registration**: Built-in support for both numeric IDs and name-based registration +- **Native Code Generation**: Generates idiomatic code with Fory annotations/macros + +### Low Integration Overhead + +Generated code uses native language constructs: + +- Java: Plain POJOs with `@ForyField` annotations +- Python: Dataclasses with type hints +- Go: Structs with struct tags +- Rust: Structs with `#[derive(ForyStruct)]` +- C++: Structs with `FORY_STRUCT` macros +- C#: `[ForyStruct]` classes, `[ForyEnum]` enums, `[ForyUnion]` unions, and registration helpers +- JavaScript/TypeScript: Interfaces with schema module helpers +- Swift: Fory model macros with field/case metadata and registration helpers +- Dart: `@ForyStruct` classes with `@ForyField` annotations and registration helpers +- Scala: Scala 3 `case class`, normal class, enum, and ADT enum models with macro-derived serializers +- Kotlin: Kotlin `data class`, enum, and sealed class models with KSP-generated serializers + +## Quick Start + +### 1. Install the Compiler + +```bash +pip install fory-compiler +``` + +Or install from source: + +```bash +cd compiler +pip install -e . +``` + +### 2. Write Your Schema + +Create `example.fdl`: + +```protobuf +package example; + +message Person { + string name = 1; + int32 age = 2; + optional string email = 3; +} +``` + +### 3. Generate Code + +```bash +# Generate for all languages +foryc example.fdl --output ./generated + +# Generate for specific languages +foryc example.fdl --lang java,python,csharp,javascript,swift,dart,scala,kotlin --output ./generated +``` + +### 4. Use Generated Code + +**Java:** + +```java +Person person = new Person(); +person.setName("Alice"); +person.setAge(30); +byte[] data = person.toBytes(); +``` + +**Python:** + +```python +import pyfory +from example import Person + +person = Person(name="Alice", age=30) +data = bytes(person) # or `person.to_bytes()` +``` + +**JavaScript/TypeScript:** + +```ts +import { deserializePerson, serializePerson } from "./generated/example"; + +const data = serializePerson({ name: "Alice", age: 30, email: null }); +const person = deserializePerson(data); +``` + +## Documentation + +| Document | Description | +| ------------------------------------------------ | ------------------------------------------------- | +| [Fory IDL Syntax](schema-idl.md) | Complete language syntax and grammar | +| [Type System](schema-idl.md#type-system) | Primitive types, collections, and type rules | +| [RPC Services](schema-idl.md#service-definition) | Service and RPC method syntax | +| [Compiler Guide](compiler-guide.md) | CLI options and build integration | +| [Generated Code](generated-code.md) | Output format for each target language | +| [Protocol Buffers IDL Support](protobuf-idl.md) | Protobuf mapping rules and adoption guidance | +| [FlatBuffers IDL Support](flatbuffers-idl.md) | FlatBuffers mapping rules and codegen differences | + +## Key Concepts + +### Field Modifiers + +- **`optional`**: Field can be null/None +- **`ref`**: Enable reference tracking for shared/circular references +- **`list`**: Field is an ordered collection (alias: `repeated`) +- **`array`**: Field is dense one-dimensional bool or numeric data + +```protobuf +message Example { + optional string nullable = 1; + ref Node parent = 2; + list numbers = 3; +} +``` + +### Cross-Language Compatibility + +Fory IDL types map to native types in each language: + +| Fory IDL Type | Java | Python | C++ | Go | Rust | JavaScript/TypeScript | C# | Swift | Dart | Scala | Kotlin | +| ------------- | --------- | -------------- | ------------- | -------- | -------- | --------------------- | -------- | -------- | -------- | --------- | --------- | +| `int32` | `int` | `pyfory.Int32` | `int32_t` | `int32` | `i32` | `number` | `int` | `Int32` | `int` | `Int` | `Int` | +| `string` | `String` | `str` | `std::string` | `string` | `String` | `string` | `string` | `String` | `String` | `String` | `String` | +| `bool` | `boolean` | `bool` | `bool` | `bool` | `bool` | `boolean` | `bool` | `Bool` | `bool` | `Boolean` | `Boolean` | + +See [Type System](schema-idl.md#type-system) for complete mappings. + +## Best Practices + +1. **Use meaningful package names**: Group related types together +2. **Assign type IDs for performance**: Numeric IDs are faster than name-based registration +3. **Reserve ID ranges**: Leave gaps for future additions (e.g., 100-199 for users, 200-299 for orders) +4. **Use `optional` explicitly**: Make nullability clear in the schema +5. **Use `ref` for shared objects**: Enable reference tracking when objects are shared + +## Examples + +See the [examples](https://github.com/apache/fory/tree/main/compiler/examples) directory for complete working examples. diff --git a/versioned_docs/version-1.3.0/compiler/protobuf-idl.md b/versioned_docs/version-1.3.0/compiler/protobuf-idl.md new file mode 100644 index 00000000000..d9355deece2 --- /dev/null +++ b/versioned_docs/version-1.3.0/compiler/protobuf-idl.md @@ -0,0 +1,366 @@ +--- +title: Protobuf IDL Support +sidebar_position: 10 +id: protobuf_idl_support +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 page explains how Apache Fory works with Protocol Buffers (`.proto`) schemas, +how protobuf concepts map to Fory, and how to use protobuf-only Fory extension options. + +## What This Page Covers + +- Choosing protobuf vs Fory for your use case +- Syntax and semantic differences that matter during adoption +- Supported Fory extension options in protobuf files +- Practical transition patterns from protobuf to Fory + +## Quick Decision Guide + +| Situation | Recommended Format | +| ------------------------------------------------------------- | ------------------ | +| You are building gRPC APIs and rely on protobuf tooling | Protocol Buffers | +| You need maximum object-graph performance and ref tracking | Fory | +| You need circular/shared references in serialized data | Fory | +| You need strong unknown-field behavior for wire compatibility | Protocol Buffers | +| You need native structs/classes instead of protobuf wrappers | Fory | + +## Protobuf vs Fory at a Glance + +| Aspect | Protocol Buffers | Fory | +| ------------------ | ----------------------------- | ------------------------------------------------------------------- | +| Primary purpose | RPC/message contracts | High-performance object serialization | +| Encoding model | Tag-length-value | Fory binary protocol | +| Reference tracking | Not built-in | First-class (`ref`) | +| Circular refs | Not supported | Supported | +| Unknown fields | Preserved | Not preserved | +| Generated types | Protobuf-specific model types | Native language constructs | +| gRPC ecosystem | Native | Java/Python/Go/Rust/C#/Dart/Scala/Kotlin/JavaScript service codegen | + +Fory can generate Java, Python, Go, Rust, C#, Dart, Scala, Kotlin, and +JavaScript gRPC service companions with `--grpc`. JavaScript browser clients are +generated with `--grpc-web`. Those services use normal gRPC transports but serialize request +and response payloads with Fory rather than protobuf. For broad gRPC ecosystem +tooling, schema reflection, and protobuf-native interceptors, protobuf remains +the mature/default choice. + +## Why Use Apache Fory + +- Idiomatic generated code: Fory IDL generates language-idiomatic classes and + structs that can be used directly as domain objects. +- Faster serialization: In Fory benchmarks, Fory can be around 10x faster than + protobuf for object serialization workloads. +- Better graph modeling: Shared and circular references are first-class features + instead of application-level ID-link workarounds. + +See benchmark details under [Performance References](#performance-references). + +## Syntax and Semantic Mapping + +### Package and File Options + +**Protocol Buffers** + +```protobuf +syntax = "proto3"; +package example.models; +option java_package = "com.example.models"; +option go_package = "example.com/models"; +``` + +**Fory** + +```protobuf +package example.models; +``` + +Fory uses one package namespace for cross-language registration. Language-specific +package placement is still configurable in code generation. + +### Message and Enum Definitions + +**Protocol Buffers** + +```protobuf +message User { + string id = 1; + string name = 2; + optional string email = 3; + int32 age = 4; + repeated string tags = 5; + map metadata = 6; +} + +enum Status { + STATUS_UNSPECIFIED = 0; + STATUS_ACTIVE = 1; +} +``` + +**Fory** + +```protobuf +message User [id=101] { + string id = 1; + string name = 2; + optional string email = 3; + int32 age = 4; + list tags = 5; + map metadata = 6; +} + +enum Status [id=102] { + UNKNOWN = 0; + ACTIVE = 1; +} +``` + +Key differences: + +- Fory can assign stable type IDs directly (`[id=...]`). +- Fory uses `list` (with `repeated T` as alias). +- Enum naming conventions are language-driven instead of protobuf prefix style. + +### `oneof` to `union` + +Protobuf `oneof` is translated to a nested Fory `union` plus an optional field +referencing that union. + +**Protocol Buffers** + +```protobuf +message Event { + oneof payload { + string text = 1; + int32 number = 2; + } +} +``` + +**Fory-style shape after translation** + +```protobuf +message Event { + union payload { + string text = 1; + int32 number = 2; + } + optional payload payload = 1; +} +``` + +Notes: + +- Union case IDs are derived from the original `oneof` field numbers. +- The synthetic union field uses the smallest `oneof` case number. + +### Imports and Well-Known Types + +Protobuf imports are supported. Common well-known types map directly: + +- `google.protobuf.Timestamp` -> `timestamp` +- `google.protobuf.Duration` -> `duration` +- `google.protobuf.Any` -> `any` + +## Type Mapping Highlights + +| Protobuf Type | Fory Mapping | +| ---------------------------------------- | ---------------------------------------- | +| `bool` | `bool` | +| `int32`, `uint32` | variable-length 32-bit integer kinds | +| `sint32` | zigzag 32-bit integer | +| `int64`, `uint64` | variable-length 64-bit integer kinds | +| `sint64` | zigzag 64-bit integer | +| `fixed32`, `fixed64` | fixed-width unsigned integer kinds | +| `sfixed32`, `sfixed64` | fixed-width signed integer kinds | +| `float`, `double` | `float32`, `float64` | +| `string`, `bytes` | `string`, `bytes` | +| `repeated T` | `list` | +| `map` | `map` | +| `optional T` | `optional T` | +| `oneof` | `union` + optional union reference field | +| `int64 [(fory).type = "tagged int64"]` | `tagged int64` encoding | +| `uint64 [(fory).type = "tagged uint64"]` | `tagged uint64` encoding | + +## Fory Extension Options (Protobuf) + +Fory-specific options in `.proto` use the `(fory).` prefix. + +```protobuf +option (fory).enable_auto_type_id = true; + +message TreeNode { + TreeNode parent = 1 [(fory).weak_ref = true]; + repeated TreeNode children = 2 [(fory).ref = true]; +} +``` + +### File-Level Options + +| Option | Type | Description | +| ------------------------------------ | ------ | -------------------------------------------------------------------------------------------- | +| `(fory).use_record_for_java_message` | bool | Generate Java records for all messages in this file | +| `(fory).polymorphism` | bool | Enable polymorphic serialization metadata by default | +| `(fory).enable_auto_type_id` | bool | Auto-generate type IDs when omitted (compiler default is true) | +| `(fory).evolving` | bool | Default schema-evolution behavior for messages | +| `(fory).go_nested_type_style` | string | Go nested naming style: `underscore` (default) or `camelcase` | +| `(fory).swift_namespace_style` | string | Swift namespace style: `enum` (default) or `flatten`; applies only when package is non-empty | + +### Message and Enum Options + +| Option | Applies To | Type | Description | +| ---------------------------- | ------------- | ------ | ---------------------------------------- | +| `(fory).id` | message, enum | int | Explicit type ID for registration | +| `(fory).alias` | message, enum | string | Alternate name used for auto-ID hashing | +| `(fory).evolving` | message | bool | Override file-level evolution setting | +| `(fory).use_record_for_java` | message | bool | Generate Java record for this message | +| `(fory).deprecated` | message, enum | bool | Mark type as deprecated | +| `(fory).namespace` | message | string | Override default package-based namespace | + +### Field-Level Options + +| Option | Type | Description | +| ---------------------------- | ------ | ---------------------------------------------------------------------------------------------------- | +| `(fory).ref` | bool | Enable reference tracking for this field | +| `(fory).nullable` | bool | Treat field as nullable (`optional`) | +| `(fory).weak_ref` | bool | Generate weak pointer semantics (C++/Rust codegen) | +| `(fory).thread_safe_pointer` | bool | Rust ref carrier selection; default `true` uses `Arc`/`ArcWeak`, explicit `false` uses `Rc`/`RcWeak` | +| `(fory).deprecated` | bool | Mark field as deprecated | +| `(fory).type` | string | Primitive override for tagged 64-bit integer encoding | + +Reference option behavior: + +- `weak_ref = true` implies ref tracking. +- For `repeated` fields, `(fory).ref = true` applies to list elements. +- For `map` fields, `(fory).ref = true` applies to map values. +- `weak_ref` and `thread_safe_pointer` are codegen hints for C++/Rust. +- `thread_safe_pointer` defaults to `true`; it changes only the generated Rust + pointer carrier and does not change the wire format. +- In Rust codegen, `(fory).weak_ref = true` uses `ArcWeak` by default and + switches to `RcWeak` only when `(fory).thread_safe_pointer = false`. + +### Option Examples by Shape + +```protobuf +message Graph { + Node root = 1 [(fory).ref = true]; + repeated Node nodes = 2 [(fory).ref = true]; + map cache = 3 [(fory).ref = true]; + Node parent = 4 [(fory).weak_ref = true]; + Node local = 5 [(fory).ref = true, (fory).thread_safe_pointer = false]; +} +``` + +## Reference Tracking vs Protobuf IDs + +Protobuf itself does not preserve shared/cyclic object graphs. With Fory +protobuf extensions, you can opt into graph semantics. + +**Without Fory ref options (protobuf-style IDs):** + +```protobuf +message TreeNode { + string id = 1; + string parent_id = 2; + repeated string child_ids = 3; +} +``` + +**With Fory ref options (object graph):** + +```protobuf +message TreeNode { + TreeNode parent = 1 [(fory).weak_ref = true]; + repeated TreeNode children = 2 [(fory).ref = true]; +} +``` + +## Porting Protobuf Schemas To Fory + +### Step 1: Translate Schema Syntax + +- Keep package names stable. +- Replace `repeated T` with `list` (or keep `repeated` alias). +- Add explicit `[id=...]` where you need stable numeric registration. + +### Step 2: Convert `oneof` and Special Types + +- `oneof` -> `union` + optional union field. +- Map protobuf well-known types to Fory primitives (`timestamp`, `duration`, `any`). + +### Step 3: Replace Protobuf Workarounds with `ref` + +Where protobuf used manual ID links for object graphs, switch to Fory `ref` +modifiers (and optional `ref(weak=true)` where needed). + +### Step 4: Update Build/Codegen + +Replace protobuf generation steps with the Fory compiler invocation for target +languages. + +For supported service outputs, add `--grpc` to emit gRPC companion code: + +```bash +foryc api.proto --java_out=./generated/java --python_out=./generated/python --go_out=./generated/go --rust_out=./generated/rust --csharp_out=./generated/csharp --dart_out=./generated/dart --scala_out=./generated/scala --kotlin_out=./generated/kotlin --javascript_out=./generated/javascript --grpc +``` + +Generated Java service files compile against grpc-java, generated Python service +modules use `grpc.aio` by default, generated Rust service files import `tonic` +and `bytes`, generated Go service files import grpc-go, generated JavaScript +Node.js service files import `@grpc/grpc-js`, generated C# service files import +`Grpc.Core.Api` types, generated Dart service files import `package:grpc`, +generated Scala service files compile against grpc-java, and generated Kotlin +service files compile against grpc-java and grpc-kotlin. Add those dependencies +in your application build; Fory packages do not add gRPC as a hard dependency. +Use `--grpc-python-mode=sync` for sync Python `grpcio` companions. Use +`--grpc-web` with JavaScript output to generate browser clients that import +`grpc-web`. +Protobuf `oneof` fields are translated to Fory union fields inside request and +response messages. Direct union RPC request or response types are not part of +normal protobuf RPC syntax. + +### Step 5: Run Compatibility Checks + +For staged transitions, keep both formats in parallel and verify payload-level +parity with integration tests. + +## Coexistence Strategy + +You can run protobuf and Fory in parallel during a staged transition: + +```java +public byte[] serialize(Object obj, Format format) { + if (format == Format.PROTOBUF) { + return ((MessageLite) obj).toByteArray(); + } + return fory.serialize(obj); +} +``` + +Use translators at service boundaries while internal object-graph heavy paths +migrate first. + +## Performance References + +- Benchmarks: https://fory.apache.org/docs/introduction/benchmark +- Benchmark code: https://github.com/apache/fory/tree/main/benchmarks + +## Summary + +Use protobuf when your primary concern is API contracts and gRPC ecosystem +integration. Use Fory when object-graph performance, native models, and +reference semantics are the primary concern. diff --git a/versioned_docs/version-1.3.0/compiler/schema-idl.md b/versioned_docs/version-1.3.0/compiler/schema-idl.md new file mode 100644 index 00000000000..633b90f1e0e --- /dev/null +++ b/versioned_docs/version-1.3.0/compiler/schema-idl.md @@ -0,0 +1,1736 @@ +--- +title: Schema IDL +sidebar_position: 2 +id: syntax +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 provides the syntax and semantic reference for Fory IDL. + +For compiler usage and build integration, see +[Compiler Guide](compiler-guide.md). For protobuf/FlatBuffers frontend mapping +rules, see [Protocol Buffers IDL Support](protobuf-idl.md) and +[FlatBuffers IDL Support](flatbuffers-idl.md). + +## File Structure + +An Fory IDL file typically consists of: + +1. Optional package declaration +2. Optional file-level options +3. Optional import statements +4. Type definitions (enums, messages, and unions) +5. Optional service definitions + +```protobuf +// Optional package declaration +package com.example.models; + +// Optional file-level options +option java_package = "com.example.models"; + +// Import statements +import "common/types.fdl"; + +// Type definitions +enum Color [id=100] { ... } +message User [id=101] { ... } +message OrderRequest [id=102] { ... } +message Order [id=103] { ... } +union Event [id=104] { ... } + +// Service definitions +service OrderService { + rpc GetOrder (OrderRequest) returns (Order); +} +``` + +## Comments + +Fory IDL supports both single-line and block comments: + +```protobuf +// This is a single-line comment + +/* + * This is a block comment + * that spans multiple lines + */ + +message Example { + string name = 1; // Inline comment +} +``` + +## Package Declaration + +The package declaration defines the namespace for all types in the file. + +```protobuf +package com.example.models; +``` + +You can optionally specify a package alias used for auto-generated type IDs: + +```protobuf +package com.example.models alias models_v1; +``` + +**Rules:** + +- Optional but recommended +- Must appear before any type definitions +- Only one package declaration per file +- Used for name-based type registration +- Package alias is used for auto-ID hashing + +**Language Mapping:** + +| Language | Package Usage | +| --------------------- | --------------------------------- | +| Java | Java package | +| Python | Module name (dots to underscores) | +| Go | Package name (last component) | +| Rust | Module name (dots to underscores) | +| C++ | Namespace (dots to `::`) | +| C# | Namespace | +| JavaScript/TypeScript | TypeScript module name | +| Swift | Namespace wrapper or prefix | +| Dart | Library name (package segments) | +| Scala | Scala package | +| Kotlin | Kotlin package | + +## File-Level Options + +Options can be specified at file level to control language-specific code generation. + +### Syntax + +```protobuf +option option_name = value; +``` + +### Java Package Option + +Override the Java package for generated code: + +```protobuf +package payment; +option java_package = "com.mycorp.payment.v1"; + +message Payment { + string id = 1; +} +``` + +**Effect:** + +- Generated Java files will be in `com/mycorp/payment/v1/` directory +- Java package declaration will be `package com.mycorp.payment.v1;` +- Type registration still uses the Fory IDL package (`payment`) for cross-language compatibility + +### Go Package Option + +Specify the Go import path and package name: + +```protobuf +package payment; +option go_package = "github.com/mycorp/apis/gen/payment/v1;paymentv1"; + +message Payment { + string id = 1; +} +``` + +**Format:** `"import/path;package_name"` or just `"import/path"` (last segment used as package name) + +**Effect:** + +- Generated Go files will have `package paymentv1` +- The import path can be used in other Go code +- Type registration still uses the Fory IDL package (`payment`) for cross-language compatibility + +### C# Namespace Option + +Override the C# namespace for generated code: + +```protobuf +package payment; +option csharp_namespace = "MyCorp.Payment.V1"; + +message Payment { + string id = 1; +} +``` + +**Effect:** + +- Generated C# files use `namespace MyCorp.Payment.V1;` +- Output path follows namespace segments (`MyCorp/Payment/V1/` under `--csharp_out`) +- Type registration still uses the Fory IDL package (`payment`) for cross-language compatibility + +### Kotlin Package Option + +Override the Kotlin package for generated source: + +```protobuf +package payment; +option kotlin_package = "com.mycorp.payment.v1"; + +message Payment { + string id = 1; +} +``` + +**Effect:** + +- Generated Kotlin files are written under `com/mycorp/payment/v1/` +- Kotlin source uses `package com.mycorp.payment.v1` +- Type registration still uses the Fory IDL package (`payment`) for cross-language compatibility + +If `kotlin_package` is absent, Kotlin uses the FDL package. A Kotlin import +graph cannot mix default-package schemas with named Kotlin packages. + +### Go Nested Type Style Option + +Control Go naming for nested message/enum/union types: + +```protobuf +package payment; +option go_nested_type_style = "camelcase"; + +message Envelope { + message Payload { + string id = 1; + } +} +``` + +**Values:** + +- `underscore` (default): `Envelope_Payload` +- `camelcase`: `EnvelopePayload` + +The CLI flag `--go_nested_type_style` overrides this schema option when both are set. + +### Swift Namespace Style Option + +Control how package namespace is reflected in Swift generated type names: + +```protobuf +package payment.v1; +option swift_namespace_style = "flatten"; + +message Payment { + string id = 1; +} +``` + +**Values:** + +- `enum` (default): namespace wrappers (for example `Payment.V1.Payment`) +- `flatten`: package prefix on top-level types (for example `Payment_V1_Payment`) + +**Important:** namespace wrapper/prefixing is only applied when package is non-empty. If package is empty, Swift emits top-level types directly for both styles. + +The CLI flag `--swift_namespace_style` overrides this schema option when both are set. + +### Rust Chrono Temporal Types Option + +Rust generated code uses Fory's lightweight temporal carrier types by default: +`fory::Date`, `fory::Timestamp`, and `fory::Duration`. Set +`rust_use_chrono_temporal_types` when the generated Rust API should expose +chrono temporal types instead: + +```protobuf +package payment; +option rust_use_chrono_temporal_types = true; + +message Event { + date business_day = 1; + timestamp created_at = 2; + duration timeout = 3; +} +``` + +With this option, Rust code maps `date` to `chrono::NaiveDate`, `timestamp` to +`chrono::NaiveDateTime`, and `duration` to `chrono::Duration`. The Rust crate +that compiles the generated code must depend on `chrono` and enable Fory's +`chrono` feature. + +### Java Outer Classname Option + +Generate all types as inner classes of a single outer wrapper class: + +```protobuf +package payment; +option java_outer_classname = "DescriptorProtos"; + +enum Status { + UNKNOWN = 0; + ACTIVE = 1; +} + +message Payment { + string id = 1; + Status status = 2; +} +``` + +**Effect:** + +- Generates a single file `DescriptorProtos.java` instead of separate files +- All enums and messages become `public static` inner classes +- The outer class is `public final` with a private constructor +- Useful for grouping related types together + +**Generated structure:** + +```java +public final class DescriptorProtos { + private DescriptorProtos() {} + + public static enum Status { + UNKNOWN, + ACTIVE; + } + + public static class Payment { + private String id; + private Status status; + // ... + } +} +``` + +**Combined with java_package:** + +```protobuf +package payment; +option java_package = "com.example.proto"; +option java_outer_classname = "PaymentProtos"; + +message Payment { + string id = 1; +} +``` + +This generates `com/example/proto/PaymentProtos.java` with all types as inner classes. + +### Java Multiple Files Option + +Control whether types are generated in separate files or as inner classes: + +```protobuf +package payment; +option java_outer_classname = "PaymentProtos"; +option java_multiple_files = true; + +message Payment { + string id = 1; +} + +message Receipt { + string id = 1; +} +``` + +**Behavior:** + +| `java_outer_classname` | `java_multiple_files` | Result | +| ---------------------- | --------------------- | ------------------------------------------- | +| Not set | Any | Separate files (one per type) | +| Set | `false` (default) | Single file with all types as inner classes | +| Set | `true` | Separate files (overrides outer class) | + +**Effect of `java_multiple_files = true`:** + +- Each top-level enum and message gets its own `.java` file +- Overrides `java_outer_classname` behavior +- Useful when you want separate files but still specify an outer class name for other purposes + +**Example without java_multiple_files (default):** + +```protobuf +option java_outer_classname = "PaymentProtos"; +// Generates: PaymentProtos.java containing Payment and Receipt as inner classes +``` + +**Example with java_multiple_files = true:** + +```protobuf +option java_outer_classname = "PaymentProtos"; +option java_multiple_files = true; +// Generates: Payment.java, Receipt.java (separate files) +``` + +### Multiple Options + +Multiple options can be specified: + +```protobuf +package payment; +option java_package = "com.mycorp.payment.v1"; +option go_package = "github.com/mycorp/apis/gen/payment/v1;paymentv1"; +option deprecated = true; + +message Payment { + string id = 1; +} +``` + +### Protobuf Extension Syntax + +In `.fdl` files, use native Fory IDL syntax only (for example, `[id=100]`, `ref`, +`optional`, `nullable=true`). + +Protobuf extension syntax with `(fory).` is for `.proto` files and the protobuf +frontend only. + +For protobuf extension options, see +[Protocol Buffers IDL Support](protobuf-idl.md#fory-extension-options-protobuf). + +### Option Priority + +For language-specific packages/namespaces: + +1. Language-specific option (`java_package`, `go_package`, `csharp_namespace`, + `kotlin_package`) +2. Fory IDL package declaration (fallback) + +**Example:** + +```protobuf +package myapp.models; +option java_package = "com.example.generated"; +``` + +| Scenario | Java Package Used | +| ---------------------- | ------------------------- | +| `java_package` present | `com.example.generated` | +| No `java_package` | `myapp.models` (fallback) | + +### Cross-Language Type Registration + +Language-specific options only affect where code is generated, not the type namespace used for serialization. This ensures cross-language compatibility: + +```protobuf +package myapp.models; +option java_package = "com.mycorp.generated"; +option go_package = "github.com/mycorp/gen;genmodels"; + +message User { + string name = 1; +} +``` + +All languages will register `User` with namespace `myapp.models`, enabling: + +- Java serialized data → Go deserialization +- Go serialized data → Java deserialization +- Any language combination works seamlessly + +## Import Statement + +Import statements allow you to use types defined in other Fory IDL files. + +### Basic Syntax + +```protobuf +import "path/to/file.fdl"; +``` + +### Multiple Imports + +```protobuf +import "common/types.fdl"; +import "common/enums.fdl"; +import "models/address.fdl"; +``` + +### Path Resolution + +Import paths are resolved relative to the importing file: + +``` +project/ +├── common/ +│ └── types.fdl +├── models/ +│ ├── user.fdl # import "../common/types.fdl" +│ └── order.fdl # import "../common/types.fdl" +└── main.fdl # import "common/types.fdl" +``` + +**Rules:** + +- Import paths are quoted strings (double or single quotes) +- Paths are resolved relative to the importing file's directory +- Imported types become available as if defined in the current file +- Circular imports are detected and reported as errors +- Transitive imports work (if A imports B and B imports C, A has access to C's types) + +### Complete Example + +**common/types.fdl:** + +```protobuf +package common; + +enum Status [id=100] { + PENDING = 0; + ACTIVE = 1; + COMPLETED = 2; +} + +message Address [id=101] { + string street = 1; + string city = 2; + string country = 3; +} +``` + +**models/user.fdl:** + +```protobuf +package models; +import "../common/types.fdl"; + +message User [id=200] { + string id = 1; + string name = 2; + Address home_address = 3; // Uses imported type + Status status = 4; // Uses imported enum +} +``` + +### Unsupported Import Syntax + +The following protobuf import modifiers are **not supported**: + +```protobuf +// NOT SUPPORTED - will produce an error +import public "other.fdl"; +import weak "other.fdl"; +``` + +**`import public`**: Fory IDL uses a simpler import model. All imported types are available to the importing file only. Re-exporting is not supported. Import each file directly where needed. + +**`import weak`**: Fory IDL requires all imports to be present at compile time. Optional dependencies are not supported. + +### Import Errors + +The compiler reports errors for: + +- **File not found**: The imported file doesn't exist +- **Circular import**: A imports B which imports A (directly or indirectly) +- **Parse errors**: Syntax errors in imported files +- **Unsupported syntax**: `import public` or `import weak` + +## Enum Definition + +Enums define a set of named integer constants. + +### Basic Syntax + +```protobuf +enum Status { + PENDING = 0; + ACTIVE = 1; + COMPLETED = 2; +} +``` + +### With Explicit Type ID + +```protobuf +enum Status [id=100] { + PENDING = 0; + ACTIVE = 1; + COMPLETED = 2; +} +``` + +### Reserved Values + +Reserve field numbers or names to prevent reuse: + +```protobuf +enum Status { + reserved 2, 15, 9 to 11, 40 to max; // Reserved numbers + reserved "OLD_STATUS", "DEPRECATED"; // Reserved names + PENDING = 0; + ACTIVE = 1; + COMPLETED = 3; +} +``` + +### Enum Type Options + +Enum-level options are declared inline in `[]` after the enum name: + +```protobuf +enum Status [deprecated=true] { + PENDING = 0; + ACTIVE = 1; +} +``` + +FDL does not support `option ...;` statements inside enum bodies. + +**Unsupported:** + +- `allow_alias` is **not supported**. Each enum value must have a unique integer. + +### Language Mapping + +| Language | Implementation | +| --------------------- | -------------------------------------- | +| Java | `enum Status { UNKNOWN, ACTIVE, ... }` | +| Python | `class Status(IntEnum): UNKNOWN = 0` | +| Go | `type Status int32` with constants | +| Rust | `#[repr(i32)] enum Status { Unknown }` | +| C++ | `enum class Status : int32_t { ... }` | +| C# | `enum Status { Unknown, Active, ... }` | +| JavaScript/TypeScript | `export enum Status { UNKNOWN, ... }` | +| Swift | `enum Status` with stable IDs | +| Dart | `enum Status { unknown, active, ... }` | +| Scala | Scala 3 `enum Status` | +| Kotlin | `enum class Status` | + +### 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: + +```protobuf +// Input with prefix +enum DeviceTier { + DEVICE_TIER_UNKNOWN = 0; + DEVICE_TIER_TIER1 = 1; + DEVICE_TIER_TIER2 = 2; +} +``` + +**Generated code:** + +| Language | Output | Style | +| --------------------- | ----------------------------------------- | -------------- | +| Java | `UNKNOWN, TIER1, TIER2` | Scoped enum | +| Rust | `Unknown, Tier1, Tier2` | Scoped enum | +| C++ | `UNKNOWN, TIER1, TIER2` | Scoped enum | +| Python | `UNKNOWN, TIER1, TIER2` | Scoped IntEnum | +| Go | `DeviceTierUnknown, DeviceTierTier1, ...` | Unscoped const | +| JavaScript/TypeScript | `UNKNOWN, TIER1, TIER2` | Scoped enum | +| C# | `Unknown, Tier1, Tier2` | Scoped enum | +| Swift | `unknown, tier1, tier2` | Scoped enum | +| Dart | `unknown, tier1, tier2` | Scoped enum | +| Scala | `Unknown, Tier1, Tier2` | Scoped enum | +| Kotlin | `UNKNOWN, TIER1, TIER2` | Scoped enum | + +**Note:** The prefix is only stripped if the remainder is a valid identifier. For example, `DEVICE_TIER_1` is kept unchanged because `1` is not a valid identifier name. + +**Grammar:** + +``` +enum_def := 'enum' IDENTIFIER [type_options] '{' enum_body '}' +type_options := '[' type_option (',' type_option)* ']' +type_option := IDENTIFIER '=' option_value +enum_body := (reserved_stmt | enum_value)* +reserved_stmt := 'reserved' reserved_items ';' +enum_value := IDENTIFIER '=' INTEGER ';' +``` + +**Rules:** + +- Enum names must be unique within the file +- Enum values must have explicit integer assignments +- Value integers must be unique within the enum (no aliases) +- Type ID (`[id=100]`) is optional for enums but recommended for cross-language use + +**Example with All Features:** + +```protobuf +// HTTP status code categories +enum HttpCategory [id=200] { + reserved 10 to 20; // Reserved for future use + reserved "UNKNOWN"; // Reserved name + INFORMATIONAL = 1; + SUCCESS = 2; + REDIRECTION = 3; + CLIENT_ERROR = 4; + SERVER_ERROR = 5; +} +``` + +## Message Definition + +Messages define structured data types with typed fields. + +### Basic Syntax + +```protobuf +message Person { + string name = 1; + int32 age = 2; +} +``` + +### With Explicit Type ID + +```protobuf +message Person [id=101] { + string name = 1; + int32 age = 2; +} +``` + +### Without Explicit Type ID + +```protobuf +message Person { // Auto-generated when enable_auto_type_id = true + string name = 1; + int32 age = 2; +} +``` + +### Language Mapping + +| Language | Implementation | +| --------------------- | ----------------------------------- | +| Java | POJO class with getters/setters | +| Python | `@dataclass` class | +| Go | Struct with exported fields | +| Rust | Struct with `#[derive(ForyStruct)]` | +| C++ | Struct with `FORY_STRUCT` macro | +| C# | `[ForyStruct]` class | +| JavaScript/TypeScript | `export interface` declaration | +| Swift | `@ForyStruct` struct or class | +| Dart | `@ForyStruct` `final class` | +| Scala | Scala 3 `case class` or class | +| Kotlin | `data class` or class | + +Type IDs control cross-language registration for messages, unions, and enums. See +[Type IDs](#type-ids) for auto-generation, aliases, and collision handling. + +### Reserved Fields + +Reserve field numbers or names to prevent reuse after removing fields: + +```protobuf +message User { + reserved 2, 15, 9 to 11; // Reserved field numbers + reserved "old_field", "temp"; // Reserved field names + string id = 1; + string name = 3; +} +``` + +### Message Type Options + +Message-level options are declared inline in `[]` after the message name: + +```protobuf +message User [deprecated=true] { + string id = 1; + string name = 2; +} +``` + +FDL does not support `option ...;` statements inside message or enum bodies. + +**Grammar:** + +``` +message_def := 'message' IDENTIFIER [type_options] '{' message_body '}' +type_options := '[' type_option (',' type_option)* ']' +type_option := IDENTIFIER '=' option_value +message_body := (reserved_stmt | nested_type | field_def)* +nested_type := enum_def | message_def | union_def +``` + +**Rules:** + +- Type IDs follow the rules in [Type IDs](#type-ids). + +## Nested Types + +Messages can contain nested message, enum, and union definitions. This is useful for defining types that are closely related to their parent message. + +### Nested Messages + +```protobuf +message SearchResponse { + message Result { + string url = 1; + string title = 2; + list snippets = 3; + } + list results = 1; +} +``` + +### Nested Enums + +```protobuf +message Container { + enum Status { + STATUS_UNKNOWN = 0; + STATUS_ACTIVE = 1; + STATUS_INACTIVE = 2; + } + Status status = 1; +} +``` + +### Qualified Type Names + +Nested types can be referenced from other messages using qualified names (Parent.Child): + +```protobuf +message SearchResponse { + message Result { + string url = 1; + string title = 2; + } +} + +message SearchResultCache { + // Reference nested type with qualified name + SearchResponse.Result cached_result = 1; + list all_results = 2; +} +``` + +### Deeply Nested Types + +Nesting can be multiple levels deep: + +```protobuf +message Outer { + message Middle { + message Inner { + string value = 1; + } + Inner inner = 1; + } + Middle middle = 1; +} + +message OtherMessage { + // Reference deeply nested type + Outer.Middle.Inner deep_ref = 1; +} +``` + +### Language-Specific Generation + +| Language | Nested Type Generation | +| --------------------- | --------------------------------------------------------------------------------- | +| Java | Static inner classes (`SearchResponse.Result`) | +| Python | Nested classes within dataclass | +| Go | Flat structs with underscore (`SearchResponse_Result`, configurable to camelcase) | +| Rust | Nested modules (`search_response::Result`) | +| C++ | Nested classes (`SearchResponse::Result`) | +| C# | Nested classes (`SearchResponse.Result`) | +| JavaScript/TypeScript | Flat names (`Result`) | +| Swift | Nested namespace wrappers or flattened names | +| Dart | Flat classes with underscore (`SearchResponse_Result`) | +| Scala | Nested companion/object scope | +| Kotlin | Flat generated names | + +**Note:** Go defaults to underscore-separated nested names; set `option go_nested_type_style = "camelcase";` to use concatenated names. Rust emits nested modules for nested types. + +### Nested Type Rules + +- Nested type names must be unique within their parent message +- Nested types can have their own type IDs +- Numeric type IDs must be globally unique (including nested types); see [Type IDs](#type-ids) + for auto-generation and collision handling +- Within a message, you can reference nested types by simple name +- From outside, use the qualified name (Parent.Child) + +## Union Definition + +Unions define a value that can hold exactly one of several case types. + +### Basic Syntax + +```protobuf +union Animal [id=106] { + Dog dog = 1; + Cat cat = 2; +} +``` + +### Using a Union in a Message + +```protobuf +message Person [id=100] { + Animal pet = 1; + optional Animal favorite_pet = 2; +} +``` + +### Rules + +- Case IDs must be non-negative and unique within the union +- Language-specific unknown-case markers only select the carrier and do not add + entries to the schema case table +- Cases cannot be `optional` or `ref` +- Union cases support field options for payload metadata, such as scalar encoding + and collection element metadata +- Case types can be primitives, enums, messages, or other named types +- Union type IDs follow the rules in [Type IDs](#type-ids). + +**Grammar:** + +``` +union_def := 'union' IDENTIFIER [type_options] '{' union_field* '}' +union_field := ['repeated'] field_type IDENTIFIER '=' INTEGER [field_options] ';' +``` + +## Service Definition + +Services define RPC method contracts in Fory IDL. They are optional: schemas +with services still generate the normal data model types, and gRPC service code +is generated only when the compiler is run with `--grpc` for supported language +outputs such as Java, Python, Go, Rust, C#, Dart, Scala, Kotlin, and JavaScript. +JavaScript browser gRPC-Web clients are generated with `--grpc-web`. + +```protobuf +message GetPetRequest [id=200] { + string name = 1; +} + +message PetRecord [id=201] { + string name = 1; + Animal animal = 2; +} + +service PetDirectory { + rpc GetPet (GetPetRequest) returns (PetRecord); + rpc Classify (Animal) returns (Animal); +} +``` + +The first method uses message request and response types. The second method uses +a direct union request and response type, which is supported in Fory IDL. + +### Streaming RPCs + +Use `stream` before the request type, the response type, or both: + +```protobuf +service PetDirectory { + rpc GetPet (GetPetRequest) returns (PetRecord); // unary + rpc WatchPets (GetPetRequest) returns (stream PetRecord); // server streaming + rpc ImportPets (stream PetRecord) returns (PetRecord); // client streaming + rpc ChatPets (stream Animal) returns (stream Animal); // bidirectional streaming +} +``` + +### RPC Type Rules + +- Request and response types must reference named message or union types. +- Enum, primitive, collection, map, and array types are not valid direct RPC + request or response types. Wrap those values in a message when they are part + of a service contract. +- The generated gRPC companions use Fory serialization for each RPC payload. + Applications that compile or run those companions provide their own gRPC + dependency, such as grpc-java, grpc-kotlin, `grpcio`, grpc-go, Rust `tonic` + and `bytes`, Scala grpc-java APIs, `@grpc/grpc-js`, `grpc-web`, C# + `Grpc.Core.Api` plus a server or client package, or Dart `package:grpc`. Python + companions use `grpc.aio` by default and can be generated in sync mode with + `--grpc-python-mode=sync`. + +**Grammar:** + +``` +service_def := 'service' IDENTIFIER '{' rpc_method* '}' +rpc_method := 'rpc' IDENTIFIER '(' ['stream'] named_type ')' + 'returns' '(' ['stream'] named_type ')' ';' +``` + +## Field Definition + +Fields define the properties of a message. + +### Basic Syntax + +```protobuf +field_type field_name = field_number; +``` + +### With Modifiers + +```protobuf +optional list tags = 1; // Nullable list +list tags = 2; // Elements may be null +list nodes = 3; // Elements tracked as references +``` + +**Grammar:** + +``` +field_def := [modifiers] field_type IDENTIFIER '=' INTEGER ';' +modifiers := { 'optional' | 'ref' } +field_type := primitive_type | named_type | list_type | array_type | map_type +list_type := 'list' '<' { 'optional' | 'ref' | scalar_encoding } field_type '>' +array_type := 'array' '<' array_element_type '>' +``` + +`optional` before `list` applies to the collection field. `ref` is only valid +for named message/union fields; for collection contents, use `list` or +`map`. `repeated` is accepted as an alias for `list`. + +### Field Modifiers + +#### `optional` + +Marks the field as nullable: + +```protobuf +message User { + string name = 1; // Required, non-null + optional string email = 2; // Nullable +} +``` + +**Generated Code:** + +| Language | Non-optional | Optional | +| --------------------- | ------------------ | --------------------------------- | +| Java | `String name` | `@Nullable String email` | +| Python | `name: str` | `name: Optional[str]` | +| Go | `Name string` | `Name *string` | +| Rust | `name: String` | `name: Option` | +| C++ | `std::string name` | `std::optional name` | +| C# | `string name` | `string? email` | +| JavaScript/TypeScript | `name: string` | `name?: string \| null` | +| Swift | `String name` | `String? email` | +| Dart | `String name` | `String? email` | +| Scala | `name: String` | `email: Option[String]` | +| Kotlin | `name: String` | `email: String?` | + +**Default Values:** + +| Type | Default Value | +| ------------------ | ------------------- | +| Non-optional types | Language default | +| Optional types | `null`/`None`/`nil` | + +#### `ref` + +Enables reference tracking for shared/circular references: + +```protobuf +message Node { + string value = 1; + ref Node parent = 2; // Can point to shared object + list children = 3; +} +``` + +**Use Cases:** + +- Shared objects (same object referenced multiple times) +- Circular references (object graphs with cycles) +- Tree structures with parent pointers + +**Generated Code:** + +| Language | Without `ref` | With `ref` | +| --------------------- | -------------- | ------------------------------------------ | +| Java | `Node parent` | `Node parent` with `@Ref` | +| Python | `parent: Node` | `parent: Node = pyfory.field(ref=True)` | +| Go | `Parent Node` | `Parent *Node` with `fory:"ref"` | +| Rust | `parent: Node` | `parent: Arc` | +| C++ | `Node parent` | `std::shared_ptr parent` | +| C# | `Node parent` | `Node? parent` with reference tracking | +| JavaScript/TypeScript | `parent: Node` | `parent: Node` (no ref distinction) | +| Swift | `Node parent` | class reference with reference tracking | +| Dart | `Node parent` | `Node parent` with `@ForyField(ref: true)` | +| Scala | `parent: Node` | `@Ref parent: Node` | +| Kotlin | `parent: Node` | `@Ref parent: Node?` | + +Rust uses `Arc` and `ArcWeak` by default for ref-tracked fields. Use +`ref(thread_safe=false)` when a generated Rust type must use single-threaded +`Rc` or `RcWeak` carriers. This setting is a Rust codegen carrier choice; it +does not change the wire format or make the referenced value itself +thread-safe. For protobuf option syntax, see +[Protocol Buffers IDL Support](protobuf-idl.md#field-level-options). + +Rust pointer carrier mapping: + +| Fory IDL | Rust type | +| ----------------------------------------------- | --------------- | +| `ref Node parent` | `Arc` | +| `ref(thread_safe=false) Node parent` | `Rc` | +| `ref(weak=true) Node parent` | `ArcWeak` | +| `ref(weak=true, thread_safe=false) Node parent` | `RcWeak` | + +#### `list` + +Marks the field as an ordered collection: + +```protobuf +message Document { + list tags = 1; + list authors = 2; +} +``` + +**Generated Code:** + +| Language | Type | +| --------------------- | -------------------------- | +| Java | `List` | +| Python | `List[str]` | +| Go | `[]string` | +| Rust | `Vec` | +| C++ | `std::vector` | +| C# | `List` | +| JavaScript/TypeScript | `string[]` | +| Swift | `[String]` | +| Dart | `List` | +| Scala | `List[String]` | +| Kotlin | `List` | + +### Combining Modifiers + +Modifiers can be combined: + +```fdl +message Example { + optional list tags = 1; // Nullable list + list aliases = 2; // Elements may be null + list children = 3; // Elements tracked as references + optional ref User owner = 4; // Nullable tracked reference +} +``` + +`optional` before `list` applies to the field/collection. `ref` before `list` or +`map` is invalid; put `ref` inside the element/value type instead. `repeated` is +accepted as an alias for `list`. + +**List modifier mapping:** + +| Fory IDL | Java | Python | Go | Rust | C++ | Dart | Scala | +| ----------------------- | ---------------------------------- | --------------------- | ----------------------- | --------------------- | ----------------------------------------- | ------------------------------------------------------------- | ---------------------- | +| `optional list` | `@Nullable List` | `Optional[List[str]]` | `[]string` + `nullable` | `Option>` | `std::optional>` | `List?` | `Option[List[String]]` | +| `list` | `List` (nullable elements) | `List[Optional[str]]` | `[]*string` | `Vec>` | `std::vector>` | `List` | `List[Option[String]]` | +| `list` | `List<@Ref User>` | `List[User]` | `[]*User` + `ref=false` | `Vec>` | `std::vector>` | `List` + `@ListField(element: DeclaredType(ref: true))` | `List[User @Ref]` | + +Use `ref(thread_safe=false)` in Fory IDL (or +`[(fory).thread_safe_pointer = false]` in protobuf) to generate `Rc` instead of +`Arc` in Rust. + +## Field Numbers + +Each field must have a unique positive integer identifier: + +```protobuf +message Example { + string first = 1; + string second = 2; + string third = 3; +} +``` + +**Rules and best practices:** + +- Numbers must be unique within a message. +- Numbers must be positive integers. +- Gaps are allowed and are useful when fields are removed. +- Prefer sequential numbering from `1`. +- Never reuse a removed field number for a different field. + +## Type System + +Fory IDL provides a cross-language type system for primitives, named types, and +collections. Field modifiers (`optional`, `ref`) control nullability and +reference tracking, while `list` and `array` choose collection schema kind +(see [Field Modifiers](#field-modifiers)). + +The compact tables in this section show common generated carriers. For the +complete 1.0 language support surface, including C#, Swift, Dart, Scala, and Kotlin, see +the [xlang type-mapping specification](../specification/xlang_type_mapping.md). + +### Primitive Types + +| Type | Description | Size | +| ----------- | ---------------------------------------------- | -------- | +| `bool` | Boolean value | 1 byte | +| `int8` | Signed 8-bit integer | 1 byte | +| `int16` | Signed 16-bit integer | 2 bytes | +| `int32` | Signed 32-bit integer, varint by default | 4 bytes | +| `int64` | Signed 64-bit integer, PVL varint by default | 8 bytes | +| `uint8` | Unsigned 8-bit integer | 1 byte | +| `uint16` | Unsigned 16-bit integer | 2 bytes | +| `uint32` | Unsigned 32-bit integer, varint by default | 4 bytes | +| `uint64` | Unsigned 64-bit integer, PVL varint by default | 8 bytes | +| `float16` | IEEE 754 binary16 floating point | 2 bytes | +| `bfloat16` | Brain floating point | 2 bytes | +| `float32` | 32-bit floating point | 4 bytes | +| `float64` | 64-bit floating point | 8 bytes | +| `string` | UTF-8 string | Variable | +| `bytes` | Binary data | Variable | +| `date` | Calendar date | Variable | +| `timestamp` | Date and time with timezone | Variable | +| `duration` | Duration | Variable | +| `decimal` | Decimal value | Variable | +| `any` | Dynamic value (concrete type) | Variable | + +#### Boolean + +| Language | Type | Notes | +| --------------------- | --------------------- | ------------------ | +| Java | `boolean` / `Boolean` | Primitive or boxed | +| Python | `bool` | | +| Go | `bool` | | +| Rust | `bool` | | +| C++ | `bool` | | +| JavaScript/TypeScript | `boolean` | | +| Dart | `bool` | | + +#### Integer Types + +Fory IDL provides fixed-width signed integers (varint encoding for 32/64-bit by default): + +| Fory IDL Type | Size | Range | +| ------------- | ------ | ----------------- | +| `int8` | 8-bit | -128 to 127 | +| `int16` | 16-bit | -32,768 to 32,767 | +| `int32` | 32-bit | -2^31 to 2^31 - 1 | +| `int64` | 64-bit | -2^63 to 2^63 - 1 | + +**Language Mapping (Signed):** + +| Fory IDL | Java | Python | Go | Rust | C++ | JavaScript/TypeScript | Dart | +| -------- | ------- | -------------- | ------- | ----- | --------- | --------------------- | ------- | +| `int8` | `byte` | `pyfory.Int8` | `int8` | `i8` | `int8_t` | `number` | `int` | +| `int16` | `short` | `pyfory.Int16` | `int16` | `i16` | `int16_t` | `number` | `int` | +| `int32` | `int` | `pyfory.Int32` | `int32` | `i32` | `int32_t` | `number` | `int` | +| `int64` | `long` | `pyfory.Int64` | `int64` | `i64` | `int64_t` | `bigint \| number` | `Int64` | + +Fory IDL provides fixed-width unsigned integers (varint encoding for 32/64-bit by default): + +| Fory IDL | Size | Range | +| -------- | ------ | ------------- | +| `uint8` | 8-bit | 0 to 255 | +| `uint16` | 16-bit | 0 to 65,535 | +| `uint32` | 32-bit | 0 to 2^32 - 1 | +| `uint64` | 64-bit | 0 to 2^64 - 1 | + +**Language Mapping (Unsigned):** + +| Fory IDL | Java | Python | Go | Rust | C++ | JavaScript/TypeScript | Dart | +| -------- | ------- | --------------- | -------- | ----- | ---------- | --------------------- | -------- | +| `uint8` | `short` | `pyfory.UInt8` | `uint8` | `u8` | `uint8_t` | `number` | `int` | +| `uint16` | `int` | `pyfory.UInt16` | `uint16` | `u16` | `uint16_t` | `number` | `int` | +| `uint32` | `long` | `pyfory.UInt32` | `uint32` | `u32` | `uint32_t` | `number` | `int` | +| `uint64` | `long` | `pyfory.UInt64` | `uint64` | `u64` | `uint64_t` | `bigint \| number` | `Uint64` | + +#### Integer Encoding Modifiers + +For 32/64-bit integers, Fory IDL uses variable-length encoding by default. Add a +scalar encoding modifier when you need a different wire encoding: + +| Modifier | Valid types | Notes | +| -------- | ------------------------------------ | ---------------------------- | +| `varint` | `int32`, `int64`, `uint32`, `uint64` | Explicit spelling of default | +| `fixed` | `int32`, `int64`, `uint32`, `uint64` | Fixed-width little-endian | +| `tagged` | `int64`, `uint64` | Tagged 64-bit encoding | + +Modifiers are part of the scalar type expression, so they can be used in nested +list and map positions: + +```protobuf +fixed int32 id = 1; +list offsets = 2; +map counters = 3; +``` + +Underscore spellings for integer encoding are not FDL type names. + +#### Floating-Point Types + +| Fory IDL Type | Size | Precision | +| ------------- | ------ | ------------- | +| `float32` | 32-bit | ~7 digits | +| `float64` | 64-bit | ~15-16 digits | + +**Language Mapping:** + +| Fory IDL | Java | Python annotation/value | Go | Rust | C++ | JavaScript/TypeScript | Dart | +| ---------- | ---------- | --------------------------- | ------------------- | ---------- | ------------------ | --------------------- | --------- | +| `float16` | `Float16` | `pyfory.Float16` / `float` | `float16.Float16` | `Float16` | `fory::float16_t` | `number` | `double` | +| `bfloat16` | `BFloat16` | `pyfory.BFloat16` / `float` | `bfloat16.BFloat16` | `BFloat16` | `fory::bfloat16_t` | `number` | `double` | +| `float32` | `float` | `pyfory.Float32` | `float32` | `f32` | `float` | `number` | `Float32` | +| `float64` | `double` | `pyfory.Float64` | `float64` | `f64` | `double` | `number` | `double` | + +#### String Type + +| Language | Type | Notes | +| --------------------- | ------------- | --------------------- | +| Java | `String` | Immutable | +| Python | `str` | | +| Go | `string` | Immutable | +| Rust | `String` | Owned, heap-allocated | +| C++ | `std::string` | | +| JavaScript/TypeScript | `string` | | +| Dart | `String` | | + +#### Bytes Type + +| Language | Type | Notes | +| --------------------- | ---------------------- | --------- | +| Java | `byte[]` | | +| Python | `bytes` | Immutable | +| Go | `[]byte` | | +| Rust | `Vec` | | +| C++ | `std::vector` | | +| JavaScript/TypeScript | `Uint8Array` | | +| Dart | `Uint8List` | | + +#### Temporal Types + +##### Date + +| Language | Type | Notes | +| --------------------- | --------------------------- | --------------------------------------------------------------------------- | +| Java | `java.time.LocalDate` | | +| Python | `datetime.date` | | +| Go | `time.Time` | Time portion ignored | +| Rust | `fory::Date` | Set `rust_use_chrono_temporal_types = true` to generate `chrono::NaiveDate` | +| C++ | `fory::serialization::Date` | | +| JavaScript/TypeScript | `Date` | | +| Dart | `LocalDate` | Fory package type | + +##### Timestamp + +| Language | Type | Notes | +| --------------------- | -------------------------------- | ------------------------------------------------------------------------------- | +| Java | `java.time.Instant` | UTC-based | +| Python | `datetime.datetime` | | +| Go | `time.Time` | | +| Rust | `fory::Timestamp` | Set `rust_use_chrono_temporal_types = true` to generate `chrono::NaiveDateTime` | +| C++ | `fory::serialization::Timestamp` | | +| JavaScript/TypeScript | `Date` | | +| Dart | `Timestamp` | Fory package type | + +##### Duration + +| Language | Type | Notes | +| -------- | ------------------------------- | -------------------------------------------------------------------------- | +| Java | `java.time.Duration` | | +| Python | `datetime.timedelta` | | +| Go | `time.Duration` | | +| Rust | `fory::Duration` | Set `rust_use_chrono_temporal_types = true` to generate `chrono::Duration` | +| C++ | `fory::serialization::Duration` | | +| Dart | `Duration` | | + +#### Any + +| Language | Type | Notes | +| --------------------- | ---------------------------- | ------------------------------------ | +| Java | `Object` | Concrete value type metadata written | +| Python | `Any` | Concrete value type metadata written | +| Go | `any` | Concrete value type metadata written | +| Rust | `Arc` | Concrete value type metadata written | +| C++ | `std::any` | Concrete value type metadata written | +| JavaScript/TypeScript | `any` | Concrete value type metadata written | +| Dart | `Object?` | Concrete value type metadata written | + +**Example:** + +```protobuf +enum EventType [id=120] { + CREATED = 0; + DELETED = 1; +} + +message UserCreated [id=121] { + string user_id = 1; +} + +message Envelope [id=122] { + EventType type = 1; + any payload = 2; +} +``` + +**Generated Code (`Envelope.payload`):** + +| Language | Generated Field Type | +| --------------------- | ------------------------------------- | +| Java | `Object payload` | +| Python | `payload: Any` | +| Go | `Payload any` | +| Rust | `payload: Arc` | +| C++ | `std::any payload` | +| JavaScript/TypeScript | `payload: any` | +| Dart | `Object? payload` | + +**Notes:** + +- `any` always writes a null flag (same as `nullable`) because values may be empty. +- Allowed dynamic values are limited to `bool`, `string`, `enum`, `message`, and `union`. + Other primitives (numeric, bytes, date/time) and list/map are not supported; wrap them in a + message or use explicit fields instead. +- `ref` is not allowed on `any` fields (including list/map values). Wrap `any` in a message + if you need reference tracking. +- The concrete type must be registered in the target language schema/IDL registration; unknown + types fail to deserialize. + +### Named Types + +Reference other messages, enums, or unions by name: + +```protobuf +enum Status { ... } +message User { ... } + +message Order { + User customer = 1; // Reference to User message + Status status = 2; // Reference to Status enum +} +``` + +### Collection Types + +#### List (`list`) + +Use the `list<...>` type for list fields. `repeated` is accepted as an alias. See [Field Modifiers](#field-modifiers) for +modifier combinations and language mapping. + +Nested collection support is target-capability based. The C++ generator accepts +nested collection specs such as `list>`, `list>`, and +`map<..., list<...>>`; targets that have not implemented nested field specs +continue to reject them. Use a message wrapper when you need portable schemas +across all targets. + +#### Array (`array`) + +Use `array` for dynamic-length dense numeric data. `array` is a distinct +schema kind from `list` and uses the packed primitive-array wire payload. + +```protobuf +message Embedding { + array indices = 1; + array values = 2; + array pixels = 3; +} +``` + +`array` accepts `bool`, integer, and floating-point element domains only. It +does not accept `optional`, `ref`, named/object types, `string`, `bytes`, maps, +or scalar integer encoding modifiers such as `array`; array +elements are always fixed-width by the array contract. + +Generated carriers are language-specific, but the schema kind is not: + +| IDL schema | Java default | Python default | Dart default | JavaScript/TypeScript | +| ----------------- | ---------------------------- | ---------------------- | -------------- | ------------------------ | +| `list` | `BoolList` / `List` | `List[bool]` | `List` | `Type.list(Type.bool())` | +| `array` | `boolean[]` | `pyfory.BoolArray` | `BoolList` | `Type.boolArray()` | +| `array` | `@Int8Type byte[]` | `pyfory.Int8Array` | `Int8List` | `Type.int8Array()` | +| `array` | `short[]` | `pyfory.Int16Array` | `Int16List` | `Type.int16Array()` | +| `array` | `int[]` | `pyfory.Int32Array` | `Int32List` | `Type.int32Array()` | +| `array` | `long[]` | `pyfory.Int64Array` | `Int64List` | `Type.int64Array()` | +| `array` | `@UInt8Type byte[]` | `pyfory.UInt8Array` | `Uint8List` | `Type.uint8Array()` | +| `array` | `@UInt16Type short[]` | `pyfory.UInt16Array` | `Uint16List` | `Type.uint16Array()` | +| `array` | `@UInt32Type int[]` | `pyfory.UInt32Array` | `Uint32List` | `Type.uint32Array()` | +| `array` | `@UInt64Type long[]` | `pyfory.UInt64Array` | `Uint64List` | `Type.uint64Array()` | +| `array` | `Float16Array` | `pyfory.Float16Array` | `Float16List` | `Type.float16Array()` | +| `array` | `BFloat16Array` | `pyfory.BFloat16Array` | `Bfloat16List` | `Type.bfloat16Array()` | +| `array` | `float[]` | `pyfory.Float32Array` | `Float32List` | `Type.float32Array()` | +| `array` | `double[]` | `pyfory.Float64Array` | `Float64List` | `Type.float64Array()` | + +For handwritten Dart models, `array` requires `BoolList` plus +`@ArrayField(element: BoolType())` or +`@ForyField(type: ArrayType(element: BoolType()))`; `List` remains +`list`. For handwritten Java models, unsigned primitive arrays use +type-use annotations on the element type, for example +`private @UInt32Type int[] ids;`. +For generated Kotlin models, `array` uses `@ArrayType ByteArray`, +including nested collection and map positions. + +#### Map + +Maps with typed keys and values: + +```protobuf +message Config { + map properties = 1; + map counts = 2; + map users = 3; +} +``` + +**Language Mapping:** + +| Fory IDL | Java | Python | Go | Rust | C++ | JavaScript/TypeScript | Dart | +| -------------------- | ---------------------- | ----------------- | ------------------ | ----------------------- | ------------------------------------------ | --------------------- | ------------------- | +| `map` | `Map` | `Dict[str, int]` | `map[string]int32` | `HashMap` | `std::unordered_map` | `Map` | `Map` | +| `map` | `Map` | `Dict[str, User]` | `map[string]User` | `HashMap` | `std::unordered_map` | `Map` | `Map` | + +**Key Type Restrictions:** + +- `string` (most common) +- `bool` +- Integer types (`int8`, `int16`, `int32`, `int64`, `uint8`, `uint16`, `uint32`, `uint64`) +- Temporal scalar types (`date`, `timestamp`, `duration`) +- Enums + +Map keys do not support binary `bytes`, floating-point types, `decimal`, `list`, `array`, +or nested `map` types. Put those types in map values or wrap them in a message with a +portable scalar or enum key. + +### Type Compatibility Matrix + +This matrix shows which type conversions are safe across languages: + +| From -> To | bool | int8 | int16 | int32 | int64 | float32 | float64 | string | +| ---------- | ---- | ---- | ----- | ----- | ----- | ------- | ------- | ------ | +| bool | Y | Y | Y | Y | Y | - | - | - | +| int8 | - | Y | Y | Y | Y | Y | Y | - | +| int16 | - | - | Y | Y | Y | Y | Y | - | +| int32 | - | - | - | Y | Y | - | Y | - | +| int64 | - | - | - | - | Y | - | - | - | +| float32 | - | - | - | - | - | Y | Y | - | +| float64 | - | - | - | - | - | - | Y | - | +| string | - | - | - | - | - | - | - | Y | + +Y = Safe conversion, - = Not recommended + +### Best Practices + +- Use `int32` as the default for most integers; use `int64` for large values. +- Use `string` for text data (UTF-8) and `bytes` for binary data. +- Use `optional` only when the field may legitimately be absent. +- Use `ref` only when needed for shared or circular references. +- Prefer `list` for ordered sequences and `map` for key-value lookups. + +## Type IDs + +Type IDs enable efficient cross-language serialization and are used for +messages, unions, and enums. When `enable_auto_type_id = true` (default) and +`id` is omitted, the compiler auto-generates one using +`MurmurHash3(utf8(package.type_name))` (32-bit) and annotates it in generated +code. When `enable_auto_type_id = false`, types without explicit IDs are +registered by namespace and name instead. Collisions are detected at +compile-time across the current file and all imports; when a collision occurs, +the compiler raises an error and asks for an explicit `id` or an `alias`. +For Java and Scala generated code, nested name registration appends the parent +path to the namespace and keeps the nested type's simple name. For example, +`package demo; message Envelope { message Payload { ... } }` registers +`Payload` as namespace `demo.Envelope` and type name `Payload` in those JVM +targets. + +```protobuf +enum Color [id=100] { ... } +message User [id=101] { ... } +union Event [id=102] { ... } +``` + +Enum type IDs remain optional; if omitted they are auto-generated using the same +hash when `enable_auto_type_id = true`. + +### With Explicit Type ID + +```protobuf +message User [id=101] { ... } +message User [id=101, deprecated=true] { ... } // Multiple options +``` + +### Without Explicit Type ID + +```protobuf +message Config { ... } // Auto-generated when enable_auto_type_id = true +``` + +You can set `[alias="..."]` to change the hash source without renaming the type. + +### Practical Notes + +- If a type omits `id` and `enable_auto_type_id = true`, Fory generates an ID + with `MurmurHash3(utf8(package.type_name))` (32-bit). +- Package alias and type alias change the hash input and can be used to resolve + hash collisions without renaming public types. +- Manual IDs in the small varint range (`0-127`) are compact on the wire; auto + IDs are typically larger and usually consume 4-5 bytes. + +### ID Assignment Strategy + +```protobuf +// Enums: 100-199 +enum Status [id=100] { ... } +enum Priority [id=101] { ... } + +// User domain: 200-299 +message User [id=200] { ... } +message UserProfile [id=201] { ... } + +// Order domain: 300-399 +message Order [id=300] { ... } +message OrderItem [id=301] { ... } +``` + +## Complete Example + +```protobuf +// E-commerce domain model +package com.shop.models; + +// Enums with type IDs +enum OrderStatus [id=100] { + PENDING = 0; + CONFIRMED = 1; + SHIPPED = 2; + DELIVERED = 3; + CANCELLED = 4; +} + +enum PaymentMethod [id=101] { + CREDIT_CARD = 0; + DEBIT_CARD = 1; + PAYPAL = 2; + BANK_TRANSFER = 3; +} + +// Messages with type IDs +message Address [id=200] { + string street = 1; + string city = 2; + string state = 3; + string country = 4; + string postal_code = 5; +} + +message Customer [id=201] { + string id = 1; + string name = 2; + optional string email = 3; + optional string phone = 4; + optional Address billing_address = 5; + optional Address shipping_address = 6; +} + +message Product [id=202] { + string sku = 1; + string name = 2; + string description = 3; + float64 price = 4; + int32 stock = 5; + list categories = 6; + map attributes = 7; +} + +message OrderItem [id=203] { + ref Product product = 1; // Track reference to avoid duplication + int32 quantity = 2; + float64 unit_price = 3; +} + +message Order [id=204] { + string id = 1; + ref Customer customer = 2; + list items = 3; + OrderStatus status = 4; + PaymentMethod payment_method = 5; + float64 total = 6; + optional string notes = 7; + timestamp created_at = 8; + optional timestamp shipped_at = 9; +} + +// Config without explicit type ID (auto-generated when enable_auto_type_id = true) +message ShopConfig { + string store_name = 1; + string currency = 2; + float64 tax_rate = 3; + list supported_countries = 4; +} +``` + +For protobuf-specific extension options and `(fory).` syntax, see +[Protocol Buffers IDL Support](protobuf-idl.md#fory-extension-options-protobuf). + +## Grammar Summary + +``` +file := [package_decl] file_option* import_decl* definition* + +package_decl := 'package' package_name ['alias' package_name] ';' +package_name := IDENTIFIER ('.' IDENTIFIER)* + +file_option := 'option' option_name '=' option_value ';' +option_name := IDENTIFIER + +import_decl := 'import' STRING ';' + +definition := type_def | service_def +type_def := enum_def | message_def | union_def + +enum_def := 'enum' IDENTIFIER [type_options] '{' enum_body '}' +enum_body := (reserved_stmt | enum_value)* +enum_value := IDENTIFIER '=' INTEGER ';' + +message_def := 'message' IDENTIFIER [type_options] '{' message_body '}' +message_body := (reserved_stmt | nested_type | field_def)* +nested_type := enum_def | message_def | union_def +field_def := [modifiers] field_type IDENTIFIER '=' INTEGER [field_options] ';' + +union_def := 'union' IDENTIFIER [type_options] '{' union_field* '}' +union_field := ['repeated'] field_type IDENTIFIER '=' INTEGER [field_options] ';' + +service_def := 'service' IDENTIFIER '{' rpc_method* '}' +rpc_method := 'rpc' IDENTIFIER '(' ['stream'] named_type ')' + 'returns' '(' ['stream'] named_type ')' ';' +option_value := 'true' | 'false' | IDENTIFIER | INTEGER | STRING + +reserved_stmt := 'reserved' reserved_items ';' +reserved_items := reserved_item (',' reserved_item)* +reserved_item := INTEGER | INTEGER 'to' INTEGER | INTEGER 'to' 'max' | STRING + +modifiers := { 'optional' | 'ref' | 'repeated' } + +field_type := [scalar_encoding] (primitive_type | named_type | list_type | array_type | map_type) +primitive_type := 'bool' + | 'int8' | 'int16' | 'int32' | 'int64' + | 'uint8' | 'uint16' | 'uint32' | 'uint64' + | 'float16' | 'bfloat16' | 'float32' | 'float64' + | 'string' | 'bytes' + | 'date' | 'timestamp' | 'duration' | 'decimal' + | 'any' +scalar_encoding := 'varint' | 'fixed' | 'tagged' +named_type := qualified_name +qualified_name := IDENTIFIER ('.' IDENTIFIER)* // e.g., Parent.Child +list_type := 'list' '<' { 'optional' | 'ref' | scalar_encoding } field_type '>' +array_type := 'array' '<' array_element_type '>' +map_type := 'map' '<' field_type ',' field_type '>' + +type_options := '[' type_option (',' type_option)* ']' +type_option := IDENTIFIER '=' option_value // e.g., id=100, deprecated=true +field_options := '[' field_option (',' field_option)* ']' +field_option := IDENTIFIER '=' option_value // e.g., deprecated=true, ref=true + +STRING := '"' [^"\n]* '"' | "'" [^'\n]* "'" +IDENTIFIER := [a-zA-Z_][a-zA-Z0-9_]* +INTEGER := '-'? [0-9]+ +``` diff --git a/versioned_docs/version-1.3.0/guide/_category_.json b/versioned_docs/version-1.3.0/guide/_category_.json new file mode 100644 index 00000000000..40e7c85c122 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/_category_.json @@ -0,0 +1,4 @@ +{ + "position": 4, + "label": "Guide" +} diff --git a/versioned_docs/version-1.3.0/guide/cpp/_category_.json b/versioned_docs/version-1.3.0/guide/cpp/_category_.json new file mode 100644 index 00000000000..9660bafa82e --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/cpp/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "C++", + "position": 4, + "collapsible": true, + "collapsed": true +} diff --git a/versioned_docs/version-1.3.0/guide/cpp/basic-serialization.md b/versioned_docs/version-1.3.0/guide/cpp/basic-serialization.md new file mode 100644 index 00000000000..b4f9c589347 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/cpp/basic-serialization.md @@ -0,0 +1,362 @@ +--- +title: Basic Serialization +sidebar_position: 1 +id: basic_serialization +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 page covers basic object graph serialization and the core serialization APIs. + +## Object Graph Serialization + +Apache Fory™ provides automatic serialization of complex object graphs, preserving the structure and relationships between objects. The `FORY_STRUCT` macro generates efficient serialization code at compile time, eliminating reflection overhead. + +**Key capabilities:** + +- Nested struct serialization with arbitrary depth +- Collection types (vector, set, map) +- Optional fields with `std::optional` +- Smart pointers (`std::shared_ptr`, `std::unique_ptr`) +- Automatic handling of primitive types and strings +- Efficient binary encoding with variable-length integers + +```cpp +#include "fory/serialization/fory.h" +#include +#include + +using namespace fory::serialization; + +// Define structs +struct Address { + std::string street; + std::string city; + std::string country; + + bool operator==(const Address &other) const { + return street == other.street && city == other.city && + country == other.country; + } +}; +FORY_STRUCT(Address, street, city, country); + +struct Person { + std::string name; + int32_t age; + Address address; + std::vector hobbies; + std::map metadata; + + bool operator==(const Person &other) const { + return name == other.name && age == other.age && + address == other.address && hobbies == other.hobbies && + metadata == other.metadata; + } +}; +FORY_STRUCT(Person, name, age, address, hobbies, metadata); + +int main() { + auto fory = Fory::builder().xlang(true).build(); + fory.register_struct
(100); + fory.register_struct(200); + + Person person{ + "John Doe", + 30, + {"123 Main St", "New York", "USA"}, + {"reading", "coding"}, + {{"role", "developer"}} + }; + + auto result = fory.serialize(person); + auto decoded = fory.deserialize(result.value()); + assert(person == decoded.value()); +} +``` + +## Serialization APIs + +### Serialize to New Vector + +```cpp +auto fory = Fory::builder().xlang(true).build(); +fory.register_struct(1); + +MyStruct obj{/* ... */}; + +// Serialize - returns Result, Error> +auto result = fory.serialize(obj); +if (result.ok()) { + std::vector bytes = std::move(result).value(); + // Use bytes... +} else { + // Handle error + std::cerr << result.error().to_string() << std::endl; +} +``` + +### Serialize to Existing Buffer + +```cpp +// Serialize to existing Buffer (fastest path) +Buffer buffer; +auto result = fory.serialize_to(buffer, obj); +if (result.ok()) { + size_t bytes_written = result.value(); + // buffer now contains serialized data +} + +// Serialize to existing vector (zero-copy) +std::vector output; +auto result = fory.serialize_to(output, obj); +if (result.ok()) { + size_t bytes_written = result.value(); + // output now contains serialized data +} +``` + +### Deserialize from Byte Array + +```cpp +// Deserialize from raw pointer +auto result = fory.deserialize(data_ptr, data_size); +if (result.ok()) { + MyStruct obj = std::move(result).value(); +} + +// Deserialize from vector +std::vector data = /* ... */; +auto result = fory.deserialize(data); + +// Deserialize from Buffer (updates reader_index) +Buffer buffer(data); +auto result = fory.deserialize(buffer); +``` + +## Error Handling + +Fory uses a `Result` type for error handling: + +```cpp +auto result = fory.serialize(obj); + +// Check if operation succeeded +if (result.ok()) { + auto value = std::move(result).value(); + // Use value... +} else { + Error error = result.error(); + std::cerr << "Error: " << error.to_string() << std::endl; +} + +// Or use FORY_TRY macro for early return +FORY_TRY(bytes, fory.serialize(obj)); +// Use bytes directly... +``` + +Common error types: + +- `Error::type_mismatch` - Type ID mismatch during deserialization +- `Error::invalid_data` - Invalid or corrupted data +- `Error::buffer_out_of_bound` - Buffer overflow/underflow +- `Error::type_error` - Type registration error + +## The FORY_STRUCT Macro + +The `FORY_STRUCT` macro registers a class for serialization (struct works the +same way): + +```cpp +class MyStruct { +public: + int32_t x; + std::string y; + std::vector z; + FORY_STRUCT(MyStruct, x, y, z); +}; +``` + +Private fields are supported when the macro is placed in a `public:` section: + +```cpp +class PrivateUser { +public: + PrivateUser(int32_t id, std::string name) : id_(id), name_(std::move(name)) {} + + bool operator==(const PrivateUser &other) const { + return id_ == other.id_ && name_ == other.name_; + } + +private: + int32_t id_ = 0; + std::string name_; + +public: + FORY_STRUCT(PrivateUser, id_, name_); +}; +``` + +### Accessor Properties + +Use `FORY_PROPERTY` when the serialized field is exposed through accessor +methods instead of a data member. This keeps the type registered as a normal +struct type: + +```cpp +struct AccountImpl { + int32_t id = 0; +}; + +class Account { +public: + explicit Account(AccountImpl *impl) : impl_(impl) {} + + const int32_t &id() const { return impl_->id; } + Account &id(int32_t value) { + impl_->id = value; + return *this; + } + +private: + AccountImpl *impl_ = nullptr; + +public: + FORY_STRUCT(Account, FORY_PROPERTY(id)); +}; +``` + +`FORY_PROPERTY(id)` calls `obj.id()` to read the field and `obj.id(value)` to +write it. The field type is inferred from the const getter return type with +cv-qualifiers and references removed, so `const int32_t &` is treated as +`int32_t`. + +Use the three-argument form when the getter and setter have different names: + +```cpp +class User { +public: + const int32_t &get_id() const; + void set_id(int32_t value); + + FORY_STRUCT(User, FORY_PROPERTY(id, get_id, set_id)); +}; +``` + +Field metadata can be attached as the final argument: + +```cpp +FORY_STRUCT(Account, FORY_PROPERTY(id, fory::F().varint())); +FORY_STRUCT(User, FORY_PROPERTY(id, get_id, set_id, fory::F(1).varint())); +``` + +When `FORY_STRUCT` is declared at namespace scope, the accessor methods must be +public. For private PIMPL accessors or private data members, place +`FORY_STRUCT` inside the class in a `public:` section. + +The macro: + +1. Generates compile-time field metadata +2. Enables member or ADL (Argument-Dependent Lookup) discovery for serialization +3. Creates efficient serialization code via template specialization + +**Requirements:** + +- Must be declared inside the class definition (struct works the same way) or + at namespace scope +- Must be placed after all field declarations (when used inside the class) +- When used inside a class, the macro must be placed in a `public:` section +- All listed fields must be serializable types +- Field order in the macro is not important + +## External / Third-Party Types + +When you cannot modify a third-party type, use `FORY_STRUCT` at namespace +scope. This only works with public data members or public accessor methods. + +```cpp +namespace thirdparty { +struct Foo { + int32_t id; + std::string name; +}; + +FORY_STRUCT(Foo, id, name); +} // namespace thirdparty +``` + +**Limitations:** + +- Must be declared at namespace scope in the same namespace as the type +- Only public data members or accessor methods are supported + +## Inherited Fields + +To include base-class fields in a derived type, use `FORY_BASE(Base)` inside +`FORY_STRUCT`. The base must define its own `FORY_STRUCT` so its fields can be +referenced. + +```cpp +struct Base { + int32_t a; + FORY_STRUCT(Base, a); +}; + +struct Derived : Base { + int32_t b; + FORY_STRUCT(Derived, FORY_BASE(Base), b); +}; +``` + +**Notes:** + +- Base fields are serialized before derived fields. +- Only fields visible from the derived type are supported. + +## Nested Structs + +Nested structs are fully supported: + +```cpp +struct Inner { + int32_t value; + FORY_STRUCT(Inner, value); +}; + +struct Outer { + Inner inner; + std::string label; + FORY_STRUCT(Outer, inner, label); +}; + +// Both must be registered +fory.register_struct(1); +fory.register_struct(2); +``` + +## Performance Tips + +- **Buffer Reuse**: Use `serialize_to(buffer, obj)` with pre-allocated buffers +- **Pre-registration**: Register all types before serialization starts +- **Single-Threaded**: Use `build()` instead of `build_thread_safe()` when possible +- **Disable Tracking**: Use `track_ref(false)` when references aren't needed +- **Compact Encoding**: Variable-length encoding for space efficiency + +## Related Topics + +- [Configuration](configuration.md) - Builder options +- [Type Registration](type-registration.md) - Registering types +- [Supported Types](supported-types.md) - All supported types diff --git a/versioned_docs/version-1.3.0/guide/cpp/configuration.md b/versioned_docs/version-1.3.0/guide/cpp/configuration.md new file mode 100644 index 00000000000..d6174500418 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/cpp/configuration.md @@ -0,0 +1,231 @@ +--- +title: Configuration +sidebar_position: 4 +id: configuration +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 page covers C++ Fory instance configuration. `Fory::builder()` creates xlang +payloads by default. Compatible mode is also enabled by default. Select native +mode only when the payload stays in C++. + +## Builder Pattern + +Use `Fory::builder()` to construct Fory instances with custom configuration: + +```cpp +#include "fory/serialization/fory.h" + +using namespace fory::serialization; + +// Default xlang mode. +auto fory = Fory::builder().build(); + +// Native mode for C++-only payloads. +auto fory = Fory::builder() + .xlang(false) + .build(); + +// Same-schema optimization. Use only when every reader and writer +// always uses the same schema. +auto fory = Fory::builder() + .compatible(false) + .build(); +``` + +## Configuration + +### xlang(bool) + +Select the wire mode. + +```cpp +auto fory = Fory::builder() + .xlang(false) + .build(); +``` + +When `true`, C++ writes the xlang wire format used by Java, Python, Go, Rust, +JavaScript/TypeScript, C#, Swift, Dart, Scala, and Kotlin. When `false`, C++ +writes native-mode payloads for C++-only traffic. + +**Default:** `true` + +### compatible(bool) + +Compatible mode is enabled by default. No builder call is required for the +default compatible mode. Set `.compatible(false)` only when every reader and +writer always uses the same schema and you want faster serialization and smaller +size. + +```cpp +auto fory = Fory::builder() + .compatible(false) + .build(); +``` + +For xlang payloads, use `.compatible(false)` only after verifying that every language uses the same schema, or when native types are generated from Fory schema IDL. + +**Default:** `true` + +### track_ref(bool) + +Enable/disable reference tracking for shared and circular references. + +```cpp +auto fory = Fory::builder() + .track_ref(true) // Enable reference tracking + .build(); +``` + +When enabled, avoids duplicating shared objects and handles cycles. + +**Default:** `true` + +### max_dyn_depth(uint32_t) + +Set maximum allowed nesting depth for dynamically-typed objects. + +```cpp +auto fory = Fory::builder() + .max_dyn_depth(10) // Allow up to 10 levels + .build(); +``` + +This limits the maximum depth for nested polymorphic object serialization (e.g., `shared_ptr`, `unique_ptr`). This prevents stack overflow from deeply nested structures in dynamic serialization scenarios. + +**Default:** `5` + +**When to adjust:** + +- **Increase**: For legitimate deeply nested data structures +- **Decrease**: For stricter security requirements or shallow data structures + +### max_schema_versions_per_type(uint32_t) + +Set the maximum accepted remote metadata versions for one logical type. + +```cpp +auto fory = Fory::builder() + .max_schema_versions_per_type(10) + .build(); +``` + +**Default:** `10` + +### max_type_fields(uint32_t) + +Set the maximum fields accepted in one received remote struct metadata body. + +```cpp +auto fory = Fory::builder() + .max_type_fields(512) + .build(); +``` + +**Default:** `512` + +### max_type_meta_bytes(uint32_t) + +Set the maximum encoded body bytes accepted for one received TypeDef body, +excluding the 8-byte header and any extended-size varint. + +```cpp +auto fory = Fory::builder() + .max_type_meta_bytes(4096) + .build(); +``` + +**Default:** `4096` + +### max_average_schema_versions_per_type(uint32_t) + +Set the average accepted remote metadata versions across accepted remote types. +The effective global floor is `8192` schemas. + +```cpp +auto fory = Fory::builder() + .max_average_schema_versions_per_type(3) + .build(); +``` + +**Default:** `3` + +### check_struct_version(bool) + +Enable/disable struct version checking. + +```cpp +auto fory = Fory::builder() + .compatible(false) + .check_struct_version(true) // Enable version checking + .build(); +``` + +When enabled, validates type hashes to detect schema mismatches. + +**Default:** `false` + +## Thread-Safe vs Single-Threaded + +### Single-Threaded (Fastest) + +```cpp +auto fory = Fory::builder().build(); // Returns Fory +``` + +Single-threaded `Fory` is the fastest option, but NOT thread-safe. Use one instance per thread. + +### Thread-Safe + +```cpp +auto fory = Fory::builder().build_thread_safe(); // Returns ThreadSafeFory +``` + +`ThreadSafeFory` uses a pool of Fory instances to provide thread-safe serialization. Slightly slower due to pool overhead, but safe to use from multiple threads concurrently. + +## Configuration Summary + +| Option | Description | Default | +| ------------------------------------------------ | ------------------------------------------------- | ------- | +| `xlang(bool)` | Use xlang mode | `true` | +| `compatible(bool)` | Enable schema evolution | `true` | +| `track_ref(bool)` | Enable reference tracking | `true` | +| `max_dyn_depth(uint32_t)` | Maximum nesting depth for dynamic types | `5` | +| `max_type_fields(uint32_t)` | Max fields in one received struct metadata body | `512` | +| `max_type_meta_bytes(uint32_t)` | Max encoded bytes in one received metadata body | `4096` | +| `max_schema_versions_per_type(uint32_t)` | Max remote metadata versions for one logical type | `10` | +| `max_average_schema_versions_per_type(uint32_t)` | Average remote metadata versions across types | `3` | +| `check_struct_version(bool)` | Enable struct version checking | `false` | + +## Security + +Security-related configuration: + +- Register all structs and polymorphic implementations before deserializing untrusted payloads. +- Use `check_struct_version(true)` with `compatible(false)` for intentional same-schema payloads. +- Keep `max_dyn_depth(...)` as low as your model permits to reject unexpectedly deep polymorphic + graphs. +- Keep the remote schema metadata limits at their defaults unless the data is not malicious and a + trusted peer sends larger metadata or many schema versions. +- Prefer concrete fields over broad polymorphic fields for untrusted input. + +## Related Topics + +- [Basic Serialization](basic-serialization.md) - Using configured Fory +- [Xlang Serialization](xlang-serialization.md) - xlang mode details +- [Type Registration](type-registration.md) - Registering types diff --git a/versioned_docs/version-1.3.0/guide/cpp/custom-serializers.md b/versioned_docs/version-1.3.0/guide/cpp/custom-serializers.md new file mode 100644 index 00000000000..60b969ae617 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/cpp/custom-serializers.md @@ -0,0 +1,371 @@ +--- +title: Custom Serializers +sidebar_position: 10 +id: custom_serializers +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. +--- + +For types that don't support `FORY_STRUCT`, implement a `Serializer` template specialization manually. + +## When to Use Custom Serializers + +- External types from third-party libraries +- Types with special serialization requirements +- Existing data format compatibility +- Performance-critical custom encoding +- Cross-language interoperability with custom protocols + +## Implementing the Serializer Template + +To create a custom serializer, specialize the `Serializer` template for your type within the `fory::serialization` namespace: + +```cpp +#include "fory/serialization/fory.h" + +using namespace fory::serialization; + +// Define your custom type +struct MyExt { + int32_t id; + bool operator==(const MyExt &other) const { return id == other.id; } +}; + +namespace fory { +namespace serialization { + +template <> +struct Serializer { + // Declare as extension type for custom serialization + static constexpr TypeId type_id = TypeId::EXT; + + // Main write method - handles null checking and type info + static void write(const MyExt &value, WriteContext &ctx, RefMode ref_mode, + bool write_type, bool has_generics = false) { + (void)has_generics; + write_not_null_ref_flag(ctx, ref_mode); + if (write_type) { + auto result = ctx.write_any_type_info( + static_cast(TypeId::UNKNOWN), + std::type_index(typeid(MyExt))); + if (!result.ok()) { + ctx.set_error(std::move(result).error()); + return; + } + } + write_data(value, ctx); + } + + // write only the data (no type info) + static void write_data(const MyExt &value, WriteContext &ctx) { + Serializer::write_data(value.id, ctx); + } + + // write data with generics support + static void write_data_generic(const MyExt &value, WriteContext &ctx, + bool has_generics) { + (void)has_generics; + write_data(value, ctx); + } + + // Main read method - handles null checking and type info + static MyExt read(ReadContext &ctx, RefMode ref_mode, bool read_type) { + bool has_value = read_null_only_flag(ctx, ref_mode); + if (ctx.has_error() || !has_value) { + return MyExt{}; + } + if (read_type) { + const TypeInfo *type_info = ctx.read_any_type_info(ctx.error()); + if (ctx.has_error()) { + return MyExt{}; + } + if (!type_info) { + ctx.set_error(Error::type_error("TypeInfo for MyExt not found")); + return MyExt{}; + } + } + return read_data(ctx); + } + + // Read only the data (no type info) + static MyExt read_data(ReadContext &ctx) { + MyExt value; + value.id = Serializer::read_data(ctx); + return value; + } + + // Read data with generics support + static MyExt read_data_generic(ReadContext &ctx, bool has_generics) { + (void)has_generics; + return read_data(ctx); + } + + // Read with pre-resolved type info + static MyExt read_with_type_info(ReadContext &ctx, RefMode ref_mode, + const TypeInfo &type_info) { + (void)type_info; + return read(ctx, ref_mode, false); + } +}; + +} // namespace serialization +} // namespace fory +``` + +## Required Methods + +A custom serializer must implement these static methods: + +| Method | Purpose | +| --------------------- | ----------------------------------------------- | +| `write` | Main serialization entry point with type info | +| `write_data` | Serialize data only (no type info) | +| `write_data_generic` | Serialize data with generics support | +| `read` | Main deserialization entry point with type info | +| `read_data` | Deserialize data only (no type info) | +| `read_data_generic` | Deserialize data with generics support | +| `read_with_type_info` | Deserialize with pre-resolved TypeInfo | + +The `type_id` constant should be set to `TypeId::EXT` for custom extension types. + +## Registering Custom Serializers + +Register your custom serializer with Fory before use: + +```cpp +auto fory = Fory::builder().xlang(true).build(); + +// Register with numeric type ID (must match across languages) +auto result = fory.register_extension_type(103); +if (!result.ok()) { + std::cerr << "Failed to register: " << result.error().to_string() << std::endl; +} + +// Or register with type name for named type systems +fory.register_extension_type("my_ext"); + +// Or with a namespace prefix +fory.register_extension_type("com.example.MyExt"); +``` + +## Complete Example + +```cpp +#include "fory/serialization/fory.h" +#include + +using namespace fory::serialization; + +struct CustomType { + int32_t value; + std::string name; + + bool operator==(const CustomType &other) const { + return value == other.value && name == other.name; + } +}; + +namespace fory { +namespace serialization { + +template <> +struct Serializer { + static constexpr TypeId type_id = TypeId::EXT; + + static void write(const CustomType &value, WriteContext &ctx, + RefMode ref_mode, bool write_type, bool has_generics = false) { + (void)has_generics; + write_not_null_ref_flag(ctx, ref_mode); + if (write_type) { + auto result = ctx.write_any_type_info( + static_cast(TypeId::UNKNOWN), + std::type_index(typeid(CustomType))); + if (!result.ok()) { + ctx.set_error(std::move(result).error()); + return; + } + } + write_data(value, ctx); + } + + static void write_data(const CustomType &value, WriteContext &ctx) { + // write value as varint for compact encoding + Serializer::write_data(value.value, ctx); + // Delegate string serialization to built-in serializer + Serializer::write_data(value.name, ctx); + } + + static void write_data_generic(const CustomType &value, WriteContext &ctx, + bool has_generics) { + (void)has_generics; + write_data(value, ctx); + } + + static CustomType read(ReadContext &ctx, RefMode ref_mode, bool read_type) { + bool has_value = read_null_only_flag(ctx, ref_mode); + if (ctx.has_error() || !has_value) { + return CustomType{}; + } + if (read_type) { + const TypeInfo *type_info = ctx.read_any_type_info(ctx.error()); + if (ctx.has_error()) { + return CustomType{}; + } + if (!type_info) { + ctx.set_error(Error::type_error("TypeInfo for CustomType not found")); + return CustomType{}; + } + } + return read_data(ctx); + } + + static CustomType read_data(ReadContext &ctx) { + CustomType value; + value.value = Serializer::read_data(ctx); + value.name = Serializer::read_data(ctx); + return value; + } + + static CustomType read_data_generic(ReadContext &ctx, bool has_generics) { + (void)has_generics; + return read_data(ctx); + } + + static CustomType read_with_type_info(ReadContext &ctx, RefMode ref_mode, + const TypeInfo &type_info) { + (void)type_info; + return read(ctx, ref_mode, false); + } +}; + +} // namespace serialization +} // namespace fory + +int main() { + auto fory = Fory::builder().xlang(true).build(); + fory.register_extension_type(100); + + CustomType original{42, "test"}; + + auto serialized = fory.serialize(original); + if (!serialized.ok()) { + std::cerr << "Serialization failed" << std::endl; + return 1; + } + + auto deserialized = fory.deserialize(serialized.value()); + if (!deserialized.ok()) { + std::cerr << "Deserialization failed" << std::endl; + return 1; + } + + assert(original == deserialized.value()); + std::cout << "Custom serializer works!" << std::endl; + return 0; +} +``` + +## WriteContext Methods + +The `WriteContext` provides methods for writing data: + +```cpp +// Primitive types +ctx.write_uint8(value); +ctx.write_int8(value); +ctx.write_uint16(value); + +// Variable-length integers (compact encoding) +ctx.write_var_uint32(value); // Unsigned varint +ctx.write_var_int32(value); // Signed zigzag varint +ctx.write_var_uint64(value); // Unsigned varint +ctx.write_var_int64(value); // Signed zigzag varint + +// Tagged integers (for mixed-size encoding) +ctx.write_tagged_uint64(value); +ctx.write_tagged_int64(value); + +// Raw bytes +ctx.write_bytes(data_ptr, length); + +// Access underlying buffer for advanced operations +ctx.buffer().write_int32(value); +ctx.buffer().write_float(value); +ctx.buffer().write_double(value); +``` + +## ReadContext Methods + +The `ReadContext` provides methods for reading data: + +```cpp +// Primitive types (use error reference pattern) +uint8_t u8 = ctx.read_uint8(ctx.error()); +int8_t i8 = ctx.read_int8(ctx.error()); + +// Variable-length integers +uint32_t u32 = ctx.read_var_uint32(ctx.error()); +int32_t i32 = ctx.read_var_int32(ctx.error()); +uint64_t u64 = ctx.read_var_uint64(ctx.error()); +int64_t i64 = ctx.read_var_int64(ctx.error()); + +// Check for errors after read operations +if (ctx.has_error()) { + return MyType{}; // Return default on error +} + +// Access underlying buffer for advanced operations +int32_t value = ctx.buffer().read_int32(ctx.error()); +float f = ctx.buffer().read_float(ctx.error()); +double d = ctx.buffer().read_double(ctx.error()); +``` + +## Delegating to Built-in Serializers + +Reuse existing serializers for nested types: + +```cpp +static void write_data(const MyType &value, WriteContext &ctx) { + // Delegate to built-in serializers + Serializer::write_data(value.int_field, ctx); + Serializer::write_data(value.string_field, ctx); + Serializer>::write_data(value.vec_field, ctx); +} + +static MyType read_data(ReadContext &ctx) { + MyType value; + value.int_field = Serializer::read_data(ctx); + value.string_field = Serializer::read_data(ctx); + value.vec_field = Serializer>::read_data(ctx); + return value; +} +``` + +## Best Practices + +1. **Use variable-length encoding** for integers that may be small +2. **Check errors after read operations** using `ctx.has_error()` +3. **Return default values on error** to maintain consistent behavior +4. **Delegate to built-in serializers** for standard types +5. **Match type IDs across languages** for cross-language compatibility +6. **Use `(void)param`** to suppress unused parameter warnings + +## Related Topics + +- [Type Registration](type-registration.md) - Registering serializers +- [Basic Serialization](basic-serialization.md) - Using FORY_STRUCT macro +- [Schema Evolution](schema-evolution.md) - Compatible mode +- [Xlang Serialization](xlang-serialization.md) - Cross-language serialization diff --git a/versioned_docs/version-1.3.0/guide/cpp/index.md b/versioned_docs/version-1.3.0/guide/cpp/index.md new file mode 100644 index 00000000000..5f73481b618 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/cpp/index.md @@ -0,0 +1,284 @@ +--- +title: C++ Serialization Guide +sidebar_position: 0 +id: serialization_index +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. +--- + +**Apache Fory™** is a blazing fast multi-language serialization framework powered by **JIT compilation** and **zero-copy** techniques, providing up to **ultra-fast performance** while maintaining ease of use and safety. + +The C++ implementation provides high-performance serialization with compile-time type safety using modern C++17 features and template metaprogramming. It supports both xlang mode for cross-language payloads and native mode for C++-only payloads. + +## Why Apache Fory™ C++? + +- **Fast binary encoding**: Fast serialization and optimized binary protocols +- **Xlang**: Seamlessly serialize/deserialize data across Java, Python, C++, Go, + Rust, JavaScript/TypeScript, C#, Swift, Dart, Scala, and Kotlin +- **Type-safe**: Compile-time type checking with macro-based struct registration +- **Reference tracking**: Automatic tracking of shared and circular references +- **Schema evolution**: Compatible mode for independent schema changes +- **Two formats**: Object graph serialization and zero-copy row-based format +- **Thread safety**: Both single-threaded and thread-safe variants + +## Installation + +The C++ implementation supports both CMake and Bazel build systems. + +### Prerequisites + +- CMake 3.16+ (for CMake build) or Bazel 8+ (for Bazel build) +- C++17 compatible compiler (GCC 7+, Clang 5+, MSVC 2017+) + +When building with MSVC, configure your build system to pass +`/Zc:preprocessor`. + +### Using CMake (Recommended) + +The easiest way to use Fory is with CMake's `FetchContent` module: + +```cmake +cmake_minimum_required(VERSION 3.16) +project(my_project LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +if(MSVC) + add_compile_options(/Zc:preprocessor) +endif() +include(FetchContent) +FetchContent_Declare( + fory + GIT_REPOSITORY https://github.com/apache/fory.git + GIT_TAG v1.3.0 + SOURCE_SUBDIR cpp +) +FetchContent_MakeAvailable(fory) + +add_executable(my_app main.cc) +target_link_libraries(my_app PRIVATE fory::serialization) +``` + +Then build and run: + +```bash +mkdir build && cd build +cmake .. -DCMAKE_BUILD_TYPE=Release +cmake --build . --parallel +./my_app +``` + +### Using Bazel + +Create a `MODULE.bazel` file in your project root: + +```bazel +module( + name = "my_project", + version = "1.0.0", +) + +bazel_dep(name = "rules_cc", version = "0.1.1") + +bazel_dep(name = "fory", version = "1.3.0") +git_override( + module_name = "fory", + remote = "https://github.com/apache/fory.git", + commit = "v1.3.0", # Or use a specific commit hash for reproducibility +) +``` + +Create a `BUILD` file for your application: + +```bazel +cc_binary( + name = "my_app", + srcs = ["main.cc"], + deps = ["@fory//cpp/fory/serialization:fory_serialization"], +) +``` + +When building with MSVC, add the conforming preprocessor option to your Bazel +configuration: + +```bazel +# .bazelrc +build --cxxopt=/Zc:preprocessor +``` + +Then build and run: + +```bash +bazel build //:my_app +bazel run //:my_app +``` + +For local development, you can use `local_path_override` instead: + +```bazel +bazel_dep(name = "fory", version = "1.3.0") +local_path_override( + module_name = "fory", + path = "/path/to/fory", +) +``` + +### Examples + +See the [examples/cpp](https://github.com/apache/fory/tree/main/examples/cpp) directory for complete working examples: + +- [hello_world](https://github.com/apache/fory/tree/main/examples/cpp/hello_world) - Object graph serialization +- [hello_row](https://github.com/apache/fory/tree/main/examples/cpp/hello_row) - Row format encoding + +## Quick Start + +### Basic Example + +```cpp +#include "fory/serialization/fory.h" +#include +#include + +using namespace fory::serialization; + +// Define a struct +struct Person { + std::string name; + int32_t age; + std::vector hobbies; + + bool operator==(const Person &other) const { + return name == other.name && age == other.age && hobbies == other.hobbies; + } +}; +FORY_STRUCT(Person, name, age, hobbies); + +int main() { + // Create an xlang Fory instance + auto fory = Fory::builder() + .xlang(true) + .track_ref(false) // Disable reference tracking for simple types + .build(); + + // Register the type with a unique ID + fory.register_struct(1); + + // Create an object + Person person{"Alice", 30, {"reading", "coding"}}; + + // Serialize + auto result = fory.serialize(person); + if (!result.ok()) { + // Handle error + return 1; + } + std::vector bytes = std::move(result).value(); + + // Deserialize + auto deser_result = fory.deserialize(bytes); + if (!deser_result.ok()) { + // Handle error + return 1; + } + Person decoded = std::move(deser_result).value(); + + assert(person == decoded); + return 0; +} +``` + +### Inherited Fields + +To include base-class fields in a derived type, list `FORY_BASE(Base)` inside +`FORY_STRUCT`. The base must define its own `FORY_STRUCT` so its fields can be +referenced. + +```cpp +struct Base { + int32_t id; + FORY_STRUCT(Base, id); +}; + +struct Derived : Base { + std::string name; + FORY_STRUCT(Derived, FORY_BASE(Base), name); +}; +``` + +## Xlang Mode And Native Mode + +Use xlang mode for cross-language payloads and schemas shared with other Fory implementations. Xlang mode is the default C++ wire mode, and C++ examples that use it set `.xlang(true)` explicitly so the mode choice is visible. + +Use native mode for C++-only traffic. Native mode is selected with `.xlang(false)` and keeps C++ object serialization in C++-native form. It is optimized for C++ types and avoids portable xlang type-mapping constraints when the payload never leaves C++. Compatible mode is enabled by default. Set `.compatible(false)` only when every reader and writer uses the same C++ schema and you want faster serialization and smaller size. + +See [Xlang Serialization](xlang-serialization.md) for C++ xlang registration and interoperability rules, and [Native Serialization](native-serialization.md) for C++-only payloads. + +## Thread Safety + +Apache Fory™ C++ provides two variants for different threading needs: + +### Single-Threaded (Fastest) + +```cpp +// Single-threaded Fory - fastest, NOT thread-safe +auto fory = Fory::builder().xlang(true).build(); +``` + +### Thread-Safe + +```cpp +// Thread-safe Fory - uses context pools +auto fory = Fory::builder().xlang(true).build_thread_safe(); + +// Can be used from multiple threads safely +std::thread t1([&]() { + auto result = fory.serialize(obj1); +}); +std::thread t2([&]() { + auto result = fory.serialize(obj2); +}); +``` + +**Tip:** Perform type registrations before spawning threads so every worker sees the same metadata. + +## Use Cases + +### Object Serialization + +- Complex data structures with nested objects and references +- Cross-language communication in microservices +- General-purpose serialization with full type safety +- Schema evolution with compatible mode + +### Row-Based Serialization + +- High-throughput data processing +- Analytics workloads requiring fast field access +- Memory-constrained environments +- Zero-copy scenarios + +## Next Steps + +- [Configuration](configuration.md) - Builder options and modes +- [Basic Serialization](basic-serialization.md) - Object graph serialization +- [Xlang Serialization](xlang-serialization.md) - xlang mode and interoperability +- [Native Serialization](native-serialization.md) - C++-only serialization +- [Schema Metadata](schema-metadata.md) - Field-level metadata (nullable, ref tracking) +- [Schema Evolution](schema-evolution.md) - Compatible mode and schema changes +- [Type Registration](type-registration.md) - Registering types +- [Supported Types](supported-types.md) - All supported types +- [Custom Serializers](custom-serializers.md) - Extend serialization behavior +- [Row Format](row-format.md) - Zero-copy row-based format diff --git a/versioned_docs/version-1.3.0/guide/cpp/native-serialization.md b/versioned_docs/version-1.3.0/guide/cpp/native-serialization.md new file mode 100644 index 00000000000..81bf6c3ad37 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/cpp/native-serialization.md @@ -0,0 +1,214 @@ +--- +title: Native Serialization +sidebar_position: 3 +id: native_serialization +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. +--- + +C++ native serialization is the C++-only wire mode selected with `.xlang(false)`. Use it when every +writer and reader is C++ and the payload should follow C++ type behavior instead of the portable +xlang type system. + +Use [Xlang Serialization](xlang-serialization.md), the default C++ mode, when +bytes must be read by Java, Python, Go, Rust, JavaScript/TypeScript, C#, Swift, +Dart, Scala, Kotlin, or another non-C++ Fory implementation. + +## When To Use Native Serialization + +Use native serialization when: + +- A payload is produced and consumed only by C++ applications. +- The data model uses C++-specific types such as character types, unsigned-native type IDs, + `std::tuple`, smart pointers, or C++ polymorphic models. +- You want faster serialization and smaller size, and every reader uses the same schema as the + writer. +- You need compatible schema evolution for C++-only rolling deployments. +- You want to avoid portable xlang type-mapping constraints for a C++ boundary. + +## Create a Native-Mode Fory Instance + +```cpp +#include "fory/serialization/fory.h" +#include +#include +#include + +using namespace fory::serialization; + +struct Order { + int64_t id; + double amount; + + bool operator==(const Order &other) const { + return id == other.id && amount == other.amount; + } +}; +FORY_STRUCT(Order, id, amount); + +int main() { + auto fory = Fory::builder() + .xlang(false) + .build(); + fory.register_struct(100); + + Order order{1, 42.5}; + auto bytes = fory.serialize(order).value(); + auto decoded = fory.deserialize(bytes).value(); + assert(order == decoded); +} +``` + +Use one configured `Fory` instance per thread, or build a thread-safe Fory instance when the same instance +is shared by multiple threads: + +```cpp +auto fory = Fory::builder() + .xlang(false) + .track_ref(true) + .build_thread_safe(); +``` + +Register types before concurrent serialization starts. + +## Schema Evolution + +Native serialization defaults to compatible mode. Keep that default when C++-only writer and reader +schemas can differ: + +```cpp +auto fory = Fory::builder() + .xlang(false) + .build(); +``` + +Compatible mode writes schema metadata so readers can tolerate added, removed, or reordered fields +when field identity remains compatible. See [Schema Evolution](schema-evolution.md). + +For faster serialization and smaller size, set `.compatible(false)` only when +every reader and writer always uses the same C++ schema. + +## Registration + +Register structs with stable IDs or names before serialization: + +```cpp +fory.register_struct(100); +fory.register_struct("example.Order"); +``` + +Use numeric IDs for compact payloads. Use name registration when independent teams coordinate type +identity by names; add a namespace prefix with `.` when needed. + +## C++ Object Surface + +Native serialization owns the C++-specific object surface: + +- Structs and classes described by `FORY_STRUCT`. +- Standard containers such as `std::vector`, `std::map`, `std::unordered_map`, `std::set`, and + `std::unordered_set`. +- `std::optional`, `std::variant`, and tuple-like values. +- `std::shared_ptr` and `std::unique_ptr`. +- Character types such as `char`, `char16_t`, and `char32_t`. +- Unsigned integer types with native-mode type IDs. +- Polymorphic serialization registered through the C++ implementation. + +Use [Supported Types](supported-types.md) for the full type surface and xlang mapping notes. + +## References And Smart Pointers + +Native serialization supports smart pointers and reference tracking: + +```cpp +auto fory = Fory::builder() + .xlang(false) + .track_ref(true) + .build(); +``` + +When reference tracking is enabled, shared pointer identity can be preserved and cyclic object +graphs can be represented through supported pointer patterns. Disable reference tracking for +value-shaped data when identity is not part of the model. + +## Native-Only Scalar Shapes + +Some C++ scalar shapes are not portable xlang payloads. Use native serialization when these shapes +must round-trip as C++ values: + +```cpp +auto fory = Fory::builder().xlang(false).build(); + +auto char_bytes = fory.serialize(char32_t{U'A'}).value(); +auto value = fory.deserialize(char_bytes).value(); + +auto unsigned_bytes = fory.serialize(uint64_t{42}).value(); +auto unsigned_value = fory.deserialize(unsigned_bytes).value(); +``` + +For xlang payloads, use metadata and the shared xlang type mapping instead of relying on +C++ native-only type IDs. + +## Performance Guidelines + +- Reuse configured `Fory` instances. +- Use single-threaded `Fory` per thread for the fastest path; use `build_thread_safe()` for shared + concurrent use. +- Use `.compatible(false)` only when every reader and writer always uses the same C++ schema and + the application wants faster serialization and smaller size. +- Register structs with explicit numeric IDs for compact payloads. +- Disable reference tracking for value-shaped graphs. +- Prefer concrete types over polymorphic/dynamic fields on hot paths. + +## Native And Xlang Comparison + +| Requirement | Use native serialization | Use xlang serialization | +| ---------------------------------------- | ------------------------ | ----------------------- | +| C++-only payloads | Yes | Optional | +| Non-C++ readers or writers | No | Yes | +| C++ native character and unsigned shapes | Yes | Limited | +| Smart pointers and C++ object graphs | Yes | Limited | +| Same-schema compact payloads | Yes | No | +| Compatible schema evolution by default | Yes | Yes | +| Portable type mapping across languages | No | Yes | + +## Troubleshooting + +### A non-C++ implementation cannot read the payload + +The writer is using native serialization. Rebuild it with `.xlang(true)` and align type +registration with every peer. + +### A rolling deployment fails after a field change + +Native serialization defaults to compatible mode. Keep that default when schemas can differ. + +### A native-only scalar does not map to another language + +Use xlang serialization with explicit metadata for portable payloads. Native C++ type IDs +are only for C++ readers. + +### A shared pointer graph loses identity + +Enable `.track_ref(true)` and verify the graph uses supported pointer patterns. + +## Related Topics + +- [Xlang Serialization](xlang-serialization.md) - Cross-language C++ payloads +- [Configuration](configuration.md) - Builder options +- [Basic Serialization](basic-serialization.md) - Object graph serialization +- [Supported Types](supported-types.md) - C++ type support +- [Polymorphic Serialization](polymorphism.md) - Polymorphic object models +- [Schema Evolution](schema-evolution.md) - Compatible mode diff --git a/versioned_docs/version-1.3.0/guide/cpp/polymorphism.md b/versioned_docs/version-1.3.0/guide/cpp/polymorphism.md new file mode 100644 index 00000000000..3c9bbb3ff12 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/cpp/polymorphism.md @@ -0,0 +1,479 @@ +--- +title: Polymorphic Serialization +sidebar_position: 8 +id: polymorphism +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. +--- + +Apache Fory™ supports polymorphic serialization through smart pointers (`std::shared_ptr` and `std::unique_ptr`), enabling dynamic dispatch and type flexibility for inheritance hierarchies. + +## Supported Polymorphic Types + +- `std::shared_ptr` - Shared ownership with polymorphic dispatch +- `std::unique_ptr` - Exclusive ownership with polymorphic dispatch +- Collections: `std::vector>`, `std::map>` +- Optional: `std::optional>` + +## Basic Polymorphic Serialization + +```cpp +#include "fory/serialization/fory.h" + +using namespace fory::serialization; + +// Define base class with virtual methods +struct Animal { + virtual ~Animal() = default; + virtual std::string speak() const = 0; + int32_t age = 0; +}; +FORY_STRUCT(Animal, age); + +// Define derived classes +struct Dog : Animal { + std::string speak() const override { return "Woof!"; } + std::string breed; +}; +FORY_STRUCT(Dog, age, breed); + +struct Cat : Animal { + std::string speak() const override { return "Meow!"; } + std::string color; +}; +FORY_STRUCT(Cat, age, color); + +// Struct with polymorphic field +struct Zoo { + std::shared_ptr star_animal; +}; +FORY_STRUCT(Zoo, star_animal); + +int main() { + auto fory = Fory::builder().xlang(true).track_ref(true).build(); + + // Register all types with unique type IDs + fory.register_struct(100); + fory.register_struct(101); + fory.register_struct(102); + + // Create object with polymorphic field + Zoo zoo; + zoo.star_animal = std::make_shared(); + zoo.star_animal->age = 3; + static_cast(zoo.star_animal.get())->breed = "Labrador"; + + // Serialize + auto bytes_result = fory.serialize(zoo); + assert(bytes_result.ok()); + + // Deserialize - concrete type is preserved + auto decoded_result = fory.deserialize(bytes_result.value()); + assert(decoded_result.ok()); + + auto decoded = std::move(decoded_result).value(); + assert(decoded.star_animal->speak() == "Woof!"); + assert(decoded.star_animal->age == 3); + + auto* dog_ptr = dynamic_cast(decoded.star_animal.get()); + if (dog_ptr == nullptr || dog_ptr->breed != "Labrador") { + return 1; + } +} +``` + +## Type Registration for Polymorphism + +For polymorphic serialization, register derived types with unique type IDs: + +```cpp +// Register with numeric type ID +fory.register_struct(100); +fory.register_struct(101); +``` + +**Why type ID registration?** + +- Compact binary representation +- Fast type lookup and dispatch +- Consistent with non-polymorphic type registration + +## Automatic Polymorphism Detection + +Fory automatically detects polymorphic types using `std::is_polymorphic`: + +```cpp +struct Base { + virtual ~Base() = default; // Virtual destructor makes it polymorphic + int32_t value = 0; +}; + +struct NonPolymorphic { + int32_t value = 0; // No virtual methods +}; + +// Polymorphic field - type info written automatically +struct Container1 { + std::shared_ptr ptr; // Auto-detected as polymorphic +}; + +// Non-polymorphic field - no type info written +struct Container2 { + std::shared_ptr ptr; // Not polymorphic +}; +``` + +## Controlling Dynamic Dispatch + +Use `fory::F().dynamic(V)` in `FORY_STRUCT` to override automatic +polymorphism detection: + +```cpp +struct Animal { + virtual ~Animal() = default; + virtual std::string speak() const = 0; +}; + +struct Pet { + // Auto-detected: type info written (Animal has virtual methods) + std::shared_ptr animal1; + + // Force dynamic: type info written explicitly + std::shared_ptr animal2; + + // Force non-dynamic: skip type info (faster but no subtype support) + std::shared_ptr animal3; +}; +FORY_STRUCT(Pet, animal1, (animal2, fory::F().dynamic(true)), + (animal3, fory::F().dynamic(false))); +``` + +**When to use `dynamic(false)`:** + +- You know the concrete type will always match the declared type +- Performance is critical and you don't need subtype support +- Working with monomorphic data despite having a polymorphic base class + +### Schema Metadata + +Configure field metadata directly in `FORY_STRUCT`: + +```cpp +struct Zoo { + std::shared_ptr star; // Auto-detected as polymorphic + std::shared_ptr backup; // Nullable polymorphic field + std::shared_ptr mascot; // Non-dynamic (no subtype dispatch) +}; +FORY_STRUCT(Zoo, (star, fory::F(0)), + (backup, fory::F(1).nullable()), + (mascot, fory::F(2).dynamic(false))); +``` + +See [Schema Metadata](schema-metadata.md) for complete details on +`nullable()`, `ref()`, and other field-level options. + +## std::unique_ptr Polymorphism + +`std::unique_ptr` works the same way as `std::shared_ptr` for polymorphic types: + +```cpp +struct Container { + std::unique_ptr pet; +}; +FORY_STRUCT(Container, pet); + +auto fory = Fory::builder().xlang(true).track_ref(true).build(); +fory.register_struct(200); +fory.register_struct(201); + +Container container; +container.pet = std::make_unique(); +static_cast(container.pet.get())->breed = "Beagle"; + +auto bytes = fory.serialize(container).value(); +auto decoded = fory.deserialize(bytes).value(); + +// Runtime type preserved +auto* dog = dynamic_cast(decoded.pet.get()); +assert(dog != nullptr); +assert(dog->breed == "Beagle"); +``` + +## Collections of Polymorphic Objects + +```cpp +#include +#include + +struct AnimalShelter { + std::vector> animals; + std::map> registry; +}; +FORY_STRUCT(AnimalShelter, animals, registry); + +auto fory = Fory::builder().xlang(true).track_ref(true).build(); +fory.register_struct(100); +fory.register_struct(101); +fory.register_struct(102); + +AnimalShelter shelter; +shelter.animals.push_back(std::make_shared()); +shelter.animals.push_back(std::make_shared()); +shelter.registry["pet1"] = std::make_unique(); + +auto bytes = fory.serialize(shelter).value(); +auto decoded = fory.deserialize(bytes).value(); + +// All concrete types preserved +assert(dynamic_cast(decoded.animals[0].get()) != nullptr); +assert(dynamic_cast(decoded.animals[1].get()) != nullptr); +assert(dynamic_cast(decoded.registry["pet1"].get()) != nullptr); +``` + +## Reference Tracking + +Reference tracking for `std::shared_ptr` works the same with polymorphic types. +See [Supported Types](supported-types.md) for details and examples. + +## Nested Polymorphism Depth Limit + +To prevent stack overflow from deeply nested polymorphic structures, Fory limits the maximum dynamic nesting depth: + +```cpp +struct Container { + virtual ~Container() = default; + int32_t value = 0; + std::shared_ptr nested; +}; +FORY_STRUCT(Container, value, nested); + +// Default max_dyn_depth is 5 +auto fory1 = Fory::builder().xlang(true).build(); +assert(fory1.config().max_dyn_depth == 5); + +// Increase limit for deeper nesting +auto fory2 = Fory::builder().xlang(true).max_dyn_depth(10).build(); +fory2.register_struct(1); + +// Create deeply nested structure +auto level3 = std::make_shared(); +level3->value = 3; + +auto level2 = std::make_shared(); +level2->value = 2; +level2->nested = level3; + +auto level1 = std::make_shared(); +level1->value = 1; +level1->nested = level2; + +// Serialization succeeds +auto bytes = fory2.serialize(level1).value(); + +// Deserialization succeeds with sufficient depth +auto decoded = fory2.deserialize>(bytes).value(); +``` + +**Depth exceeded error:** + +```cpp +auto fory_shallow = Fory::builder().xlang(true).max_dyn_depth(2).build(); +fory_shallow.register_struct(1); + +// 3 levels exceeds max_dyn_depth=2 +auto result = fory_shallow.deserialize>(bytes); +assert(!result.ok()); // Fails with depth exceeded error +``` + +**When to adjust:** + +- **Increase `max_dyn_depth`**: For legitimate deeply nested polymorphic data structures +- **Decrease `max_dyn_depth`**: For stricter security requirements or shallow data structures + +## Nullability for Polymorphic Fields + +By default, `std::shared_ptr` and `std::unique_ptr` fields are treated as +non-nullable in the schema. To allow `nullptr`, mark the field nullable in +`FORY_STRUCT`. + +```cpp +struct Pet { + // Non-nullable (default) + std::shared_ptr primary; + + // Nullable via explicit field metadata + std::shared_ptr optional; +}; +FORY_STRUCT(Pet, primary, (optional, fory::F().nullable())); +``` + +See [Schema Metadata](schema-metadata.md) for more details. + +## Combining Polymorphism with Other Features + +### Polymorphism + Reference Tracking + +```cpp +struct GraphNode { + virtual ~GraphNode() = default; + int32_t id = 0; + std::vector> neighbors; +}; +FORY_STRUCT(GraphNode, id, neighbors); + +struct WeightedNode : GraphNode { + double weight = 0.0; +}; +FORY_STRUCT(WeightedNode, id, neighbors, weight); + +// Enable ref tracking to handle shared references and cycles +auto fory = Fory::builder().xlang(true).track_ref(true).build(); +fory.register_struct(100); +fory.register_struct(101); + +// Create cyclic graph +auto node1 = std::make_shared(); +node1->id = 1; + +auto node2 = std::make_shared(); +node2->id = 2; + +node1->neighbors.push_back(node2); +node2->neighbors.push_back(node1); // Cycle + +auto bytes = fory.serialize(node1).value(); +auto decoded = fory.deserialize>(bytes).value(); +// Cycle handled correctly +``` + +### Polymorphism + Schema Evolution + +Compatible mode is enabled by default for schema evolution with polymorphic types: + +```cpp +auto fory = Fory::builder() + .xlang(true) + .track_ref(true) + .build(); +``` + +## Best Practices + +1. **Use type ID registration** for polymorphic types: + + ```cpp + fory.register_struct(100); + ``` + +2. **Enable reference tracking** for polymorphic types: + + ```cpp + auto fory = Fory::builder().xlang(true).track_ref(true).build(); + ``` + +3. **Virtual destructors required**: Ensure base classes have virtual destructors: + + ```cpp + struct Base { + virtual ~Base() = default; // Required for polymorphism + }; + ``` + +4. **Register all concrete types** before serialization/deserialization: + + ```cpp + fory.register_struct(100); + fory.register_struct(101); + ``` + +5. **Use `dynamic_cast`** to downcast after deserialization: + + ```cpp + auto* derived = dynamic_cast(base_ptr.get()); + if (derived) { + // Use derived-specific members + } + ``` + +6. **Adjust `max_dyn_depth`** based on your data structure depth: + + ```cpp + auto fory = Fory::builder().xlang(true).max_dyn_depth(10).build(); + ``` + +7. **Use `nullable()`** for optional polymorphic fields: + + ```cpp + FORY_STRUCT(Holder, (optional_ptr, fory::F().nullable())); + ``` + +## Error Handling + +```cpp +auto bytes_result = fory.serialize(obj); +if (!bytes_result.ok()) { + std::cerr << "Serialization failed: " + << bytes_result.error().to_string() << std::endl; + return; +} + +auto decoded_result = fory.deserialize(bytes_result.value()); +if (!decoded_result.ok()) { + std::cerr << "Deserialization failed: " + << decoded_result.error().to_string() << std::endl; + return; +} +``` + +**Common errors:** + +- **Type not registered**: Register all concrete types with unique IDs before use +- **Depth exceeded**: Increase `max_dyn_depth` for deeply nested structures +- **Type ID conflict**: Ensure each type has a unique type ID across all registered types + +## Performance Considerations + +**Polymorphic serialization overhead:** + +- Type metadata written for each polymorphic object (~16-32 bytes) +- Dynamic type resolution during deserialization +- Virtual function calls for dynamic dispatch + +**Optimization tips:** + +1. **Use `dynamic(false)`** when the concrete type matches the declared type: + + ```cpp + FORY_STRUCT(Holder, (fixed_type, fory::F().dynamic(false))); + ``` + +2. **Minimize nesting depth** to reduce metadata overhead + +3. **Batch polymorphic objects** in collections rather than individual fields + +4. **Consider non-polymorphic alternatives** when polymorphism isn't needed: + + ```cpp + std::variant animal; // Type-safe union instead of polymorphism + ``` + +## Related Topics + +- [Type Registration](type-registration.md) - Registering types for serialization +- [Schema Metadata](schema-metadata.md) - Field-level metadata and options +- [Supported Types](supported-types.md) - Smart pointers and collections +- [Configuration](configuration.md) - `max_dyn_depth` and other settings +- [Basic Serialization](basic-serialization.md) - Core serialization concepts diff --git a/versioned_docs/version-1.3.0/guide/cpp/row-format.md b/versioned_docs/version-1.3.0/guide/cpp/row-format.md new file mode 100644 index 00000000000..5957cb63675 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/cpp/row-format.md @@ -0,0 +1,508 @@ +--- +title: Row Format +sidebar_position: 11 +id: row_format +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 page covers the row-based serialization format for high-performance, cache-friendly data access. + +## Overview + +Apache Fory™ Row Format is a binary format optimized for: + +- **Random Access**: Read any field without deserializing the entire object +- **Zero-copy**: Direct memory access without data copying +- **Cache-Friendly**: Contiguous memory layout for CPU cache efficiency +- **Columnar Conversion**: Easy conversion to Apache Arrow format +- **Partial Serialization**: Serialize only needed fields + +## When to Use Row Format + +| Use Case | Row Format | Object Graph | +| ----------------------------- | ------------- | ------------- | +| Analytics/OLAP | Supported | Not supported | +| Random field access | Supported | Not supported | +| Full object serialization | Not supported | Supported | +| Complex object graphs | Not supported | Supported | +| Reference tracking | Not supported | Supported | +| Cross-language (simple types) | Supported | Supported | + +## Quick Start + +```cpp +#include "fory/encoder/row_encoder.h" +#include "fory/row/writer.h" + +using namespace fory::row; +using namespace fory::row::encoder; + +struct Person { + int32_t id; + std::string name; + float score; + FORY_STRUCT(Person, id, name, score); +}; + +int main() { + // Create encoder + RowEncoder encoder; + + // encode a person + Person person{1, "Alice", 95.5f}; + encoder.encode(person); + + // get the encoded row + auto row = encoder.get_writer().to_row(); + + // Random access to fields + assert(row->get_int32(0) == 1); + assert(row->get_string(1) == "Alice"); + assert(row->get_float(2) == 95.5f); + + return 0; +} +``` + +## Row Encoder + +### Basic Usage + +The `RowEncoder` template class provides type-safe encoding: + +```cpp +#include "fory/encoder/row_encoder.h" + +struct Point { + double x; + double y; + FORY_STRUCT(Point, x, y); +}; + +// Create encoder +RowEncoder encoder; + +// Access schema (for inspection) +const Schema& schema = encoder.get_schema(); +std::cout << "Fields: " << schema.field_names().size() << std::endl; + +// encode value +Point p{1.0, 2.0}; +encoder.encode(p); + +// get result as Row +auto row = encoder.get_writer().to_row(); +``` + +### Nested Structs + +```cpp +struct Address { + std::string city; + std::string country; + FORY_STRUCT(Address, city, country); +}; + +struct Person { + std::string name; + Address address; + FORY_STRUCT(Person, name, address); +}; + +// encode nested struct +RowEncoder encoder; +Person person{"Alice", {"New York", "USA"}}; +encoder.encode(person); + +auto row = encoder.get_writer().to_row(); +std::string name = row->get_string(0); + +// Access nested struct +auto address_row = row->get_struct(1); +std::string city = address_row->get_string(0); +std::string country = address_row->get_string(1); +``` + +### Arrays / Lists + +```cpp +struct Record { + std::vector values; + std::string label; + FORY_STRUCT(Record, values, label); +}; + +RowEncoder encoder; +Record record{{1, 2, 3, 4, 5}, "test"}; +encoder.encode(record); + +auto row = encoder.get_writer().to_row(); +auto array = row->get_array(0); + +int count = array->num_elements(); +for (int i = 0; i < count; i++) { + int32_t value = array->get_int32(i); +} +``` + +### Encoding Arrays Directly + +```cpp +// encode a vector directly (not inside a struct) +std::vector people{ + {"Alice", {"NYC", "USA"}}, + {"Bob", {"London", "UK"}} +}; + +RowEncoder encoder; +encoder.encode(people); + +// get array data +auto array = encoder.get_writer().copy_to_array_data(); +auto first_person = array->get_struct(0); +std::string first_name = first_person->get_string(0); +``` + +## Row Data Access + +### Row Class + +The `Row` class provides random access to struct fields: + +```cpp +class Row { +public: + // Null check + bool is_null_at(int i) const; + + // Primitive getters + bool get_boolean(int i) const; + int8_t get_int8(int i) const; + int16_t get_int16(int i) const; + int32_t get_int32(int i) const; + int64_t get_int64(int i) const; + float get_float(int i) const; + double get_double(int i) const; + + // String/binary getter + std::string get_string(int i) const; + std::vector get_binary(int i) const; + + // Nested types + std::shared_ptr get_struct(int i) const; + std::shared_ptr get_array(int i) const; + std::shared_ptr get_map(int i) const; + + // Metadata + int num_fields() const; + SchemaPtr schema() const; + + // Debug + std::string to_string() const; +}; +``` + +### ArrayData Class + +The `ArrayData` class provides access to nested sequence elements: + +```cpp +class ArrayData { +public: + // Null check + bool is_null_at(int i) const; + + // Element count + int num_elements() const; + + // Primitive getters (same as Row) + int32_t get_int32(int i) const; + // ... other primitives + + // String getter + std::string get_string(int i) const; + + // Nested types + std::shared_ptr get_struct(int i) const; + std::shared_ptr get_array(int i) const; + std::shared_ptr get_map(int i) const; + + // Type info + ListTypePtr type() const; +}; +``` + +### MapData Class + +The `MapData` class provides access to map key-value pairs: + +```cpp +class MapData { +public: + // Element count + int num_elements(); + + // Access keys and values as arrays + std::shared_ptr keys_array(); + std::shared_ptr values_array(); + + // Type info + MapTypePtr type(); +}; +``` + +## Schema and Types + +### Schema Definition + +Schemas define the structure of row data: + +```cpp +#include "fory/row/schema.h" + +using namespace fory::row; + +// Create schema programmatically +auto person_schema = schema({ + field("id", int32()), + field("name", utf8()), + field("score", float32()), + field("active", boolean()) +}); + +// Access schema info +for (const auto& f : person_schema->fields()) { + std::cout << f->name() << ": " << f->type()->name() << std::endl; +} +``` + +### Type System + +Available types for row format: + +```cpp +// Primitive types +DataTypePtr boolean(); // bool +DataTypePtr int8(); // int8_t +DataTypePtr int16(); // int16_t +DataTypePtr int32(); // int32_t +DataTypePtr int64(); // int64_t +DataTypePtr float32(); // float +DataTypePtr float64(); // double + +// String and binary +DataTypePtr utf8(); // std::string +DataTypePtr binary(); // std::vector + +// Complex types +DataTypePtr list(DataTypePtr element_type); +DataTypePtr map(DataTypePtr key_type, DataTypePtr value_type); +DataTypePtr struct_(std::vector fields); +``` + +### Type Inference + +The `RowEncodeTrait` template automatically infers types: + +```cpp +// Type inference for primitives +RowEncodeTrait::Type(); // Returns int32() +RowEncodeTrait::Type(); // Returns float32() +RowEncodeTrait::Type(); // Returns utf8() + +// Type inference for collections +RowEncodeTrait>::Type(); // Returns list(int32()) + +// Type inference for maps +RowEncodeTrait>::Type(); +// Returns map(utf8(), int32()) + +// Type inference for structs (requires FORY_STRUCT) +RowEncodeTrait::Type(); // Returns struct_({...}) +RowEncodeTrait::Schema(); // Returns schema({...}) +``` + +## Row Writer + +### RowWriter + +For manual row construction: + +```cpp +#include "fory/row/writer.h" + +// Create schema +auto my_schema = schema({ + field("x", int32()), + field("y", float64()), + field("name", utf8()) +}); + +// Create writer +RowWriter writer(my_schema); +writer.reset(); + +// write fields +writer.write(0, 42); // x = 42 +writer.write(1, 3.14); // y = 3.14 +writer.write_string(2, "test"); // name = "test" + +// get result +auto row = writer.to_row(); +``` + +### ArrayWriter + +For manual array construction: + +```cpp +// Create array type +auto array_type = list(int32()); + +// Create writer +ArrayWriter writer(array_type); +writer.reset(5); // 5 elements + +// write elements +for (int i = 0; i < 5; i++) { + writer.write(i, i * 10); +} + +// get result +auto array = writer.copy_to_array_data(); +``` + +### Null Values + +```cpp +// Set null at specific index +writer.set_null_at(2); // Field 2 is null + +// Check null when reading +if (!row->is_null_at(2)) { + std::string value = row->get_string(2); +} +``` + +## Memory Layout + +### Row Layout + +``` ++------------------+--------------------+--------------------+ +| Null Bitmap | Fixed-Size Data | Variable-Size Data | ++------------------+--------------------+--------------------+ +| ceil(n/8) B | 8 * n bytes | variable | ++------------------+--------------------+--------------------+ +``` + +- **Null Bitmap**: One bit per field, indicates null values +- **Fixed-Size Data**: 8 bytes per field (primitives stored directly, offset+size for variable) +- **Variable-Size Data**: Strings, arrays, nested structs + +### Array Layout + +``` ++------------+------------------+--------------------+--------------------+ +| Num Elems | Null Bitmap | Fixed-Size Data | Variable-Size Data | ++------------+------------------+--------------------+--------------------+ +| 8 bytes | ceil(n/8) bytes | elem_size * n | variable | ++------------+------------------+--------------------+--------------------+ +``` + +### Map Layout + +``` ++------------------+------------------+ +| Keys Array | Values Array | ++------------------+------------------+ +``` + +## Performance Tips + +### 1. Reuse Encoders + +```cpp +RowEncoder encoder; + +// encode multiple records +for (const auto& person : people) { + encoder.encode(person); + auto row = encoder.get_writer().to_row(); + // Process row... +} +``` + +### 2. Pre-allocate Buffer + +```cpp +// get buffer reference for pre-allocation +auto& buffer = encoder.get_writer().buffer(); +buffer->reserve(expected_size); +``` + +### 3. Batch Processing + +```cpp +// Process in batches for better cache utilization +std::vector batch; +batch.reserve(BATCH_SIZE); + +while (has_more()) { + batch.clear(); + fill_batch(batch); + + for (const auto& person : batch) { + encoder.encode(person); + process(encoder.get_writer().to_row()); + } +} +``` + +### 4. Zero-copy Reading + +```cpp +// Point to existing buffer (zero-copy) +Row row(schema); +row.point_to(buffer, offset, size); + +// Access fields directly from buffer +int32_t id = row.get_int32(0); +``` + +## Supported Types Summary + +| C++ Type | Row Type | Fixed Size | +| -------------------- | ---------------- | ---------- | +| `bool` | `boolean()` | 1 byte | +| `int8_t` | `int8()` | 1 byte | +| `int16_t` | `int16()` | 2 bytes | +| `int32_t` | `int32()` | 4 bytes | +| `int64_t` | `int64()` | 8 bytes | +| `float` | `float32()` | 4 bytes | +| `double` | `float64()` | 8 bytes | +| `std::string` | `utf8()` | Variable | +| `std::vector` | `list(T)` | Variable | +| `std::map` | `map(K,V)` | Variable | +| `std::optional` | Inner type | Nullable | +| Struct (FORY_STRUCT) | `struct_({...})` | Variable | + +## Related Topics + +- [Basic Serialization](basic-serialization.md) - Object graph serialization +- [Configuration](configuration.md) - Builder options +- [Supported Types](supported-types.md) - All supported types diff --git a/versioned_docs/version-1.3.0/guide/cpp/schema-evolution.md b/versioned_docs/version-1.3.0/guide/cpp/schema-evolution.md new file mode 100644 index 00000000000..1637674d33e --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/cpp/schema-evolution.md @@ -0,0 +1,424 @@ +--- +title: Schema Evolution +sidebar_position: 6 +id: schema_evolution +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. +--- + +Apache Fory™ supports schema evolution in **Compatible mode**, allowing serialization and deserialization peers to have different type definitions. + +## Compatible Mode + +Compatible mode is enabled by default: + +```cpp +#include "fory/serialization/fory.h" + +using namespace fory::serialization; + +// Version 1: Original schema +struct PersonV1 { + std::string name; + int32_t age; +}; +FORY_STRUCT(PersonV1, name, age); + +// Version 2: Added email field +struct PersonV2 { + std::string name; + int32_t age; + std::string email; // NEW FIELD +}; +FORY_STRUCT(PersonV2, name, age, email); + +int main() { + // Create separate Fory instances for each schema version + auto fory_v1 = Fory::builder() + .xlang(true) + .build(); + + auto fory_v2 = Fory::builder() + .xlang(true) + .build(); + + // Register with the SAME type ID for schema evolution + constexpr uint32_t PERSON_TYPE_ID = 100; + fory_v1.register_struct(PERSON_TYPE_ID); + fory_v2.register_struct(PERSON_TYPE_ID); + + // Serialize with V1 + PersonV1 v1{"Alice", 30}; + auto bytes = fory_v1.serialize(v1).value(); + + // Deserialize as V2 - email gets default value (empty string) + auto v2 = fory_v2.deserialize(bytes).value(); + assert(v2.name == "Alice"); + assert(v2.age == 30); + assert(v2.email == ""); // Default value for missing field + + return 0; +} +``` + +## Schema Evolution Features + +Compatible mode supports the following schema changes: + +| Change Type | Support | Behavior | +| ------------------ | ------------- | --------------------------------------- | +| Add new fields | Supported | Missing fields use default values | +| Remove fields | Supported | Extra fields are skipped | +| Reorder fields | Supported | Fields matched by name, not position | +| Change nullability | Supported | `T` ↔ `std::optional` | +| Change field types | Selected | Scalar changes must be lossless | +| Rename fields | Not supported | Field names must match (case-sensitive) | + +Compatible readers can handle selected scalar field type changes when the value is lossless. A +matched field can read between `bool`, `std::string`, numeric scalars, and decimal fields when the +converted value has the same logical value. Boolean strings must be exactly `"0"`, `"1"`, `"true"`, +or `"false"`. Numeric strings must use finite ASCII decimal notation without whitespace, a leading +`+`, Unicode digits, separators, or non-finite values such as `NaN` and `Infinity`. Numbers and +decimals can be read as canonical strings, and numeric widening or narrowing succeeds only when no +precision or range is lost. Nullable and optional field wrappers still compose with these conversions, +but reference-tracked scalar type changes are incompatible. Invalid strings and lossy conversions fail +during deserialization. + +## Adding Fields (Backward Compatibility) + +When deserializing old data with a new schema that has additional fields: + +```cpp +// Old schema (V1) +struct ProductV1 { + std::string name; + double price; +}; +FORY_STRUCT(ProductV1, name, price); + +// New schema (V2) with additional fields +struct ProductV2 { + std::string name; + double price; + std::vector tags; // NEW + std::map attributes; // NEW +}; +FORY_STRUCT(ProductV2, name, price, tags, attributes); + +// Serialize V1 +ProductV1 v1{"Laptop", 999.99}; +auto bytes = fory_v1.serialize(v1).value(); + +// Deserialize as V2 +auto v2 = fory_v2.deserialize(bytes).value(); +assert(v2.name == "Laptop"); +assert(v2.price == 999.99); +assert(v2.tags.empty()); // Default: empty vector +assert(v2.attributes.empty()); // Default: empty map +``` + +## Removing Fields (Forward Compatibility) + +When deserializing new data with an old schema that has fewer fields: + +```cpp +// Full schema +struct UserFull { + int64_t id; + std::string username; + std::string email; + std::string password_hash; + int32_t login_count; +}; +FORY_STRUCT(UserFull, id, username, email, password_hash, login_count); + +// Minimal schema (removed 3 fields) +struct UserMinimal { + int64_t id; + std::string username; +}; +FORY_STRUCT(UserMinimal, id, username); + +// Serialize full version +UserFull full{12345, "johndoe", "john@example.com", "hash123", 42}; +auto bytes = fory_full.serialize(full).value(); + +// Deserialize as minimal - extra fields are skipped +auto minimal = fory_minimal.deserialize(bytes).value(); +assert(minimal.id == 12345); +assert(minimal.username == "johndoe"); +// email, password_hash, login_count are skipped +``` + +## Field Reordering + +In compatible mode, fields are matched by name, not by position: + +```cpp +// Original field order +struct ConfigOriginal { + std::string host; + int32_t port; + bool enable_ssl; + std::string protocol; +}; +FORY_STRUCT(ConfigOriginal, host, port, enable_ssl, protocol); + +// Reordered fields +struct ConfigReordered { + bool enable_ssl; // Moved to first + std::string protocol; // Moved to second + std::string host; // Moved to third + int32_t port; // Moved to last +}; +FORY_STRUCT(ConfigReordered, enable_ssl, protocol, host, port); + +// Serialize with original order +ConfigOriginal orig{"localhost", 8080, true, "https"}; +auto bytes = fory_orig.serialize(orig).value(); + +// Deserialize with different field order - works correctly +auto reordered = fory_reord.deserialize(bytes).value(); +assert(reordered.host == "localhost"); +assert(reordered.port == 8080); +assert(reordered.enable_ssl == true); +assert(reordered.protocol == "https"); +``` + +## Nested Struct Evolution + +Schema evolution works recursively for nested structs: + +```cpp +// V1 Address +struct AddressV1 { + std::string street; + std::string city; +}; +FORY_STRUCT(AddressV1, street, city); + +// V2 Address with new fields +struct AddressV2 { + std::string street; + std::string city; + std::string country; // NEW + std::string zipcode; // NEW +}; +FORY_STRUCT(AddressV2, street, city, country, zipcode); + +// V1 Employee with V1 Address +struct EmployeeV1 { + std::string name; + AddressV1 home_address; +}; +FORY_STRUCT(EmployeeV1, name, home_address); + +// V2 Employee with V2 Address and new field +struct EmployeeV2 { + std::string name; + AddressV2 home_address; // Nested struct evolved + std::string employee_id; // NEW +}; +FORY_STRUCT(EmployeeV2, name, home_address, employee_id); + +// Register types with same IDs +constexpr uint32_t ADDRESS_TYPE_ID = 100; +constexpr uint32_t EMPLOYEE_TYPE_ID = 101; + +fory_v1.register_struct(ADDRESS_TYPE_ID); +fory_v1.register_struct(EMPLOYEE_TYPE_ID); +fory_v2.register_struct(ADDRESS_TYPE_ID); +fory_v2.register_struct(EMPLOYEE_TYPE_ID); + +// Serialize V1 +EmployeeV1 emp_v1{"Jane Doe", {"123 Main St", "NYC"}}; +auto bytes = fory_v1.serialize(emp_v1).value(); + +// Deserialize as V2 +auto emp_v2 = fory_v2.deserialize(bytes).value(); +assert(emp_v2.name == "Jane Doe"); +assert(emp_v2.home_address.street == "123 Main St"); +assert(emp_v2.home_address.city == "NYC"); +assert(emp_v2.home_address.country == ""); // Default +assert(emp_v2.home_address.zipcode == ""); // Default +assert(emp_v2.employee_id == ""); // Default +``` + +## Bidirectional Evolution + +Schema evolution works in both directions: + +```cpp +// V2 -> V1 (downgrade) +PersonV2 v2{"Charlie", 35, "charlie@example.com"}; +auto bytes = fory_v2.serialize(v2).value(); + +auto v1 = fory_v1.deserialize(bytes).value(); +assert(v1.name == "Charlie"); +assert(v1.age == 35); +// email field is discarded during deserialization +``` + +## Default Values + +When fields are missing, C++ default initialization is used: + +| Type | Default Value | +| ---------------------- | ------------------- | +| `int8_t`, `int16_t`... | `0` | +| `float`, `double` | `0.0` | +| `bool` | `false` | +| `std::string` | `""` | +| `std::vector` | Empty vector | +| `std::map` | Empty map | +| `std::set` | Empty set | +| `std::optional` | `std::nullopt` | +| Struct types | Default-constructed | + +## Same-Schema Optimization + +Compatible mode is the default in both xlang and native mode. Set `compatible(false)` explicitly +only when every reader and writer always uses the same schema and you want faster serialization and smaller size: + +```cpp +// Same-schema optimization +auto fory = Fory::builder() + .xlang(true) + .compatible(false) + .build(); + +// Serialization/deserialization requires identical schemas +// Schema mismatches may cause errors or undefined behavior +``` + +**Use `compatible(false)` when:** + +- Every reader and writer always uses the same schema +- You want faster serialization and smaller size +- For xlang payloads, every language uses the same verified schema or native types generated from Fory schema IDL + +### Per-Struct Opt-Out + +For one struct, you can opt out of evolution metadata with `FORY_STRUCT_EVOLVING` after +`FORY_STRUCT`: + +```cpp +struct SameSchemaMessage { + int32_t id; +}; +FORY_STRUCT(SameSchemaMessage, id); +FORY_STRUCT_EVOLVING(SameSchemaMessage, false); +``` + +**Use Compatible mode when:** + +- Schemas may evolve independently +- Cross-version compatibility is required +- Different services may have different schema versions + +## Type ID Requirements + +For schema evolution to work: + +1. **Same Type ID**: Different versions of the same struct must use the same type ID +2. **Consistent IDs**: Type IDs must be consistent across all Fory instances +3. **Register All Versions**: Each Fory instance registers its own struct version + +```cpp +constexpr uint32_t PERSON_TYPE_ID = 100; + +// Instance 1 uses PersonV1 +fory_v1.register_struct(PERSON_TYPE_ID); + +// Instance 2 uses PersonV2 +fory_v2.register_struct(PERSON_TYPE_ID); + +// Same type ID enables schema evolution +``` + +## Best Practices + +### 1. Plan for Evolution + +Design schemas with future changes in mind: + +```cpp +// Good: Use optional for fields that might be removed +struct Config { + std::string host; + int32_t port; + std::optional deprecated_field; // Can be removed later +}; +``` + +### 2. Use Meaningful Default Values + +Consider what default values make sense for new fields: + +```cpp +struct Settings { + int32_t timeout_ms; // Default: 0 (might want a sensible default) + bool enabled; // Default: false + std::string mode; // Default: "" (might want "default") +}; +``` + +### 3. Document Schema Versions + +Track schema changes for debugging: + +```cpp +// V1: Initial schema (2024-01-01) +// V2: Added email field (2024-02-01) +// V3: Added phone, address fields (2024-03-01) +``` + +### 4. Test Evolution Paths + +Test both upgrade and downgrade scenarios: + +```cpp +// Test V1 -> V2 +// Test V2 -> V1 +// Test V1 -> V3 +// Test V3 -> V1 +``` + +## Xlang Schema Evolution + +Schema evolution works across languages when using xlang mode: + +```cpp +// C++ with compatible mode, the default +auto fory = Fory::builder() + .xlang(true) + .build(); +``` + +```java +// Java with compatible mode, the default +Fory fory = Fory.builder() + .withXlang(true) + .build(); +``` + +Both instances can exchange data even with different schema versions. + +## Related Topics + +- [Configuration](configuration.md) - Enabling compatible mode +- [Type Registration](type-registration.md) - Type ID management +- [Xlang Serialization](xlang-serialization.md) - Cross-language considerations diff --git a/versioned_docs/version-1.3.0/guide/cpp/schema-metadata.md b/versioned_docs/version-1.3.0/guide/cpp/schema-metadata.md new file mode 100644 index 00000000000..fe455dd60c8 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/cpp/schema-metadata.md @@ -0,0 +1,220 @@ +--- +title: Schema Metadata +sidebar_position: 5 +id: schema_metadata +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. +--- + +Field configuration is embedded directly in `FORY_STRUCT`. A field entry may be +bare, it may be a tuple containing the member name and a `fory::F(...)` +builder, or it may be a configured accessor property: + +```cpp +#include "fory/serialization/fory.h" + +struct DataV2 { + uint32_t id; + uint64_t timestamp; + std::optional version; +}; + +FORY_STRUCT(DataV2, id, (timestamp, fory::F().tagged()), version); +``` + +Accessor properties use the same field metadata as data members: + +```cpp +class Counter { +public: + const uint32_t &value() const; + Counter &value(uint32_t value); + + FORY_STRUCT(Counter, FORY_PROPERTY(value, fory::F().varint())); +}; +``` + +The configuration is compile-time metadata. It does not allocate codec objects +or add virtual dispatch on the serialization path. + +## Field Identity + +`fory::F()` uses name-mode field identity. Bare fields are also name-mode: + +```cpp +FORY_STRUCT(DataV2, id, (timestamp, fory::F().tagged()), version); +FORY_STRUCT(Counter, FORY_PROPERTY(value, fory::F().varint())); +``` + +`fory::F(id)` uses explicit id-based field identity. IDs must be +non-negative: + +```cpp +FORY_STRUCT(DataV2, (id, fory::F(0)), (timestamp, fory::F(1).tagged()), + (version, fory::F(2))); +``` + +Fields without explicit IDs still use their snake_case field names. Explicit +IDs sort before name-based fields within the same protocol field group, so a +single `FORY_STRUCT` may mix `fory::F(id)`, `fory::F()`, and bare fields. + +## Scalar Encoding + +Integer encoding is configured on the field or on a nested value-node spec: + +```cpp +struct Counters { + uint32_t fixed_id; + uint64_t tagged_time; + int64_t signed_score; +}; + +FORY_STRUCT(Counters, (fixed_id, fory::F().fixed()), + (tagged_time, fory::F().tagged()), + (signed_score, fory::F().varint())); +``` + +Supported scalar encoding methods are: + +| Method | Meaning | +| ---------- | -------------------------------------------- | +| `fixed()` | Fixed-width integer encoding where valid | +| `varint()` | Variable-length integer encoding where valid | +| `tagged()` | Tagged integer encoding where valid | + +Invalid scalar/type combinations fail at compile time. + +## Nested Specs + +Use the `fory::T` namespace for value-node specs inside containers and wrapper +carriers. Untyped specs infer the actual C++ type at that node: + +```cpp +namespace T = fory::T; + +struct Foo { + std::vector values; + std::map> nested; +}; + +FORY_STRUCT(Foo, + (values, fory::F().list(T::fixed())), + (nested, fory::F().map(T::varint(), + T::list(T::tagged())))); +``` + +Typed specs are optional validators and make the intended node type explicit: + +```cpp +FORY_STRUCT(Foo, (nested, fory::F().map(T::uint32().varint(), + T::list(T::int64().tagged())))); +``` + +Supported recursive composition methods are: + +| Method | Applies to | +| ------------------- | ----------------------------------------------------------------- | +| `list(elem)` | `std::vector` and list-like fields | +| `set(elem)` | `std::set` and set-like fields | +| `map(key, value)` | `std::map`, `std::unordered_map`, and map-like fields | +| `map().key(spec)` | Override only the map key | +| `map().value(spec)` | Override only the map value | +| `inner(child)` | Transparent single-child carriers | + +Partial map overrides are useful when only one side needs a non-default +encoding: + +```cpp +FORY_STRUCT(Foo, + (nested, fory::F().map().key(T::varint())), + (other, fory::F().map().value(T::list(T::tagged())))); +``` + +## Carrier Inner Specs + +Use `.inner(...)` for wrapper-like carriers. The carrier kind still comes from +the actual C++ type, and controls nullable/reference behavior: + +```cpp +struct WrapperFields { + std::optional> maybe_values; + std::shared_ptr> shared_values; +}; + +FORY_STRUCT(WrapperFields, + (maybe_values, fory::F().inner(T::list(T::varint()))), + (shared_values, + fory::F().nullable().ref().inner(T::list(T::tagged())))); +``` + +`.inner(...)` is the only public combinator for `std::optional`, +`std::shared_ptr`, `std::unique_ptr`, and +`fory::serialization::SharedWeak`. + +## Nullability, Reference Tracking, And Dynamic Fields + +`std::optional` is nullable by default. Smart pointers may be marked nullable +or reference-tracked in the field spec: + +```cpp +struct Node { + std::string name; + std::shared_ptr next; +}; + +FORY_STRUCT(Node, name, (next, fory::F().nullable().ref())); +``` + +For polymorphic pointer fields, use `.dynamic(true)` to always write concrete +type information, `.dynamic(false)` to use the declared type directly, or omit +it to let Fory infer the behavior from the C++ type: + +```cpp +struct Zoo { + std::shared_ptr star; + std::shared_ptr mascot; +}; + +FORY_STRUCT(Zoo, (star, fory::F().nullable().dynamic(true)), + (mascot, fory::F().nullable().dynamic(false))); +``` + +## Unions + +`FORY_UNION` cases must use explicit ids. Name-mode `fory::F()` is invalid for +union metadata: + +```cpp +struct Choice { + std::variant value; + + static Choice text(std::string value); + static Choice code(uint32_t value); +}; + +FORY_UNION(Choice, (text, std::string, fory::F(1)), + (code, uint32_t, fory::F(2).fixed())); +``` + +Generated C++ may omit the explicit case type when it can infer the payload type +from a non-overloaded one-argument factory: + +```cpp +FORY_UNION(GeneratedChoice, (text, fory::F(1)), + (code, fory::F(2).fixed())); +``` + +The three-element form is the stable public form for handwritten code. diff --git a/versioned_docs/version-1.3.0/guide/cpp/supported-types.md b/versioned_docs/version-1.3.0/guide/cpp/supported-types.md new file mode 100644 index 00000000000..c4dd9f00406 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/cpp/supported-types.md @@ -0,0 +1,281 @@ +--- +title: Supported Types +sidebar_position: 9 +id: supported_types +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 page documents all types supported by Fory C++ serialization. + +## Primitive Types + +All C++ primitive types are supported with efficient binary encoding: + +| Type | Size | Fory TypeId | Notes | +| ------------------ | ------ | ----------- | --------------------- | +| `bool` | 1 byte | BOOL | True/false | +| `int8_t` | 1 byte | INT8 | Signed byte | +| `uint8_t` | 1 byte | INT8 | Unsigned byte | +| `int16_t` | 2 byte | INT16 | Signed short | +| `uint16_t` | 2 byte | INT16 | Unsigned short | +| `int32_t` | 4 byte | INT32 | Signed integer | +| `uint32_t` | 4 byte | INT32 | Unsigned integer | +| `int64_t` | 8 byte | INT64 | Signed long | +| `uint64_t` | 8 byte | INT64 | Unsigned long | +| `float` | 4 byte | FLOAT32 | IEEE 754 single | +| `double` | 8 byte | FLOAT64 | IEEE 754 double | +| `fory::bfloat16_t` | 2 byte | BFLOAT16 | IEEE 754 bfloat16 | +| `char` | 1 byte | INT8 | Character (as signed) | +| `char16_t` | 2 byte | INT16 | 16-bits characters | +| `char32_t` | 4 byte | INT32 | 32-bits characters | + +```cpp +int32_t value = 42; +auto bytes = fory.serialize(value).value(); +auto decoded = fory.deserialize(bytes).value(); +assert(value == decoded); +``` + +## String Types + +| Type | Fory TypeId | Notes | +| ------------------ | ----------- | ------------------------ | +| `std::string` | STRING | UTF-8 encoded | +| `std::string_view` | STRING | Zero-copy view (read) | +| `std::u16string` | STRING | UTF-16 (converted) | +| `binary` | BINARY | Raw bytes without length | + +```cpp +std::string text = "Hello, World!"; +auto bytes = fory.serialize(text).value(); +auto decoded = fory.deserialize(bytes).value(); +assert(text == decoded); +``` + +## Collection Types + +### Vector / List + +`std::vector` for any serializable element type: + +`std::vector` can be used as the dense carrier when the field metadata declares `array` schema. + +```cpp +std::vector numbers{1, 2, 3, 4, 5}; +auto bytes = fory.serialize(numbers).value(); +auto decoded = fory.deserialize>(bytes).value(); + +// Nested vectors +std::vector> nested{ + {"a", "b"}, + {"c", "d", "e"} +}; +``` + +### Set + +`std::set` and `std::unordered_set`: + +```cpp +std::set tags{"cpp", "serialization", "fory"}; +auto bytes = fory.serialize(tags).value(); +auto decoded = fory.deserialize>(bytes).value(); + +std::unordered_set ids{1, 2, 3}; +``` + +### Map + +`std::map` and `std::unordered_map`: + +```cpp +std::map scores{ + {"Alice", 100}, + {"Bob", 95} +}; +auto bytes = fory.serialize(scores).value(); +auto decoded = fory.deserialize>(bytes).value(); + +// Unordered map +std::unordered_map lookup{ + {1, "one"}, + {2, "two"} +}; +``` + +## Smart Pointers + +### std::optional + +Nullable wrapper for any type: + +```cpp +std::optional maybe_value = 42; +std::optional empty_value = std::nullopt; + +auto bytes = fory.serialize(maybe_value).value(); +auto decoded = fory.deserialize>(bytes).value(); +assert(decoded.has_value() && *decoded == 42); +``` + +### std::shared_ptr + +Shared ownership with reference tracking: + +```cpp +auto shared = std::make_shared("Alice", 30); + +auto bytes = fory.serialize(shared).value(); +auto decoded = fory.deserialize>(bytes).value(); +``` + +**With reference tracking enabled (default, `track_ref(true)`):** + +- Shared objects are serialized once +- References to the same object are preserved +- Circular references are handled automatically + +### std::unique_ptr + +Exclusive ownership: + +```cpp +auto unique = std::make_unique("Bob", 25); + +auto bytes = fory.serialize(unique).value(); +auto decoded = fory.deserialize>(bytes).value(); +``` + +## Variant Type + +`std::variant` for type-safe unions: + +```cpp +using MyVariant = std::variant; + +MyVariant v1 = 42; +MyVariant v2 = std::string("hello"); +MyVariant v3 = 3.14; + +auto bytes = fory.serialize(v1).value(); +auto decoded = fory.deserialize(bytes).value(); +assert(std::get(decoded) == 42); +``` + +### std::monostate + +Empty variant alternative: + +```cpp +using OptionalInt = std::variant; + +OptionalInt empty = std::monostate{}; +OptionalInt value = 42; +``` + +## Temporal Types + +### Duration + +`std::chrono::nanoseconds`: + +```cpp +using Duration = std::chrono::nanoseconds; + +Duration d = std::chrono::seconds(30); +auto bytes = fory.serialize(d).value(); +auto decoded = fory.deserialize(bytes).value(); +``` + +### Timestamp + +Point in time since Unix epoch: + +```cpp +using Timestamp = std::chrono::time_point; + +Timestamp now = std::chrono::system_clock::now(); +auto bytes = fory.serialize(now).value(); +auto decoded = fory.deserialize(bytes).value(); +``` + +### Date + +Days since Unix epoch: + +```cpp +Date date{18628}; // Days since 1970-01-01 + +auto bytes = fory.serialize(date).value(); +auto decoded = fory.deserialize(bytes).value(); +``` + +## User-Defined Structs + +Any struct can be made serializable with `FORY_STRUCT`: + +```cpp +struct Point { + double x; + double y; + double z; +}; +FORY_STRUCT(Point, x, y, z); + +struct Line { + Point start; + Point end; + std::string label; +}; +FORY_STRUCT(Line, start, end, label); +``` + +## Enum Types + +Both scoped and unscoped enums are supported with `FORY_ENUM`: + +```cpp +// Scoped enum (C++11 enum class) +enum class Color { RED = 0, GREEN = 1, BLUE = 2 }; + +// Unscoped enum with incontinuous values +enum Priority : int32_t { LOW = -10, NORMAL = 0, HIGH = 10 }; +FORY_ENUM(Priority, LOW, NORMAL, HIGH); +// FORY_ENUM must be defined at namespace scope. + +// Usage +Color c = Color::GREEN; +auto bytes = fory.serialize(c).value(); +auto decoded = fory.deserialize(bytes).value(); +``` + +## Unsupported Types + +Currently not supported: + +- Raw pointers (`T*`) - use smart pointers instead +- `std::tuple` - use structs instead +- `std::array` - use `std::vector` instead +- Function pointers +- References (`T&`, `const T&`) - by value only + +## Related Topics + +- [Basic Serialization](basic-serialization.md) - Using these types +- [Type Registration](type-registration.md) - Registering types +- [Xlang Serialization](xlang-serialization.md) - Cross-language compatibility diff --git a/versioned_docs/version-1.3.0/guide/cpp/type-registration.md b/versioned_docs/version-1.3.0/guide/cpp/type-registration.md new file mode 100644 index 00000000000..e7e94b2726f --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/cpp/type-registration.md @@ -0,0 +1,254 @@ +--- +title: Type Registration +sidebar_position: 7 +id: type_registration +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 page explains how to register types for serialization. + +## Overview + +Apache Fory™ requires explicit type registration for struct types. This design enables: + +- **Xlang compatibility**: Registered type IDs are used across language boundaries +- **Type Safety**: Detects type mismatches at deserialization time +- **Polymorphic Serialization**: Enables serialization of polymorphic objects via smart pointers + +## Registering Structs + +Use `register_struct(type_id)` to register a struct type: + +```cpp +#include "fory/serialization/fory.h" + +using namespace fory::serialization; + +struct Person { + std::string name; + int32_t age; +}; +FORY_STRUCT(Person, name, age); + +int main() { + auto fory = Fory::builder().xlang(true).build(); + + // Register with a unique type ID + fory.register_struct(100); + + Person person{"Alice", 30}; + auto bytes = fory.serialize(person).value(); + auto decoded = fory.deserialize(bytes).value(); +} +``` + +## Type ID Guidelines + +Type IDs must be: + +1. **Unique**: Each type must have a unique ID within a Fory instance +2. **Consistent**: Same ID must be used across all languages and versions + +User-registered type IDs are in a separate namespace from built-in type IDs, so you can start from 0: + +```cpp +// User type IDs can start from 0 +fory.register_struct
(0); +fory.register_struct(1); +fory.register_struct(2); +``` + +## Registering Enums + +Use `register_enum(type_id)` to register an enum type. For simple enums with continuous values starting from 0, no macro is needed: + +```cpp +// Simple continuous enum - no FORY_ENUM needed +enum class Color { RED, GREEN, BLUE }; // Values: 0, 1, 2 + +// Register with register_enum +fory.register_enum(0); +``` + +For enums with non-continuous values, use the `FORY_ENUM` macro to map values to ordinals: + +```cpp +// Non-continuous enum values - FORY_ENUM required +enum class Priority { LOW = 10, MEDIUM = 50, HIGH = 100 }; +FORY_ENUM(Priority, LOW, MEDIUM, HIGH); +// FORY_ENUM must be defined at namespace scope. + +// Global namespace enum (prefix with ::) +enum SparseStatus { UNKNOWN = -1, OK = 0, ERROR = 1 }; +FORY_ENUM(::SparseStatus, UNKNOWN, OK, ERROR); + +// Register after FORY_ENUM +fory.register_enum(1); +fory.register_enum(2); +``` + +**When to use `FORY_ENUM`:** + +- Enum values don't start from 0 +- Enum values are not continuous (e.g., 10, 50, 100) +- You need name-to-value mapping at compile time + +## Thread-Safe Registration + +For `ThreadSafeFory`, register types before spawning threads: + +```cpp +auto fory = Fory::builder().xlang(true).build_thread_safe(); + +// Register all types first +fory.register_struct(100); +fory.register_struct(101); + +// Now safe to use from multiple threads +std::thread t1([&]() { + auto result = fory.serialize(obj_a); +}); +std::thread t2([&]() { + auto result = fory.serialize(obj_b); +}); +``` + +## Xlang Registration + +For cross-language compatibility, ensure: + +1. **Same Type ID**: Use identical IDs in all languages +2. **Compatible Types**: Use equivalent types across languages + +### Java + +```java +Fory fory = Fory.builder().withXlang(true).build(); +fory.register(Person.class, 100); +fory.register(Address.class, 101); +``` + +### Python + +```python +import pyfory + +fory = pyfory.Fory(xlang=True) +fory.register(Person, type_id=100) +fory.register(Address, type_id=101) +``` + +### C++ + +```cpp +auto fory = Fory::builder().xlang(true).build(); +fory.register_struct(100); +fory.register_struct
(101); +``` + +## Built-in Type IDs + +Built-in types have pre-assigned type IDs and don't need registration: + +| Type ID | Type | +| ------- | ----------------------- | +| 0 | UNKNOWN | +| 1 | BOOL | +| 2 | INT8 | +| 3 | INT16 | +| 4 | INT32 | +| 5 | VARINT32 | +| 6 | INT64 | +| 7 | VARINT64 | +| 8 | TAGGED_INT64 | +| 9 | UINT8 | +| 10 | UINT16 | +| 11 | UINT32 | +| 12 | VAR_UINT32 | +| 13 | UINT64 | +| 14 | VAR_UINT64 | +| 15 | TAGGED_UINT64 | +| 16 | FLOAT8 | +| 17 | FLOAT16 | +| 18 | BFLOAT16 | +| 19 | FLOAT32 | +| 20 | FLOAT64 | +| 21 | STRING | +| 22 | LIST | +| 23 | SET | +| 24 | MAP | +| 25 | ENUM | +| 26 | NAMED_ENUM | +| 27 | STRUCT | +| 28 | COMPATIBLE_STRUCT | +| 29 | NAMED_STRUCT | +| 30 | NAMED_COMPATIBLE_STRUCT | +| 31 | EXT | +| 32 | NAMED_EXT | +| 33 | UNION | +| 34 | TYPED_UNION | +| 35 | NAMED_UNION | +| 36 | NONE | +| 37 | DURATION | +| 38 | TIMESTAMP | +| 39 | DATE | +| 40 | DECIMAL | +| 41 | BINARY | +| 42 | ARRAY | +| 43 | BOOL_ARRAY | +| 44 | INT8_ARRAY | +| 45 | INT16_ARRAY | +| 46 | INT32_ARRAY | +| 47 | INT64_ARRAY | +| 48 | UINT8_ARRAY | +| 49 | UINT16_ARRAY | +| 50 | UINT32_ARRAY | +| 51 | UINT64_ARRAY | +| 52 | FLOAT8_ARRAY | +| 53 | FLOAT16_ARRAY | +| 54 | BFLOAT16_ARRAY | +| 55 | FLOAT32_ARRAY | +| 56 | FLOAT64_ARRAY | +| 64 | CHAR | +| 65 | CHAR16 | +| 66 | CHAR32 | + +## Error Handling + +Registration errors are checked at serialization/deserialization time: + +```cpp +// Attempting to serialize unregistered type +auto result = fory.serialize(unregistered_obj); +if (!result.ok()) { + // Error: "Type not registered: ..." + std::cerr << result.error().to_string() << std::endl; +} + +// Type ID mismatch during deserialization +auto result = fory.deserialize(bytes); +if (!result.ok()) { + // Error: "Type mismatch: expected X, got Y" + std::cerr << result.error().to_string() << std::endl; +} +``` + +## Related Topics + +- [Basic Serialization](basic-serialization.md) - Using registered types +- [Xlang Serialization](xlang-serialization.md) - Cross-language considerations +- [Supported Types](supported-types.md) - All supported types diff --git a/versioned_docs/version-1.3.0/guide/cpp/xlang-serialization.md b/versioned_docs/version-1.3.0/guide/cpp/xlang-serialization.md new file mode 100644 index 00000000000..fc943a7d4fa --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/cpp/xlang-serialization.md @@ -0,0 +1,295 @@ +--- +title: Xlang Serialization +sidebar_position: 2 +id: xlang_serialization +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 page explains how to use Fory xlang serialization between C++ and other languages. + +## Overview + +Apache Fory™ enables seamless data exchange between C++, Java, Python, Go, Rust, +JavaScript/TypeScript, C#, Swift, Dart, Scala, and Kotlin. Xlang mode ensures +binary compatibility across all supported languages. + +## Create an Xlang Fory Instance + +C++ defaults to xlang mode. Compatible schema evolution is also the xlang default. Set the mode explicitly in xlang examples: + +```cpp +#include "fory/serialization/fory.h" + +using namespace fory::serialization; + +auto fory = Fory::builder().xlang(true).build(); +``` + +## Xlang Example + +### C++ Producer + +```cpp +#include "fory/serialization/fory.h" +#include + +using namespace fory::serialization; + +struct Message { + std::string topic; + int64_t timestamp; + std::map headers; + std::vector payload; + + bool operator==(const Message &other) const { + return topic == other.topic && timestamp == other.timestamp && + headers == other.headers && payload == other.payload; + } +}; +FORY_STRUCT(Message, topic, timestamp, headers, payload); + +int main() { + auto fory = Fory::builder().xlang(true).build(); + fory.register_struct(100); + + Message msg{ + "events.user", + 1699999999000, + {{"content-type", "application/json"}}, + {'h', 'e', 'l', 'l', 'o'} + }; + + auto result = fory.serialize(msg); + if (result.ok()) { + auto bytes = std::move(result).value(); + // write to file, send over network, etc. + std::ofstream file("message.bin", std::ios::binary); + file.write(reinterpret_cast(bytes.data()), bytes.size()); + } + return 0; +} +``` + +### Java Consumer + +```java +import org.apache.fory.Fory; + +public class Message { + public String topic; + public long timestamp; + public Map headers; + public byte[] payload; +} + +public class Consumer { + public static void main(String[] args) throws Exception { + Fory fory = Fory.builder() + .withXlang(true) + .build(); + fory.register(Message.class, 100); // Same ID as C++ + + byte[] bytes = Files.readAllBytes(Path.of("message.bin")); + Message msg = (Message) fory.deserialize(bytes); + + System.out.println("Topic: " + msg.topic); + System.out.println("Timestamp: " + msg.timestamp); + } +} +``` + +### Python Consumer + +```python +import pyfory + +class Message: + topic: str + timestamp: int + headers: dict[str, str] + payload: bytes + +fory = pyfory.Fory(xlang=True) +fory.register(Message, type_id=100) # Same ID as C++ + +with open("message.bin", "rb") as f: + data = f.read() + +msg = fory.deserialize(data) +print(f"Topic: {msg.topic}") +print(f"Timestamp: {msg.timestamp}") +``` + +## Type Mapping + +### Primitive Types + +| C++ Type | Java Type | Python Type | Go Type | Rust Type | +| ------------------ | ---------- | ----------------- | ------------------- | ---------- | +| `bool` | `boolean` | `bool` | `bool` | `bool` | +| `int8_t` | `byte` | `int` | `int8` | `i8` | +| `int16_t` | `short` | `int` | `int16` | `i16` | +| `int32_t` | `int` | `int` | `int32` | `i32` | +| `int64_t` | `long` | `int` | `int64` | `i64` | +| `float` | `float` | `float` | `float32` | `f32` | +| `double` | `double` | `float` | `float64` | `f64` | +| `fory::float16_t` | `Float16` | `pyfory.Float16` | `float16.Float16` | `Float16` | +| `fory::bfloat16_t` | `BFloat16` | `pyfory.BFloat16` | `bfloat16.BFloat16` | `BFloat16` | + +### String Types + +| C++ Type | Java Type | Python Type | Go Type | Rust Type | +| ------------- | --------- | ----------- | -------- | --------- | +| `std::string` | `String` | `str` | `string` | `String` | + +### Collection Types + +| C++ Type | Java Type | Python Type | Go Type | Rust Type | +| ------------------------------------------- | -------------- | --------------- | --------------------- | --------------- | +| `std::vector` | `List` | `list` | `[]T` | `Vec` | +| `std::vector` | `Float16List` | `Float16Array` | `[]float16.Float16` | `Vec` | +| `std::vector` | `BFloat16List` | `BFloat16Array` | `[]bfloat16.BFloat16` | `Vec` | +| `std::set` | `Set` | `set` | `map[T]struct{}` | `HashSet` | +| `std::map` / `std::unordered_map` | `Map` | `dict` | `map[K]V` | `HashMap` | + +### Lists and Dense Arrays + +`std::vector` maps to Fory `list` by default in handwritten C++ structs. +Use the field metadata DSL's array node when the schema is dense `array`. + +| Fory schema | C++ metadata sketch | +| ----------------- | ---------------------------------------- | +| `list` | `fory::F(id).list(fory::T::int32())` | +| `array` | `fory::F(id).array(fory::T::bool_())` | +| `array` | `fory::F(id).array(fory::T::int8())` | +| `array` | `fory::F(id).array(fory::T::int16())` | +| `array` | `fory::F(id).array(fory::T::int32())` | +| `array` | `fory::F(id).array(fory::T::int64())` | +| `array` | `fory::F(id).array(fory::T::uint8())` | +| `array` | `fory::F(id).array(fory::T::uint16())` | +| `array` | `fory::F(id).array(fory::T::uint32())` | +| `array` | `fory::F(id).array(fory::T::uint64())` | +| `array` | `fory::F(id).array(fory::T::float16())` | +| `array` | `fory::F(id).array(fory::T::bfloat16())` | +| `array` | `fory::F(id).array(fory::T::float32())` | +| `array` | `fory::F(id).array(fory::T::float64())` | + +### Temporal Types + +| C++ Type | Java Type | Python Type | Go Type | +| ----------- | ----------- | --------------- | --------------- | +| `Timestamp` | `Instant` | `datetime` | `time.Time` | +| `Duration` | `Duration` | `timedelta` | `time.Duration` | +| `Date` | `LocalDate` | `datetime.date` | `time.Time` | + +## Field Order Requirements + +**Critical:** Fields are sorted by snake_case field name. The converted names must match across languages. + +### C++ + +```cpp +struct Person { + std::string name; // Field 0 + int32_t age; // Field 1 + std::string email; // Field 2 +}; +FORY_STRUCT(Person, name, age, email); // Order matters! +``` + +### Java + +```java +public class Person { + public String name; // Field 0 + public int age; // Field 1 + public String email; // Field 2 +} +``` + +### Python + +```python +class Person: + name: str # Field 0 + age: int # Field 1 + email: str # Field 2 +``` + +## Type ID Consistency + +All languages must use the same type IDs: + +```cpp +// C++ +fory.register_struct(100); +fory.register_struct
(101); +fory.register_struct(102); +``` + +```java +// Java +fory.register(Person.class, 100); +fory.register(Address.class, 101); +fory.register(Order.class, 102); +``` + +```python +# Python +fory.register(Person, type_id=100) +fory.register(Address, type_id=101) +fory.register(Order, type_id=102) +``` + +## Compatible Mode + +Xlang mode already uses compatible schema evolution by default. Keep that default for schemas that +may evolve independently: + +```cpp +auto fory = Fory::builder().xlang(true).build(); +``` + +Compatible mode allows: + +- Adding new fields (with defaults) +- Removing unused fields +- Reordering fields + +## Troubleshooting + +### Type Mismatch Errors + +``` +Error: Type mismatch: expected 100, got 101 +``` + +**Solution:** Ensure type IDs match across all languages. + +### Encoding Errors + +``` +Error: Invalid UTF-8 sequence +``` + +**Solution:** Ensure strings are valid UTF-8 in all languages. + +## Related Topics + +- [Configuration](configuration.md) - Builder options +- [Type Registration](type-registration.md) - Registering types +- [Supported Types](supported-types.md) - Type compatibility diff --git a/versioned_docs/version-1.3.0/guide/csharp/_category_.json b/versioned_docs/version-1.3.0/guide/csharp/_category_.json new file mode 100644 index 00000000000..c5e03ee3c59 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/csharp/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "C#", + "position": 7, + "collapsible": true, + "collapsed": true +} diff --git a/versioned_docs/version-1.3.0/guide/csharp/basic-serialization.md b/versioned_docs/version-1.3.0/guide/csharp/basic-serialization.md new file mode 100644 index 00000000000..d112990ccdf --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/csharp/basic-serialization.md @@ -0,0 +1,131 @@ +--- +title: Basic Serialization +sidebar_position: 1 +id: basic_serialization +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 page covers typed serialization APIs in Apache Fory™ C#. + +## Object Graph Serialization + +Use `[ForyStruct]` on your classes/structs and register them before use. + +```csharp +using Apache.Fory; + +[ForyStruct] +public sealed class Address +{ + public string Street { get; set; } = string.Empty; + public int Zip { get; set; } +} + +[ForyStruct] +public sealed class Person +{ + public long Id { get; set; } + public string Name { get; set; } = string.Empty; + public string? Nickname { get; set; } + public List Scores { get; set; } = []; + public List
Addresses { get; set; } = []; +} + +Fory fory = Fory.Builder().Build(); +fory.Register
(100); +fory.Register(101); + +Person person = new() +{ + Id = 42, + Name = "Alice", + Nickname = null, + Scores = [10, 20, 30], + Addresses = [new Address { Street = "Main", Zip = 94107 }], +}; + +byte[] payload = fory.Serialize(person); +Person decoded = fory.Deserialize(payload); +``` + +## Typed API + +### Serialize / Deserialize with byte arrays + +```csharp +byte[] payload = fory.Serialize(value); +MyType decoded = fory.Deserialize(payload); +``` + +### Deserialize from `ReadOnlySpan` + +```csharp +ReadOnlySpan span = payload; +MyType decoded = fory.Deserialize(span); +``` + +### Stream-style frame consumption + +```csharp +using System.Buffers; + +ReadOnlySequence sequence = GetFramedSequence(); +MyType first = fory.Deserialize(ref sequence); +MyType second = fory.Deserialize(ref sequence); +``` + +## Dynamic Payloads via Generic Object API + +When the compile-time type is unknown or heterogeneous, use the generic API with `object?`. + +```csharp +Dictionary value = new() +{ + ["k1"] = 7, + [2] = "v2", + [true] = null, +}; + +byte[] payload = fory.Serialize(value); +object? decoded = fory.Deserialize(payload); +``` + +## Buffer Writer API + +Serialize directly into `IBufferWriter` targets. + +```csharp +using System.Buffers; + +ArrayBufferWriter writer = new(); +fory.Serialize(writer, value); + +ArrayBufferWriter dynamicWriter = new(); +fory.Serialize(dynamicWriter, value); +``` + +## Notes + +- Reuse the same `Fory` or `ThreadSafeFory` instance for better performance. +- Primitive types and collections do not require user registration. +- User `[ForyStruct]`, `[ForyEnum]`, `[ForyUnion]`, and custom serializer types should be registered explicitly. + +## Related Topics + +- [Type Registration](type-registration.md) +- [Supported Types](supported-types.md) +- [References](references.md) diff --git a/versioned_docs/version-1.3.0/guide/csharp/configuration.md b/versioned_docs/version-1.3.0/guide/csharp/configuration.md new file mode 100644 index 00000000000..e7c0c24d427 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/csharp/configuration.md @@ -0,0 +1,184 @@ +--- +title: Configuration +sidebar_position: 2 +id: configuration +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 page covers `ForyBuilder` options and default configuration values for Apache Fory™ C#. +`Config` is an immutable configuration snapshot created by `ForyBuilder`. + +## Build a Fory Instance + +```csharp +using Apache.Fory; + +Fory fory = Fory.Builder().Build(); +ThreadSafeFory threadSafe = Fory.Builder().BuildThreadSafe(); +``` + +## Default Configuration + +`Fory.Builder().Build()` uses: + +| Option | Default | Description | +| --------------------------------- | ------- | ------------------------------------------------- | +| `TrackRef` | `false` | Reference tracking disabled | +| `Compatible` | `true` | Compatible schema-evolution metadata enabled | +| `CheckStructVersion` | `false` | Struct schema hash checks disabled | +| `MaxDepth` | `20` | Max dynamic nesting depth | +| `MaxTypeFields` | `512` | Max fields in one received struct metadata body | +| `MaxTypeMetaBytes` | `4096` | Max encoded bytes in one received metadata body | +| `MaxSchemaVersionsPerType` | `10` | Max remote metadata versions for one logical type | +| `MaxAverageSchemaVersionsPerType` | `3` | Average remote metadata versions across types | + +## Builder Options + +C# always uses xlang-compatible framing, so `ForyBuilder` does not expose a mode toggle. + +### `TrackRef(bool enabled = false)` + +Enables reference tracking for shared/circular object graphs. + +```csharp +Fory fory = Fory.Builder() + .TrackRef(true) + .Build(); +``` + +### `Compatible(bool enabled = false)` + +Enables schema evolution mode. C# uses the xlang wire format only, so compatible mode is enabled by +default for independently deployed peers. Use `.Build()` without calling this method for the +default compatible mode. Passing `false`, or calling `Compatible()` without an argument, opts into +same-schema payloads. Use that only when every reader and writer always uses the same schema and you want faster serialization and smaller size. For cross-language payloads, call `Compatible(false)` only after verifying that every peer uses the same schema, or when native types are generated from Fory schema IDL. + +```csharp +Fory fory = Fory.Builder() + .Compatible(false) + .Build(); +``` + +### `CheckStructVersion(bool enabled = false)` + +Checks the schema hash when you intentionally use same-schema payloads. + +```csharp +Fory fory = Fory.Builder() + .Compatible(false) + .CheckStructVersion(true) + .Build(); +``` + +### `MaxDepth(int value)` + +Sets max nesting depth for dynamic object graphs. + +```csharp +Fory fory = Fory.Builder() + .MaxDepth(32) + .Build(); +``` + +`value` must be greater than `0`. + +### `MaxTypeFields(int value)` + +Sets the maximum fields accepted in one received remote struct metadata body. + +```csharp +Fory fory = Fory.Builder() + .MaxTypeFields(512) + .Build(); +``` + +### `MaxTypeMetaBytes(int value)` + +Sets the maximum encoded body bytes accepted for one received TypeMeta body, +excluding the 8-byte header and any extended-size varint. + +```csharp +Fory fory = Fory.Builder() + .MaxTypeMetaBytes(4096) + .Build(); +``` + +### `MaxSchemaVersionsPerType(int value)` + +Sets the maximum accepted remote metadata versions for one logical type. + +```csharp +Fory fory = Fory.Builder() + .MaxSchemaVersionsPerType(10) + .Build(); +``` + +### `MaxAverageSchemaVersionsPerType(int value)` + +Sets the average accepted remote metadata versions across accepted remote types. +The effective global floor is `8192` schemas. + +```csharp +Fory fory = Fory.Builder() + .MaxAverageSchemaVersionsPerType(3) + .Build(); +``` + +## Common Configurations + +### Compatible service + +```csharp +Fory fory = Fory.Builder() + .TrackRef(true) + .Build(); +``` + +### Same-schema optimization + +Use this only when every reader and writer always uses the same schema. + +```csharp +Fory fory = Fory.Builder() + .Compatible(false) + .Build(); +``` + +### Thread-safe service instance + +```csharp +ThreadSafeFory fory = Fory.Builder() + .TrackRef(true) + .BuildThreadSafe(); +``` + +## Security + +Security-related configuration: + +- Register only the expected types before deserializing untrusted payloads. +- Use `CheckStructVersion(true)` with `Compatible(false)` for intentional same-schema payloads. +- Set `MaxDepth(...)` to reject unexpectedly deep dynamic object graphs. +- Keep the remote schema metadata limits at their defaults unless the data is not malicious and a + trusted peer sends larger metadata or many schema versions. +- Prefer generated or registered concrete models over broad dynamic fields for untrusted input. + +## Related Topics + +- [Basic Serialization](basic-serialization.md) +- [Schema Evolution](schema-evolution.md) +- [Thread Safety](thread-safety.md) diff --git a/versioned_docs/version-1.3.0/guide/csharp/custom-serializers.md b/versioned_docs/version-1.3.0/guide/csharp/custom-serializers.md new file mode 100644 index 00000000000..4db844dfe92 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/csharp/custom-serializers.md @@ -0,0 +1,90 @@ +--- +title: Custom Serializers +sidebar_position: 11 +id: custom_serializers +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. +--- + +Use custom serializers when a type is not generated with `[ForyStruct]` or requires specialized encoding. + +## Implement `Serializer` + +```csharp +using Apache.Fory; + +public sealed class Point +{ + public int X { get; set; } + public int Y { get; set; } +} + +public sealed class PointSerializer : Serializer +{ + public override Point DefaultValue => new(); + + public override void WriteData(WriteContext context, in Point value, bool hasGenerics) + { + context.Writer.WriteVarInt32(value.X); + context.Writer.WriteVarInt32(value.Y); + } + + public override Point ReadData(ReadContext context) + { + return new Point + { + X = context.Reader.ReadVarInt32(), + Y = context.Reader.ReadVarInt32(), + }; + } +} +``` + +## Register the Serializer + +```csharp +Fory fory = Fory.Builder().Build(); +fory.Register(200); + +Point value = new() { X = 10, Y = 20 }; +byte[] payload = fory.Serialize(value); +Point decoded = fory.Deserialize(payload); +``` + +Use the named overload when peers identify the type by name instead of a numeric ID: + +```csharp +fory.Register("com.example.Point"); +``` + +## Serializer Behavior Notes + +- `WriteData` / `ReadData` only handle payload content. +- Ref flags and type info are handled by base `Serializer.Write` / `Read` unless overridden. +- `DefaultValue` is used for null/default fallback paths. + +## Best Practices + +1. Keep serializers deterministic and symmetric. +2. Use varint/fixed/tagged encoding intentionally for integer-heavy payloads. +3. Register custom serializers on all reader/writer peers. +4. Prefer generated `[ForyStruct]` serializers for normal domain models. + +## Related Topics + +- [Type Registration](type-registration.md) +- [Schema Metadata](schema-metadata.md) +- [Troubleshooting](troubleshooting.md) diff --git a/versioned_docs/version-1.3.0/guide/csharp/grpc-support.md b/versioned_docs/version-1.3.0/guide/csharp/grpc-support.md new file mode 100644 index 00000000000..daf2c4fe65b --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/csharp/grpc-support.md @@ -0,0 +1,311 @@ +--- +title: gRPC Support +sidebar_position: 12 +id: grpc_support +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. +--- + +Fory can generate C# gRPC service companions for schemas that define services. +The generated code uses normal gRPC clients, service bases, method descriptors, +metadata, deadlines, cancellations, and status codes, while request and response +objects are serialized with Fory instead of protobuf. + +Use this mode when both RPC peers are generated from the same Fory IDL, +protobuf IDL, or FlatBuffers IDL and both sides expect Fory-encoded message +bodies. Use normal protobuf gRPC generation for APIs that must be consumed by +generic protobuf clients, reflection tools, or components that expect protobuf +message bytes. + +## Add Dependencies + +The `Apache.Fory` package does not add gRPC dependencies. Add the gRPC packages +in the application that compiles or runs generated service companions. + +Server project: + +```xml + + + + +``` + +Client project: + +```xml + + + + + +``` + +`Grpc.Core.Api` is the API surface used by generated companions. Server and +client applications can choose their normal gRPC hosting or transport packages. + +## Define a Service + +Service definitions can come from Fory IDL, protobuf IDL, or FlatBuffers +`rpc_service` definitions. A Fory IDL service looks like this: + +```protobuf +package demo.greeter; +option csharp_namespace = "Demo.Greeter"; + +message HelloRequest { + string name = 1; +} + +message HelloReply { + string reply = 1; +} + +service Greeter { + rpc SayHello (HelloRequest) returns (HelloReply); +} +``` + +Generate C# model and gRPC companion code with `--grpc`: + +```bash +foryc service.fdl --csharp_out=./Generated --grpc +``` + +For this schema, the C# generator emits: + +| File | Purpose | +| ------------------------------------------- | -------------------------------------------- | +| `Demo/Greeter/Service.cs` | Fory model types and schema module | +| `Demo/Greeter/GreeterGrpc.cs` | gRPC service base, client, and descriptors | +| `ServiceForyModule` in `Service.cs` | Fory registration module for generated types | +| `Greeter.GreeterBase` in `GreeterGrpc.cs` | Base class for server implementations | +| `Greeter.GreeterClient` in `GreeterGrpc.cs` | Client stub for gRPC calls | + +## Implement a Server + +Extend the generated `Greeter.GreeterBase` class and map it through normal +ASP.NET Core gRPC hosting: + +```csharp +using System.Threading.Tasks; +using Demo.Greeter; +using Grpc.Core; + +var builder = WebApplication.CreateBuilder(args); +builder.Services.AddGrpc(); + +var app = builder.Build(); +app.MapGrpcService(); +app.Run(); + +public sealed class GreeterService : Greeter.GreeterBase +{ + public override Task SayHello( + HelloRequest request, + ServerCallContext context) + { + return Task.FromResult(new HelloReply + { + Reply = "Hello, " + request.Name, + }); + } +} +``` + +Generated request and response types are registered by the generated schema +module used by the service companion, so service implementations do not perform +manual serializer registration. + +## Create a Client + +Use the generated client with a `Grpc.Net.Client` call invoker: + +```csharp +using Demo.Greeter; +using Grpc.Net.Client; + +using GrpcChannel channel = GrpcChannel.ForAddress("https://localhost:5001"); +var client = new Greeter.GreeterClient(channel.CreateCallInvoker()); + +HelloReply reply = await client.SayHelloAsync( + new HelloRequest { Name = "Fory" }); +Console.WriteLine(reply.Reply); +``` + +The generated client also exposes synchronous unary methods and the normal +gRPC streaming call shapes. + +## Streaming RPCs + +Fory service definitions can use the same gRPC streaming shapes: + +```protobuf +service Greeter { + rpc SayHello (HelloRequest) returns (HelloReply); + rpc LotsOfReplies (HelloRequest) returns (stream HelloReply); + rpc LotsOfGreetings (stream HelloRequest) returns (HelloReply); + rpc Chat (stream HelloRequest) returns (stream HelloReply); +} +``` + +Generated C# service methods follow gRPC C# conventions: + +| IDL shape | Server method | Client method | +| ----------------------------------------- | ----------------------------------------------------------------------------- | -------------------------------------------------- | +| `rpc A (Req) returns (Res)` | `Task A(Req request, ServerCallContext context)` | `Res A(...)` and `AsyncUnaryCall AAsync(...)` | +| `rpc A (Req) returns (stream Res)` | `Task A(Req request, IServerStreamWriter responseStream, ...)` | `AsyncServerStreamingCall A(...)` | +| `rpc A (stream Req) returns (Res)` | `Task A(IAsyncStreamReader requestStream, ...)` | `AsyncClientStreamingCall A(...)` | +| `rpc A (stream Req) returns (stream Res)` | `Task A(IAsyncStreamReader requestStream, IServerStreamWriter ...)` | `AsyncDuplexStreamingCall A(...)` | + +Server implementations can use the generated streaming method shapes directly: + +```csharp +using System.Collections.Generic; +using System.Threading.Tasks; +using Demo.Greeter; +using Grpc.Core; + +public sealed class GreeterService : Greeter.GreeterBase +{ + public override async Task LotsOfReplies( + HelloRequest request, + IServerStreamWriter responseStream, + ServerCallContext context) + { + foreach (string reply in new[] + { + "Hello, " + request.Name, + "Welcome, " + request.Name, + }) + { + await responseStream.WriteAsync(new HelloReply { Reply = reply }); + } + } + + public override async Task LotsOfGreetings( + IAsyncStreamReader requestStream, + ServerCallContext context) + { + List names = new(); + while (await requestStream.MoveNext(context.CancellationToken)) + { + names.Add(requestStream.Current.Name); + } + + return new HelloReply { Reply = string.Join(", ", names) }; + } + + public override async Task Chat( + IAsyncStreamReader requestStream, + IServerStreamWriter responseStream, + ServerCallContext context) + { + while (await requestStream.MoveNext(context.CancellationToken)) + { + await responseStream.WriteAsync(new HelloReply + { + Reply = "Hello, " + requestStream.Current.Name, + }); + } + } +} +``` + +Generated clients return the standard gRPC streaming call objects: + +```csharp +using System; +using System.Threading; +using System.Threading.Tasks; +using Demo.Greeter; +using Grpc.Core; + +using AsyncServerStreamingCall replies = + client.LotsOfReplies(new HelloRequest { Name = "Fory" }); +while (await replies.ResponseStream.MoveNext(CancellationToken.None)) +{ + Console.WriteLine(replies.ResponseStream.Current.Reply); +} + +using AsyncClientStreamingCall greetings = + client.LotsOfGreetings(); +await greetings.RequestStream.WriteAsync(new HelloRequest { Name = "Ada" }); +await greetings.RequestStream.WriteAsync(new HelloRequest { Name = "Grace" }); +await greetings.RequestStream.CompleteAsync(); +HelloReply summary = await greetings.ResponseAsync; +Console.WriteLine(summary.Reply); + +using AsyncDuplexStreamingCall chat = client.Chat(); +Task readTask = Task.Run(async () => +{ + while (await chat.ResponseStream.MoveNext(CancellationToken.None)) + { + Console.WriteLine(chat.ResponseStream.Current.Reply); + } +}); +await chat.RequestStream.WriteAsync(new HelloRequest { Name = "Fory" }); +await chat.RequestStream.CompleteAsync(); +await readTask; +``` + +The generated descriptors preserve the exact IDL service and method names for +the gRPC path. + +## Generated Module Names + +C# schema module names come from the source file stem. They do not come from +`csharp_namespace` and they do not come from gRPC service names. + +For example: + +| Schema input | Model file | Schema module | +| ------------------ | ---------------- | ----------------------- | +| `service.fdl` | `Service.cs` | `ServiceForyModule` | +| `order-events.fdl` | `OrderEvents.cs` | `OrderEventsForyModule` | +| `greeter.fdl` | `Greeter.cs` | `GreeterForyModule` | +| `Greeter.fdl` | `Greeter.cs` | `GreeterForyModule` | + +A gRPC service named `Greeter` still generates the service companion +`GreeterGrpc.cs`; it does not change the schema module name. This lets several +schema files target the same C# namespace without colliding. No +namespace-derived or service-derived module alias is generated. + +## gRPC Runtime Behavior + +The generated service code only replaces request and response serialization. +All normal gRPC operational features still belong to your gRPC stack: + +- Deadlines and cancellations +- TLS and authentication +- Name resolution and load balancing +- Client and server interceptors +- Status codes and metadata +- Channel pooling and lifecycle management + +## Troubleshooting + +### Missing `Grpc.Core` Types + +Add `Grpc.Core.Api` or a server/client package that brings it transitively. +Generated Fory service files import gRPC APIs, but `Apache.Fory` intentionally +does not depend on gRPC. + +### Protobuf Clients Cannot Decode the Service + +Fory gRPC companions do not use protobuf wire encoding for messages. Use a +Fory-generated client for Fory-generated services, or expose a separate protobuf +service endpoint for generic protobuf clients. diff --git a/versioned_docs/version-1.3.0/guide/csharp/index.md b/versioned_docs/version-1.3.0/guide/csharp/index.md new file mode 100644 index 00000000000..e808d7bf1d3 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/csharp/index.md @@ -0,0 +1,106 @@ +--- +title: C# Serialization Guide +sidebar_position: 0 +id: serialization_index +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. +--- + +Apache Fory™ C# is a high-performance, cross-language serialization library for .NET. It provides object graph serialization, schema evolution, generic object payload support, and a thread-safe wrapper for concurrent workloads. + +## Why Fory C#? + +- High performance binary serialization for .NET 8+ +- Xlang compatibility with Fory implementations in Java, Python, C++, Go, Rust, + JavaScript/TypeScript, Swift, Dart, Scala, and Kotlin +- Source-generator-based serializers for `[ForyStruct]` types, plus `[ForyEnum]` and `[ForyUnion]` registration +- Optional reference tracking for shared and circular object graphs +- Compatible mode for schema evolution +- Thread-safe wrapper (`ThreadSafeFory`) for multi-threaded services + +## Quick Start + +### Requirements + +- .NET SDK 8.0+ +- C# language version 12+ + +### Install from NuGet + +Reference the single `Apache.Fory` package. It includes the Fory library and the source generator for `[ForyStruct]`, `[ForyEnum]`, and `[ForyUnion]` types. + +```xml + + + +``` + +### Basic Example + +```csharp +using Apache.Fory; + +[ForyStruct] +public sealed class User +{ + public long Id { get; set; } + public string Name { get; set; } = string.Empty; + public string? Email { get; set; } +} + +Fory fory = Fory.Builder().Build(); +fory.Register(1); + +User user = new() +{ + Id = 1, + Name = "Alice", + Email = "alice@example.com", +}; + +byte[] payload = fory.Serialize(user); +User decoded = fory.Deserialize(payload); +``` + +## Core API Surface + +- `Serialize(in T value)` / `Deserialize(...)` +- `Serialize(...)` / `Deserialize(...)` for dynamic payloads +- `Register(uint typeId)` and name registration APIs +- `Register(...)` for custom serializers + +## Documentation + +| Topic | Description | +| --------------------------------------------- | --------------------------------------------- | +| [Configuration](configuration.md) | Builder options and mode settings | +| [Basic Serialization](basic-serialization.md) | Typed and dynamic serialization APIs | +| [Xlang Serialization](xlang-serialization.md) | Interoperability guidance | +| [Schema Metadata](schema-metadata.md) | `[ForyField]` ids and schema type descriptors | +| [Type Registration](type-registration.md) | Registering user types and custom serializers | +| [Custom Serializers](custom-serializers.md) | Implementing `Serializer` | +| [References](references.md) | Shared/circular reference handling | +| [Schema Evolution](schema-evolution.md) | Compatible mode behavior | +| [Supported Types](supported-types.md) | Built-in and generated type support | +| [Thread Safety](thread-safety.md) | `Fory` vs `ThreadSafeFory` usage | +| [gRPC Support](grpc-support.md) | Generated Fory-backed gRPC service companions | +| [Troubleshooting](troubleshooting.md) | Common errors and debugging steps | + +## Related Resources + +- [Xlang serialization specification](../../specification/xlang_serialization_spec.md) +- [Xlang guide](../xlang/index.md) +- [C# source directory](https://github.com/apache/fory/tree/main/csharp) diff --git a/versioned_docs/version-1.3.0/guide/csharp/references.md b/versioned_docs/version-1.3.0/guide/csharp/references.md new file mode 100644 index 00000000000..772b2535caf --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/csharp/references.md @@ -0,0 +1,72 @@ +--- +title: References +sidebar_position: 7 +id: references +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. +--- + +Apache Fory™ C# can preserve shared and circular references when `TrackRef(true)` is enabled. + +## Enable Reference Tracking + +```csharp +Fory fory = Fory.Builder() + .TrackRef(true) + .Build(); +``` + +When enabled: + +- Shared object identity is preserved. +- Circular object graphs can be serialized/deserialized safely. + +## Circular Reference Example + +```csharp +using Apache.Fory; + +[ForyStruct] +public sealed class Node +{ + public int Value { get; set; } + public Node? Next { get; set; } +} + +Fory fory = Fory.Builder() + .TrackRef(true) + .Build(); +fory.Register(200); + +Node node = new() { Value = 7 }; +node.Next = node; + +byte[] payload = fory.Serialize(node); +Node decoded = fory.Deserialize(payload); + +// The cycle is preserved. +System.Diagnostics.Debug.Assert(object.ReferenceEquals(decoded, decoded.Next)); +``` + +## When to Use `TrackRef(false)` + +`TrackRef(false)` can be faster for tree-like, acyclic data where reference identity does not matter. + +## Related Topics + +- [Configuration](configuration.md) +- [Basic Serialization](basic-serialization.md) +- [Thread Safety](thread-safety.md) diff --git a/versioned_docs/version-1.3.0/guide/csharp/schema-evolution.md b/versioned_docs/version-1.3.0/guide/csharp/schema-evolution.md new file mode 100644 index 00000000000..8143343e153 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/csharp/schema-evolution.md @@ -0,0 +1,99 @@ +--- +title: Schema Evolution +sidebar_position: 8 +id: schema_evolution +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. +--- + +Apache Fory™ C# supports schema evolution in compatible mode. Compatible mode is enabled by default. + +## Compatible Mode + +```csharp +Fory fory = Fory.Builder() + .Build(); +``` + +Compatible mode writes type metadata that allows readers and writers with different struct definitions to interoperate. + +Compatible readers also tolerate selected scalar field type changes when the value is lossless. A +matched field can read between `bool`, `string`, numeric scalars, and `decimal` when the converted +value has the same logical value. Boolean strings must be exactly `"0"`, `"1"`, `"true"`, or +`"false"`. Numeric strings use finite ASCII decimal syntax without whitespace, a leading plus sign, +Unicode digits, underscores, hexadecimal notation, `NaN`, or infinities. Numbers and decimals read as +strings use canonical plain decimal text. Nullable fields still compose with these conversions, but +reference-tracked scalar type changes are incompatible. Invalid strings, out-of-range values, and lossy +numeric conversions fail during deserialization. + +## Example: Add a Field + +```csharp +using Apache.Fory; + +[ForyStruct] +public sealed class OneStringField +{ + public string? F1 { get; set; } +} + +[ForyStruct] +public sealed class TwoStringField +{ + public string F1 { get; set; } = string.Empty; + public string F2 { get; set; } = string.Empty; +} + +Fory fory1 = Fory.Builder().Build(); +fory1.Register(200); + +Fory fory2 = Fory.Builder().Build(); +fory2.Register(200); + +byte[] payload = fory1.Serialize(new OneStringField { F1 = "hello" }); +TwoStringField evolved = fory2.Deserialize(payload); + +// F2 falls back to default value on reader side. +System.Diagnostics.Debug.Assert(evolved.F1 == "hello"); +System.Diagnostics.Debug.Assert(evolved.F2 == string.Empty); +``` + +## Same-Schema Optimization + +Use this only when every reader and writer always uses the same schema and you +want faster serialization and smaller size: + +```csharp +Fory sameSchema = Fory.Builder() + .Compatible(false) + .CheckStructVersion(true) + .Build(); +``` + +Because C# uses the xlang wire format only, use `Compatible(false)` only after verifying that every peer uses the same schema, or when native types are generated from Fory schema IDL. This mode throws on schema hash mismatches. + +## Best Practices + +1. Keep compatible mode enabled for independently deployed services. +2. Keep stable type IDs across versions. +3. Add new fields with safe defaults. +4. Use `CheckStructVersion(true)` with `Compatible(false)` for intentional same-schema payloads. + +## Related Topics + +- [Configuration](configuration.md) +- [Type Registration](type-registration.md) +- [Troubleshooting](troubleshooting.md) diff --git a/versioned_docs/version-1.3.0/guide/csharp/schema-metadata.md b/versioned_docs/version-1.3.0/guide/csharp/schema-metadata.md new file mode 100644 index 00000000000..7c4194d0d6e --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/csharp/schema-metadata.md @@ -0,0 +1,123 @@ +--- +title: Schema Metadata +sidebar_position: 4 +id: schema_metadata +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 page covers schema metadata for C# generated serializers. + +## `[ForyStruct]` and `[ForyField]` + +Use `[ForyStruct]` to enable source-generated serializers. Use `[ForyField]` to assign an optional stable non-negative field id or to override the Fory schema type used for a field. + +```csharp +using Apache.Fory; +using S = Apache.Fory.Schema.Types; + +[ForyStruct] +public sealed class Metrics +{ + [ForyField(Type = typeof(S.UInt32))] + public uint Count { get; set; } + + [ForyField(Type = typeof(S.Tagged))] + public ulong TraceId { get; set; } + + public long LatencyMicros { get; set; } +} +``` + +`Id` is optional. When it is omitted, compatible mode still matches the field by name. + +```csharp +using Apache.Fory; +using S = Apache.Fory.Schema.Types; + +[ForyStruct] +public sealed class NestedMetrics +{ + [ForyField(Type = typeof(S.Map, S.List>>))] + public Dictionary?> Values { get; set; } = []; + + [ForyField(3, Type = typeof(S.UInt64))] + public ulong StableCount { get; set; } +} +``` + +## Schema Descriptor Types + +Schema descriptors live under `Apache.Fory.Schema.Types` and are metadata only. They do not replace normal C# carrier types. + +Common scalar descriptors include: + +- `S.Int32`, `S.UInt32` +- `S.Int64`, `S.UInt64` +- `S.Float16`, `S.BFloat16`, `S.Float32`, `S.Float64` + +Container descriptors are composable: + +- `S.Fixed` and `S.Tagged` for scalar integer encodings +- `S.List` +- `S.Set` +- `S.Map` +- `S.Array` + +Dense array fields use `S.Array`, for example `S.Array` or `S.Array`. + +Nullability comes from the C# carrier type. Use `List` for nullable list elements and `NullableKeyDictionary` when a map needs nullable keys. + +## `[ForyUnion]` and `[ForyCase]` + +Generated union cases use `[ForyCase]` for both the stable case ID and optional +case payload schema type. Do not put `[ForyField]` on union case payload +members. Known case record names use PascalCase FDL case names; payload types +use qualified references when needed to avoid name conflicts. A typed union must +declare at least one non-`Unknown` case; `Unknown(UnknownCase)` is only the +Fory-owned forward-compatibility carrier. The marker only selects the carrier and +does not add an entry to the schema case table. + +```csharp +using Apache.Fory; +using S = Apache.Fory.Schema.Types; + +[ForyUnion] +public abstract partial record Shape +{ + private Shape() {} + + [ForyUnknownCase] + public sealed partial record Unknown(UnknownCase Value) : Shape; + + [ForyCase(0)] + public sealed partial record Circle(global::example.Circle Value) : Shape; + + [ForyCase(1, Type = typeof(S.Fixed))] + public sealed partial record Code(int Value) : Shape; +} +``` + +## Nullability and Reference Tracking + +- Field nullability comes from C# type nullability (`string?`, nullable value types, etc.). +- Reference tracking is configured with `ForyBuilder.TrackRef(...)`. + +## Related Topics + +- [Configuration](configuration.md) +- [Schema Evolution](schema-evolution.md) +- [Supported Types](supported-types.md) diff --git a/versioned_docs/version-1.3.0/guide/csharp/supported-types.md b/versioned_docs/version-1.3.0/guide/csharp/supported-types.md new file mode 100644 index 00000000000..3148694ade8 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/csharp/supported-types.md @@ -0,0 +1,100 @@ +--- +title: Supported Types +sidebar_position: 9 +id: supported_types +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 page summarizes built-in and generated type support in Apache Fory™ C#. + +## Primitive Types + +| C# Type | Notes | +| ---------------------------------------- | --------- | +| `bool` | Supported | +| `sbyte`, `short`, `int`, `long` | Supported | +| `byte`, `ushort`, `uint`, `ulong` | Supported | +| `float`, `double` | Supported | +| `Half`, `BFloat16` | Supported | +| `string` | Supported | +| `byte[]` | Supported | +| Nullable primitives (for example `int?`) | Supported | + +## Arrays + +- Primitive numeric arrays (`bool[]`, `int[]`, `ulong[]`, etc.) +- `Half[]`, `List` with `S.Array` for `array` +- `BFloat16[]`, `List` with `S.Array` for `array` +- `byte[]` +- General arrays (`T[]`) through collection serializers + +## Collections + +### List-like + +- `List` +- `LinkedList` +- `Queue` +- `Stack` + +### Set-like + +- `HashSet` +- `SortedSet` +- `ImmutableHashSet` + +### Map-like + +- `Dictionary` +- `SortedDictionary` +- `SortedList` +- `ConcurrentDictionary` +- `NullableKeyDictionary` + +## Time Types + +| C# Type | Wire Type | +| ---------------- | ----------- | +| `DateOnly` | `Date` | +| `DateTime` | `Timestamp` | +| `DateTimeOffset` | `Timestamp` | +| `TimeSpan` | `Duration` | + +## User Types + +- `[ForyStruct]` classes/structs via source-generated serializers, plus `[ForyEnum]` enums and `[ForyUnion]` ADT records +- Custom serializer types registered through `Register(...)` +- `Union` / `Union2<...>` typed union support + +## Dynamic Types + +Dynamic object payloads via `Serialize` / `Deserialize` support: + +- Primitive/object values +- Dynamic lists/sets/maps +- Nested dynamic structures + +## Notes + +- User-defined types should be registered explicitly. +- For cross-language usage, follow the [xlang guide](../xlang/index.md). + +## Related Topics + +- [Basic Serialization](basic-serialization.md) +- [Type Registration](type-registration.md) +- [Xlang Serialization](xlang-serialization.md) diff --git a/versioned_docs/version-1.3.0/guide/csharp/thread-safety.md b/versioned_docs/version-1.3.0/guide/csharp/thread-safety.md new file mode 100644 index 00000000000..c4d44b17ce3 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/csharp/thread-safety.md @@ -0,0 +1,72 @@ +--- +title: Thread Safety +sidebar_position: 10 +id: thread_safety +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. +--- + +Apache Fory™ C# provides two Fory instance forms with different threading guarantees. + +## `Fory` (Single-Threaded Instance) + +`Fory` is optimized for single-threaded reuse and must not be used concurrently by multiple threads. + +```csharp +Fory fory = Fory.Builder().Build(); +``` + +Use one `Fory` instance per thread when managing thread affinity explicitly. + +## `ThreadSafeFory` (Concurrent Wrapper) + +`ThreadSafeFory` wraps one `Fory` instance per thread and exposes thread-safe APIs. + +```csharp +using Apache.Fory; + +using ThreadSafeFory fory = Fory.Builder() + .TrackRef(true) + .BuildThreadSafe(); + +fory.Register(100); + +Parallel.For(0, 64, i => +{ + byte[] payload = fory.Serialize(i); + int decoded = fory.Deserialize(payload); +}); +``` + +## Registration Behavior + +- `ThreadSafeFory.Register(...)` stores registrations centrally. +- Existing per-thread Fory instances are updated. +- New threads receive all previous registrations automatically. + +## Disposal + +`ThreadSafeFory` implements `IDisposable` and should be disposed when no longer needed. + +```csharp +using ThreadSafeFory fory = Fory.Builder().BuildThreadSafe(); +``` + +## Related Topics + +- [Configuration](configuration.md) +- [Type Registration](type-registration.md) +- [References](references.md) diff --git a/versioned_docs/version-1.3.0/guide/csharp/troubleshooting.md b/versioned_docs/version-1.3.0/guide/csharp/troubleshooting.md new file mode 100644 index 00000000000..e5d6bb6bd65 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/csharp/troubleshooting.md @@ -0,0 +1,120 @@ +--- +title: Troubleshooting +sidebar_position: 13 +id: troubleshooting +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 page covers common C# issues and fixes. + +## `TypeNotRegisteredException` + +**Symptom**: `Type not registered: ...` + +**Cause**: A user type was serialized/deserialized without registration. + +**Fix**: + +```csharp +Fory fory = Fory.Builder().Build(); +fory.Register(100); +``` + +Ensure the same type-ID/name mapping exists on both write and read sides. + +## `InvalidDataException: xlang bitmap mismatch` + +**Cause**: The payload is not an xlang Fory frame, or it came from a peer mode that does +not emit the xlang header C# requires. + +**Fix**: Ensure the payload was produced by an xlang-compatible peer. C# always expects the +xlang header and does not expose a mode switch, so configure the writer instead: + +```java +Fory fory = Fory.builder() + .withXlang(true) + .build(); +``` + +```python +fory = pyfory.Fory(xlang=True) +``` + +## Schema Version Mismatch with Same-Schema Payloads + +**Symptom**: `InvalidDataException` while deserializing generated struct types. + +**Cause**: `Compatible(false)` with `CheckStructVersion(true)` checks schema hashes for intentional +same-schema payloads. + +**Fix options**: + +- Keep compatible mode enabled for schema evolution. +- Use `Compatible(false)` only when every reader and writer always uses the same schema. + +## Circular Reference Failures + +**Symptom**: Stack overflow-like recursion or graph reconstruction issues. + +**Cause**: Cyclic graphs with `TrackRef(false)`. + +**Fix**: + +```csharp +Fory fory = Fory.Builder().TrackRef(true).Build(); +``` + +## Concurrency Issues + +**Cause**: Sharing a single `Fory` instance across threads. + +**Fix**: Use `BuildThreadSafe()`. + +## Generated gRPC Compile Errors + +**Symptom**: Generated `*Grpc.cs` files cannot find `Grpc.Core` types. + +**Cause**: gRPC packages are application dependencies. The `Apache.Fory` +package does not add gRPC as a hard dependency. + +**Fix**: Add `Grpc.Core.Api` and your chosen gRPC server or client package, such +as `Grpc.AspNetCore` for server hosting or `Grpc.Net.Client` for clients. See +[gRPC Support](grpc-support.md). + +## Protobuf Client Cannot Decode a Fory gRPC Service + +**Cause**: Fory gRPC companions use gRPC transports with Fory-encoded message +bodies. They do not send protobuf message bytes. + +**Fix**: Use a Fory-generated client and server for the Fory endpoint, or expose +a separate protobuf endpoint for generic protobuf clients. + +## Validation Commands + +Run C# tests from repo root: + +```bash +cd csharp +dotnet test Fory.sln -c Release +``` + +## Related Topics + +- [Configuration](configuration.md) +- [gRPC Support](grpc-support.md) +- [Schema Evolution](schema-evolution.md) +- [Thread Safety](thread-safety.md) diff --git a/versioned_docs/version-1.3.0/guide/csharp/type-registration.md b/versioned_docs/version-1.3.0/guide/csharp/type-registration.md new file mode 100644 index 00000000000..bd9f54edc7d --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/csharp/type-registration.md @@ -0,0 +1,91 @@ +--- +title: Type Registration +sidebar_position: 5 +id: type_registration +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 page covers how to register user types in Apache Fory™ C#. + +## Register by Numeric Type ID + +Use explicit IDs for compact and stable cross-service mapping. + +```csharp +Fory fory = Fory.Builder().Build(); +fory.Register(100); +fory.Register(101); +``` + +## Register by Type Name + +Use name registration when you prefer symbolic mappings. The single-string overload accepts the +full user-facing name and splits it at the last dot. + +```csharp +Fory fory = Fory.Builder().Build(); +fory.Register("com.example.User"); +``` + +Names without dots use an empty namespace: + +```csharp +fory.Register("User"); +``` + +The split overload is also available when you already have the namespace and final type name +separately: + +```csharp +fory.Register("com.example", "User"); +``` + +## Register a Custom Serializer + +```csharp +Fory fory = Fory.Builder().Build(); +fory.Register(200); +``` + +Name-based custom serializer registration is also supported: + +```csharp +fory.Register("com.example.MyType"); +``` + +## Thread-Safe Registration + +`ThreadSafeFory` exposes the same registration APIs. Registrations are propagated to all per-thread Fory instances. + +```csharp +using ThreadSafeFory fory = Fory.Builder().BuildThreadSafe(); +fory.Register(100); +fory.Register(101); +``` + +## Registration Rules + +- Register user-defined types on both writer and reader sides. +- Keep ID/name mappings consistent across services and languages. +- For the split overloads, `typeName` must be non-empty and must not contain dots. +- Register before high-volume serialization workloads to avoid missing type metadata. + +## Related Topics + +- [Basic Serialization](basic-serialization.md) +- [Custom Serializers](custom-serializers.md) +- [Xlang Serialization](xlang-serialization.md) diff --git a/versioned_docs/version-1.3.0/guide/csharp/xlang-serialization.md b/versioned_docs/version-1.3.0/guide/csharp/xlang-serialization.md new file mode 100644 index 00000000000..bf3c76c1b85 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/csharp/xlang-serialization.md @@ -0,0 +1,129 @@ +--- +title: Xlang Serialization +sidebar_position: 3 +id: xlang_serialization +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. +--- + +Apache Fory™ C# supports xlang serialization with other Fory implementations. + +## Xlang Fory Instance + +C# always writes and reads the xlang frame header. There is no mode switch, so interoperability code +only needs to configure the remaining settings such as compatibility mode and reference +tracking. + +```csharp +Fory fory = Fory.Builder() + .Build(); +``` + +## Register with Stable IDs + +```csharp +[ForyStruct] +public sealed class Person +{ + public string Name { get; set; } = string.Empty; + public int Age { get; set; } +} + +Fory fory = Fory.Builder() + .Build(); + +fory.Register(100); +``` + +Use the same ID mapping on all languages. + +## Register by Name + +```csharp +fory.Register("com.example.Person"); +``` + +## Xlang Example + +### C# (Serializer) + +```csharp +Person person = new() { Name = "Alice", Age = 30 }; +byte[] payload = fory.Serialize(person); +``` + +### Java (Deserializer) + +```java +Fory fory = Fory.builder() + .withXlang(true) + .withRefTracking(true) + .build(); + +fory.register(Person.class, 100); +Person value = (Person) fory.deserialize(payloadFromCSharp); +``` + +### Python (Deserializer) + +```python +import pyfory + +fory = pyfory.Fory(xlang=True, ref=True) +fory.register_type(Person, type_id=100) +value = fory.deserialize(payload_from_csharp) +``` + +## Type Mapping Reference + +See [xlang guide](../xlang/index.md) for complete mapping. + +For reduced-precision numeric payloads, use `Half` / `Half[]` or `List` for xlang `float16`, and `BFloat16` / `BFloat16[]` or `List` for xlang `bfloat16`. + +## Lists and Dense Arrays + +C# `List` maps to Fory `list`. Use the schema marker +`Apache.Fory.Schema.Types.Array` when a field is dense `array`. + +| Fory schema | C# schema marker sketch | +| ----------------- | ----------------------- | +| `list` | `S.List` | +| `array` | `S.Array` | +| `array` | `S.Array` | +| `array` | `S.Array` | +| `array` | `S.Array` | +| `array` | `S.Array` | +| `array` | `S.Array` | +| `array` | `S.Array` | +| `array` | `S.Array` | +| `array` | `S.Array` | +| `array` | `S.Array` | +| `array` | `S.Array` | +| `array` | `S.Array` | +| `array` | `S.Array` | + +## Best Practices + +1. Keep type IDs stable and documented. +2. Keep compatible mode enabled for rolling upgrades. +3. Register all user types on both read/write peers. +4. Validate integration with real payload round trips. + +## Related Topics + +- [Type Registration](type-registration.md) +- [Schema Evolution](schema-evolution.md) +- [Supported Types](supported-types.md) diff --git a/versioned_docs/version-1.3.0/guide/dart/_category_.json b/versioned_docs/version-1.3.0/guide/dart/_category_.json new file mode 100644 index 00000000000..b2d06cfcd25 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/dart/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "Dart", + "position": 9, + "collapsible": true, + "collapsed": true +} diff --git a/versioned_docs/version-1.3.0/guide/dart/basic-serialization.md b/versioned_docs/version-1.3.0/guide/dart/basic-serialization.md new file mode 100644 index 00000000000..78f48b88546 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/dart/basic-serialization.md @@ -0,0 +1,145 @@ +--- +title: Basic Serialization +sidebar_position: 1 +id: basic_serialization +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 page shows how to serialize and deserialize values with Apache Fory™ Dart. + +## Create a `Fory` Instance + +Create one instance and reuse it — creating a new `Fory` for every call wastes resources. + +```dart +import 'package:fory/fory.dart'; + +final fory = Fory(); +``` + +## Serialize and Deserialize Annotated Types + +```dart +import 'package:fory/fory.dart'; + +part 'person.fory.dart'; + +@ForyStruct() +class Person { + Person(); + + String name = ''; + + @ForyField(type: Int32Type()) + int age = 0; +} + +void main() { + final fory = Fory(); + PersonForyModule.register( + fory, + Person, + name: 'example.Person', + ); + + final person = Person() + ..name = 'Ada' + ..age = 36; + + final bytes = fory.serialize(person); + final roundTrip = fory.deserialize(bytes); + print(roundTrip.name); +} +``` + +`deserialize` returns the decoded value cast to `T`. If the payload describes a different type than `T`, it throws. + +## Null Values + +Serializing `null` is supported directly: + +```dart +final fory = Fory(); +final bytes = fory.serialize(null); +final value = fory.deserialize(bytes); +``` + +## Serialize Collections and Dynamic Payloads + +You can serialize collection values directly: + +```dart +final fory = Fory(); +final bytes = fory.serialize([ + 'hello', + 42, + true, +]); +final value = fory.deserialize>(bytes); +``` + +For heterogeneous collections, deserialize to `Object?`, `List`, or `Map`. + +## Reference Tracking + +By default, Fory does not track object identity — if the same object appears twice in a list, it is serialized twice. Enable reference tracking when your data contains shared references or circular structures. + +For a top-level collection: + +```dart +final fory = Fory(); +final shared = String.fromCharCodes('shared'.codeUnits); +final bytes = fory.serialize([shared, shared], trackRef: true); +final roundTrip = fory.deserialize>(bytes); +print(identical(roundTrip[0], roundTrip[1])); // true +``` + +For fields inside a generated struct, use `@ForyField(ref: true)` on that field instead. + +## Reusing a Buffer + +If you want to avoid allocating a new `Uint8List` on every call, use `serializeTo` and `deserializeFrom` with an explicit `Buffer`: + +```dart +final fory = Fory(); +final buffer = Buffer(); + +fory.serializeTo('Ada', buffer); +final value = fory.deserializeFrom(buffer); +``` + +This is an optimization. For most applications the default `serialize`/`deserialize` pair is fine. + +## Register Your Types Before Serializing + +Before you can serialize a custom class or enum, register it with `Fory`. The generated code makes this easy: + +```dart +PersonForyModule.register( + fory, + Person, + id: 100, +); +``` + +If you skip registration, deserialization fails with `Type ... is not registered`. See [Type Registration](type-registration.md) and [Code Generation](code-generation.md). + +## Related Topics + +- [Configuration](configuration.md) +- [Type Registration](type-registration.md) +- [Schema Metadata](schema-metadata.md) diff --git a/versioned_docs/version-1.3.0/guide/dart/code-generation.md b/versioned_docs/version-1.3.0/guide/dart/code-generation.md new file mode 100644 index 00000000000..09d8ee7df93 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/dart/code-generation.md @@ -0,0 +1,114 @@ +--- +title: Code Generation +sidebar_position: 3 +id: code_generation +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. +--- + +Fory generates fast serializer code for your Dart classes at build time. You annotate your models, run `build_runner`, and Fory takes care of the rest. + +## Step 1 — Annotate Your Models + +Add `@ForyStruct()` to each class you want to serialize. Include the generated part directive at the top of the file. + +```dart +import 'package:fory/fory.dart'; + +part 'models.fory.dart'; + +@ForyStruct() +class Address { + Address(); + + String city = ''; + String street = ''; +} + +@ForyStruct() +class User { + User(); + + String name = ''; + + @ForyField(type: Int32Type()) + int age = 0; + Address address = Address(); +} +``` + +Enums defined in the same file are automatically included in the generated registration. + +## Step 2 — Run the Generator + +From the directory that contains your `pubspec.yaml`: + +```bash +dart run build_runner build --delete-conflicting-outputs +``` + +This emits a `.fory.dart` file next to your source file. Re-run this command any time you add or rename annotated types. + +## Step 3 — Register and Use + +The generator creates a Fory module class (named after your file) with a `register` function. Call it before serializing: + +```dart +final fory = Fory(); +ModelsForyModule.register(fory, Address, id: 1); +ModelsForyModule.register(fory, User, id: 2); +``` + +Or use a stable name instead of a numeric ID (useful for cross-language scenarios): + +```dart +ModelsForyModule.register( + fory, + User, + name: 'example.User', +); +``` + +See [Type Registration](type-registration.md) for guidance on choosing between IDs and names. + +## Schema Evolution: `evolving` + +`@ForyStruct()` defaults to `evolving: true`, which is the right choice for most applications. + +- `evolving: true` — Fory stores enough metadata so that if you add or remove fields later, old and new code can still exchange messages. Enable this whenever different versions of your app or service may be running at the same time. +- `evolving: false` — Faster serialization and smaller size. Use only when every reader and writer always uses the same struct schema. + +```dart +// evolving: true is the default, you can omit it +@ForyStruct(evolving: true) +class Event { + Event(); + + String name = ''; +} +``` + +When using evolving structs, also assign stable field IDs with `@ForyField(id: ...)` before you ship your first payload — those IDs are how Fory matches fields after a schema change. + +## When Not to Use Code Generation + +If you cannot annotate a type (e.g., it comes from a package you do not own), write a [Custom Serializer](custom-serializers.md) instead. + +## Related Topics + +- [Type Registration](type-registration.md) +- [Schema Metadata](schema-metadata.md) +- [Schema Evolution](schema-evolution.md) diff --git a/versioned_docs/version-1.3.0/guide/dart/configuration.md b/versioned_docs/version-1.3.0/guide/dart/configuration.md new file mode 100644 index 00000000000..6a4c640f6ae --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/dart/configuration.md @@ -0,0 +1,145 @@ +--- +title: Configuration +sidebar_position: 2 +id: configuration +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 page explains the `Fory` constructor options. + +## Creating a `Fory` Instance + +Pass options directly to the constructor: + +```dart +import 'package:fory/fory.dart'; + +// defaults: xlang wire format with compatible schema evolution +final fory = Fory(); + +// customize limits while keeping default compatible mode +final fory = Fory( + maxDepth: 512, + maxTypeFields: 512, + maxTypeMetaBytes: 4096, + maxSchemaVersionsPerType: 10, + maxAverageSchemaVersionsPerType: 3, +); +``` + +Create one instance per application and reuse it; there is no benefit to creating a new `Fory` per request. + +## Options + +### `compatible` + +Compatible mode is enabled by default. Keep it enabled when your service needs to handle payloads +from different versions of the same model, for example during rolling deployments or client/server +version skew. + +When `compatible: true`: + +- Adding or removing fields on one side does not break the other. +- Peers must still use the same `name` (or numeric `id`) to identify types. + +When `compatible: false`: + +- Both sides must have exactly the same schema. Use this only when every reader and writer always + uses that schema and you want faster serialization and smaller size. For cross-language payloads, set `compatible: false` only after verifying that every language uses the same schema, or when native types are generated from Fory schema IDL. + +```dart +final fory = Fory(compatible: false); +``` + +### `checkStructVersion` + +Relevant only when `compatible: false`. When `true`, Fory validates that the schema version in the payload matches the one the receiver knows about, catching accidental mismatches for intentional same-schema payloads. + +```dart +final fory = Fory( + compatible: false, + checkStructVersion: true, // default +); +``` + +This option has no effect when `compatible: true`. + +### `maxDepth` + +Limits how deeply nested an object graph can be. Increase this if you have legitimately deep trees; lower it to reject unexpectedly deep payloads fast. + +```dart +final fory = Fory(maxDepth: 128); +``` + +### Remote schema metadata limits + +Compatible mode can receive remote metadata for schema evolution. These limits +bound metadata size and accepted schema versions: + +```dart +final fory = Fory( + maxTypeFields: 512, + maxTypeMetaBytes: 4096, + maxSchemaVersionsPerType: 10, + maxAverageSchemaVersionsPerType: 3, +); +``` + +- `maxTypeFields` limits fields in one received struct metadata body. +- `maxTypeMetaBytes` limits encoded body bytes in one received TypeMeta body, excluding the 8-byte + header and any extended-size varint. +- `maxSchemaVersionsPerType` limits accepted remote metadata versions for one logical type. +- `maxAverageSchemaVersionsPerType` limits the average across accepted remote types. The + effective global floor is `8192` schemas. + +## Defaults + +| Option | Default | +| --------------------------------- | ------- | +| `compatible` | `true` | +| `checkStructVersion` | `false` | +| `maxDepth` | 256 | +| `maxTypeFields` | 512 | +| `maxTypeMetaBytes` | 4096 | +| `maxSchemaVersionsPerType` | 10 | +| `maxAverageSchemaVersionsPerType` | 3 | + +## Xlang Notes + +When Fory is used to communicate between services written in different languages: + +- Keep compatible mode enabled on all sides if any side needs schema evolution. +- Use the same numeric IDs or `name` values on every side. +- Match the `compatible` setting on both the writing and reading side — mismatching modes will fail. + +## Security + +Security-related configuration: + +- Register only the expected generated models before deserializing untrusted payloads. +- Use `checkStructVersion: true` with `compatible: false` for intentional same-schema payloads. +- Set `maxDepth` to reject unexpectedly deep payload shapes. +- Keep the remote schema metadata limits at their defaults unless the data is not malicious and a + trusted peer sends larger metadata or many schema versions. +- Prefer generated schemas and explicit field metadata over broad dynamic fields for untrusted input. + +## Related Topics + +- [Basic Serialization](basic-serialization.md) +- [Schema Evolution](schema-evolution.md) +- [Xlang Serialization](xlang-serialization.md) diff --git a/versioned_docs/version-1.3.0/guide/dart/custom-serializers.md b/versioned_docs/version-1.3.0/guide/dart/custom-serializers.md new file mode 100644 index 00000000000..4e767364112 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/dart/custom-serializers.md @@ -0,0 +1,138 @@ +--- +title: Custom Serializers +sidebar_position: 9 +id: custom_serializers +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. +--- + +A custom serializer lets you control exactly how a type is encoded and decoded. You only need one when: + +- the type comes from a package you cannot modify and cannot be annotated with `@ForyStruct()` +- you need a completely custom binary layout +- you are implementing a union/discriminated type + +For your own models, `@ForyStruct()` with code generation is almost always the better choice. + +## Implement `Serializer` + +Subclass `Serializer` and implement `write` and `read`. Use `context.buffer` to read and write raw bytes: + +```dart +import 'package:fory/fory.dart'; + +final class Person { + Person(this.name, this.age); + + final String name; + final int age; +} + +final class PersonSerializer extends Serializer { + const PersonSerializer(); + + @override + void write(WriteContext context, Person value) { + final buffer = context.buffer; + buffer.writeUtf8(value.name); + buffer.writeInt64FromInt(value.age); + } + + @override + Person read(ReadContext context) { + final buffer = context.buffer; + return Person(buffer.readUtf8(), buffer.readInt64AsInt()); + } +} +``` + +Register the serializer before you use it: + +```dart +final fory = Fory(); +fory.registerSerializer( + Person, + const PersonSerializer(), + name: 'example.Person', +); +``` + +## Writing Nested Objects + +When your serializer has a field that is itself a Fory-managed type, use `context.writeRef` and `context.readRef` rather than calling `fory.serialize` recursively. This keeps reference tracking correct and avoids writing a full root frame inside a nested payload. + +```dart +@override +void write(WriteContext context, Wrapper value) { + context.writeRef(value.child); +} + +@override +Wrapper read(ReadContext context) { + return Wrapper(context.readRef() as Child); +} +``` + +If you do not need reference identity tracking for a nested value (i.e., you know the value will never appear more than once in a graph), use `writeNonRef`: + +```dart +context.writeNonRef(value.child); +``` + +## Unions + +For a discriminated/tagged union, extend `UnionSerializer` instead of `Serializer`. Write a discriminant value first, then the active variant; read the discriminant and dispatch accordingly. + +```dart +final class ShapeSerializer extends UnionSerializer { + const ShapeSerializer(); + + @override + void write(WriteContext context, Shape value) { + // write active variant + } + + @override + Shape read(ReadContext context) { + // read discriminant, return correct variant + throw UnimplementedError(); + } +} +``` + +## Circular References in Custom Serializers + +If your serializer can encounter circular object graphs, bind the object to the reference tracker **before** reading its nested fields: + +```dart +final value = Node.empty(); +context.reference(value); // register the object first +value.next = context.readRef() as Node?; // now nested reads can refer back to it +return value; +``` + +Skipping this step causes back-references to that object to resolve to `null`. + +## Tips + +- Use `context.buffer` for direct byte reads/writes in hot paths. +- Register the serializer with the same identity (`id` or `name`) on every side. + +## Related Topics + +- [Type Registration](type-registration.md) +- [Xlang Serialization](xlang-serialization.md) +- [Troubleshooting](troubleshooting.md) diff --git a/versioned_docs/version-1.3.0/guide/dart/grpc-support.md b/versioned_docs/version-1.3.0/guide/dart/grpc-support.md new file mode 100644 index 00000000000..c8eb6a3b4ab --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/dart/grpc-support.md @@ -0,0 +1,292 @@ +--- +title: gRPC Support +sidebar_position: 12 +id: grpc_support +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. +--- + +Fory can generate Dart gRPC service companions for schemas that define services. +The generated code uses normal `package:grpc` clients, service bases, method +descriptors, call options, deadlines, cancellations, and status codes, while +request and response objects are serialized with Fory instead of protobuf. + +Use this mode when both RPC peers are generated from the same Fory IDL, protobuf +IDL, or FlatBuffers IDL and both sides expect Fory-encoded message bodies. Use +normal protobuf gRPC generation for APIs that must be consumed by generic +protobuf clients, reflection tools, or components that expect protobuf message +bytes. + +## Add Dependencies + +The `fory` package does not add gRPC dependencies. Add `grpc` (and the +`build_runner` dev dependency that generates the Fory serializer code) in the +application that compiles or runs generated service companions: + +```yaml +dependencies: + fory: ^1.3.0 + grpc: ^4.0.0 + +dev_dependencies: + build_runner: ^2.4.0 +``` + +The same dependencies cover both client and server applications. + +## Define a Service + +Service definitions can come from Fory IDL, protobuf IDL, or FlatBuffers +`rpc_service` definitions. A Fory IDL service looks like this: + +```protobuf +package demo.greeter; + +message HelloRequest { + string name = 1; +} + +message HelloReply { + string reply = 1; +} + +service Greeter { + rpc SayHello (HelloRequest) returns (HelloReply); +} +``` + +Generate Dart model and gRPC companion code with `--grpc`: + +```bash +foryc service.fdl --dart_out=./lib/generated --grpc +``` + +Then run `build_runner` once to emit the Fory serializer part file for the +generated models (this step is required before the code can run): + +```bash +dart run build_runner build --delete-conflicting-outputs +``` + +For this schema, the Dart generator emits (the model file and module are named +from the package leaf, `greeter`): + +| File | Purpose | +| ------------------------------------------- | ---------------------------------------------------- | +| `demo/greeter/greeter.dart` | Fory model types and the schema module | +| `demo/greeter/greeter.fory.dart` | Serializers and registration (built by build_runner) | +| `demo/greeter/greeter_grpc.dart` | gRPC client, service base, and method descriptors | +| `GreeterForyModule` in `greeter.dart` | Fory registration module for generated types | +| `GreeterServiceBase` in `greeter_grpc.dart` | Base class for server implementations | +| `GreeterClient` in `greeter_grpc.dart` | Client stub for gRPC calls | + +The generated client and service base obtain a ready `Fory` automatically and +register the schema's types on first use, so no manual registration step is +required. To share a custom `Fory` (for example one configured with extra +modules), call `GreeterForyModule.install(yourFory)` once before the first RPC; +this is optional. + +## Implement a Server + +Extend the generated `GreeterServiceBase` and host it with grpc-dart's `Server`: + +```dart +import 'dart:io'; + +import 'package:grpc/grpc.dart'; +import 'demo/greeter/greeter.dart'; +import 'demo/greeter/greeter_grpc.dart'; + +class GreeterService extends GreeterServiceBase { + @override + Future sayHello(ServiceCall call, HelloRequest request) async { + final reply = HelloReply()..reply = 'Hello, ${request.name}'; + return reply; + } +} + +Future main() async { + final server = Server.create(services: [GreeterService()]); + await server.serve(address: InternetAddress.loopbackIPv4, port: 50051); +} +``` + +## Create a Client + +Use the generated client with a `ClientChannel`: + +```dart +import 'package:grpc/grpc.dart'; +import 'demo/greeter/greeter.dart'; +import 'demo/greeter/greeter_grpc.dart'; + +Future main() async { + final channel = ClientChannel( + 'localhost', + port: 50051, + options: const ChannelOptions( + credentials: ChannelCredentials.insecure(), + ), + ); + final client = GreeterClient(channel); + + final reply = await client.sayHello(HelloRequest()..name = 'Fory'); + print(reply.reply); + + await channel.shutdown(); +} +``` + +## Streaming RPCs + +Fory service definitions can use the same gRPC streaming shapes: + +```protobuf +service Greeter { + rpc SayHello (HelloRequest) returns (HelloReply); + rpc LotsOfReplies (HelloRequest) returns (stream HelloReply); + rpc LotsOfGreetings (stream HelloRequest) returns (HelloReply); + rpc Chat (stream HelloRequest) returns (stream HelloReply); +} +``` + +Generated Dart methods follow grpc-dart conventions. Single responses return a +`ResponseFuture` (client-streaming adapts the call with `.single`); streaming +responses return a `ResponseStream`. On the server, single requests arrive as +the message type and streaming requests as a `Stream`; the method returns a +`Future` for single responses and a `Stream` for streaming responses: + +| IDL shape | Client method | Server method (override) | +| ----------------------------------------- | ---------------------------------------------------- | ------------------------------------------------------ | +| `rpc A (Req) returns (Res)` | `ResponseFuture a(Req request, {CallOptions?})` | `Future a(ServiceCall call, Req request)` | +| `rpc A (Req) returns (stream Res)` | `ResponseStream a(Req request, {CallOptions?})` | `Stream a(ServiceCall call, Req request)` | +| `rpc A (stream Req) returns (Res)` | `ResponseFuture a(Stream request, {...})` | `Future a(ServiceCall call, Stream request)` | +| `rpc A (stream Req) returns (stream Res)` | `ResponseStream a(Stream request, {...})` | `Stream a(ServiceCall call, Stream request)` | + +Server implementations use the generated streaming method shapes directly: + +```dart +class GreeterService extends GreeterServiceBase { + @override + Stream lotsOfReplies( + ServiceCall call, + HelloRequest request, + ) async* { + for (final greeting in ['Hello, ${request.name}', 'Welcome, ${request.name}']) { + yield HelloReply()..reply = greeting; + } + } + + @override + Future lotsOfGreetings( + ServiceCall call, + Stream request, + ) async { + final names = []; + await for (final message in request) { + names.add(message.name); + } + return HelloReply()..reply = names.join(', '); + } + + @override + Stream chat( + ServiceCall call, + Stream request, + ) async* { + await for (final message in request) { + yield HelloReply()..reply = 'Hello, ${message.name}'; + } + } +} +``` + +Generated clients return the standard grpc-dart call objects: + +```dart +// Server streaming. +await for (final reply in client.lotsOfReplies(HelloRequest()..name = 'Fory')) { + print(reply.reply); +} + +// Client streaming. +final summary = await client.lotsOfGreetings( + Stream.fromIterable([ + HelloRequest()..name = 'Ada', + HelloRequest()..name = 'Grace', + ]), +); +print(summary.reply); + +// Bidirectional streaming. +await for (final reply in client.chat( + Stream.fromIterable([HelloRequest()..name = 'Fory']), +)) { + print(reply.reply); +} +``` + +The generated descriptors preserve the exact IDL service and method names for +the gRPC path, while the Dart methods use camelCase names. + +## Generated Module Names + +Dart model files and schema modules are named after the package's last segment, +not the gRPC service name. (When a schema has no package, the source file stem is +used instead.) + +| Schema input (package) | Model file | Schema module | +| ------------------------------- | ------------------- | ----------------------- | +| `service.fdl` (`demo.greeter`) | `greeter.dart` | `GreeterForyModule` | +| `api.fdl` (`demo.order_events`) | `order_events.dart` | `OrderEventsForyModule` | +| `greeter.fdl` (`demo.greeter`) | `greeter.dart` | `GreeterForyModule` | + +A gRPC service named `Greeter` still generates the companion +`_grpc.dart` with `GreeterClient` and `GreeterServiceBase`; it does not +change the schema module name. If several schema files use the same package +leaf, place them in distinct output directories or choose package/file names that +produce distinct Dart model files. + +## Operations + +The generated service code only replaces request and response serialization. +All normal gRPC operational features still belong to your gRPC stack: + +- Deadlines and cancellations +- TLS and authentication +- Name resolution and load balancing +- Client and server interceptors +- Status codes and metadata +- Channel lifecycle management + +## Troubleshooting + +### Missing `package:grpc` Types + +Add `grpc` to your application dependencies. Generated Fory service files import +grpc-dart APIs, but `fory` intentionally does not depend on gRPC. + +### Generated Code References a Missing `.fory.dart` Part + +Run `dart run build_runner build --delete-conflicting-outputs` after generating +or regenerating the Dart sources. The serializer part file is produced by +`build_runner`, not by `foryc`. + +### Protobuf Clients Cannot Decode the Service + +Fory gRPC companions do not use protobuf wire encoding for messages. Use a +Fory-generated client for Fory-generated services, or expose a separate protobuf +service endpoint for generic protobuf clients. diff --git a/versioned_docs/version-1.3.0/guide/dart/index.md b/versioned_docs/version-1.3.0/guide/dart/index.md new file mode 100644 index 00000000000..19bd363d1a9 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/dart/index.md @@ -0,0 +1,149 @@ +--- +title: Dart Serialization Guide +sidebar_position: 0 +id: serialization_index +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. +--- + +Apache Fory™ Dart lets you serialize Dart objects to bytes and deserialize them +back, including across services written in Java, Python, C++, Go, Rust, +JavaScript/TypeScript, C#, Swift, Scala, Kotlin, and other Fory-supported +languages. + +## Why Fory Dart? + +- **Xlang**: serialize in Dart, deserialize in Java, Go, C#, and more without writing any glue code +- **Platform support**: use the same generated-serializer API on Dart VM/AOT, Flutter, and web +- **Fast**: generated serializer code replaces reflection during serialization +- **Schema evolution**: add or remove fields without breaking existing messages +- **Circular references**: optional reference tracking handles shared or recursive object graphs +- **Escape hatch**: write a manual serializer for any type that cannot be annotated + +## Quick Start + +### Requirements + +- Dart SDK 3.6 or later +- `build_runner` (generates the serializer code) + +### Install + +Add the dependency to your `pubspec.yaml`: + +```yaml +dependencies: + fory: ^1.3.0 + +dev_dependencies: + build_runner: ^2.4.0 +``` + +### Basic Example + +Define your model, run the generator once, then serialize: + +```dart +import 'package:fory/fory.dart'; + +part 'person.fory.dart'; + +enum Color { + red, + blue, +} + +@ForyStruct() +class Person { + Person(); + + String name = ''; + + @ForyField(type: Int32Type()) + int age = 0; + Color favoriteColor = Color.red; + List tags = []; +} + +void main() { + final fory = Fory(); + PersonForyModule.register( + fory, + Color, + name: 'example.Color', + ); + PersonForyModule.register( + fory, + Person, + name: 'example.Person', + ); + + final person = Person() + ..name = 'Ada' + ..age = 36 + ..favoriteColor = Color.blue + ..tags = ['engineer', 'mathematician']; + + final bytes = fory.serialize(person); + final roundTrip = fory.deserialize(bytes); + print(roundTrip.name); +} +``` + +Generate the companion file before running the program: + +```bash +dart run build_runner build --delete-conflicting-outputs +``` + +`PersonForyModule` is generated by `build_runner`. The `name` value is how peers in other languages identify the same type; keep it stable once your service is in production. Use `.` inside `name` to add a namespace prefix. + +## API Overview + +- `Fory(...)` — create a serializer instance; create once and reuse it +- `fory.serialize(value)` — returns `Uint8List` bytes +- `fory.deserialize(bytes)` — returns a `T` +- `@ForyStruct()` — marks a class for code generation +- `@ForyField(...)` — per-field options and canonical `type:` overrides +- `@ListField(...)`, `@SetField(...)`, `@MapField(...)` — container sugar for nested `type:` trees +- Exact-value wrappers: `Int64`, `Uint64`, `Float32` +- Reduced-precision scalar fields: `double` with `Float16Type` or `Bfloat16Type` +- 16-bit float arrays: `Float16List`, `Bfloat16List` +- Time types: `LocalDate`, `Timestamp`, `Duration` + +## Documentation + +| Topic | Description | +| ----------------------------------------------- | --------------------------------------------------------------- | +| [Configuration](configuration.md) | Fory options, compatible mode, and safety limits | +| [Basic Serialization](basic-serialization.md) | `serialize`, `deserialize`, generated registration, root graphs | +| [Code Generation](code-generation.md) | `@ForyStruct`, build runner, and generated modules | +| [Xlang Serialization](xlang-serialization.md) | Interoperability rules and field alignment | +| [Schema Metadata](schema-metadata.md) | `@ForyField`, field IDs, nullability, references, polymorphism | +| [Type Registration](type-registration.md) | ID-based vs name-based registration and registration rules | +| [Custom Serializers](custom-serializers.md) | Manual `Serializer` implementations and unions | +| [Supported Types](supported-types.md) | Built-in xlang values, wrappers, collections, and structs | +| [Schema Evolution](schema-evolution.md) | Compatible structs and evolving schemas | +| [Web Platform Support](web-platform-support.md) | Dart VM/AOT, Flutter, and web support, limits, and validation | +| [gRPC Support](grpc-support.md) | Generated Fory-backed gRPC service companions | +| [Troubleshooting](troubleshooting.md) | Common errors, diagnostics, and validation steps | + +## Related Resources + +- [Xlang serialization specification](../../specification/xlang_serialization_spec.md) +- [Xlang implementation guide](../../specification/xlang_implementation_guide.md) +- [Xlang guide](../xlang/index.md) +- [Dart implementation source directory](https://github.com/apache/fory/tree/main/dart) diff --git a/versioned_docs/version-1.3.0/guide/dart/schema-evolution.md b/versioned_docs/version-1.3.0/guide/dart/schema-evolution.md new file mode 100644 index 00000000000..46410de5a2b --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/dart/schema-evolution.md @@ -0,0 +1,105 @@ +--- +title: Schema Evolution +sidebar_position: 8 +id: schema_evolution +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. +--- + +Schema evolution lets different versions of your app exchange messages safely — a v2 writer can produce a message that a v1 reader can still decode, and vice versa. + +## Compatible Mode + +Compatible mode is the Dart default. Keep this default when services may run different versions at +the same time, for example during a rolling deployment or when clients are not updated immediately. + +```dart +final fory = Fory(); +``` + +In compatible mode, Fory includes enough field metadata in each message so that the reader can skip unknown fields and use defaults for missing ones. Use stable field IDs (see below) to anchor the schema across changes. + +Compatible readers also tolerate selected scalar field type changes when the value is lossless. A +matched field can read between `bool`, `String`, numeric scalars, and `Decimal` when the converted +value has the same logical value. For example, `"true"` and `"false"` can be read as booleans, +`"123"` can be read as a numeric field that can hold `123`, numbers and decimals can be read as +canonical strings, and numeric widening or narrowing succeeds only when no precision or range is +lost. Scalar conversion applies only to matched compatible fields, not root values or collection +elements. String-to-number conversion accepts finite ASCII decimal literals without whitespace, a +leading `+`, Unicode digits, underscores, `NaN`, or `Infinity`. Nullable fields still compose with +these conversions, but reference-tracked scalar type changes are incompatible. Invalid strings, +out-of-range values, and lossy conversions fail with `InvalidDataException` during deserialization. + +## Setting Up for Evolution + +To use compatible mode safely, mark your structs with `@ForyStruct(evolving: true)` (the default) and assign a stable `@ForyField(id: ...)` to every field **before you ship your first payload**: + +```dart +@ForyStruct(evolving: true) +class UserProfile { + UserProfile(); + + @ForyField(id: 1) + String name = ''; + + @ForyField(id: 2, nullable: true) + String? nickname; +} +``` + +If you add field IDs after payloads are already in production, existing stored messages won't have them and evolution won't work correctly. + +## What You Can Safely Change + +**Safe changes** (compatible on both sides): + +- Add a new optional field with a new, unused field ID. +- Rename a field — as long as the `@ForyField(id: ...)` stays the same. +- Remove a field — the peer will just ignore the missing value and use the Dart default. +- Change selected scalar field types when all deployed values convert without precision or range + loss. + +**Unsafe changes** (may break existing messages): + +- Reuse an existing field ID for a different field. +- Change a field's type to an incompatible type or to a scalar type that cannot represent the peer + values exactly. +- Change the registration identity (`id` or `name`) of a type after messages are in production. +- Change a field's logical meaning without changing its ID. + +## Xlang Notes + +Evolution only works when **all** peers that exchange messages agree on: + +1. The same `compatible` setting. +2. The same type registration identity (numeric ID or `name`). +3. The logical meaning of field IDs. + +Test rolling-upgrade scenarios with real round trips before deploying. + +## Same-Schema Optimization + +Use `compatible: false` only when every reader and writer always uses the same schema and you want faster serialization and smaller size. For xlang payloads, set `compatible: false` only after verifying that every language uses the same schema, or when native types are generated from Fory schema IDL. + +```dart +final fory = Fory(compatible: false); +``` + +## Related Topics + +- [Configuration](configuration.md) +- [Schema Metadata](schema-metadata.md) +- [Xlang Serialization](xlang-serialization.md) diff --git a/versioned_docs/version-1.3.0/guide/dart/schema-metadata.md b/versioned_docs/version-1.3.0/guide/dart/schema-metadata.md new file mode 100644 index 00000000000..b24597ddbb1 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/dart/schema-metadata.md @@ -0,0 +1,145 @@ +--- +title: Schema Metadata +sidebar_position: 5 +id: schema_metadata +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. +--- + +Add `@ForyField(...)` to a field inside a `@ForyStruct()` class to change how that field is serialized. + +## Quick Reference + +```dart +@ForyField( + skip: false, // include the field; set true to exclude it + id: 10, // stable field ID for schema evolution + nullable: true, // override nullability detection + ref: true, // enable reference tracking for this field + dynamic: false, // control whether the concrete type is written +) +``` + +## `skip` + +Exclude a field from serialization entirely. Useful for cached, computed, or UI-only values that should not land in a persisted or transmitted message. + +```dart +@ForyField(skip: true) +String cachedDisplayName = ''; +``` + +## `id` + +Assigns a stable identity to the field so that Fory can match it by ID after a schema change (a field rename or reorder). **If you plan to add, remove, or rename fields in the future, assign IDs to all fields now** — before you ship the first payload. + +```dart +@ForyField(id: 1) +String name = ''; +``` + +Once a payload is shared across services, never reuse an `id` for a different field. + +## `nullable` + +Explicitly marks a field as nullable or non-nullable, overriding what Fory infers from the Dart type. Use this when the Dart type is non-nullable but you want Fory to accept `null` on the wire (e.g., reading messages from an older producer that can omit the field). + +```dart +@ForyField(nullable: true) +String nickname = ''; +``` + +In cross-language scenarios, make sure the nullability contract also matches what peer languages expect. + +## `ref` + +Enables reference tracking for a specific field. Use this when multiple objects in the graph can point to the same instance, or when the field type can be circular. Without `ref: true`, Fory serializes the same object value twice if it appears in two fields. + +```dart +@ForyField(ref: true) +List sharedNodes = []; +``` + +Note: scalar types like `int`, `double`, and `bool` never benefit from reference tracking even if `ref: true` is set. + +## `dynamic` + +Controls whether Fory writes the concrete type of the field value into the payload. + +- `null` (default) — Fory decides automatically based on the declared type. +- `false` — always use the declared field type; more compact but the deserializer must know the exact type. +- `true` — always write the actual concrete type; needed when the field is declared as `Object?` or a base class but can hold different concrete types (polymorphism). + +```dart +@ForyField(dynamic: true) +Object? payload; // can hold any registered type +``` + +## Numeric Field Types + +Dart `int` stores a 64-bit value. When exchanging messages with Java, Go, or C#, the receiving side may expect a narrower integer. Use `@ForyField(type: ...)` to pin the exact wire format: + +```dart +@ForyStruct() +class Sample { + Sample(); + + @ForyField(type: Int32Type(encoding: Encoding.fixed)) + int fixedWidthInt = 0; + + @ForyField(type: Int64Type(encoding: Encoding.tagged)) + Int64 compactLong = Int64(0); + + @ForyField(type: Uint32Type()) + int smallUnsigned = 0; +} +``` + +Available scalar type nodes include `Int8Type`, `Int16Type`, `Int32Type`, +`Int64Type`, `Uint8Type`, `Uint16Type`, `Uint32Type`, `Uint64Type`, +`Float16Type`, `Bfloat16Type`, and `Float32Type`. + +For nested containers, use `ListField`, `SetField`, `MapField`, or a full +`ForyField(type: ...)` tree: + +```dart +@MapField( + value: ListType( + element: Int32Type(encoding: Encoding.fixed), + ), +) +Map> metrics = >{}; +``` + +Generic `List` still uses the `list` wire type even with primitive element +specs. Packed `*_array` wire kinds come from dedicated carriers such as +`Int32List`, `Uint32List`, `Int64List`, and `Uint64List`. If you annotate a +generic `List` with a non-null fixed-width primitive element spec, code +generation rejects it and tells you to use the matching typed list carrier. + +## Aligning Fields Across Languages + +When the same model is defined in multiple languages: + +- Assign stable `id` values to every field that might change over time. +- Use `dynamic: true` for fields that are genuinely polymorphic. +- Keep the logical meaning of each field consistent across languages — Fory matches fields by name or ID, but cannot reconcile semantic differences. + +## Related Topics + +- [Code Generation](code-generation.md) +- [Schema Evolution](schema-evolution.md) +- [Xlang Serialization](xlang-serialization.md) diff --git a/versioned_docs/version-1.3.0/guide/dart/supported-types.md b/versioned_docs/version-1.3.0/guide/dart/supported-types.md new file mode 100644 index 00000000000..220ed988cd5 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/dart/supported-types.md @@ -0,0 +1,159 @@ +--- +title: Supported Types +sidebar_position: 7 +id: supported_types +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 page lists the Dart types you can use in Fory messages, and flags where you need to be careful for cross-language compatibility. + +## Built-in Primitive Types + +The following Dart types serialize directly without any special handling: + +| Dart type | Cross-language notes | +| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `bool` | Direct mapping | +| `int` | Serialized as 64-bit by default. Use `@ForyField(type: Int8Type/Int16Type/Int32Type/Uint8Type/Uint16Type/Uint32Type)` when the peer expects a narrower integer | +| `double` | Maps to 64-bit float. Use `Float32` wrapper when the peer expects 32-bit | +| `String` | Direct mapping | +| `Uint8List` | Binary blob | +| `List`, `Set`, `Map` | Supported; element types must also be supported | +| `DateTime` | Use `Timestamp` or `LocalDate` wrappers for explicit semantics | + +## Integer Fields + +Dart VM/native `int` can represent signed 64-bit values, while Dart web `int` +is limited to JavaScript-safe integer precision. If the peer language expects a +32-bit integer (Java `int`, Go `int32`, C# `int`) and you send a Dart `int`, +the deserialization may fail or silently truncate. For browser and Flutter web +precision rules, see [Web Platform Support](web-platform-support.md). + +Use field metadata to select the wire type explicitly for 8/16/32-bit fields: + +```dart +@ForyStruct() +class Metrics { + Metrics(); + + @ForyField(type: Int8Type()) + int tiny = 0; + + @ForyField(type: Int32Type(encoding: Encoding.fixed)) + int age = 0; + + @ForyField(type: Uint32Type()) + int count = 0; + + Int64 sequence = Int64(0); + Uint64 offset = Uint64(0); +} +``` + +Generated serializers range-check annotated `int` values before writing them. +Use `Int64` and `Uint64` when you need full-range 64-bit values, especially on +web. A plain root `int` value serializes as xlang `int64`; exact 8/16/32-bit +wire widths are selected through field metadata or low-level `Buffer` APIs. + +On Dart VM, `Int64` and `Uint64` are extension types over `int`. Once a value is +passed through an `Object`-typed dynamic/root boundary, the VM cannot recover +whether it was originally a plain `int`, `Int64`, or `Uint64`. Use generated +field metadata or explicit `Buffer` APIs when native VM payloads must preserve +unsigned 64-bit identity across dynamic boundaries. Dart web uses wrapper +classes, so web root `Uint64` values keep `varuint64` metadata. + +## Floating-Point Types + +Dart `double` maps to 64-bit float by default. If the peer uses +reduced-precision floating-point values, keep the Dart field as `double` and +mark the exact wire type with field metadata: + +- `Float32` — 32-bit float (matches Java `float`, C# `float`, Go `float32`) +- `@ForyField(type: Float16Type()) double value` — half-precision scalar +- `@ForyField(type: Bfloat16Type()) double value` — bfloat16 scalar + +For contiguous 16-bit floating-point arrays, use `Float16List` and +`Bfloat16List` rather than `Uint16List` when the schema is `array` +or `array`. + +## Time and Date Types + +Avoid sending raw `DateTime` across languages — time zone handling and epoch differences vary. Use the explicit wrappers instead: + +- `Timestamp` — a UTC instant with nanosecond precision (seconds + nanoseconds) +- `LocalDate` — a calendar date without time or time zone +- `Duration` — an elapsed time value using Dart's built-in `Duration` + +```dart +final now = Timestamp.fromDateTime(DateTime.now().toUtc()); +final birthday = LocalDate(1990, 12, 1); +final timeout = const Duration(seconds: 30); +``` + +The temporal wrappers expose conversion helpers: + +- `Timestamp.fromDateTime(...)` and `timestamp.toDateTime()` +- `LocalDate.fromEpochDay(Int64(...))`, `date.toEpochDay()` returns `Int64` +- `LocalDate.fromDateTime(...)` and `date.toDateTime()` + +`Duration` support in Dart is exact to microseconds. Incoming xlang duration +payloads that use sub-microsecond nanoseconds are rejected instead of being +silently truncated. + +## Structs and Enums + +Annotate classes with `@ForyStruct()` and run `build_runner` to make them serializable. Enums in the same file are included automatically. + +```dart +@ForyStruct() +class User { + User(); + + String name = ''; + + @ForyField(type: Int32Type()) + int age = 0; // use explicit field metadata when peers expect a 32-bit integer +} +``` + +See [Code Generation](code-generation.md). + +## Collections + +Fory supports `List`, `Set`, and `Map`. Element and key types must +also be serializable types. Avoid using mutable objects as map keys. + +Generic `List` with primitive element metadata still uses `list` schema. +Dedicated dense array schema comes from dedicated carriers: + +- `BoolList` plus `@ArrayField(element: BoolType())` for `array`. + Plain `List` maps to `list`. +- `Int8List`, `Int16List`, `Int32List`, `Int64List` +- `Uint8List`, `Uint16List`, `Uint32List`, `Uint64List` +- `Float16List`, `Bfloat16List`, `Float32List`, `Float64List` + +## Compatibility Tip + +When in doubt about whether a Dart type will match what the peer expects, make +the width explicit with `@ForyField(type: ...)`. Guessing the wrong numeric +width is one of the most common cross-language bugs. + +## Related Topics + +- [Schema Metadata](schema-metadata.md) +- [Xlang Serialization](xlang-serialization.md) +- [Schema Evolution](schema-evolution.md) diff --git a/versioned_docs/version-1.3.0/guide/dart/troubleshooting.md b/versioned_docs/version-1.3.0/guide/dart/troubleshooting.md new file mode 100644 index 00000000000..5f066920a22 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/dart/troubleshooting.md @@ -0,0 +1,163 @@ +--- +title: Troubleshooting +sidebar_position: 11 +id: troubleshooting +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 page covers common Dart issues and fixes. + +## `Only xlang payloads are supported by the Dart implementation.` + +The writer is sending a native-mode payload. Make sure every peer writes the xlang wire format: + +- **Java**: configure the peer for xlang mode instead of native mode. +- **Go**: configure the peer for xlang mode. +- **Other languages**: check their respective guides for xlang mode. + +## `Type ... is not registered.` + +Fory does not know how to serialize or deserialize this type. Fix it by: + +1. Running code generation if you haven't: `dart run build_runner build --delete-conflicting-outputs` +2. Calling the generated `register` function (or `registerSerializer`) for the type **before** calling `serialize` or `deserialize`. +3. Registering **all** types that appear in a message, not just the root type. For example, if `Order` contains an `Address`, register both. + +## Generated part file is missing or stale + +Regenerate code: + +```bash +cd dart/packages/fory +dart run build_runner build --delete-conflicting-outputs +``` + +If you moved files or renamed types, rebuild before re-running analysis or tests. + +## `Deserialized value has type ..., expected ...` + +The payload describes a different type than `T` in `deserialize`. Common causes: + +- You registered the type on the writing side with a different ID or name than on the reading side. +- The payload was produced by a different code path that serializes a different root object. +- You are trying to deserialize a heterogeneous container — decode it as `Object?` or `List` first, then cast. + +## Objects aren't the same instance after deserialization + +Fory does not track object identity by default, so two fields pointing to the same object will produce two independent copies after a round trip. + +To preserve identity: + +- For fields inside a `@ForyStruct`, add `@ForyField(ref: true)` to those fields. +- For a top-level collection, pass `trackRef: true` to `fory.serialize(...)`. +- In a custom serializer, use `context.writeRef` / `context.readRef` and call `context.reference(obj)` before reading nested fields. + +## Cross-language field mismatch (missing data or wrong values) + +Symptoms: fields come back as default values or wrong types after a round trip to another language. + +Checklist: + +1. Same registration identity on both sides (same numeric ID **or** same `name`). +2. Stable `@ForyField(id: ...)` assigned before the first payload was produced. +3. Compatible numeric widths — use `@ForyField(type: Int32Type())` in Dart when the peer field is `int` (Java), `int32` (Go), or `int` (C#). +4. `Timestamp` / `LocalDate` instead of raw `DateTime` for date/time fields. +5. Compatible schema evolution on both sides. Dart enables it by default; make sure peers have not explicitly selected `compatible: false`. + +## Int64 or Uint64 values fail on web + +On Dart VM builds, Dart `int` can represent signed 64-bit values. On Dart web +builds, Dart `int` values are backed by JavaScript numbers and are only precise +inside the JS-safe integer range: + +```text +-9007199254740991 <= value <= 9007199254740991 +``` + +If a generated serializer writes an `int64` field declared as Dart `int`, +web builds reject values outside that range instead of silently writing +corrupted bytes. To exchange full signed 64-bit values on web, declare the +field as Fory's `Int64` wrapper: + +```dart +@ForyStruct() +class LedgerEntry { + LedgerEntry(); + + Int64 sequence = Int64(0); // full signed 64-bit range on VM and web +} +``` + +For unsigned 64-bit values, prefer `Uint64` rather than Dart `int`. Dart `int` +cannot represent the full `uint64` range on either VM or web: + +```dart +@ForyStruct() +class FileBlock { + FileBlock(); + + Uint64 offset = Uint64(0); // full unsigned 64-bit range +} +``` + +`@ForyField(type: Int64Type(...))` changes the wire encoding for a Dart `int` +field, but it does not remove the web integer precision limit. Use `Int64` for +full-range signed values and `Uint64` for full-range unsigned values. See +[Web Platform Support](web-platform-support.md) for the full browser support +matrix and platform guidance. + +## Running Tests Locally + +Main Dart package: + +```bash +dart run build_runner build --delete-conflicting-outputs +dart analyze +dart test +``` + +Integration test package: + +```bash +cd dart/packages/fory-test +dart run build_runner build --delete-conflicting-outputs +dart test +``` + +## Generated gRPC files cannot find `package:grpc` types + +**Cause**: gRPC packages are application dependencies. The `fory` package does +not add gRPC as a hard dependency. + +**Fix**: Add `grpc` to your `pubspec.yaml` (and the `build_runner` dev +dependency), then run `dart pub get`. See [gRPC Support](grpc-support.md). + +## A protobuf client cannot decode a Fory gRPC service + +**Cause**: Fory gRPC companions use gRPC transports with Fory-encoded message +bodies, not protobuf wire encoding. + +**Fix**: Use a Fory-generated client for Fory-generated services, or expose a +separate protobuf service endpoint for generic protobuf clients. + +## Related Topics + +- [Xlang Serialization](xlang-serialization.md) +- [Code Generation](code-generation.md) +- [Custom Serializers](custom-serializers.md) +- [Web Platform Support](web-platform-support.md) +- [gRPC Support](grpc-support.md) diff --git a/versioned_docs/version-1.3.0/guide/dart/type-registration.md b/versioned_docs/version-1.3.0/guide/dart/type-registration.md new file mode 100644 index 00000000000..61e3c20ce84 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/dart/type-registration.md @@ -0,0 +1,97 @@ +--- +title: Type Registration +sidebar_position: 6 +id: type_registration +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. +--- + +Fory needs to know which class corresponds to which type in a serialized message. You do this by registering each class before you serialize or deserialize it. + +## Choosing a Registration Strategy + +Fory offers two strategies. Pick one and use it consistently across every language that reads or writes the type. + +### Strategy 1: Numeric ID + +Compact and fast. Good when a small team can coordinate IDs across services. + +```dart +ModelsForyModule.register(fory, User, id: 100); +``` + +The same number must be used in every other language: + +```java +// Java side +fory.register(User.class, 100); +``` + +### Strategy 2: Name + +More self-describing. Good when multiple teams or packages define types independently and numeric ID coordination is impractical. + +```dart +ModelsForyModule.register( + fory, + User, + name: 'example.User', +); +``` + +Every peer that reads or writes this type must use the same name. Use `.` inside `name` +to add a namespace prefix. + +> **Do not mix strategies for the same type.** If one side uses a numeric ID and the other uses a name, deserialization will fail. + +## Registering Generated Types + +Call the generated `register` function from the `.fory.dart` file. It installs all the serializer metadata for you: + +```dart +UserModelsForyModule.register(fory, User, id: 100); +``` + +## Registering a Custom Serializer + +For types that you cannot annotate with `@ForyStruct()`, pass a serializer instance directly: + +```dart +fory.registerSerializer( + ExternalType, + const ExternalTypeSerializer(), + name: 'example.ExternalType', +); +``` + +See [Custom Serializers](custom-serializers.md) for how to implement a serializer. + +## Rules to Follow + +- Register **before** the first `serialize` or `deserialize` call. +- Register **every** class that can appear in a message, not only the root type. +- Keep IDs (or names) **stable** once payloads are persisted or exchanged across services. Changing them will break deserialization of old messages. +- Do not mix a numeric ID on one side with a name on the other for the same type. + +## Xlang Requirements + +The same numeric ID or name must be used in every peer that reads or writes the type. See [Xlang Serialization](xlang-serialization.md) for examples. + +## Related Topics + +- [Code Generation](code-generation.md) +- [Xlang Serialization](xlang-serialization.md) +- [Custom Serializers](custom-serializers.md) diff --git a/versioned_docs/version-1.3.0/guide/dart/web-platform-support.md b/versioned_docs/version-1.3.0/guide/dart/web-platform-support.md new file mode 100644 index 00000000000..20004509921 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/dart/web-platform-support.md @@ -0,0 +1,218 @@ +--- +title: Web Platform Support +sidebar_position: 10 +id: web_platform_support +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. +--- + +Fory Dart supports Dart VM/AOT, Flutter, browser, and Flutter web builds +through generated serializers and platform-specific implementations. +The public API and registration flow are the same across these platforms, but +web builds have stricter integer precision rules because Dart `int` is +represented by JavaScript numbers. + +## Supported Targets + +Fory Dart supports: + +- Dart VM/JIT applications. +- Dart AOT/native applications. +- Flutter mobile and desktop applications. +- Dart applications compiled to JavaScript for browsers. +- Flutter web applications. +- Generated `@ForyStruct` serializers and manually registered serializers on + all supported targets. + +## Code Generation Is Required + +Fory Dart uses explicit registration instead of runtime reflection. For +annotated structs, run code generation and register the generated serializer +before serializing or deserializing values: + +```dart +import 'package:fory/fory.dart'; + +part 'account.fory.dart'; + +@ForyStruct() +class Account { + Account(); + + String name = ''; + Int64 sequence = Int64(0); +} + +void main() { + final fory = Fory(); + AccountForyModule.register( + fory, + Account, + name: 'example.Account', + ); + + final bytes = fory.serialize(Account()..name = 'web'); + final account = fory.deserialize(bytes); + print(account.name); +} +``` + +Generate the companion file before building or testing: + +```bash +cd dart/packages/fory +dart run build_runner build --delete-conflicting-outputs +``` + +The registration call is the same on VM/AOT, Flutter, and web. Manual +serializers use `registerSerializer(...)`; generated structs use the generated +`register` wrapper. + +## 64-Bit Integer Rules + +Dart VM `int` values are signed 64-bit values. Dart web `int` values are backed +by JavaScript numbers and are precise only in the JS-safe integer range: + +```text +-9007199254740991 <= value <= 9007199254740991 +``` + +Use this rule when choosing field types: + +| Logical value | Recommended Dart field type on web | Notes | +| ---------------------------------------- | ---------------------------------- | ------------------------------------------------------------------------------------ | +| Signed 64-bit value within JS-safe range | `int` | Works with default `int64` mapping and `@ForyField(type: Int64Type(...))` encodings. | +| Full signed 64-bit range | `Int64` | Preserves values outside the JS-safe range. | +| Unsigned 64-bit value | `Uint64` | Required for values that do not fit in signed or JS-safe Dart `int`. | +| 8/16/32-bit integer | `int` + `@ForyField(type: ...)` | Use explicit field metadata to match peer languages exactly. | + +`@ForyField(type: Int64Type(...))` controls the wire encoding of a Dart `int` +field: + +```dart +@ForyStruct() +class SafeCounter { + SafeCounter(); + + @ForyField(type: Int64Type(encoding: Encoding.tagged)) + int count = 0; // keep web values inside the JS-safe range +} +``` + +It does not make Dart `int` capable of storing every 64-bit value on web. For +full-range signed values, use `Int64`: + +```dart +@ForyStruct() +class FullRangeCounter { + FullRangeCounter(); + + Int64 count = Int64(0); +} +``` + +For unsigned values, use `Uint64`: + +```dart +@ForyStruct() +class StorageExtent { + StorageExtent(); + + Uint64 byteOffset = Uint64(0); +} +``` + +## Custom Serializers + +Custom serializers can use the same `Buffer`, `WriteContext`, and `ReadContext` +APIs on VM/AOT, Flutter, and web. For 64-bit values: + +- Use `buffer.writeInt64(Int64(...))` and `buffer.readInt64()` for full-range + signed 64-bit values. +- Use `buffer.writeUint64(Uint64(...))` and `buffer.readUint64()` for full-range + unsigned 64-bit values. +- Use `writeInt64FromInt`, `writeVarInt64FromInt`, and matching `AsInt` reads + only when the value is intended to be a Dart `int` and therefore must stay + JS-safe on web. + +Example: + +```dart +final class OffsetSerializer extends Serializer { + const OffsetSerializer(); + + @override + void write(WriteContext context, StorageExtent value) { + context.buffer.writeUint64(value.byteOffset); + } + + @override + StorageExtent read(ReadContext context) { + return StorageExtent()..byteOffset = context.buffer.readUint64(); + } +} +``` + +## Collections And Typed Arrays + +`List`, `Set`, `Map`, `Uint8List`, numeric typed arrays, `Int64List`, and +`Uint64List` are supported on web. The `Int64List` and `Uint64List` +implementations preserve 64-bit values without depending on JavaScript integer +precision. Use the Fory wrapper list types when the schema is `array` +or `array`. + +## Testing Browser Builds + +Run the package tests in both VM and Chrome when changing code that must work on +web: + +```bash +cd dart/packages/fory +dart run build_runner build --delete-conflicting-outputs +dart test +dart test -p chrome +``` + +If Chrome tests fail with a stale generated file or missing part file, rerun +`build_runner` and then retry the test command from `dart/packages/fory`. + +## Common Web Failures + +### `Dart int value ... is outside the JS-safe signed int64 range` + +The serializer is trying to write a Dart `int` as a signed 64-bit value on web, +but the value is outside the range that JavaScript numbers can represent +exactly. Change the field type to `Int64`, or keep the value inside the JS-safe +range. + +### `Int64 value ... is not a JS-safe int` + +The deserializer read a full-range `Int64`, but the target field or custom +serializer asked for a Dart `int`. Change the field type to `Int64`, or decode +with `readInt64()` instead of an `AsInt` helper. + +### `Uint64 value ... is not a JS-safe int` + +The code is converting a `Uint64` to Dart `int` on web. Keep the value as +`Uint64` unless the application has already validated that it is in the +JS-safe non-negative range. + +## Related Topics + +- [Supported Types](supported-types.md) +- [Schema Metadata](schema-metadata.md) +- [Code Generation](code-generation.md) +- [Troubleshooting](troubleshooting.md) diff --git a/versioned_docs/version-1.3.0/guide/dart/xlang-serialization.md b/versioned_docs/version-1.3.0/guide/dart/xlang-serialization.md new file mode 100644 index 00000000000..8b1a5874d3a --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/dart/xlang-serialization.md @@ -0,0 +1,219 @@ +--- +title: Xlang Serialization +sidebar_position: 4 +id: xlang_serialization +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. +--- + +Apache Fory™ Dart serializes to the same binary format as the Java, Go, C#, Python, Rust, and Swift Fory implementations. You can write a message in Dart and read it in Java — or any other direction — without any conversion layer. + +## Setup + +Create a `Fory` instance as normal. There is no separate xlang option to enable in Dart: + +```dart +final fory = Fory(); // xlang payloads with compatible schema evolution +``` + +The key requirement is that both sides register the same type using the same identity. + +## Registration Identity + +The most important rule: **use the same type identity on every side**. You have two options: + +### Numeric ID + +Simpler for small, tightly-coordinated teams: + +```dart +// Dart +ModelsForyModule.register(fory, Person, id: 100); +``` + +### Namespace + Type Name + +Better when multiple teams define types independently: + +```dart +// Dart +ModelsForyModule.register( + fory, + Person, + name: 'example.Person', +); +``` + +Do not mix the two strategies for the same type across implementations. + +## Dart to Java Example + +### Dart + +```dart +import 'package:fory/fory.dart'; + +part 'person.fory.dart'; + +@ForyStruct() +class Person { + Person(); + + String name = ''; + + @ForyField(type: Int32Type()) + int age = 0; +} + +final fory = Fory(); +PersonForyModule.register(fory, Person, id: 100); +final bytes = fory.serialize(Person() + ..name = 'Alice' + ..age = 30); +``` + +### Java + +```java +Fory fory = Fory.builder() + .withXlang(true) + .build(); + +fory.register(Person.class, 100); +Person value = (Person) fory.deserialize(bytesFromDart); +``` + +## Dart to C# Example + +### Dart + +```dart +final fory = Fory(); +PersonForyModule.register(fory, Person, id: 100); +final bytes = fory.serialize(Person() + ..name = 'Alice' + ..age = 30); +``` + +### CSharp + +```csharp +[ForyStruct] +public sealed class Person +{ + public string Name { get; set; } = string.Empty; + public int Age { get; set; } +} + +Fory fory = Fory.Builder() + .Build(); + +fory.Register(100); +Person person = fory.Deserialize(payloadFromDart); +``` + +## Dart to Go Example + +### Dart + +```dart +final fory = Fory(); +PersonForyModule.register(fory, Person, id: 100); +final bytes = fory.serialize(Person() + ..name = 'Alice' + ..age = 30); +``` + +### Go + +```go +type Person struct { + Name string + Age int32 +} + +f := fory.New(fory.WithXlang(true)) +_ = f.RegisterStruct(Person{}, 100) + +var person Person +_ = f.Deserialize(bytesFromDart, &person) +``` + +## Field Matching Rules + +Fory matches fields by name or by stable field ID. For robust cross-language interop: + +1. Use the same type identity on every side (same numeric ID or same `name`). +2. Assign stable `@ForyField(id: ...)` values to all fields before shipping the first payload. +3. Keep field names consistent or rely on IDs, since Dart typically uses `lowerCamelCase` while Go uses `PascalCase` for exported fields and C# often uses `PascalCase` properties. +4. Use explicit numeric field metadata: `@ForyField(type: Int32Type())` in Dart for Java `int`, Go `int32`, and C# `int`; `double` in Dart for 64-bit floats; `double` plus `Float16Type` or `Bfloat16Type` for 16-bit floats; `Float32` for 32-bit; `Int64` / `Uint64` for full-range 64-bit values. +5. Use `Timestamp`, `LocalDate`, and `Duration` for temporal fields rather than raw `DateTime`. +6. Validate real round trips across all languages before shipping. + +## Type Mapping Notes for Dart + +Because Dart `int` is not itself a promise about the exact xlang wire width, prefer explicit field metadata when exact cross-language interpretation matters: + +- `@ForyField(type: Int32Type())` for xlang `int32` +- `@ForyField(type: Uint32Type())` for xlang `uint32` +- `@ForyField(type: Int8Type())` / `@ForyField(type: Int16Type())` / `@ForyField(type: Uint8Type())` / `@ForyField(type: Uint16Type())` for narrower integer widths +- `Int64` and `Uint64` for full-range 64-bit values on web +- `double` fields annotated with `Float16Type` or `Bfloat16Type` for 16-bit + floating-point scalars, and `Float32` for single-precision values +- `Float16List` and `Bfloat16List` for 16-bit floating-point array payloads +- `Timestamp`, `LocalDate`, and `Duration` for explicit temporal semantics + +### Lists and Dense Arrays + +`List` always represents Fory `list` unless a field has explicit array +metadata. Use `array` only for dense one-dimensional bool or numeric data. + +| Fory schema | Dart field carrier and annotation | +| ----------------- | --------------------------------------------------- | +| `list` | `List` | +| `array` | `@ArrayField(element: BoolType()) BoolList` | +| `array` | `@ArrayField(element: Int8Type()) Int8List` | +| `array` | `@ArrayField(element: Int16Type()) Int16List` | +| `array` | `@ArrayField(element: Int32Type()) Int32List` | +| `array` | `@ArrayField(element: Int64Type()) Int64List` | +| `array` | `@ArrayField(element: Uint8Type()) Uint8List` | +| `array` | `@ArrayField(element: Uint16Type()) Uint16List` | +| `array` | `@ArrayField(element: Uint32Type()) Uint32List` | +| `array` | `@ArrayField(element: Uint64Type()) Uint64List` | +| `array` | `@ArrayField(element: Float16Type()) Float16List` | +| `array` | `@ArrayField(element: Bfloat16Type()) Bfloat16List` | +| `array` | `@ArrayField(element: Float32Type()) Float32List` | +| `array` | `@ArrayField(element: Float64Type()) Float64List` | + +See [Supported Types](supported-types.md) and [xlang type mapping](../../specification/xlang_type_mapping.md). + +## Validation + +Before relying on a cross-language contract in production, test a payload end-to-end through every implementation you support. + +Run the Dart side: + +```bash +dart run build_runner build --delete-conflicting-outputs +dart analyze +dart test +``` + +## Related Topics + +- [Type Registration](type-registration.md) +- [Schema Evolution](schema-evolution.md) +- [Xlang guide](../xlang/index.md) diff --git a/versioned_docs/version-1.3.0/guide/go/_category_.json b/versioned_docs/version-1.3.0/guide/go/_category_.json new file mode 100644 index 00000000000..e4d176a7628 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/go/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "Go", + "position": 5, + "collapsible": true, + "collapsed": true +} diff --git a/versioned_docs/version-1.3.0/guide/go/basic-serialization.md b/versioned_docs/version-1.3.0/guide/go/basic-serialization.md new file mode 100644 index 00000000000..137ccf82bc4 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/go/basic-serialization.md @@ -0,0 +1,409 @@ +--- +title: Basic Serialization +sidebar_position: 1 +id: basic_serialization +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 guide covers the core serialization APIs in Fory Go. + +## Creating a Fory Instance + +Create a Fory instance and register your types before serialization: + +```go +import "github.com/apache/fory/go/fory" + +f := fory.New(fory.WithXlang(true)) + +// Register struct with a type ID +f.RegisterStruct(User{}, 1) +f.RegisterStruct(Order{}, 2) + +// Or register with a name (more flexible, less prone to ID conflicts, but higher serialization cost) +f.RegisterStructByName(User{}, "example.User") + +// Register enum types +f.RegisterEnum(Color(0), 3) +``` + +`fory.New()` uses xlang mode with compatible schema evolution. The example sets +`fory.WithXlang(true)` explicitly so the mode choice is visible. For Go-only +payloads that need native mode, configure `fory.WithXlang(false)` explicitly in +the native-mode examples. + +**Important**: The Fory instance should be reused across serialization calls. Creating a new instance involves allocating internal buffers, type caches, and resolvers, which is expensive. The default Fory instance is not thread-safe; for concurrent usage, use the thread-safe wrapper (see [Thread Safety](thread-safety.md)). + +See [Type Registration](type-registration.md) for more details. + +## Core API + +### Serialize and Deserialize + +The primary API for serialization: + +```go +// Serialize any value +data, err := f.Serialize(value) +if err != nil { + // Handle error +} + +// Deserialize into target +var result MyType +err = f.Deserialize(data, &result) +if err != nil { + // Handle error +} +``` + +### Marshal and Unmarshal + +Aliases for `Serialize` and `Deserialize` (familiar to Go developers): + +```go +data, err := f.Marshal(value) +err = f.Unmarshal(data, &result) +``` + +## Serializing Primitives + +```go +// Integers +data, _ := f.Serialize(int64(42)) +var i int64 +f.Deserialize(data, &i) // i = 42 + +// Floats +data, _ = f.Serialize(float64(3.14)) +var fl float64 +f.Deserialize(data, &fl) // fl = 3.14 + +// Strings +data, _ = f.Serialize("hello") +var s string +f.Deserialize(data, &s) // s = "hello" + +// Booleans +data, _ = f.Serialize(true) +var b bool +f.Deserialize(data, &b) // b = true +``` + +## Serializing Collections + +### Slices + +```go +// String slice +strs := []string{"a", "b", "c"} +data, _ := f.Serialize(strs) + +var result []string +f.Deserialize(data, &result) +// result = ["a", "b", "c"] + +// Integer slice +nums := []int64{1, 2, 3} +data, _ = f.Serialize(nums) + +var intResult []int64 +f.Deserialize(data, &intResult) +// intResult = [1, 2, 3] +``` + +### Maps + +```go +// String to string map +m := map[string]string{"key": "value"} +data, _ := f.Serialize(m) + +var result map[string]string +f.Deserialize(data, &result) +// result = {"key": "value"} + +// String to int map +m2 := map[string]int64{"count": 42} +data, _ = f.Serialize(m2) + +var result2 map[string]int64 +f.Deserialize(data, &result2) +// result2 = {"count": 42} +``` + +## Serializing Structs + +### Basic Struct Serialization + +Only **exported fields** (starting with uppercase) are serialized: + +```go +type User struct { + ID int64 // Serialized + Name string // Serialized + password string // NOT serialized (unexported) +} + +f.RegisterStruct(User{}, 1) + +user := &User{ID: 1, Name: "Alice", password: "secret"} +data, _ := f.Serialize(user) + +var result User +f.Deserialize(data, &result) +// result.ID = 1, result.Name = "Alice", result.password = "" +``` + +### Nested Structs + +```go +type Address struct { + City string + Country string +} + +type Person struct { + Name string + Address Address +} + +f.RegisterStruct(Address{}, 1) +f.RegisterStruct(Person{}, 2) + +person := &Person{ + Name: "Alice", + Address: Address{City: "NYC", Country: "USA"}, +} + +data, _ := f.Serialize(person) + +var result Person +f.Deserialize(data, &result) +// result.Address.City = "NYC" +``` + +### Pointer Fields + +```go +type Node struct { + Value int32 + Child *Node +} + +// Use WithTrackRef for pointer fields +f := fory.New(fory.WithXlang(true), fory.WithTrackRef(true)) +f.RegisterStruct(Node{}, 1) + +root := &Node{ + Value: 1, + Child: &Node{Value: 2, Child: nil}, +} + +data, _ := f.Serialize(root) + +var result Node +f.Deserialize(data, &result) +// result.Child.Value = 2 +``` + +## Streaming API + +For scenarios where you want to control the buffer: + +### SerializeTo + +Serialize to an existing buffer: + +```go +buf := fory.NewByteBuffer(nil) + +// Serialize multiple values to same buffer +f.SerializeTo(buf, value1) +f.SerializeTo(buf, value2) + +// Get all serialized data +data := buf.GetByteSlice(0, buf.WriterIndex()) +``` + +### DeserializeFrom + +Deserialize from an existing buffer: + +```go +buf := fory.NewByteBuffer(data) + +var result1, result2 MyType +f.DeserializeFrom(buf, &result1) +f.DeserializeFrom(buf, &result2) +``` + +## Generic API (Type-Safe) + +Fory Go provides generic functions for type-safe serialization: + +```go +import "github.com/apache/fory/go/fory" + +type User struct { + ID int64 + Name string +} + +// Type-safe serialization +user := &User{ID: 1, Name: "Alice"} +data, err := fory.Serialize(f, user) + +// Type-safe deserialization +var result User +err = fory.Deserialize(f, data, &result) +``` + +The generic API: + +- Infers type at compile time +- Provides better type safety +- May offer performance benefits + +## Error Handling + +Always check errors from serialization operations: + +```go +data, err := f.Serialize(value) +if err != nil { + switch e := err.(type) { + case fory.Error: + fmt.Printf("Fory error: %s (kind: %d)\n", e.Error(), e.Kind()) + default: + fmt.Printf("Unknown error: %v\n", err) + } + return +} + +err = f.Deserialize(data, &result) +if err != nil { + // Handle deserialization error +} +``` + +Common error kinds: + +- `ErrKindBufferOutOfBound`: Read/write beyond buffer bounds +- `ErrKindTypeMismatch`: Type ID mismatch during deserialization +- `ErrKindUnknownType`: Unknown type encountered +- `ErrKindMaxDepthExceeded`: Recursion depth limit exceeded +- `ErrKindHashMismatch`: Struct hash mismatch (schema changed) + +See [Troubleshooting](troubleshooting.md) for error resolution. + +## Nil Handling + +### Nil Pointers + +```go +var ptr *User = nil +data, _ := f.Serialize(ptr) + +var result *User +f.Deserialize(data, &result) +// result = nil +``` + +### Empty Collections + +```go +// Nil slice +var slice []string = nil +data, _ := f.Serialize(slice) + +var result []string +f.Deserialize(data, &result) +// result = nil + +// Empty slice (different from nil) +empty := []string{} +data, _ = f.Serialize(empty) + +f.Deserialize(data, &result) +// result = [] (empty, not nil) +``` + +## Complete Example + +```go +package main + +import ( + "fmt" + "github.com/apache/fory/go/fory" +) + +type Order struct { + ID int64 + Customer string + Items []Item + Total float64 +} + +type Item struct { + Name string + Quantity int32 + Price float64 +} + +func main() { + f := fory.New(fory.WithXlang(true)) + f.RegisterStruct(Order{}, 1) + f.RegisterStruct(Item{}, 2) + + order := &Order{ + ID: 12345, + Customer: "Alice", + Items: []Item{ + {Name: "Widget", Quantity: 2, Price: 9.99}, + {Name: "Gadget", Quantity: 1, Price: 24.99}, + }, + Total: 44.97, + } + + // Serialize + data, err := f.Serialize(order) + if err != nil { + panic(err) + } + fmt.Printf("Serialized %d bytes\n", len(data)) + + // Deserialize + var result Order + if err := f.Deserialize(data, &result); err != nil { + panic(err) + } + + fmt.Printf("Order ID: %d\n", result.ID) + fmt.Printf("Customer: %s\n", result.Customer) + fmt.Printf("Items: %d\n", len(result.Items)) + fmt.Printf("Total: %.2f\n", result.Total) +} +``` + +## Related Topics + +- [Configuration](configuration.md) +- [Type Registration](type-registration.md) +- [Supported Types](supported-types.md) +- [References](references.md) diff --git a/versioned_docs/version-1.3.0/guide/go/codegen.md b/versioned_docs/version-1.3.0/guide/go/codegen.md new file mode 100644 index 00000000000..59000972443 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/go/codegen.md @@ -0,0 +1,430 @@ +--- +title: Code Generation +sidebar_position: 11 +id: codegen +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. +--- + +:::warning Experimental Feature +Code generation is an **experimental** feature in Fory Go. The API and behavior may change in future releases. The standard dynamic serialization path remains the stable, recommended approach for most use cases. +::: + +Fory Go provides optional ahead-of-time (AOT) code generation for performance-critical paths. This generates dedicated serializers ahead of time and adds compile-time shape checks. + +## Why Code Generation? + +| Aspect | Standard Path | Code Generation | +| ----------- | ------------------ | ---------------------- | +| Setup | Zero configuration | Requires `go generate` | +| Performance | Excellent | Better on hot paths | +| Type Safety | Runtime validation | Compile-time checks | +| Maintenance | Automatic | Requires regeneration | + +**Use code generation when**: + +- Maximum performance is required +- Compile-time type safety is important +- Hot paths are performance-critical + +**Use the standard path when**: + +- Simple setup is preferred +- Types change frequently +- Dynamic typing is needed +- Code generation complexity is undesirable + +## Installation + +Install the `fory` generator binary: + +```bash +go install github.com/apache/fory/go/fory/cmd/fory@latest + +GO111MODULE=on go get -u github.com/apache/fory/go/fory/cmd/fory +``` + +Ensure `$GOBIN` or `$GOPATH/bin` is in your `PATH`. + +## Basic Usage + +### Step 1: Annotate Structs + +Add the `//fory:generate` comment above structs: + +```go +package models + +//fory:generate +type User struct { + ID int64 `json:"id"` + Name string `json:"name"` +} + +//fory:generate +type Order struct { + ID int64 + Customer string + Total float64 +} +``` + +### Step 2: Add Go Generate Directive + +Add a `go:generate` directive (once per file or package): + +```go +//go:generate fory -file models.go +``` + +Or for the entire package: + +```go +//go:generate fory -pkg . +``` + +### Step 3: Run Code Generation + +```bash +go generate ./... +``` + +This creates `models_fory_gen.go` with generated serializers. + +## Generated Code Structure + +The generator creates: + +### Type Snapshot + +A compile-time check to detect struct changes: + +```go +// Snapshot of User's underlying type at generation time +type _User_expected struct { + ID int64 + Name string +} + +// Compile-time check: fails if User no longer matches +var _ = func(x User) { _ = _User_expected(x) } +``` + +### Serializer Implementation + +Strongly-typed serialization methods: + +```go +type User_ForyGenSerializer struct{} + +func NewSerializerFor_User() fory.Serializer { + return &User_ForyGenSerializer{} +} + +func (User_ForyGenSerializer) WriteTyped(ctx *fory.WriteContext, v *User) error { + buf := ctx.Buffer() + buf.WriteInt64(v.ID) + ctx.WriteString(v.Name) + return nil +} + +func (User_ForyGenSerializer) ReadTyped(ctx *fory.ReadContext, v *User) error { + err := ctx.Err() + buf := ctx.Buffer() + v.ID = buf.ReadInt64(err) + v.Name = ctx.ReadString() + if ctx.HasError() { + return ctx.TakeError() + } + return nil +} +``` + +### Auto-Registration + +Serializers are registered in `init()`: + +```go +func init() { + fory.RegisterSerializerFactory((*User)(nil), NewSerializerFor_User) +} +``` + +## Command-Line Options + +### File-Based Generation + +Generate for a specific file: + +```bash +fory -file models.go +``` + +### Package-Based Generation + +Generate for a package: + +```bash +fory -pkg ./models +``` + +### Explicit Types + +Specify types explicitly: + +```bash +fory -pkg ./models -type "User,Order" +``` + +### Force Regeneration + +Force regeneration even if up-to-date: + +```bash +fory --force -file models.go +``` + +## When to Regenerate + +Regenerate when any of these change: + +- Field additions, removals, or renames +- Field type changes +- Struct tag changes +- New structs with `//fory:generate` + +### Automatic Detection + +Fory includes a compile-time guard: + +```go +// If struct changed, this fails to compile +var _ = func(x User) { _ = _User_expected(x) } +``` + +If you forget to regenerate, the build fails with a clear message. + +### Auto-Retry + +When invoked via `go generate`, the generator detects stale code and retries: + +1. Detects compile error from guard +2. Removes stale generated file +3. Regenerates fresh code + +## Supported Types + +Code generation supports: + +- All primitive types (`bool`, `int*`, `uint*`, `float*`, `string`) +- Slices of primitives and structs +- Maps with supported key/value types +- Nested structs (must also be generated) +- Pointers to structs + +### Nested Structs + +All nested structs must also have `//fory:generate`: + +```go +//fory:generate +type Address struct { + City string + Country string +} + +//fory:generate +type Person struct { + Name string + Address Address // Address must also be generated +} +``` + +## CI/CD Integration + +### Check In Generated Code + +**Recommended for libraries**: + +```bash +go generate ./... +git add *_fory_gen.go +git commit -m "Regenerate Fory serializers" +``` + +**Pros**: Consumers can build without generator; reproducible builds +**Cons**: Larger diffs; must remember to regenerate + +### Generate in Pipeline + +**Recommended for applications**: + +```yaml +steps: + - run: go install github.com/apache/fory/go/fory/cmd/fory@latest + - run: go generate ./... + - run: go build ./... +``` + +## Usage with Generated Code + +Generated code integrates transparently: + +```go +f := fory.New(fory.WithXlang(true)) + +// Fory automatically uses generated serializer if available +user := &User{ID: 1, Name: "Alice"} +data, _ := f.Serialize(user) + +var result User +f.Deserialize(data, &result) +``` + +No code changes needed - registration happens in `init()`. + +## Mixing Generated and Non-Generated + +You can mix approaches: + +```go +//fory:generate +type HotPathStruct struct { + // Performance-critical, use codegen +} + +type ColdPathStruct struct { + // Not annotated, uses the standard dynamic serializer +} +``` + +## Limitations + +### Experimental Status + +- API may change +- Not all edge cases tested +- May have undiscovered bugs + +### Not Supported + +- Interface fields (dynamic types) +- Recursive types without pointers +- Private (unexported) fields +- Custom serializers + +### Standard Path Fallback + +If generated serializers are unavailable, Fory falls back to the standard serializer path: + +```go +// If User_ForyGenSerializer is not linked in, Fory uses the standard path +f.Serialize(&User{}) +``` + +## Troubleshooting + +### "fory: command not found" + +Ensure the binary is in PATH: + +```bash +export PATH=$PATH:$(go env GOPATH)/bin +``` + +### Compile Error After Struct Change + +Regenerate: + +```bash +go generate ./... +``` + +Or force: + +```bash +fory --force -file yourfile.go +``` + +### Generated Code Out of Sync + +The compile-time guard catches this: + +``` +cannot use x (variable of type User) as type _User_expected in argument +``` + +Run `go generate` to fix. + +## Example Project Structure + +``` +myproject/ +├── models/ +│ ├── models.go # Struct definitions +│ ├── models_fory_gen.go # Generated code +│ └── generate.go # go:generate directive +├── main.go +└── go.mod +``` + +**models/generate.go**: + +```go +package models + +//go:generate fory -pkg . +``` + +**models/models.go**: + +```go +package models + +//fory:generate +type User struct { + ID int64 + Name string +} +``` + +## FAQ + +### Is codegen required? + +No. The standard serializer path works without code generation. + +### Does generated code work across Go versions? + +Yes. Generated code is plain Go with no version-specific features. + +### Can I mix generated and non-generated types? + +Yes. Fory automatically uses generated serializers when available. + +### How do I update generated code? + +Run `go generate ./...` after struct changes. + +### Should I commit generated files? + +For libraries: yes. For applications: either works. + +## Related Topics + +- [Basic Serialization](basic-serialization.md) +- [Configuration](configuration.md) +- [Troubleshooting](troubleshooting.md) diff --git a/versioned_docs/version-1.3.0/guide/go/configuration.md b/versioned_docs/version-1.3.0/guide/go/configuration.md new file mode 100644 index 00000000000..20d9012aeea --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/go/configuration.md @@ -0,0 +1,400 @@ +--- +title: Configuration +sidebar_position: 4 +id: configuration +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. +--- + +Fory Go uses a functional options pattern for configuration. This allows you to customize serialization behavior while maintaining sensible defaults. + +## Creating a Fory Instance + +### Default Configuration + +```go +import "github.com/apache/fory/go/fory" + +f := fory.New(fory.WithXlang(true)) +``` + +Default settings: + +| Option | Default | Description | +| ------------------------------- | ------- | ------------------------------------------------- | +| TrackRef | false | Reference tracking disabled | +| MaxDepth | 20 | Maximum nesting depth | +| IsXlang | true | Xlang mode enabled | +| Compatible | true | Compatible schema-evolution metadata enabled | +| MaxTypeFields | 512 | Max fields in one received struct metadata body | +| MaxTypeMetaBytes | 4096 | Max encoded bytes in one received metadata body | +| MaxSchemaVersionsPerType | 10 | Max remote metadata versions for one logical type | +| MaxAverageSchemaVersionsPerType | 3 | Average remote metadata versions across types | + +### With Options + +```go +f := fory.New( + fory.WithXlang(true), + fory.WithTrackRef(true), + fory.WithMaxDepth(10), + fory.WithMaxTypeFields(512), + fory.WithMaxTypeMetaBytes(4096), + fory.WithMaxSchemaVersionsPerType(10), + fory.WithMaxAverageSchemaVersionsPerType(3), +) +``` + +## Configuration + +### WithTrackRef + +Enable reference tracking to handle circular references and shared objects: + +```go +f := fory.New(fory.WithTrackRef(true)) +``` + +**When enabled:** + +- Objects appearing multiple times are serialized once +- Circular references are handled correctly +- Per-field `fory:"ref"` tags take effect +- Adds overhead for tracking object identity + +**When disabled (default):** + +- Each object occurrence is serialized independently +- Circular references cause stack overflow or max depth error +- Per-field `fory:"ref"` tags are ignored +- Better performance for simple data structures + +**Use reference tracking when:** + +- Data contains circular references +- Same object is referenced multiple times +- Serializing graph structures (trees with parent pointers, linked lists with cycles) + +See [References](references.md) for details. + +### WithCompatible + +Compatible mode is enabled by default in both xlang and native mode. Set +`WithCompatible(false)` only when every reader and writer always uses the same schema and you want +faster serialization and smaller size: + +```go +f := fory.New(fory.WithCompatible(false)) +``` + +**When enabled:** + +- Type metadata is written to serialized data +- Supports adding/removing fields between versions +- Field names or ids are used for matching (order-independent) +- Larger serialized output due to metadata + +**When disabled:** + +- Faster serialization and smaller size +- Fields matched by sorted order +- Requires consistent struct definitions across all services + +For xlang payloads, use `WithCompatible(false)` only after verifying that every language uses the same schema, or when native types are generated from Fory schema IDL. See [Schema Evolution](schema-evolution.md) for details. + +### WithMaxDepth + +Set the maximum nesting depth to prevent stack overflow: + +```go +f := fory.New(fory.WithMaxDepth(30)) +``` + +- Default: 20 +- Protects against deeply nested, recursive structures or malicious data +- Serialization fails with error when exceeded + +### WithMaxTypeFields + +Set the maximum fields accepted in one received remote struct metadata body: + +```go +f := fory.New(fory.WithMaxTypeFields(512)) +``` + +### WithMaxTypeMetaBytes + +Set the maximum encoded body bytes accepted for one received TypeDef body, +excluding the 8-byte header and any extended-size varint: + +```go +f := fory.New(fory.WithMaxTypeMetaBytes(4096)) +``` + +### WithMaxSchemaVersionsPerType + +Set the maximum accepted remote metadata versions for one logical type: + +```go +f := fory.New(fory.WithMaxSchemaVersionsPerType(10)) +``` + +### WithMaxAverageSchemaVersionsPerType + +Set the average accepted remote metadata versions across accepted remote types. +The effective global floor is `8192` schemas: + +```go +f := fory.New(fory.WithMaxAverageSchemaVersionsPerType(3)) +``` + +### WithXlang + +Select the wire mode: + +```go +native := fory.New(fory.WithXlang(false)) +xlang := fory.New(fory.WithXlang(true)) +``` + +**When enabled:** + +- Uses cross-language type system +- Compatible with Java, Python, C++, Rust, JavaScript/TypeScript, C#, Swift, + Dart, Scala, and Kotlin +- Type IDs follow xlang specification + +**When disabled:** + +- Go-native serialization mode +- Supports more Go-native type behavior +- Not compatible with other language implementations + +## Thread Safety + +The default `Fory` instance is **NOT thread-safe**. For concurrent use, use the thread-safe wrapper: + +```go +import "github.com/apache/fory/go/fory/threadsafe" + +// Create thread-safe Fory with same options +f := threadsafe.New( + fory.WithXlang(true), + fory.WithTrackRef(true), +) + +// Safe for concurrent use from multiple goroutines +go func() { + data, _ := f.Serialize(value1) + // data is already copied, safe to use after return +}() +go func() { + data, _ := f.Serialize(value2) +}() +``` + +The thread-safe wrapper: + +- Uses `sync.Pool` internally for efficient instance reuse +- Automatically copies serialized data before returning +- Accepts the same configuration options as `fory.New()` + +### Global Thread-Safe Instance + +For convenience, the threadsafe package provides global functions: + +```go +import "github.com/apache/fory/go/fory/threadsafe" + +// Uses a global thread-safe instance with default configuration +data, err := threadsafe.Marshal(&myValue) +err = threadsafe.Unmarshal(data, &result) +``` + +See [Thread Safety](thread-safety.md) for details. + +## Buffer Management + +### Zero-Copy Behavior + +The default `Fory` instance reuses its internal buffer: + +```go +f := fory.New(fory.WithXlang(true)) + +data1, _ := f.Serialize(value1) +// WARNING: data1 becomes invalid after next Serialize call! +data2, _ := f.Serialize(value2) +// data1 now points to invalid memory + +// To keep the data, copy it: +safeCopy := make([]byte, len(data1)) +copy(safeCopy, data1) +``` + +The thread-safe wrapper automatically copies data, so this is not a concern: + +```go +f := threadsafe.New(fory.WithXlang(true)) +data1, _ := f.Serialize(value1) +data2, _ := f.Serialize(value2) +// Both data1 and data2 are valid +``` + +### Manual Buffer Control + +For high-throughput scenarios, you can manage buffers manually: + +```go +f := fory.New(fory.WithXlang(true)) +buf := fory.NewByteBuffer(nil) + +// Serialize to existing buffer +err := f.SerializeTo(buf, value) + +// Get serialized data +data := buf.GetByteSlice(0, buf.WriterIndex()) + +// Process data... + +// Reset for next use +buf.Reset() +``` + +## Configuration Examples + +### Simple Xlang Data + +For simple structs without circular references: + +```go +f := fory.New(fory.WithXlang(true)) + +type Config struct { + Host string + Port int32 +} + +f.RegisterStruct(Config{}, 1) +data, _ := f.Serialize(&Config{Host: "localhost", Port: 8080}) +``` + +### Graph Structures + +For data with circular references: + +```go +f := fory.New(fory.WithXlang(true), fory.WithTrackRef(true)) + +type Node struct { + Value int32 + Next *Node `fory:"ref"` +} + +f.RegisterStruct(Node{}, 1) +n1 := &Node{Value: 1} +n2 := &Node{Value: 2} +n1.Next = n2 +n2.Next = n1 // Circular reference + +data, _ := f.Serialize(n1) +``` + +### Schema Evolution + +For data that may evolve over time: + +```go +// V1: original struct +type UserV1 struct { + ID int64 + Name string +} + +// V2: added Email field +type UserV2 struct { + ID int64 + Name string + Email string // New field +} + +// Serialize with V1 in native mode. Compatible mode is the default. +f1 := fory.New(fory.WithXlang(false)) +f1.RegisterStruct(UserV1{}, 1) +data, _ := f1.Serialize(&UserV1{ID: 1, Name: "Alice"}) + +// Deserialize into V2 - Email will have zero value +f2 := fory.New(fory.WithXlang(false)) +f2.RegisterStruct(UserV2{}, 1) +var user UserV2 +f2.Deserialize(data, &user) +``` + +### High-Performance Concurrent + +For concurrent high-throughput scenarios: + +```go +type Request struct { + ID int64 + Payload string +} + +f := threadsafe.New( + fory.WithXlang(true), + fory.WithMaxDepth(30), +) +f.RegisterStruct(Request{}, 1) + +// Process requests concurrently +for req := range requests { + go func(r Request) { + data, _ := f.Serialize(&r) + sendResponse(data) + }(req) +} +``` + +## Best Practices + +1. **Reuse Fory instances**: Creating a Fory instance involves initialization overhead. Create once and reuse. + +2. **Use thread-safe wrapper for concurrency**: Never share a non-thread-safe Fory instance across goroutines. + +3. **Enable reference tracking only when needed**: It adds overhead for tracking object identity. + +4. **Copy serialized data if keeping it**: With the default Fory, the returned byte slice is invalidated on the next operation. + +5. **Set appropriate max depth**: Increase for deeply nested structures, but be aware of memory usage. + +6. **Keep compatible mode for evolving schemas**: Use the default when struct definitions may change between service versions. + +## Security + +Security-related configuration: + +- Register only the expected structs before deserializing untrusted data. +- Use `WithMaxDepth(...)` to reject unexpectedly deep payloads. +- Keep the remote schema metadata limits at their defaults unless the data is not malicious and a + trusted peer sends larger metadata or many schema versions. +- Prefer concrete struct fields over broad `any` or interface-typed fields for untrusted input. + +## Related Topics + +- [Basic Serialization](basic-serialization.md) +- [References](references.md) +- [Schema Evolution](schema-evolution.md) +- [Thread Safety](thread-safety.md) diff --git a/versioned_docs/version-1.3.0/guide/go/custom-serializers.md b/versioned_docs/version-1.3.0/guide/go/custom-serializers.md new file mode 100644 index 00000000000..9c0461f0fc5 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/go/custom-serializers.md @@ -0,0 +1,285 @@ +--- +title: Custom Serializers +sidebar_position: 10 +id: custom_serializers +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. +--- + +Custom serializers allow you to define exactly how a type is serialized and deserialized. This is useful for types that require special handling, optimization, or cross-language compatibility. + +## When to Use Custom Serializers + +- **Special encoding**: Types that need a specific binary format +- **Third-party types**: Types from external libraries that Fory doesn't handle automatically +- **Optimization**: When you can serialize more efficiently than the default reflection-based approach +- **Cross-language compatibility**: When you need precise control over the binary format for interoperability + +## ExtensionSerializer Interface + +Custom serializers implement the `ExtensionSerializer` interface: + +```go +type ExtensionSerializer interface { + // WriteData serializes the value to the buffer. + // Only write the data - Fory handles type info and references. + // Use ctx.Buffer() to access the ByteBuffer. + // Use ctx.SetError() to report errors. + WriteData(ctx *WriteContext, value reflect.Value) + + // ReadData deserializes the value from the buffer into the provided value. + // Only read the data - Fory handles type info and references. + // Use ctx.Buffer() to access the ByteBuffer. + // Use ctx.SetError() to report errors. + ReadData(ctx *ReadContext, value reflect.Value) +} +``` + +## Basic Example + +Here's a simple custom serializer for a type with an integer field: + +```go +import ( + "reflect" + "github.com/apache/fory/go/fory" +) + +type MyExt struct { + Id int32 +} + +type MyExtSerializer struct{} + +func (s *MyExtSerializer) WriteData(ctx *fory.WriteContext, value reflect.Value) { + myExt := value.Interface().(MyExt) + ctx.Buffer().WriteVarint32(myExt.Id) +} + +func (s *MyExtSerializer) ReadData(ctx *fory.ReadContext, value reflect.Value) { + id := ctx.Buffer().ReadVarint32(ctx.Err()) + value.Set(reflect.ValueOf(MyExt{Id: id})) +} + +// Register the custom serializer +f := fory.New(fory.WithXlang(true)) +err := f.RegisterExtension(MyExt{}, 100, &MyExtSerializer{}) +``` + +## Context Methods + +The `WriteContext` and `ReadContext` provide access to serialization resources: + +| Method | Description | +| ---------------- | ---------------------------------------------- | +| `Buffer()` | Returns the `*ByteBuffer` for reading/writing | +| `Err()` | Returns `*Error` for deferred error checking | +| `SetError(err)` | Sets an error on the context | +| `HasError()` | Returns true if an error has been set | +| `TypeResolver()` | Returns the type resolver for nested types | +| `RefResolver()` | Returns the reference resolver for ref support | + +## ByteBuffer Methods + +The `ByteBuffer` provides methods for reading and writing primitive types: + +### Writing Methods + +| Method | Description | +| -------------------------- | --------------------------------------------- | +| `WriteBool(v bool)` | Write a boolean | +| `WriteInt8(v int8)` | Write a signed 8-bit integer | +| `WriteInt16(v int16)` | Write a signed 16-bit integer | +| `WriteInt32(v int32)` | Write a signed 32-bit integer | +| `WriteInt64(v int64)` | Write a signed 64-bit integer | +| `WriteFloat32(v float32)` | Write a 32-bit float | +| `WriteFloat64(v float64)` | Write a 64-bit float | +| `WriteVarint32(v int32)` | Write a variable-length signed 32-bit integer | +| `WriteVarint64(v int64)` | Write a variable-length signed 64-bit integer | +| `WriteBinary(data []byte)` | Write raw bytes | + +### Reading Methods + +All read methods take an `*Error` parameter for deferred error checking: + +| Method | Description | +| ------------------------------------------- | -------------------------------------------- | +| `ReadBool(err *Error) bool` | Read a boolean | +| `ReadInt8(err *Error) int8` | Read a signed 8-bit integer | +| `ReadInt16(err *Error) int16` | Read a signed 16-bit integer | +| `ReadInt32(err *Error) int32` | Read a signed 32-bit integer | +| `ReadInt64(err *Error) int64` | Read a signed 64-bit integer | +| `ReadFloat32(err *Error) float32` | Read a 32-bit float | +| `ReadFloat64(err *Error) float64` | Read a 64-bit float | +| `ReadVarint32(err *Error) int32` | Read a variable-length signed 32-bit integer | +| `ReadVarint64(err *Error) int64` | Read a variable-length signed 64-bit integer | +| `ReadBinary(length int, err *Error) []byte` | Read raw bytes of specified length | + +## Complex Type Example + +A custom serializer for a type with multiple fields: + +```go +type Point3D struct { + X, Y, Z float64 + Label string +} + +type Point3DSerializer struct{} + +func (s *Point3DSerializer) WriteData(ctx *fory.WriteContext, value reflect.Value) { + p := value.Interface().(Point3D) + buf := ctx.Buffer() + buf.WriteFloat64(p.X) + buf.WriteFloat64(p.Y) + buf.WriteFloat64(p.Z) + // Write string as length + bytes + labelBytes := []byte(p.Label) + buf.WriteVarint32(int32(len(labelBytes))) + buf.WriteBinary(labelBytes) +} + +func (s *Point3DSerializer) ReadData(ctx *fory.ReadContext, value reflect.Value) { + buf := ctx.Buffer() + err := ctx.Err() + x := buf.ReadFloat64(err) + y := buf.ReadFloat64(err) + z := buf.ReadFloat64(err) + labelLen := buf.ReadVarint32(err) + labelBytes := buf.ReadBinary(int(labelLen), err) + value.Set(reflect.ValueOf(Point3D{ + X: x, + Y: y, + Z: z, + Label: string(labelBytes), + })) +} + +f := fory.New(fory.WithXlang(true)) +f.RegisterExtension(Point3D{}, 101, &Point3DSerializer{}) +``` + +## Handling Pointers + +When your type contains pointers, handle nil values explicitly: + +```go +type OptionalValue struct { + Value *int64 +} + +type OptionalValueSerializer struct{} + +func (s *OptionalValueSerializer) WriteData(ctx *fory.WriteContext, value reflect.Value) { + ov := value.Interface().(OptionalValue) + buf := ctx.Buffer() + if ov.Value == nil { + buf.WriteBool(false) // nil flag + } else { + buf.WriteBool(true) // not nil + buf.WriteInt64(*ov.Value) + } +} + +func (s *OptionalValueSerializer) ReadData(ctx *fory.ReadContext, value reflect.Value) { + buf := ctx.Buffer() + err := ctx.Err() + hasValue := buf.ReadBool(err) + if !hasValue { + value.Set(reflect.ValueOf(OptionalValue{Value: nil})) + return + } + v := buf.ReadInt64(err) + value.Set(reflect.ValueOf(OptionalValue{Value: &v})) +} +``` + +## Error Handling + +Use `ctx.SetError()` to report errors: + +```go +func (s *MySerializer) ReadData(ctx *fory.ReadContext, value reflect.Value) { + buf := ctx.Buffer() + version := buf.ReadInt8(ctx.Err()) + if ctx.HasError() { + return + } + if version != 1 { + ctx.SetError(fory.DeserializationErrorf("unsupported version: %d", version)) + return + } + // Continue reading... + value.Set(reflect.ValueOf(result)) +} +``` + +## Registration Options + +### Register by ID + +More compact serialization, requires ID coordination across languages: + +```go +f.RegisterExtension(MyType{}, 100, &MySerializer{}) +``` + +### Register by Name + +More flexible but more serialization cost, type name included in serialized data: + +```go +f.RegisterExtensionByName(MyType{}, "myapp.MyType", &MySerializer{}) +``` + +## Best Practices + +1. **Keep it simple**: Only serialize what you need +2. **Use variable-length integers**: `WriteVarint32`/`WriteVarint64` for integers that are often small +3. **Handle nil explicitly**: Check for nil pointers and slices +4. **Version your format**: Consider adding a version byte for future compatibility +5. **Test round-trips**: Always verify that `Read(Write(value)) == value` +6. **Match read/write order**: Read fields in exactly the same order you write them +7. **Check errors**: Use `ctx.HasError()` after reading to handle errors gracefully +8. **Deploy before use**: Always deploy the registered serializer to all services before sending data serialized with it. If a service receives data for an unregistered serializer, deserialization will fail + +## Testing Custom Serializers + +```go +func TestMySerializer(t *testing.T) { + f := fory.New(fory.WithXlang(true)) + f.RegisterExtension(MyType{}, 100, &MySerializer{}) + + original := MyType{Field: "test"} + + // Serialize + data, err := f.Serialize(original) + require.NoError(t, err) + + // Deserialize + var result MyType + err = f.Deserialize(data, &result) + require.NoError(t, err) + + assert.Equal(t, original, result) +} +``` + +## Related Topics + +- [Type Registration](type-registration.md) +- [Supported Types](supported-types.md) +- [Xlang Serialization](xlang-serialization.md) diff --git a/versioned_docs/version-1.3.0/guide/go/grpc-support.md b/versioned_docs/version-1.3.0/guide/go/grpc-support.md new file mode 100644 index 00000000000..4c221dc90e2 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/go/grpc-support.md @@ -0,0 +1,241 @@ +--- +title: gRPC Support +sidebar_position: 13 +id: grpc_support +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. +--- + +Fory can generate Go gRPC service companions for schemas that define services. +The generated code uses grpc-go for transport and a Fory-backed `CodecV2` for +request and response payloads. + +Use this mode when every RPC peer is generated from the same Fory IDL, protobuf +IDL, or FlatBuffers IDL and you want gRPC transport semantics with Fory payload +encoding. Use standard protobuf gRPC code generation when clients or tools must +consume protobuf message bytes directly. + +## Add Dependencies + +Add grpc-go to your module. Fory Go packages do not add gRPC as a hard +dependency. + +```bash +go get google.golang.org/grpc +``` + +Your generated code also imports the Fory Go module: + +```bash +go get github.com/apache/fory/go/fory +``` + +## Define a Service + +Service definitions can come from Fory IDL, protobuf IDL, or FlatBuffers +`rpc_service` definitions. A Fory IDL service looks like this: + +```protobuf +package demo.greeter; + +message HelloRequest { + string name = 1; +} + +message HelloReply { + string reply = 1; +} + +service Greeter { + rpc SayHello (HelloRequest) returns (HelloReply); +} +``` + +Generate Go model and gRPC companion code with `--grpc`: + +```bash +foryc service.fdl --go_out=./generated/go --grpc +``` + +For this schema, the Go generator emits: + +| File | Purpose | +| ------------------------------ | -------------------------------------------- | +| `greeter/demo_greeter.go` | Fory model types and registration helpers | +| `greeter/demo_greeter_grpc.go` | grpc-go client, server interfaces, and codec | + +Generated Go methods use exported PascalCase names such as `SayHello`. The +underlying gRPC method path keeps the exact schema method name, so names such as +`sayHello` or `say_hello` continue to route by their schema spelling. + +## Implement a Server + +Implement the generated `GreeterServer` interface, create a grpc-go server with +the generated Fory codec, and register the service. + +```go +package main + +import ( + "context" + "log" + "net" + + "google.golang.org/grpc" + + "example.com/app/generated/go/greeter" +) + +type greeterService struct { + greeter.UnimplementedGreeterServer +} + +func (greeterService) SayHello( + ctx context.Context, + request *greeter.HelloRequest, +) (*greeter.HelloReply, error) { + return &greeter.HelloReply{Reply: "Hello, " + request.Name}, nil +} + +func main() { + listener, err := net.Listen("tcp", ":50051") + if err != nil { + log.Fatal(err) + } + + server := grpc.NewServer( + grpc.ForceServerCodecV2(greeter.CodecV2{}), + ) + greeter.RegisterGreeterServer(server, greeterService{}) + + if err := server.Serve(listener); err != nil { + log.Fatal(err) + } +} +``` + +`grpc.ForceServerCodecV2(...)` is required so the server decodes incoming frames +with the generated Fory codec instead of the default protobuf codec. + +Use the zero-value generated `CodecV2{}` for the service schema. The generated +client methods force the same codec for outgoing calls. + +## Create a Client + +The generated client constructor accepts a grpc-go connection. Generated client +methods force the generated Fory codec for each call. + +```go +package main + +import ( + "context" + "fmt" + "log" + "time" + + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + + "example.com/app/generated/go/greeter" +) + +func main() { + conn, err := grpc.NewClient( + "localhost:50051", + grpc.WithTransportCredentials(insecure.NewCredentials()), + ) + if err != nil { + log.Fatal(err) + } + defer conn.Close() + + client := greeter.NewGreeterClient(conn) + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + reply, err := client.SayHello(ctx, &greeter.HelloRequest{Name: "Fory"}) + if err != nil { + log.Fatal(err) + } + fmt.Println(reply.Reply) +} +``` + +## Streaming RPCs + +Fory service definitions can use unary, server-streaming, client-streaming, and +bidirectional streaming RPC shapes: + +```protobuf +service Greeter { + rpc SayHello (HelloRequest) returns (HelloReply); + rpc LotsOfReplies (HelloRequest) returns (stream HelloReply); + rpc LotsOfGreetings (stream HelloRequest) returns (HelloReply); + rpc Chat (stream HelloRequest) returns (stream HelloReply); +} +``` + +Generated Go code follows grpc-go conventions: + +- Unary methods take `context.Context`, a request pointer, and return a response + pointer plus `error`. +- Server-streaming client methods return a generated stream client. +- Client-streaming server methods receive a generated stream server. +- Bidirectional streaming methods use generated stream client and server + interfaces. +- The generated codec is used for every message frame, including streaming + frames. + +## gRPC Runtime Behavior + +The generated service companion only supplies Fory serialization. Operational +behavior remains standard grpc-go behavior: + +- Deadlines and cancellations +- TLS and credentials +- Unary and stream interceptors +- Status codes and metadata +- Name resolution and load balancing +- Connection lifecycle and backoff + +## Troubleshooting + +### Missing `google.golang.org/grpc` Packages + +Add grpc-go to your module: + +```bash +go get google.golang.org/grpc +``` + +### `grpc: error while marshaling` + +Confirm that both the client and server use the generated `CodecV2{}` and that +the generated model file is compiled into the same package as the gRPC companion. + +### `UNIMPLEMENTED` + +Confirm that the generated service was registered with +`RegisterGreeterServer(...)`, and that the client and server were generated from +the same package, service, and method names. + +### Protobuf Clients Cannot Decode the Service + +Fory gRPC companions do not use protobuf wire encoding for messages. Use a +Fory-generated client for Fory-generated services, or provide a separate +protobuf service endpoint for generic protobuf clients. diff --git a/versioned_docs/version-1.3.0/guide/go/index.md b/versioned_docs/version-1.3.0/guide/go/index.md new file mode 100644 index 00000000000..2cf4ebaf40c --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/go/index.md @@ -0,0 +1,160 @@ +--- +title: Go Serialization Guide +sidebar_position: 0 +id: index +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. +--- + +Apache Fory Go is a high-performance serialization library for Go. It supports xlang mode for cross-language payloads and native mode for Go-only payloads, with automatic object graph serialization, circular references, polymorphism, and schema-aware serializers. + +## Why Fory Go? + +- **High Performance**: Fast serialization and optimized binary protocols +- **Xlang**: Seamless data exchange with Java, Python, C++, Rust, + JavaScript/TypeScript, C#, Swift, Dart, Scala, and Kotlin +- **Automatic Serialization**: No IDL definitions or schema compilation required +- **Reference Tracking**: Built-in support for circular references and shared objects +- **Type Safety**: Strong typing with schema-aware serializers +- **Schema Evolution**: Compatible mode for forward/backward compatibility +- **Thread-Safe Option**: Pool-based thread-safe wrapper for concurrent use + +## Quick Start + +### Installation + +**Requirements**: Go 1.24 or later + +```bash +go get github.com/apache/fory/go/fory +``` + +### Basic Usage + +```go +package main + +import ( + "fmt" + "github.com/apache/fory/go/fory" +) + +type User struct { + ID int64 + Name string + Age int32 +} + +func main() { + // Create an xlang Fory instance. + f := fory.New(fory.WithXlang(true)) + + // Register struct with a type ID + if err := f.RegisterStruct(User{}, 1); err != nil { + panic(err) + } + + // Serialize + user := &User{ID: 1, Name: "Alice", Age: 30} + data, err := f.Serialize(user) + if err != nil { + panic(err) + } + + // Deserialize + var result User + if err := f.Deserialize(data, &result); err != nil { + panic(err) + } + + fmt.Printf("Deserialized: %+v\n", result) + // Output: Deserialized: {ID:1 Name:Alice Age:30} +} +``` + +## Xlang Mode And Native Mode + +Use xlang mode for cross-language payloads and schemas shared with other Fory implementations. Xlang mode is the default Go wire mode, and Go examples that use it set `fory.WithXlang(true)` explicitly so the mode choice is visible. + +Use native mode for Go-only traffic. Native mode is selected with `fory.WithXlang(false)` and keeps Go object serialization in Go-native form. It is optimized for Go structs, pointers, interfaces, and Go-specific type behavior that does not need a portable xlang mapping. Compatible mode is enabled by default. Set `fory.WithCompatible(false)` only when every reader and writer uses the same Go struct schema and you want faster serialization and smaller size. + +See [Xlang Serialization](xlang-serialization.md) for Go xlang registration and interoperability rules, and [Native Serialization](native-serialization.md) for Go-only payloads. + +## Configuration + +Fory Go uses a functional options pattern for configuration: + +```go +f := fory.New( + fory.WithXlang(true), + fory.WithTrackRef(true), // Enable reference tracking + fory.WithMaxDepth(20), // Set max nesting depth +) +``` + +See [Configuration](configuration.md) for all available options. + +## Supported Types + +Fory Go supports a wide range of types: + +- **Primitives**: `bool`, `int8`-`int64`, `uint8`-`uint64`, `float32`, `float64`, `string` +- **Collections**: slices, maps, sets +- **Time**: `time.Time`, `time.Duration` +- **Pointers**: pointer types with automatic nil handling +- **Structs**: any struct with exported fields + +See [Supported Types](supported-types.md) for the complete type mapping. + +## Xlang Serialization + +Fory Go is fully compatible with other Fory implementations. Data serialized in +Go can be deserialized in Java, Python, C++, Rust, JavaScript/TypeScript, C#, +Swift, Dart, Scala, or Kotlin: + +```go +// Go serialization +f := fory.New(fory.WithXlang(true)) +f.RegisterStruct(User{}, 1) +data, _ := f.Serialize(&User{ID: 1, Name: "Alice"}) +// 'data' can be deserialized by Java, Python, etc. +``` + +See [Xlang Serialization](xlang-serialization.md) for type mapping and compatibility details. + +## Documentation + +| Topic | Description | +| ----------------------------------------------- | -------------------------------------- | +| [Basic Serialization](basic-serialization.md) | Core APIs and usage patterns | +| [Xlang Serialization](xlang-serialization.md) | Multi-language serialization | +| [Native Serialization](native-serialization.md) | Go-only serialization | +| [Configuration](configuration.md) | Options and settings | +| [Schema Metadata](schema-metadata.md) | Field-level configuration | +| [Type Registration](type-registration.md) | Registering types for serialization | +| [Supported Types](supported-types.md) | Complete type support reference | +| [References](references.md) | Circular references and shared objects | +| [Schema Evolution](schema-evolution.md) | Forward/backward compatibility | +| [Custom Serializers](custom-serializers.md) | Extend serialization behavior | +| [Thread Safety](thread-safety.md) | Concurrent usage patterns | +| [gRPC Support](grpc-support.md) | Fory payloads over grpc-go | +| [Troubleshooting](troubleshooting.md) | Common issues and solutions | + +## Related Resources + +- [Xlang Serialization Specification](../../specification/xlang_serialization_spec.md) +- [Xlang Type Mapping](../../specification/xlang_type_mapping.md) +- [GitHub Repository](https://github.com/apache/fory) diff --git a/versioned_docs/version-1.3.0/guide/go/native-serialization.md b/versioned_docs/version-1.3.0/guide/go/native-serialization.md new file mode 100644 index 00000000000..5e74d88b4eb --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/go/native-serialization.md @@ -0,0 +1,219 @@ +--- +title: Native Serialization +sidebar_position: 3 +id: native_serialization +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. +--- + +Go native serialization is the Go-only wire mode selected with `fory.WithXlang(false)`. Use it +when every writer and reader is a Go service and the payload should follow Go's type system instead +of the portable xlang type system. + +Use [Xlang Serialization](xlang-serialization.md), the default Go mode, when bytes must be read by +Java, Python, C++, Rust, JavaScript/TypeScript, C#, Swift, Dart, Scala, Kotlin, +or another non-Go Fory implementation. + +## When To Use Native Serialization + +Use native serialization when: + +- A payload is produced and consumed only by Go applications. +- The data model uses Go-specific behavior such as native `int`/`uint`, nil slices, nil maps, + pointers, interfaces, or Go-only dynamic values. +- You want faster serialization and smaller size, and every reader uses the same struct schema as + the writer. +- You want compatible schema evolution for Go-only rolling deployments without committing to a + cross-language type mapping. +- You are using reflection or code-generated serializers for Go structs that never leave Go. + +## Create a Native-Mode Fory Instance + +```go +package main + +import "github.com/apache/fory/go/fory" + +type Order struct { + ID int64 + Amount float64 +} + +func main() { + f := fory.New(fory.WithXlang(false)) + if err := f.RegisterStruct(Order{}, 100); err != nil { + panic(err) + } + + data, err := f.Serialize(&Order{ID: 1, Amount: 42.5}) + if err != nil { + panic(err) + } + + var decoded Order + if err := f.Deserialize(data, &decoded); err != nil { + panic(err) + } +} +``` + +Reuse a configured `Fory` instance. The default instance owns reusable buffers and is not +thread-safe; use the thread-safe wrapper for concurrent goroutines. + +```go +import ( + "github.com/apache/fory/go/fory" + "github.com/apache/fory/go/fory/threadsafe" +) + +f := threadsafe.New(fory.WithXlang(false), fory.WithTrackRef(true)) +_ = f.RegisterStruct(Order{}, 100) +``` + +## Schema Evolution + +Native serialization defaults to compatible mode. Keep that default when Go-only services roll +independently: + +```go +writer := fory.New(fory.WithXlang(false)) +reader := fory.New(fory.WithXlang(false)) +``` + +Compatible mode writes schema metadata so readers can tolerate added, removed, or reordered fields +when field names or explicit field IDs remain compatible. See [Schema Evolution](schema-evolution.md). + +For faster serialization and smaller size, set `WithCompatible(false)` only when +every reader and writer always uses the same Go struct schema. + +## Registration + +Register structs before serializing them. Prefer explicit numeric IDs for long-lived payloads: + +```go +_ = f.RegisterStruct(Order{}, 100) +_ = f.RegisterStruct(LineItem{}, 101) +``` + +Name-based registration is useful when ID coordination is harder: + +```go +_ = f.RegisterStructByName(Order{}, "example.Order") +``` + +If you register without stable IDs, every writer and reader must make the same registration choices. + +## Go Object Surface + +Native serialization keeps Go data in Go-native form: + +- Primitive numeric types, including Go-native `int` and `uint`. +- Structs with exported fields. +- Slices, arrays, maps, and Fory sets. +- Pointers and nil values, including nil slices and maps. +- Interfaces and dynamic values when registered serializers can resolve their concrete types. +- Time values such as `time.Time` and `time.Duration`. +- Reflection-based and code-generated serializers. + +Use [Supported Types](supported-types.md) for the full type surface and xlang mapping details. + +## References And Pointers + +Enable reference tracking for shared object identity or cycles: + +```go +f := fory.New(fory.WithXlang(false), fory.WithTrackRef(true)) + +type Node struct { + Value int32 + Next *Node `fory:"ref"` +} +``` + +Disable reference tracking for value-shaped data. It is faster and smaller, but repeated pointers +deserialize as independent values and cyclic graphs are unsupported. + +## Buffer Ownership + +The default `Fory` instance reuses its internal buffer. Copy serialized bytes if they must outlive +the next serialization call: + +```go +data, _ := f.Serialize(value) +stable := append([]byte(nil), data...) +``` + +The thread-safe wrapper copies bytes before returning them. For high-throughput single-threaded +code, serialize into a caller-owned `ByteBuffer`: + +```go +buf := fory.NewByteBuffer(nil) +err := f.SerializeTo(buf, value) +data := buf.GetByteSlice(0, buf.WriterIndex()) +_ = err +_ = data +``` + +## Performance Guidelines + +- Reuse `Fory` or the thread-safe wrapper instead of constructing a Fory instance per request. +- Use `WithCompatible(false)` only when every reader and writer always uses the same Go struct + schema and wants faster serialization and smaller size. +- Register structs with explicit numeric IDs. +- Disable reference tracking unless the graph requires identity or cycles. +- Use code generation for hot Go structs when reflection overhead matters. +- Copy returned bytes only when the data must survive the next serialization call. + +## Native And Xlang Comparison + +| Requirement | Use native serialization | Use xlang serialization | +| -------------------------------------- | ------------------------ | ----------------------- | +| Go-only payloads | Yes | Optional | +| Non-Go readers or writers | No | Yes | +| Go-native `int`, `uint`, nil slice/map | Yes | Limited | +| Same-schema compact payloads | Yes | No | +| Compatible schema evolution by default | Yes | Yes | +| Portable type mapping across languages | No | Yes | + +## Troubleshooting + +### A non-Go implementation cannot read the payload + +The writer is using native serialization. Rebuild it with `fory.WithXlang(true)` and align type +registration with every peer. + +### A rolling deployment fails after a field change + +Native serialization defaults to compatible mode. Keep that default when struct definitions can +differ. + +### A nil slice or map changes shape + +Use native serialization for Go-only payloads that must preserve Go nil slice/map semantics. +Cross-language schemas should model nullability explicitly. + +### Returned bytes change after another serialization + +The default `Fory` instance reuses its buffer. Copy the byte slice or use `threadsafe.New(...)`. + +## Related Topics + +- [Xlang Serialization](xlang-serialization.md) - Cross-language Go payloads +- [Configuration](configuration.md) - Go options +- [Type Registration](type-registration.md) - Struct and enum registration +- [References](references.md) - Shared and circular references +- [Schema Evolution](schema-evolution.md) - Compatible mode +- [Code Generation](codegen.md) - Generated serializers diff --git a/versioned_docs/version-1.3.0/guide/go/references.md b/versioned_docs/version-1.3.0/guide/go/references.md new file mode 100644 index 00000000000..32d09d76003 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/go/references.md @@ -0,0 +1,356 @@ +--- +title: References +sidebar_position: 8 +id: references +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. +--- + +Fory Go supports reference tracking to handle circular references and shared objects. This is essential for serializing complex data structures like graphs, trees with parent pointers, and linked lists with cycles. + +## Enabling Reference Tracking + +Reference tracking is **disabled by default**. Enable it when creating a Fory instance: + +```go +f := fory.New(fory.WithXlang(true), fory.WithTrackRef(true)) +``` + +**Important**: Global reference tracking must be enabled for any reference tracking to occur. When `WithTrackRef(false)` (the default), all per-field reference tags are ignored. + +## How Reference Tracking Works + +### Without Reference Tracking (Default) + +When disabled, each object is serialized independently: + +```go +f := fory.New(fory.WithXlang(true)) // TrackRef disabled by default + +shared := &Data{Value: 42} +container := &Container{A: shared, B: shared} + +data, _ := f.Serialize(container) +// 'shared' is serialized TWICE (no deduplication) +``` + +### With Reference Tracking + +When enabled, objects are tracked by identity: + +```go +f := fory.New(fory.WithXlang(true), fory.WithTrackRef(true)) + +shared := &Data{Value: 42} +container := &Container{A: shared, B: shared} + +data, _ := f.Serialize(container) +// 'shared' is serialized ONCE, second occurrence is a reference +``` + +## Reference Flags + +Fory uses flags to indicate reference states during serialization: + +| Flag | Value | Meaning | +| ------------------ | ----- | ----------------------------------------- | +| `NullFlag` | -3 | Nil/null value | +| `RefFlag` | -2 | Reference to previously serialized object | +| `NotNullValueFlag` | -1 | Non-null value (data follows) | +| `RefValueFlag` | 0 | Reference value flag | + +## Referenceable Types + +Only certain types support reference tracking. In xlang mode, the following types can track references: + +| Type | Reference Tracked | Notes | +| ----------------------------- | ----------------- | ------------------------------ | +| `*struct` (pointer to struct) | Yes | Enable with `fory:"ref"` tag | +| `any` (interface) | Yes | Automatically tracked | +| `[]T` (slices) | Yes | Enable with `fory:"ref"` tag | +| `map[K]V` | Yes | Enable with `fory:"ref"` tag | +| `*int`, `*string`, etc. | No | Pointer to primitives excluded | +| Primitives | No | Value types | +| `time.Time`, `time.Duration` | No | Value types | +| Arrays (`[N]T`) | No | Value types | + +## Per-Field Reference Control + +By default, reference tracking is **disabled** for individual fields even when global `WithTrackRef(true)` is set. You can enable reference tracking for specific fields using the `ref` struct tag: + +```go +type Container struct { + // Enable ref tracking for this field + SharedData *Data `fory:"ref"` + + // Explicitly disable ref tracking (same as default) + SimpleData *Data `fory:"ref=false"` +} +``` + +**Important notes**: + +- Per-field tags only take effect when global `WithTrackRef(true)` is set +- When global `WithTrackRef(false)` (default), all field ref tags are ignored +- Applies to slices, maps, and pointer to struct fields +- Pointer to primitive types (e.g., `*int`, `*string`) cannot use this tag +- Default is `ref=false` (no reference tracking per field) + +See [Struct Tags](schema-metadata.md) for more details. + +## Circular References + +Reference tracking is required for circular data structures: + +### Circular Linked List + +```go +type Node struct { + Value int32 + Next *Node `fory:"ref"` +} + +f := fory.New(fory.WithXlang(true), fory.WithTrackRef(true)) +f.RegisterStruct(Node{}, 1) + +// Create circular list +n1 := &Node{Value: 1} +n2 := &Node{Value: 2} +n3 := &Node{Value: 3} +n1.Next = n2 +n2.Next = n3 +n3.Next = n1 // Circular reference back to n1 + +data, _ := f.Serialize(n1) + +var result Node +f.Deserialize(data, &result) +// Circular structure is preserved +// result.Next.Next.Next == &result +``` + +### Parent-Child Tree + +```go +type TreeNode struct { + Value string + Parent *TreeNode `fory:"ref"` + Children []*TreeNode `fory:"ref"` +} + +f := fory.New(fory.WithXlang(true), fory.WithTrackRef(true)) +f.RegisterStruct(TreeNode{}, 1) + +root := &TreeNode{Value: "root"} +child1 := &TreeNode{Value: "child1", Parent: root} +child2 := &TreeNode{Value: "child2", Parent: root} +root.Children = []*TreeNode{child1, child2} + +data, _ := f.Serialize(root) + +var result TreeNode +f.Deserialize(data, &result) +// result.Children[0].Parent == &result +``` + +### Graph Structures + +```go +type GraphNode struct { + ID int32 + Neighbors []*GraphNode `fory:"ref"` +} + +f := fory.New(fory.WithXlang(true), fory.WithTrackRef(true)) +f.RegisterStruct(GraphNode{}, 1) + +// Create a graph +a := &GraphNode{ID: 1} +b := &GraphNode{ID: 2} +c := &GraphNode{ID: 3} + +// Bidirectional connections +a.Neighbors = []*GraphNode{b, c} +b.Neighbors = []*GraphNode{a, c} +c.Neighbors = []*GraphNode{a, b} + +data, _ := f.Serialize(a) + +var result GraphNode +f.Deserialize(data, &result) +``` + +## Shared Object Deduplication + +Reference tracking also deduplicates shared objects: + +```go +type Config struct { + Setting string +} + +type Application struct { + MainConfig *Config `fory:"ref"` + BackupConfig *Config `fory:"ref"` + FallbackConfig *Config `fory:"ref"` +} + +f := fory.New(fory.WithXlang(true), fory.WithTrackRef(true)) +f.RegisterStruct(Config{}, 1) +f.RegisterStruct(Application{}, 2) + +// Shared configuration +config := &Config{Setting: "value"} + +// Multiple references to same object +app := &Application{ + MainConfig: config, + BackupConfig: config, + FallbackConfig: config, +} + +data, _ := f.Serialize(app) +// 'config' serialized once, others are references + +var result Application +f.Deserialize(data, &result) +// result.MainConfig == result.BackupConfig == result.FallbackConfig +``` + +## Performance Considerations + +### Overhead + +Reference tracking adds overhead: + +- Memory for tracking seen objects (hash map) +- Hash lookups during serialization +- Additional bytes for reference flags and IDs + +### When to Enable + +**Enable reference tracking when**: + +- Data has circular references +- Same object referenced multiple times +- Serializing graph structures +- Object identity must be preserved + +**Disable reference tracking when**: + +- Data is tree-structured (no cycles) +- Each object appears only once +- Maximum performance is required +- Object identity doesn't matter + +### Memory Usage + +Reference tracking maintains a map of serializing objects: + +```go +// Internal reference tracking structure +type RefResolver struct { + writtenObjects map[refKey]int32 // pointer -> reference ID + readObjects []reflect.Value // reference ID -> object +} +``` + +For large object graphs, this may increase memory usage. + +## Error Handling + +### Without Reference Tracking + +Circular references without tracking cause stack overflow or max depth errors: + +```go +f := fory.New(fory.WithXlang(true)) // No reference tracking + +n1 := &Node{Value: 1} +n1.Next = n1 // Self-reference + +data, err := f.Serialize(n1) +// Error: max depth exceeded (or stack overflow) +``` + +### Invalid Reference ID + +During deserialization, an invalid reference ID produces an error: + +```go +// Error type: ErrKindInvalidRefId +``` + +This occurs when serialized data contains a reference to an object that wasn't previously serialized. + +## Complete Example + +```go +package main + +import ( + "fmt" + "github.com/apache/fory/go/fory" +) + +type Person struct { + Name string + Friends []*Person `fory:"ref"` + BestFriend *Person `fory:"ref"` +} + +func main() { + f := fory.New(fory.WithXlang(true), fory.WithTrackRef(true)) + f.RegisterStruct(Person{}, 1) + + // Create people with mutual friendships + alice := &Person{Name: "Alice"} + bob := &Person{Name: "Bob"} + charlie := &Person{Name: "Charlie"} + + alice.Friends = []*Person{bob, charlie} + alice.BestFriend = bob + + bob.Friends = []*Person{alice, charlie} + bob.BestFriend = alice // Mutual best friends + + charlie.Friends = []*Person{alice, bob} + + // Serialize + data, err := f.Serialize(alice) + if err != nil { + panic(err) + } + fmt.Printf("Serialized %d bytes\n", len(data)) + + // Deserialize + var result Person + if err := f.Deserialize(data, &result); err != nil { + panic(err) + } + + // Verify circular references preserved + fmt.Printf("Alice's best friend: %s\n", result.BestFriend.Name) + fmt.Printf("Bob's best friend: %s\n", result.BestFriend.BestFriend.Name) + // Output: Alice (circular reference preserved) +} +``` + +## Related Topics + +- [Configuration](configuration.md) +- [Struct Tags](schema-metadata.md) +- [Xlang Serialization](xlang-serialization.md) diff --git a/versioned_docs/version-1.3.0/guide/go/schema-evolution.md b/versioned_docs/version-1.3.0/guide/go/schema-evolution.md new file mode 100644 index 00000000000..55095a4032e --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/go/schema-evolution.md @@ -0,0 +1,393 @@ +--- +title: Schema Evolution +sidebar_position: 9 +id: schema_evolution +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. +--- + +Schema evolution allows your data structures to change over time while maintaining compatibility with previously serialized data. Fory Go supports this through compatible mode, which is the default for both xlang and native mode. + +## Compatible Mode Defaults + +For cross-language payloads, create a Fory instance with the default xlang settings: + +```go +f := fory.New(fory.WithXlang(true)) +``` + +For Go-only native-mode payloads that need schema evolution, use native mode and keep the compatible +default: + +```go +f := fory.New(fory.WithXlang(false)) +``` + +## How It Works + +### With Compatible Mode + +- Type metadata is written to serialized data +- Supports adding, removing, and reordering fields +- Enables forward and backward compatibility + +### Same-Schema Optimization + +- Compact serialization without evolution metadata +- Struct hash is checked during deserialization +- Any schema change causes `ErrKindHashMismatch` + +## Supported Schema Changes + +### Adding Fields + +New fields can be added; they receive zero values when deserializing old data: + +```go +// Version 1 +type UserV1 struct { + ID int64 + Name string +} + +// Version 2 (added Email) +type UserV2 struct { + ID int64 + Name string + Email string // New field +} + +f := fory.New(fory.WithXlang(true)) +f.RegisterStruct(UserV1{}, 1) + +// Serialize with V1 +userV1 := &UserV1{ID: 1, Name: "Alice"} +data, _ := f.Serialize(userV1) + +// Deserialize with V2 +f2 := fory.New(fory.WithXlang(true)) +f2.RegisterStruct(UserV2{}, 1) + +var userV2 UserV2 +f2.Deserialize(data, &userV2) +// userV2.Email = "" (zero value) +``` + +### Removing Fields + +Removed fields are skipped during deserialization: + +```go +// Version 1 +type ConfigV1 struct { + Host string + Port int32 + Timeout int64 + Debug bool // Will be removed +} + +// Version 2 (removed Debug) +type ConfigV2 struct { + Host string + Port int32 + Timeout int64 + // Debug field removed +} + +f := fory.New(fory.WithXlang(true)) +f.RegisterStruct(ConfigV1{}, 1) + +// Serialize with V1 +config := &ConfigV1{Host: "localhost", Port: 8080, Timeout: 30, Debug: true} +data, _ := f.Serialize(config) + +// Deserialize with V2 +f2 := fory.New(fory.WithXlang(true)) +f2.RegisterStruct(ConfigV2{}, 1) + +var configV2 ConfigV2 +f2.Deserialize(data, &configV2) +// Debug field data is skipped +``` + +### Reordering Fields + +Field order can change between versions: + +```go +// Version 1 +type PersonV1 struct { + FirstName string + LastName string + Age int32 +} + +// Version 2 (reordered) +type PersonV2 struct { + Age int32 // Moved up + LastName string + FirstName string // Moved down +} +``` + +Compatible mode handles this automatically by matching fields by name. + +### Compatible Scalar Field Changes + +Compatible mode can also read selected scalar type changes for matched top-level +struct fields when the serialized value converts without changing its logical +value: + +- `bool` fields can be read from strings that are exactly `"0"`, `"1"`, + `"true"`, or `"false"`. Bool values read as strings become `"true"` or + `"false"`, and numeric `0` and `1` can be read as bools. +- Integer, unsigned integer, floating point, and decimal fields can be read + across numeric scalar types only when the value is represented exactly by the + target field type. +- Numeric fields can be read from strings only when the string is a finite ASCII + decimal literal with no whitespace, leading `+`, Unicode digits, separators, + radix prefixes, or special values such as `NaN` and `Infinity`. +- Numeric fields read as strings use canonical output: integers have normal + decimal text, floating point values use exact plain decimal text with a + decimal point, and decimals omit insignificant trailing fractional zeros. + +Scalar conversion composes with pointer and `optional.Optional[T]` fields when +the matched top-level scalar field is not reference-tracked. If a remote +nullable or optional field is absent, the local field follows the normal +missing/null compatible-mode behavior. Reference-tracked scalar type changes are +incompatible. If a present value cannot be converted losslessly, +deserialization fails with a data error instead of treating the field as +missing. + +## Incompatible Changes + +Some changes are NOT supported, even in compatible mode: + +### Type Changes + +```go +// NOT SUPPORTED +type V1 struct { + Value []int32 // list of int32 +} + +type V2 struct { + Value []string // Element type changed - INCOMPATIBLE +} +``` + +### Renaming Fields + +```go +// NOT SUPPORTED (treated as remove + add) +type V1 struct { + UserName string +} + +type V2 struct { + Username string // Different name - NOT a rename +} +``` + +This is treated as removing `UserName` and adding `Username`, resulting in data loss. + +## Best Practices + +### 1. Use Compatible Mode for Persistent Data + +```go +// Default xlang payloads already use compatible mode. +f := fory.New(fory.WithXlang(true)) +``` + +For Go-only native-mode data stored in databases, files, or caches, use compatible mode: + +```go +f := fory.New(fory.WithXlang(false)) +``` + +### 2. Provide Default Values + +```go +type ConfigV2 struct { + Host string + Port int32 + Timeout int64 + Retries int32 // New field +} + +func NewConfigV2() *ConfigV2 { + return &ConfigV2{ + Retries: 3, // Default value + } +} + +// After deserialize, apply defaults +if config.Retries == 0 { + config.Retries = 3 +} +``` + +## Xlang Schema Evolution + +Schema evolution works across languages: + +### Go (Producer) + +```go +type MessageV1 struct { + ID int64 + Content string +} + +f := fory.New(fory.WithXlang(true)) +f.RegisterStruct(MessageV1{}, 1) +data, _ := f.Serialize(&MessageV1{ID: 1, Content: "Hello"}) +``` + +### Java (Consumer with newer schema) + +```java +public class Message { + long id; + String content; + String author; // New field in Java +} + +Fory fory = Fory.builder().withXlang(true).build(); +fory.register(Message.class, 1); +Message msg = fory.deserialize(data, Message.class); +// msg.author will be null +``` + +## Performance Considerations + +Compatible mode mainly affects serialized size: + +| Aspect | `WithCompatible(false)` | Compatible mode | +| ------------------ | ----------------------- | -------------------------------------------------------- | +| Serialized Size | Smaller | Larger (includes metadata, especially without field IDs) | +| Speed | Fast | Similar (metadata is just memcpy) | +| Schema Flexibility | Same schema required | Add, remove, and reorder fields | + +**Note**: Using field IDs (`fory:"id=N"`) reduces metadata size in compatible mode. + +**Recommendation**: Use compatible mode for: + +- Persistent storage +- Cross-service communication +- Long-lived caches + +Use `WithCompatible(false)` only when every reader and writer always uses the same Go struct schema +and you want faster serialization and smaller size. For xlang payloads, use `WithCompatible(false)` only after verifying that every language uses the same schema, or when native types are generated from Fory schema IDL. Same-schema uses include: + +- In-memory operations +- Same-schema communication +- Faster serialization and smaller size + +### Per-Struct Opt-Out + +For one struct, you can opt out of evolution metadata by implementing `ForyEvolving` and returning +`false`: + +```go +type SameSchemaMessage struct { + ID int64 +} + +func (SameSchemaMessage) ForyEvolving() bool { + return false +} +``` + +## Error Handling + +### Hash Mismatch (Native Same-Schema Mode) + +```go +f := fory.New(fory.WithXlang(false), fory.WithCompatible(false)) + +// Schema changed without compatible mode +err := f.Deserialize(oldData, &newStruct) +// Error: ErrKindHashMismatch +``` + +### Unknown Fields + +In compatible mode, unknown fields are skipped silently. To detect them: + +```go +// Currently, Fory skips unknown fields automatically +// No explicit API for detecting unknown fields +``` + +## Complete Example + +```go +package main + +import ( + "fmt" + "github.com/apache/fory/go/fory" +) + +// V1: Initial schema +type ProductV1 struct { + ID int64 + Name string + Price float64 +} + +// V2: Added fields +type ProductV2 struct { + ID int64 + Name string + Price float64 + Description string // New + InStock bool // New +} + +func main() { + // Serialize with V1 + f1 := fory.New(fory.WithXlang(true)) + f1.RegisterStruct(ProductV1{}, 1) + + product := &ProductV1{ID: 1, Name: "Widget", Price: 9.99} + data, _ := f1.Serialize(product) + fmt.Printf("V1 serialized: %d bytes\n", len(data)) + + // Deserialize with V2 + f2 := fory.New(fory.WithXlang(true)) + f2.RegisterStruct(ProductV2{}, 1) + + var productV2 ProductV2 + if err := f2.Deserialize(data, &productV2); err != nil { + panic(err) + } + + fmt.Printf("ID: %d\n", productV2.ID) + fmt.Printf("Name: %s\n", productV2.Name) + fmt.Printf("Price: %.2f\n", productV2.Price) + fmt.Printf("Description: %q (zero value)\n", productV2.Description) + fmt.Printf("InStock: %v (zero value)\n", productV2.InStock) +} +``` + +## Related Topics + +- [Configuration](configuration.md) +- [Xlang Serialization](xlang-serialization.md) +- [Troubleshooting](troubleshooting.md) diff --git a/versioned_docs/version-1.3.0/guide/go/schema-metadata.md b/versioned_docs/version-1.3.0/guide/go/schema-metadata.md new file mode 100644 index 00000000000..aff97a9b8c4 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/go/schema-metadata.md @@ -0,0 +1,393 @@ +--- +title: Schema Metadata +sidebar_position: 5 +id: schema_metadata +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. +--- + +Fory Go uses struct tags to customize field-level serialization behavior. This allows fine-grained control over how individual fields are serialized. + +## Tag Syntax + +The general syntax for Fory struct tags: + +```go +type MyStruct struct { + Field Type `fory:"option1,option2=value"` +} +``` + +Multiple options are separated by commas (`,`). + +## Available Tags + +### Field ID + +Use `id=N` to assign a numeric ID to a field for compact encoding: + +```go +type User struct { + ID int64 `fory:"id=0"` + Name string `fory:"id=1"` + Age int32 `fory:"id=2"` +} +``` + +**Benefits**: + +- Smaller serialized size (numeric IDs vs field names) +- Faster serialization/deserialization +- Required for optimal cross-language compatibility + +**Notes**: + +- IDs must be unique within a struct +- IDs must be >= 0 +- If not specified, field name is used (larger payload) + +### Ignoring Fields + +Use `-` to exclude a field from serialization: + +```go +type User struct { + ID int64 + Name string + Password string `fory:"-"` // Not serialized +} +``` + +The `Password` field will not be included in serialized output and will remain at its zero value after deserialization. + +### Nullable + +Use `nullable` to control whether null flags are written for pointer, slice, map, or interface fields: + +```go +type Record struct { + // Write null flag for this field (allows nil values) + OptionalData *Data `fory:"nullable"` + + // Skip null flag (field must not be nil) + RequiredData *Data `fory:"nullable=false"` +} +``` + +**Notes**: + +- Only applies to pointer, slice, map, and interface fields +- When `nullable=false`, serializing a nil value will cause an error +- Xlang mode defaults top-level struct fields to nullable only for pointer and `optional` carrier + fields. Native mode defaults pointer, slice, map, and interface fields to nullable. + +### Reference Tracking + +Control per-field reference tracking for slices, maps, or pointer to struct fields: + +```go +type Container struct { + // Enable reference tracking for this field + SharedData *Data `fory:"ref"` + + // Disable reference tracking for this field + SimpleData *Data `fory:"ref=false"` +} +``` + +**Notes**: + +- Applies to slices, maps, and pointer to struct fields +- Pointer to primitive types (e.g., `*int`, `*string`) cannot use this tag +- Default is `ref=false` (no reference tracking) +- When global `WithTrackRef(false)` is set, field ref tags are ignored +- When global `WithTrackRef(true)` is set, use `ref=false` to disable for specific fields + +**Use cases**: + +- Enable for fields that may be circular or shared +- Disable for fields that are always unique (optimization) + +### Encoding + +Use `encoding` to control how numeric fields are encoded: + +```go +type Metrics struct { + // Variable-length encoding (default, smaller for small values) + Count int64 `fory:"encoding=varint"` + + // Fixed-length encoding (consistent size) + Timestamp int64 `fory:"encoding=fixed"` + + // Tagged encoding (includes type tag) + Value int64 `fory:"encoding=tagged"` +} +``` + +**Supported encodings**: + +| Type | Options | Default | +| -------- | --------------------------- | -------- | +| `int32` | `varint`, `fixed` | `varint` | +| `uint32` | `varint`, `fixed` | `varint` | +| `int64` | `varint`, `fixed`, `tagged` | `varint` | +| `uint64` | `varint`, `fixed`, `tagged` | `varint` | + +**When to use**: + +- `varint`: Best for values that are often small (default) +- `fixed`: Best for values that use full range (e.g., timestamps, hashes) +- `tagged`: When type information needs to be preserved + +### Type Overrides + +Use `type=` to override inferred carrier semantics or nested value encoding: + +```go +type Foo struct { + // Force general list protocol. + Values []int32 `fory:"type=list"` + + // Override inner integer encoding for a general list + FixedValues []int32 `fory:"type=list(element=int32(encoding=fixed))"` + + // Override nested map/list integer encoding + Nested map[string][]*uint64 `fory:"type=map(value=list(element=uint64(encoding=tagged)))"` + + // Declare dense numeric array schema explicitly. + Dense []int32 `fory:"type=array(element=int32)"` + + // Use array schema inside a map value. + Packed map[string][]int32 `fory:"type=map(value=array(element=int32))"` +} +``` + +**Notes**: + +- `list(...)`, `array(...)`, `set(...)`, and `map(...)` are explicit container overrides +- `list(...)` always uses list schema and never collapses into dense array schema +- `array(element=...)` requires a bool or numeric element domain and rejects nullable elements and scalar encoding modifiers + +## Combining Tags + +Multiple tags can be combined using comma separator: + +```go +type Document struct { + ID int64 `fory:"id=0,encoding=fixed"` + Content string `fory:"id=1"` + Author *User `fory:"id=2,nullable=false,ref"` +} +``` + +## Integration with Other Tags + +Fory tags coexist with other struct tags: + +```go +type User struct { + ID int64 `json:"id" fory:"id=0"` + Name string `json:"name,omitempty" fory:"id=1"` + Password string `json:"-" fory:"-"` +} +``` + +Each tag namespace is independent. + +## Field Visibility + +Only **exported fields** (starting with uppercase) are considered: + +```go +type User struct { + ID int64 // Serialized + Name string // Serialized + password string // NOT serialized (unexported, no tag needed) +} +``` + +Unexported fields are always ignored, regardless of tags. + +## Field Ordering + +Fields are serialized in a consistent order based on: + +1. Field name (alphabetically in snake_case) +2. Field type + +This ensures cross-language compatibility where field order matters. + +## Struct Hash + +Fory computes a hash of struct fields for version checking: + +- Hash includes field names and types +- Hash is written to serialized data +- Mismatch triggers `ErrKindHashMismatch` + +Struct field changes affect the hash: + +```go +// These produce different hashes +type V1 struct { + UserID int64 +} + +type V2 struct { + UserId int64 // Different field name = different hash +} +``` + +## Examples + +### API Response Struct + +```go +type APIResponse struct { + Status int32 `json:"status" fory:"id=0"` + Message string `json:"message" fory:"id=1"` + Data any `json:"data" fory:"id=2"` + Internal string `json:"-" fory:"-"` // Ignored in both JSON and Fory +} +``` + +### Caching with Shared References + +```go +type CacheEntry struct { + Key string + Value *CachedData `fory:"ref"` // May be shared + Metadata *Metadata `fory:"ref=false"` // Always unique + ExpiresAt int64 +} +``` + +### Document with Circular References + +```go +type Document struct { + ID int64 + Title string + Parent *Document `fory:"ref"` // May reference self or siblings + Children []*Document `fory:"ref"` +} +``` + +## Tag Parsing Errors + +Invalid tags produce errors during registration: + +```go +type BadStruct struct { + Field int `fory:"invalid=option=format"` +} + +f := fory.New(fory.WithXlang(true)) +err := f.RegisterStruct(BadStruct{}, 1) +// Error: ErrKindInvalidTag +``` + +## Native Mode vs Xlang Mode + +Field configuration behaves differently depending on the serialization mode: + +**Native Mode**: + +- **Nullable**: Pointer, slice, map, and interface types are nullable by default +- **Ref tracking**: Disabled by default (`ref` tag not set) + +**Xlang Mode**: + +- **Nullable**: Pointer and `optional.Optional[T]` fields are nullable by default (slices, + maps, and interfaces are NOT nullable unless tagged) +- **Ref tracking**: Disabled by default (`ref` tag not set) + +You **need to configure fields** when: + +- A field can be nil (use pointer types like `*string`, `*int32`) +- A field needs reference tracking for shared/circular objects (use `fory:"ref"`) +- You want to reduce metadata size (use field IDs with `fory:"id=N"`) + +```go +// Xlang mode: explicit configuration required +type User struct { + ID int64 `fory:"id=0"` + Name string `fory:"id=1"` + Email *string `fory:"id=2"` // Pointer type for nullable + Friend *User `fory:"id=3,ref"` // Must declare ref for shared objects +} +``` + +### Default Values Summary + +| Option | Default | How to Enable | +| ---------- | ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ | +| `nullable` | Pointer and `optional.Optional[T]` fields in xlang mode; pointer, slice, map, and interface fields in native mode | Use `fory:"nullable"` or `fory:"nullable=false"` | +| `ref` | `false` | Add `fory:"ref"` tag | +| `id` | omitted | Add `fory:"id=N"` tag | + +## Best Practices + +1. **Use `-` for sensitive data**: Passwords, tokens, internal state +2. **Enable ref tracking for shared objects**: When the same pointer appears multiple times +3. **Disable ref tracking for simple fields**: Optimization when you know the field is unique +4. **Keep names consistent**: Cross-language names should match +5. **Document tag usage**: Especially for non-obvious configurations + +## Common Patterns + +### Ignoring Computed Fields + +```go +type Rectangle struct { + Width float64 + Height float64 + Area float64 `fory:"-"` // Computed, don't serialize +} + +func (r *Rectangle) ComputeArea() { + r.Area = r.Width * r.Height +} +``` + +### Circular Structure with Parent + +```go +type TreeNode struct { + Value string + Parent *TreeNode `fory:"ref"` // Circular back-reference + Children []*TreeNode `fory:"ref"` +} +``` + +### Mixed Serialization Needs + +```go +type Session struct { + ID string + UserID int64 + Token string `fory:"-"` // Security: don't serialize + User *User `fory:"ref"` // May be shared across sessions + CreatedAt int64 +} +``` + +## Related Topics + +- [References](references.md) +- [Basic Serialization](basic-serialization.md) +- [Schema Evolution](schema-evolution.md) diff --git a/versioned_docs/version-1.3.0/guide/go/supported-types.md b/versioned_docs/version-1.3.0/guide/go/supported-types.md new file mode 100644 index 00000000000..8371e53c539 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/go/supported-types.md @@ -0,0 +1,374 @@ +--- +title: Supported Types +sidebar_position: 7 +id: supported_types +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. +--- + +Fory Go supports a wide range of Go types for serialization. This guide covers all supported types and their cross-language mappings. + +## Primitive Types + +| Go Type | Fory TypeId | Encoding | Notes | +| ---------------- | ------------ | --------------------- | --------------------------------- | +| `bool` | BOOL (1) | 1 byte | | +| `int8` | INT8 (2) | 1 byte, signed | | +| `int16` | INT16 (3) | 2 bytes, signed | Little-endian | +| `int32` | INT32 (4) | Varint | Variable-length encoding | +| `int64` | INT64 (6) | Varint | Variable-length encoding | +| `int` | INT32/INT64 | Varint | Platform-dependent (32 or 64 bit) | +| `uint8` / `byte` | UINT8 (9) | 1 byte, unsigned | | +| `uint16` | UINT16 (10) | 2 bytes, unsigned | Little-endian | +| `uint32` | UINT32 (11) | Varuint | Variable-length encoding | +| `uint64` | UINT64 (13) | Varuint | Variable-length encoding | +| `float32` | FLOAT32 (17) | 4 bytes | IEEE 754 | +| `float64` | FLOAT64 (18) | 8 bytes | IEEE 754 | +| `string` | STRING (19) | Length-prefixed UTF-8 | | + +### Integer Encoding + +Fory uses variable-length integer encoding (varint) for better compression: + +- Small values use fewer bytes +- Negative values use ZigZag encoding +- Platform `int` maps to `int32` on 32-bit, `int64` on 64-bit systems + +```go +f := fory.New(fory.WithXlang(true)) + +// All integer types supported +var i8 int8 = 127 +var i16 int16 = 32767 +var i32 int32 = 2147483647 +var i64 int64 = 9223372036854775807 + +data, _ := f.Serialize(i64) // Uses varint encoding +``` + +## Collection Types + +### Root And Dynamic Slices + +When a slice is serialized as a root or dynamic value, primitive slices use the +dense array wire tags for compact transport. In struct fields, unannotated Go +slices are logical `list` fields; use `fory:"type=array(element=...)"` when a +struct field is dense numeric `array` data. + +| Go Type | Fory TypeId | Notes | +| --------------- | ------------- | --------------------- | +| `[]bool` | BOOL_ARRAY | Optimized encoding | +| `[]int8` | INT8_ARRAY | Optimized encoding | +| `[]int16` | INT16_ARRAY | Optimized encoding | +| `[]int32` | INT32_ARRAY | Optimized encoding | +| `[]int64` | INT64_ARRAY | Optimized encoding | +| `[]float32` | FLOAT32_ARRAY | Optimized encoding | +| `[]float64` | FLOAT64_ARRAY | Optimized encoding | +| `[]string` | LIST | Generic list encoding | +| `[]T` (any) | LIST (20) | Any serializable type | +| `[]I` (any/any) | LIST | Any interface type | + +```go +f := fory.New(fory.WithXlang(true)) + +// Primitive root slice (optimized dense array payload) +ints := []int32{1, 2, 3, 4, 5} +data, _ := f.Serialize(ints) + +// String slices +strs := []string{"a", "b", "c"} +data, _ = f.Serialize(strs) + +// Struct slices +users := []User{{ID: 1}, {ID: 2}} +data, _ = f.Serialize(users) + +// Dynamic slices +dynamic := []any{1, "hello", true} +data, _ = f.Serialize(dynamic) +``` + +### Maps + +| Go Type | Fory TypeId | Notes | +| -------------------- | ----------- | ----------------------- | +| `map[string]string` | MAP (22) | Optimized | +| `map[string]int64` | MAP | Optimized | +| `map[string]int32` | MAP | Optimized | +| `map[string]int` | MAP | Optimized | +| `map[string]float64` | MAP | Optimized | +| `map[string]bool` | MAP | Optimized | +| `map[int32]int32` | MAP | Optimized | +| `map[int64]int64` | MAP | Optimized | +| `map[int]int` | MAP | Optimized | +| `map[string]any` | MAP | Dynamic values | +| `map[any]any` | MAP | Dynamic keys and values | + +```go +f := fory.New(fory.WithXlang(true)) + +// String key maps +m1 := map[string]string{"key": "value"} +m2 := map[string]int64{"count": 42} + +// Integer key maps +m3 := map[int32]int32{1: 100, 2: 200} + +// Dynamic maps +m4 := map[string]any{ + "name": "Alice", + "age": int64(30), +} +``` + +### Sets + +Fory provides a generic `Set[T]` type (uses `map[T]struct{}` for zero memory overhead): + +```go +// Create a set of strings +s := fory.NewSet[string]() +s.Add("a", "b", "c") + +// Check membership +if s.Contains("a") { + fmt.Println("found") +} + +// Serialize +data, _ := f.Serialize(s) +``` + +## Time Types + +| Go Type | Fory TypeId | Notes | +| --------------- | -------------- | -------------------- | +| `time.Time` | TIMESTAMP (34) | Nanosecond precision | +| `time.Duration` | DURATION (33) | Nanosecond precision | + +```go +import "time" + +f := fory.New(fory.WithXlang(true)) + +// Timestamp +t := time.Now() +data, _ := f.Serialize(t) + +// Duration +d := 5 * time.Second +data, _ = f.Serialize(d) +``` + +## Struct Types + +| Category | Fory TypeId | Notes | +| ----------------------- | ---------------------------- | -------------------------------- | +| Struct | STRUCT (25) | Registered by ID, no evolution | +| Compatible Struct | COMPATIBLE_STRUCT (26) | With schema evolution | +| Named Struct | NAMED_STRUCT (27) | Registered by name, no evolution | +| Named Compatible Struct | NAMED_COMPATIBLE_STRUCT (28) | Named with schema evolution | + +### Struct Requirements + +1. **Exported fields only**: Fields starting with uppercase are serialized +2. **Supported field types**: All types listed in this document +3. **Registration**: Structs should be registered for cross-language use + +```go +type User struct { + ID int64 // Serialized + Name string // Serialized + Age int32 // Serialized + password string // NOT serialized (unexported) +} + +f := fory.New(fory.WithXlang(true)) +f.RegisterStruct(User{}, 1) + +user := &User{ID: 1, Name: "Alice", Age: 30, password: "secret"} +data, _ := f.Serialize(user) +``` + +### Nested Structs + +```go +type Address struct { + Street string + City string + Country string +} + +type Company struct { + Name string + Address Address + Founded int32 +} + +f := fory.New(fory.WithXlang(true)) +f.RegisterStruct(Address{}, 1) +f.RegisterStruct(Company{}, 2) +``` + +## Pointer Types + +| Go Type | Behavior | +| ------- | ---------------------------------------- | +| `*T` | Nil-able, reference tracked (if enabled) | +| `**T` | Nested pointers supported | + +```go +f := fory.New(fory.WithXlang(true), fory.WithTrackRef(true)) + +type Node struct { + Value int32 + Left *Node + Right *Node +} + +f.RegisterStruct(Node{}, 1) + +root := &Node{ + Value: 1, + Left: &Node{Value: 2}, + Right: &Node{Value: 3}, +} + +data, _ := f.Serialize(root) +``` + +### Nil Handling + +```go +var ptr *User = nil +data, _ := f.Serialize(ptr) + +var result *User +f.Deserialize(data, &result) +// result == nil +``` + +## Interface Types + +| Go Type | Fory TypeId | Notes | +| ------- | ----------- | ------------------ | +| `any` | UNION (31) | Polymorphic values | + +```go +f := fory.New(fory.WithXlang(true)) + +// Serialize any +var value any = "hello" +data, _ := f.Serialize(value) + +var result any +f.Deserialize(data, &result) +// result = "hello" (string) +``` + +For struct interfaces, register all possible concrete types: + +```go +type Shape interface { + Area() float64 +} + +type Circle struct { + Radius float64 +} + +func (c Circle) Area() float64 { + return 3.14159 * c.Radius * c.Radius +} + +f := fory.New(fory.WithXlang(true)) +f.RegisterStruct(Circle{}, 1) + +var shape Shape = Circle{Radius: 5.0} +data, _ := f.Serialize(shape) +``` + +## Binary Data + +| Go Type | Fory TypeId | Notes | +| -------- | ----------- | --------------------- | +| `[]byte` | BINARY (37) | Variable-length bytes | + +```go +f := fory.New(fory.WithXlang(true)) + +data := []byte{0x01, 0x02, 0x03, 0x04} +serialized, _ := f.Serialize(data) + +var result []byte +f.Deserialize(serialized, &result) +``` + +## Enum Types + +Go uses integer types for enums: + +```go +type Status int32 + +const ( + StatusPending Status = 0 + StatusActive Status = 1 + StatusComplete Status = 2 +) + +f := fory.New(fory.WithXlang(true)) +f.RegisterEnum(Status(0), 1) + +status := StatusActive +data, _ := f.Serialize(status) +``` + +## Xlang Type Mapping + +| Go Type | Java | Python | C++ | Rust | +| --------------- | ---------- | --------- | ------------------ | -------------- | +| `bool` | boolean | bool | bool | bool | +| `int8` | byte | int | int8_t | i8 | +| `int16` | short | int | int16_t | i16 | +| `int32` | int | int | int32_t | i32 | +| `int64` | long | int | int64_t | i64 | +| `float32` | float | float | float | f32 | +| `float64` | double | float | double | f64 | +| `string` | String | str | std::string | String | +| `[]T` | `List` | list | `std::vector` | `Vec` | +| `map[K]V` | `Map` | dict | std::unordered_map | `HashMap` | +| `time.Time` | Instant | datetime | - | - | +| `time.Duration` | Duration | timedelta | - | - | + +See [Xlang Serialization](xlang-serialization.md) for detailed mapping. + +## Unsupported Types + +The following Go types are **not supported**: + +- Channels (`chan T`) +- Functions (`func()`) +- Complex numbers (`complex64`, `complex128`) +- Unsafe pointers (`unsafe.Pointer`) + +Attempting to serialize these types will result in an error. + +## Related Topics + +- [Type Registration](type-registration.md) +- [Xlang Serialization](xlang-serialization.md) +- [References](references.md) diff --git a/versioned_docs/version-1.3.0/guide/go/thread-safety.md b/versioned_docs/version-1.3.0/guide/go/thread-safety.md new file mode 100644 index 00000000000..19f92095647 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/go/thread-safety.md @@ -0,0 +1,347 @@ +--- +title: Thread Safety +sidebar_position: 12 +id: thread_safety +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 guide covers concurrent usage patterns for Fory Go, including the thread-safe wrapper and best practices for multi-goroutine environments. + +## Default Fory Instance + +The default `Fory` instance is **not thread-safe**: + +```go +f := fory.New(fory.WithXlang(true)) + +// NOT SAFE: Concurrent access from multiple goroutines +go func() { + f.Serialize(value1) // Race condition! +}() +go func() { + f.Serialize(value2) // Race condition! +}() +``` + +### Why Not Thread-Safe? + +For performance, Fory reuses internal state: + +- Buffer is cleared and reused between calls +- Reference resolvers are reset +- Context objects are recycled + +This avoids allocations but requires exclusive access. + +## Thread-Safe Wrapper + +For concurrent use, use the `threadsafe` package: + +```go +import "github.com/apache/fory/go/fory/threadsafe" + +// Create thread-safe Fory +f := threadsafe.New() + +// Safe for concurrent use +go func() { + data, _ := f.Serialize(value1) +}() +go func() { + data, _ := f.Serialize(value2) +}() +``` + +### How It Works + +The thread-safe wrapper uses `sync.Pool`: + +1. **Acquire**: Gets a Fory instance from the pool +2. **Use**: Performs serialization/deserialization +3. **Copy**: Copies result data (buffer will be reused) +4. **Release**: Returns instance to pool + +```go +// Simplified implementation +func (f *Fory) Serialize(v any) ([]byte, error) { + fory := f.pool.Get().(*fory.Fory) + defer f.pool.Put(fory) + + data, err := fory.Serialize(v) + if err != nil { + return nil, err + } + + // Copy because underlying buffer will be reused + result := make([]byte, len(data)) + copy(result, data) + return result, nil +} +``` + +### API + +```go +// Create thread-safe instance +f := threadsafe.New() + +// Instance methods +data, err := f.Serialize(value) +err = f.Deserialize(data, &target) + +// Generic functions +data, err := threadsafe.Serialize(f, &value) +err = threadsafe.Deserialize(f, data, &target) + +// Global convenience functions +data, err := threadsafe.Marshal(&value) +err = threadsafe.Unmarshal(data, &target) +``` + +## Type Registration + +Type registration should be done before concurrent use: + +```go +f := threadsafe.New() + +// Register types BEFORE concurrent access +f.RegisterStruct(User{}, 1) +f.RegisterStruct(Order{}, 2) + +// Now safe to use concurrently +go func() { + f.Serialize(&User{ID: 1}) +}() +``` + +### Thread-Safe Registration + +The thread-safe wrapper handles registration safely: + +```go +// Safe: Registration is synchronized +f := threadsafe.New() +f.RegisterStruct(User{}, 1) // Thread-safe +``` + +However, for best performance, register all types at startup before concurrent use. + +## Zero-Copy Considerations + +### Non-Thread-Safe Instance + +With the default Fory, returned byte slices are views into the internal buffer: + +```go +f := fory.New(fory.WithXlang(true)) + +data1, _ := f.Serialize(value1) +// data1 is valid + +data2, _ := f.Serialize(value2) +// data1 is NOW INVALID (buffer was reused) +``` + +### Thread-Safe Instance + +The thread-safe wrapper copies data automatically: + +```go +f := threadsafe.New() + +data1, _ := f.Serialize(value1) +data2, _ := f.Serialize(value2) +// Both data1 and data2 are valid (independent copies) +``` + +This is safer but has allocation overhead. + +## Performance Comparison + +| Scenario | Non-Thread-Safe | Thread-Safe | +| ------------------- | --------------- | ---------------------- | +| Single goroutine | Fastest | Slower (pool overhead) | +| Multiple goroutines | Unsafe | Safe, good scaling | +| Memory allocations | Minimal | Per-call copy | +| Buffer reuse | Yes | Per-pool-instance | + +### Benchmarking + +```go +func BenchmarkNonThreadSafe(b *testing.B) { + f := fory.New(fory.WithXlang(true)) + f.RegisterStruct(User{}, 1) + user := &User{ID: 1, Name: "Alice"} + + for i := 0; i < b.N; i++ { + data, _ := f.Serialize(user) + _ = data + } +} + +func BenchmarkThreadSafe(b *testing.B) { + f := threadsafe.New() + f.RegisterStruct(User{}, 1) + user := &User{ID: 1, Name: "Alice"} + + for i := 0; i < b.N; i++ { + data, _ := f.Serialize(user) + _ = data + } +} +``` + +## Patterns + +### Per-Goroutine Instance + +For maximum performance with known goroutine count: + +```go +func worker(id int) { + // Each worker has its own Fory instance + f := fory.New(fory.WithXlang(true)) + f.RegisterStruct(User{}, 1) + + for task := range tasks { + data, _ := f.Serialize(task) + process(data) + } +} + +// Start workers +for i := 0; i < numWorkers; i++ { + go worker(i) +} +``` + +### Shared Thread-Safe Instance + +For dynamic goroutine count or simplicity: + +```go +// Single shared instance +var f = threadsafe.New() + +func init() { + f.RegisterStruct(User{}, 1) +} + +func handleRequest(user *User) []byte { + // Safe from any goroutine + data, _ := f.Serialize(user) + return data +} +``` + +### HTTP Handler Example + +```go +var fory = threadsafe.New() + +func init() { + fory.RegisterStruct(Response{}, 1) +} + +func handler(w http.ResponseWriter, r *http.Request) { + response := &Response{ + Status: "ok", + Data: getData(), + } + + // Safe: threadsafe.Fory handles concurrency + data, err := fory.Serialize(response) + if err != nil { + http.Error(w, err.Error(), 500) + return + } + + w.Header().Set("Content-Type", "application/octet-stream") + w.Write(data) +} +``` + +## Common Mistakes + +### Sharing Non-Thread-Safe Instance + +```go +// WRONG: Race condition +var f = fory.New(fory.WithXlang(true)) + +func handler1() { + f.Serialize(value1) // Race! +} + +func handler2() { + f.Serialize(value2) // Race! +} +``` + +**Fix**: Use `threadsafe.New()` or per-goroutine instances. + +### Keeping Reference to Buffer + +```go +// WRONG: Buffer invalidated on next call +f := fory.New(fory.WithXlang(true)) +data, _ := f.Serialize(value1) +savedData := data // Just copies the slice header! + +f.Serialize(value2) // Invalidates data and savedData +``` + +**Fix**: Clone the data or use thread-safe wrapper. + +```go +// Correct: Clone the data +data, _ := f.Serialize(value1) +savedData := make([]byte, len(data)) +copy(savedData, data) + +// Or use thread-safe (auto-copies) +f := threadsafe.New() +data, _ := f.Serialize(value1) // Already copied +``` + +### Registering Types Concurrently + +```go +// RISKY: Concurrent registration +go func() { + f.RegisterStruct(TypeA{}, 1) +}() +go func() { + f.Serialize(value) // May not see TypeA +}() +``` + +**Fix**: Register all types before concurrent use. + +## Best Practices + +1. **Register types at startup**: Before any concurrent operations +2. **Clone data if keeping references**: With non-thread-safe instance +3. **Use per-worker instances for hot paths**: Eliminates pool contention +4. **Profile before optimizing**: Thread-safe overhead may be negligible + +## Related Topics + +- [Configuration](configuration.md) +- [Basic Serialization](basic-serialization.md) +- [Troubleshooting](troubleshooting.md) diff --git a/versioned_docs/version-1.3.0/guide/go/troubleshooting.md b/versioned_docs/version-1.3.0/guide/go/troubleshooting.md new file mode 100644 index 00000000000..2c538385e2f --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/go/troubleshooting.md @@ -0,0 +1,449 @@ +--- +title: Troubleshooting +sidebar_position: 14 +id: troubleshooting +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 guide covers common issues and solutions when using Fory Go. + +## Error Types + +Fory Go uses typed errors with specific error kinds: + +```go +type Error struct { + kind ErrorKind + message string + // Additional context fields +} + +func (e Error) Kind() ErrorKind { return e.kind } +func (e Error) Error() string { return e.message } +``` + +### Error Kinds + +| Kind | Value | Description | +| ------------------------------ | ----- | ------------------------------- | +| `ErrKindOK` | 0 | No error | +| `ErrKindBufferOutOfBound` | 1 | Read/write beyond buffer bounds | +| `ErrKindTypeMismatch` | 2 | Type ID mismatch | +| `ErrKindUnknownType` | 3 | Unknown type encountered | +| `ErrKindSerializationFailed` | 4 | General serialization failure | +| `ErrKindDeserializationFailed` | 5 | General deserialization failure | +| `ErrKindMaxDepthExceeded` | 6 | Recursion depth limit exceeded | +| `ErrKindNilPointer` | 7 | Unexpected nil pointer | +| `ErrKindInvalidRefId` | 8 | Invalid reference ID | +| `ErrKindHashMismatch` | 9 | Struct hash mismatch | +| `ErrKindInvalidTag` | 10 | Invalid fory struct tag | + +## Common Errors and Solutions + +### ErrKindUnknownType + +**Error**: `unknown type encountered` + +**Cause**: Type not registered before serialization/deserialization. + +**Solution**: + +```go +f := fory.New() + +// Register type before use +f.RegisterStruct(User{}, 1) + +// Now serialization works +data, _ := f.Serialize(&User{ID: 1}) +``` + +### ErrKindTypeMismatch + +**Error**: `type mismatch: expected X, got Y` + +**Cause**: Serialized data has different type than expected. + +**Solutions**: + +1. **Use correct target type**: + +```go +// Wrong: Deserializing User into Order +var order Order +f.Deserialize(userData, &order) // Error! + +// Correct +var user User +f.Deserialize(userData, &user) +``` + +2. **Ensure consistent registration**: + +```go +// Serializer +f1 := fory.New() +f1.RegisterStruct(User{}, 1) + +// Deserializer - must use same ID +f2 := fory.New() +f2.RegisterStruct(User{}, 1) // Same ID! +``` + +### ErrKindHashMismatch + +**Error**: `hash X is not consistent with Y for type Z` + +**Cause**: Struct definition changed between serialization and deserialization. + +**Solutions**: + +1. **Keep compatible mode enabled**: + +```go +// Remove any WithCompatible(false) override from the peers. +f := fory.New(/* existing options */) +``` + +2. **Ensure struct definitions match**: + +```go +// Both serializer and deserializer must have same struct +type User struct { + ID int64 + Name string +} +``` + +3. **Regenerate codegen** (if using): + +```bash +go generate ./... +``` + +### ErrKindMaxDepthExceeded + +**Error**: `max depth exceeded` + +**Cause**: Data nesting exceeds maximum depth limit. + +**Possible causes**: + +- Deeply nested data structures exceeding the default limit (20) +- Unintended circular references without reference tracking enabled +- **Malicious data**: Attackers may craft deeply nested payloads to cause resource exhaustion + +**Solutions**: + +1. **Increase max depth** (default is 20): + +```go +f := fory.New(fory.WithMaxDepth(50)) +``` + +2. **Enable reference tracking** (for circular data): + +```go +f := fory.New(fory.WithTrackRef(true)) +``` + +3. **Check for unintended circular references** in your data. + +4. **Validate untrusted data**: When deserializing data from untrusted sources, do not blindly increase max depth. Consider validating input size and structure before deserialization. + +### ErrKindBufferOutOfBound + +**Error**: `buffer out of bound: offset=X, need=Y, size=Z` + +**Cause**: Reading beyond available data. + +**Solutions**: + +1. **Ensure complete data transfer**: + +```go +// Wrong: Truncated data +data := fullData[:100] +f.Deserialize(data, &target) // Error if data was larger + +// Correct: Use full data +f.Deserialize(fullData, &target) +``` + +2. **Check for data corruption**: Verify data integrity during transmission. + +### ErrKindInvalidRefId + +**Error**: `invalid reference ID` + +**Cause**: Reference to non-existent or unknown object in serialized data. + +**Solutions**: + +1. **Ensure reference tracking consistency**: + +```go +// Serializer and deserializer must have same setting +f1 := fory.New(fory.WithTrackRef(true)) +f2 := fory.New(fory.WithTrackRef(true)) // Must match! +``` + +2. **Check for data corruption**. + +### ErrKindInvalidTag + +**Error**: `invalid fory struct tag` + +**Cause**: Invalid struct tag configuration. + +**Common causes**: + +1. **Invalid tag ID**: ID must be non-negative + +```go +// Wrong: negative ID +type Bad struct { + Field int `fory:"id=-5"` +} + +// Correct +type Good struct { + Field int `fory:"id=0"` +} +``` + +2. **Duplicate tag IDs**: Each field must have a unique ID within the struct + +```go +// Wrong: duplicate IDs +type Bad struct { + Field1 int `fory:"id=0"` + Field2 int `fory:"id=0"` // Duplicate! +} + +// Correct +type Good struct { + Field1 int `fory:"id=0"` + Field2 int `fory:"id=1"` +} +``` + +## Xlang Issues + +### Field Order Mismatch + +**Symptom**: Data deserializes but fields have wrong values. + +**Cause**: Different field ordering between languages. When compatible mode is disabled, fields are sorted by their snake_case names. CamelCase field names (e.g., `FirstName`) are converted to snake_case (e.g., `first_name`) for sorting. + +**Solutions**: + +1. **Ensure converted snake_case names are consistent**: Field names across languages must produce the same snake_case ordering: + +```go +type User struct { + FirstName string // Go: FirstName -> first_name + LastName string // Go: LastName -> last_name + // Sorted alphabetically by snake_case: first_name, last_name +} +``` + +2. **Use field IDs for consistent ordering**: Field IDs (non-negative integers) act as aliases for field names, used for both sorting and field matching during deserialization: + +```go +type User struct { + FirstName string `fory:"id=0"` + LastName string `fory:"id=1"` +} +``` + +Ensure the same field IDs are used across all languages for corresponding fields. + +### Name Registration Mismatch + +**Symptom**: `unknown type` in other languages. + +**Solution**: Use identical names: + +```go +// Go +f.RegisterStructByName(User{}, "example.User") + +// Java - must match exactly +fory.register(User.class, "example.User"); + +// Python +fory.register_type(User, name="example.User") +``` + +## Performance Issues + +### Slow Serialization + +**Possible causes**: + +1. **Large object graphs**: Reduce data size or serialize incrementally. + +2. **Excessive reference tracking**: Disable if not needed: + +```go +f := fory.New(fory.WithTrackRef(false)) +``` + +3. **Deep nesting**: Flatten data structures where possible. + +### High Memory Usage + +**Possible causes**: + +1. **Large serialized data**: Process in chunks. + +2. **Reference tracking overhead**: Disable if not needed. + +3. **Buffer not released**: Reuse buffers: + +```go +buf := fory.NewByteBuffer(nil) +f.SerializeTo(buf, value) +// Process data +buf.Reset() // Reuse for next serialization +``` + +### Thread Contention + +**Symptom**: Slowdown under concurrent load. + +**Solutions**: + +1. **Use per-goroutine instances** for hot paths: + +```go +func worker() { + f := fory.New() // Each worker has own instance + for task := range tasks { + f.Serialize(task) + } +} +``` + +2. **Profile pool usage** with thread-safe wrapper. + +## Debugging Techniques + +### Enable Debug Output + +Set environment variable: + +```bash +ENABLE_FORY_DEBUG_OUTPUT=1 go test ./... +``` + +### Inspect Serialized Data + +```go +data, _ := f.Serialize(value) +fmt.Printf("Serialized %d bytes\n", len(data)) +fmt.Printf("Header: %x\n", data[:4]) // Magic + flags +``` + +### Check Type Registration + +```go +// Verify type is registered +f := fory.New() +err := f.RegisterStruct(User{}, 1) +if err != nil { + fmt.Printf("Registration failed: %v\n", err) +} +``` + +### Compare Struct Hashes + +If getting hash mismatch, compare struct definitions: + +```go +// Print struct info for debugging +t := reflect.TypeOf(User{}) +for i := 0; i < t.NumField(); i++ { + f := t.Field(i) + fmt.Printf("Field: %s, Type: %s\n", f.Name, f.Type) +} +``` + +## Testing Tips + +### Test Round-Trip + +```go +func TestRoundTrip(t *testing.T) { + f := fory.New() + f.RegisterStruct(User{}, 1) + + original := &User{ID: 1, Name: "Alice"} + + data, err := f.Serialize(original) + require.NoError(t, err) + + var result User + err = f.Deserialize(data, &result) + require.NoError(t, err) + + assert.Equal(t, original.ID, result.ID) + assert.Equal(t, original.Name, result.Name) +} +``` + +### Test Xlang + +```bash +cd java/fory-core +FORY_GO_JAVA_CI=1 mvn test -Dtest=org.apache.fory.xlang.GoXlangTest +``` + +### Test Schema Evolution + +```go +func TestSchemaEvolution(t *testing.T) { + f1 := fory.New() + f1.RegisterStruct(UserV1{}, 1) + + data, _ := f1.Serialize(&UserV1{ID: 1, Name: "Alice"}) + + f2 := fory.New() + f2.RegisterStruct(UserV2{}, 1) + + var result UserV2 + err := f2.Deserialize(data, &result) + require.NoError(t, err) +} +``` + +## Getting Help + +If you encounter issues not covered here: + +1. **Check GitHub Issues**: [github.com/apache/fory/issues](https://github.com/apache/fory/issues) +2. **Enable debug output**: `ENABLE_FORY_DEBUG_OUTPUT=1` +3. **Create minimal reproduction**: Isolate the problem +4. **Report the issue**: Include Go version, Fory version, and minimal code + +## Related Topics + +- [Configuration](configuration.md) +- [Xlang Serialization](xlang-serialization.md) +- [Schema Evolution](schema-evolution.md) +- [Thread Safety](thread-safety.md) diff --git a/versioned_docs/version-1.3.0/guide/go/type-registration.md b/versioned_docs/version-1.3.0/guide/go/type-registration.md new file mode 100644 index 00000000000..356815939e5 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/go/type-registration.md @@ -0,0 +1,264 @@ +--- +title: Type Registration +sidebar_position: 6 +id: type_registration +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. +--- + +Type registration tells Fory how to identify and serialize your custom types. Registration is required for struct, enum, and extension types. + +## Why Register Types? + +1. **Type Identification**: Fory needs to identify the actual type during deserialization +2. **Polymorphism**: When deserializing interface types, Fory must know which concrete type to create +3. **Xlang compatibility**: Other languages need to recognize and deserialize your types + +## Struct Registration + +### Register by ID + +Register a struct with a numeric type ID for compact serialization: + +```go +type User struct { + ID int64 + Name string +} + +f := fory.New(fory.WithXlang(true)) +err := f.RegisterStruct(User{}, 1) +if err != nil { + panic(err) +} +``` + +**ID Guidelines**: + +- IDs must be unique within your application +- IDs must be consistent across all languages for cross-language serialization +- Use the same ID for the same type in serializer and deserializer + +### Register by Name + +Register a struct with a type name string. This is more flexible but has higher serialization cost: + +```go +f := fory.New(fory.WithXlang(true)) +err := f.RegisterStructByName(User{}, "example.User") +if err != nil { + panic(err) +} +``` + +**Name Guidelines**: + +- Use fully-qualified names following `namespace.TypeName` convention +- Names must be unique and consistent across all languages +- Names are case-sensitive + +## Enum Registration + +Go doesn't have native enums, but you can register integer types as enums: + +### Register by ID + +```go +type Status int32 + +const ( + StatusPending Status = 0 + StatusActive Status = 1 + StatusComplete Status = 2 +) + +f := fory.New(fory.WithXlang(true)) +err := f.RegisterEnum(Status(0), 1) +``` + +### Register by Name + +```go +err := f.RegisterEnumByName(Status(0), "example.Status") +``` + +## Extension Types + +For types requiring custom serialization logic, register as extension types with a custom serializer: + +```go +f := fory.New(fory.WithXlang(true)) + +// Register by ID +err := f.RegisterExtension(CustomType{}, 1, &CustomSerializer{}) + +// Or register by name +err = f.RegisterExtensionByName(CustomType{}, "example.Custom", &CustomSerializer{}) +``` + +See [Custom Serializers](custom-serializers.md) for details on implementing the `ExtensionSerializer` interface. + +## Registration Scope + +Type registration is per-Fory-instance: + +```go +f1 := fory.New(fory.WithXlang(true)) +f2 := fory.New(fory.WithXlang(true)) + +// Types registered on f1 are NOT available on f2 +f1.RegisterStruct(User{}, 1) + +// f2 cannot deserialize User unless also registered +f2.RegisterStruct(User{}, 1) +``` + +## Registration Timing + +Register types after creating a Fory instance and before any serialize/deserialize calls: + +```go +f := fory.New(fory.WithXlang(true)) + +// Register before use +f.RegisterStruct(User{}, 1) +f.RegisterStruct(Order{}, 2) + +// Now serialize/deserialize +data, _ := f.Serialize(&User{ID: 1, Name: "Alice"}) +``` + +## Nested Type Registration + +Register all struct types in the object graph, including nested types: + +```go +type Address struct { + City string + Country string +} + +type Person struct { + Name string + Address Address +} + +f := fory.New(fory.WithXlang(true)) + +// Register ALL struct types used in the object graph +f.RegisterStruct(Address{}, 1) +f.RegisterStruct(Person{}, 2) +``` + +## Xlang Registration + +For cross-language serialization, types must be registered consistently across all languages. + +### Using IDs + +All languages use the same numeric ID: + +**Go**: + +```go +f.RegisterStruct(User{}, 1) +``` + +**Java**: + +```java +fory.register(User.class, 1); +``` + +**Python**: + +```python +fory.register(User, type_id=1) +``` + +### Using Names + +All languages use the same type name: + +**Go**: + +```go +f.RegisterStructByName(User{}, "example.User") +``` + +**Java**: + +```java +fory.register(User.class, "example.User"); +``` + +**Python**: + +```python +fory.register_type(User, name="example.User") +``` + +**Rust**: + +```rust +use fory::{Fory, ForyStruct}; + +#[derive(ForyStruct)] +struct User { + id: i64, + name: String, +} + +let mut fory = Fory::default(); +fory.register_by_name::("example.User")?; +``` + +## Best Practices + +1. **Register early**: Register all types at application startup before any serialization +2. **Be consistent**: Use the same ID or name across all languages and all instances +3. **Register all types**: Include nested struct types, not just top-level types +4. **Prefer IDs for performance**: Numeric IDs have lower serialization overhead than names +5. **Use names for flexibility**: Names are easier to manage and less prone to conflicts + +## Common Errors + +### Unregistered Type + +``` +error: unknown type encountered +``` + +**Solution**: Register the type before serialization/deserialization. + +### ID/Name Mismatch + +Data serialized with one ID or name cannot be deserialized if registered with a different ID or name. + +**Solution**: Use consistent IDs or names across serializer and deserializer. + +### Duplicate Registration + +Two types registered with the same ID will conflict. + +**Solution**: Ensure unique IDs for each type. + +## Related Topics + +- [Basic Serialization](basic-serialization.md) +- [Xlang Serialization](xlang-serialization.md) +- [Supported Types](supported-types.md) +- [Troubleshooting](troubleshooting.md) diff --git a/versioned_docs/version-1.3.0/guide/go/xlang-serialization.md b/versioned_docs/version-1.3.0/guide/go/xlang-serialization.md new file mode 100644 index 00000000000..ddbc0ffdd8d --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/go/xlang-serialization.md @@ -0,0 +1,307 @@ +--- +title: Xlang Serialization +sidebar_position: 2 +id: xlang_serialization +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. +--- + +Fory Go enables seamless data exchange with Java, Python, C++, Rust, +JavaScript/TypeScript, C#, Swift, Dart, Scala, and Kotlin. This guide covers +xlang compatibility and type mapping. + +## Create an Xlang Fory Instance + +Go defaults to xlang mode with compatible schema evolution. Set the mode explicitly in xlang examples: + +```go +f := fory.New(fory.WithXlang(true)) +``` + +## Type Registration for Xlang + +Use consistent type IDs across all languages: + +### Go + +```go +type User struct { + ID int64 + Name string +} + +f := fory.New(fory.WithXlang(true)) +f.RegisterStruct(User{}, 1) +data, _ := f.Serialize(&User{ID: 1, Name: "Alice"}) +``` + +### Java + +```java +public class User { + public long id; + public String name; +} +Fory fory = Fory.builder().withXlang(true).build(); +fory.register(User.class, 1); +User user = fory.deserialize(data, User.class); +``` + +### Python + +```python +from dataclasses import dataclass +import pyfory + +@dataclass +class User: + id: pyfory.Int64 + name: str + +fory = pyfory.Fory(xlang=True) +fory.register(User, type_id=1) +user = fory.deserialize(data) +``` + +## Type Mapping + +See [Type Mapping Specification](../../specification/xlang_type_mapping.md) for detailed type mappings across all languages. + +## Field Ordering + +Cross-language serialization requires consistent field ordering. Fory sorts fields by their snake_case names alphabetically. + +Go field names are converted to snake_case for sorting: + +```go +type Example struct { + UserID int64 // -> user_id + FirstName string // -> first_name + Age int32 // -> age +} + +// Sorted order: age, first_name, user_id +``` + +Ensure other languages use matching field names that produce the same snake_case ordering, or use field IDs for explicit control: + +```go +type Example struct { + UserID int64 `fory:"id=0"` + FirstName string `fory:"id=1"` + Age int32 `fory:"id=2"` +} +``` + +## Examples + +### Go to Java + +**Go (Serializer)**: + +```go +type Order struct { + ID int64 + Customer string + Total float64 + Items []string +} + +f := fory.New(fory.WithXlang(true)) +f.RegisterStruct(Order{}, 1) + +order := &Order{ + ID: 12345, + Customer: "Alice", + Total: 99.99, + Items: []string{"Widget", "Gadget"}, +} +data, _ := f.Serialize(order) +// Send 'data' to Java service +``` + +**Java (Deserializer)**: + +```java +public class Order { + public long id; + public String customer; + public double total; + public List items; +} + +Fory fory = Fory.builder().withXlang(true).build(); +fory.register(Order.class, 1); + +Order order = fory.deserialize(data, Order.class); +``` + +### Python to Go + +**Python (Serializer)**: + +```python +from dataclasses import dataclass +import pyfory + +@dataclass +class Message: + id: pyfory.Int64 + content: str + timestamp: pyfory.Int64 + +fory = pyfory.Fory(xlang=True) +fory.register(Message, type_id=1) + +msg = Message(id=1, content="Hello from Python", timestamp=1234567890) +data = fory.serialize(msg) +``` + +**Go (Deserializer)**: + +```go +type Message struct { + ID int64 + Content string + Timestamp int64 +} + +f := fory.New(fory.WithXlang(true)) +f.RegisterStruct(Message{}, 1) + +var msg Message +f.Deserialize(data, &msg) +fmt.Println(msg.Content) // "Hello from Python" +``` + +### Nested Structures + +Cross-language nested structures require all types to be registered: + +## Lists and Dense Arrays + +Go slices are ordinary `list` carriers unless a field tag explicitly requests +the dense `array` schema. Use `array` only for one-dimensional bool or +numeric data. + +| Fory schema | Go carrier and tag sketch | +| ----------------- | ------------------------------------------------------ | +| `list` | `[]int32` / `fory:"type=list(element=int32)"` | +| `array` | `[]bool` / `fory:"type=array(element=bool)"` | +| `array` | `[]int8` / `fory:"type=array(element=int8)"` | +| `array` | `[]int16` / `fory:"type=array(element=int16)"` | +| `array` | `[]int32` / `fory:"type=array(element=int32)"` | +| `array` | `[]int64` / `fory:"type=array(element=int64)"` | +| `array` | `[]uint8` / `fory:"type=array(element=uint8)"` | +| `array` | `[]uint16` / `fory:"type=array(element=uint16)"` | +| `array` | `[]uint32` / `fory:"type=array(element=uint32)"` | +| `array` | `[]uint64` / `fory:"type=array(element=uint64)"` | +| `array` | `[]float16.Float16` / `type=array(element=float16)` | +| `array` | `[]bfloat16.BFloat16` / `type=array(element=bfloat16)` | +| `array` | `[]float32` / `fory:"type=array(element=float32)"` | +| `array` | `[]float64` / `fory:"type=array(element=float64)"` | + +**Go**: + +```go +type Address struct { + Street string + City string + Country string +} + +type Company struct { + Name string + Address Address +} + +f := fory.New(fory.WithXlang(true)) +f.RegisterStruct(Address{}, 1) +f.RegisterStruct(Company{}, 2) +``` + +**Java**: + +```java +public class Address { + public String street; + public String city; + public String country; +} + +public class Company { + public String name; + public Address address; +} + +fory.register(Address.class, 1); +fory.register(Company.class, 2); +``` + +## Common Issues + +### Field Name Mismatch + +Go uses PascalCase, other languages may use camelCase or snake_case. Fields are matched by their snake_case conversion: + +```go +// Go +type User struct { + FirstName string // -> first_name +} + +// Java - field name converted to snake_case must match +public class User { + public String firstName; // -> first_name (matches) +} +``` + +### Type Interpretation + +Go unsigned types map to Java signed types with the same bit pattern: + +```go +var value uint64 = 18446744073709551615 // Max uint64 +``` + +Java's `long` holds the same bits but interprets as -1. Use `Long.toUnsignedString()` in Java if unsigned interpretation is needed. + +### Nil vs Null + +Go nil slices/maps serialize differently based on configuration: + +```go +var slice []string = nil +// In xlang mode: serializes based on nullable configuration +``` + +Ensure other languages handle null appropriately. + +## Best Practices + +1. **Use consistent type IDs**: Same numeric ID for the same type across all languages +2. **Register all types**: Including nested struct types +3. **Match field ordering**: Use same snake_case names or explicit field IDs +4. **Test cross-language**: Run integration tests early and often +5. **Handle type differences**: Be aware of signed/unsigned interpretation differences + +## Related Topics + +- [Type Registration](type-registration.md) +- [Supported Types](supported-types.md) +- [Schema Evolution](schema-evolution.md) +- [Xlang Serialization Specification](../../specification/xlang_serialization_spec.md) +- [Type Mapping Specification](../../specification/xlang_type_mapping.md) diff --git a/versioned_docs/version-1.3.0/guide/java/_category_.json b/versioned_docs/version-1.3.0/guide/java/_category_.json new file mode 100644 index 00000000000..3508cf90083 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/java/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "Java", + "position": 1, + "collapsible": true, + "collapsed": true +} diff --git a/versioned_docs/version-1.3.0/guide/java/advanced-features.md b/versioned_docs/version-1.3.0/guide/java/advanced-features.md new file mode 100644 index 00000000000..d1e048cfa58 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/java/advanced-features.md @@ -0,0 +1,160 @@ +--- +title: Advanced Features +sidebar_position: 16 +id: advanced_features +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 page covers advanced Java Fory features that are not part of first-use serialization. +Java native-mode zero-copy serialization is documented in [Native Serialization](native-serialization.md), and deep +copy semantics are documented in [Object Copy](object-copy.md). + +## Memory Allocation Customization + +Fory provides a `MemoryAllocator` interface that allows you to customize how memory buffers are allocated and grown during serialization operations. This can be useful for performance optimization, memory pooling, or debugging memory usage. + +### MemoryAllocator Interface + +The `MemoryAllocator` interface defines two key methods: + +```java +public interface MemoryAllocator { + /** + * Allocates a new MemoryBuffer with the specified initial capacity. + */ + MemoryBuffer allocate(int initialCapacity); + + /** + * Grows an existing buffer to accommodate the new capacity. + * The implementation must grow the buffer in-place by modifying + * the existing buffer instance. + */ + void grow(MemoryBuffer buffer, int newCapacity); +} +``` + +### Using Custom Memory Allocators + +You can set a global memory allocator that will be used by all `MemoryBuffer` instances: + +```java +// Create a custom allocator +MemoryAllocator customAllocator = new MemoryAllocator() { + @Override + public MemoryBuffer allocate(int initialCapacity) { + // Add extra capacity for debugging or pooling + return MemoryBuffer.fromByteArray(new byte[initialCapacity + 100]); + } + + @Override + public void grow(MemoryBuffer buffer, int newCapacity) { + if (newCapacity <= buffer.size()) { + return; + } + + // Custom growth strategy - add 100% extra capacity + int newSize = (int) (newCapacity * 2); + byte[] data = new byte[newSize]; + buffer.get(0, data, 0, buffer.size()); + buffer.initHeapBuffer(data, 0, data.length); + } +}; + +// Set the custom allocator globally +MemoryBuffer.setGlobalAllocator(customAllocator); + +// All subsequent MemoryBuffer allocations will use your custom allocator +Fory fory = Fory.builder().withXlang(false).build(); +byte[] bytes = fory.serialize(someObject); // Uses custom allocator +``` + +### Default Memory Allocator Behavior + +The default allocator uses the following growth strategy: + +- For buffers smaller than `BUFFER_GROW_STEP_THRESHOLD` (100MB): multiply capacity by 2 +- For larger buffers: multiply capacity by 1.5 (capped at `Integer.MAX_VALUE - 8`) + +This provides a balance between avoiding frequent reallocations and preventing excessive memory usage. + +### Use Cases + +Custom memory allocators are useful for: + +- **Memory Pooling**: Reuse allocated buffers to reduce GC pressure +- **Performance Tuning**: Use different growth strategies based on your workload +- **Debugging**: Add logging or tracking to monitor memory usage +- **Off-heap Memory**: Integrate with off-heap memory management systems + +## Logging + +### ForyLogger + +By default, Fory uses a custom logger `ForyLogger` for internal needs at `WARN` level, or `INFO` level when `ENABLE_FORY_DEBUG_OUTPUT=1` is set. Set `FORY_LOG_LEVEL` to `ERROR`, `WARN`, `INFO`, or `DEBUG` to configure the process default level before startup. `ForyLogger` builds resulting logged data into a single string and sends it directly to `System.out`. The result line layout is similar to (in Log4j notation): + +``` +%d{yyyy-MM-dd hh:mm:ss} %p %C:%L [%t] - %m%n +``` + +The layout can't be changed. + +Example output: + +``` +2025-11-07 08:49:59 INFO CompileUnit:55 [main] - Generate code for org.apache.fory.builder.SerializedLambdaForyCodec_0 took 35 ms. +2025-11-07 08:50:00 INFO JaninoUtils:121 [main] - Compile [SerializedLambdaForyCodec_0] take 144 ms +``` + +### Slf4jLogger + +If a more sophisticated logger is required, configure Fory to use Slf4j via `LoggerFactory.useSlf4jLogging()`. For example, enabling Slf4j before creating Fory: + +```java +public static final ThreadSafeFory FORY; + +static { + LoggerFactory.useSlf4jLogging(true); + FORY = Fory.builder().withXlang(false) + .buildThreadSafeFory(); +} +``` + +**Note:** Enabling Slf4j via `useSlf4jLogging` will be ignored when the application runs in a GraalVM native image. + +### Suppress Fory Logs + +Both `ForyLogger` and `Slf4jLogger` allow controlling log output level or suppressing logs entirely. Configure logger level via `LoggerFactory.setLogLevel()`: + +```java +static { + // to log only WARN and higher + LoggerFactory.setLogLevel(LogLevel.WARN_LEVEL); + + // to disable logging entirely + LoggerFactory.disableLogging(); +} +``` + +**Note:** Selected logging level is applied before Slf4j implementation's logger level. So if you set `WARN_LEVEL` (as in the example above) then you will not see INFO messages from Fory even if INFO is enabled in Logback. + +## Related Topics + +- [Compression](compression.md) - Data compression options +- [Configuration](configuration.md) - All ForyBuilder options +- [Native Serialization](native-serialization.md) - Java-only serialization, JDK hooks, and zero-copy buffers +- [Object Copy](object-copy.md) - Deep copy functionality +- [Xlang Serialization](xlang-serialization.md) - Java xlang interoperability diff --git a/versioned_docs/version-1.3.0/guide/java/android-support.md b/versioned_docs/version-1.3.0/guide/java/android-support.md new file mode 100644 index 00000000000..7aede67a3c7 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/java/android-support.md @@ -0,0 +1,134 @@ +--- +title: Android Support +sidebar_position: 15 +id: android_support +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. +--- + +## Android Runtime + +Fory Java supports Android 8.0+ (API level 26+) through the regular `fory-core` artifact. No separate +Android artifact is required for core object serialization. + +Use core object serialization on Android: + +- `Fory#serialize(Object)` and `Fory#deserialize(byte[])`. +- `BaseFory#deserialize(ByteBuffer)` for heap, direct, and read-only `ByteBuffer` inputs. +- Stream, channel, and out-of-band buffer APIs through byte-array, heap-buffer, or `ByteBuffer` copy + paths. +- Java collections/maps and xlang collections/maps. + +`java/fory-format` row-format APIs are JVM-only and are not supported on Android. + +## Runtime Codegen + +Runtime serializer code generation is disabled on Android. If `withCodegen(true)` is set, Fory keeps +Android serialization on the non-codegen path and logs a warning. + +Android apps that need generated serializers should use build-time static generated serializers +instead. + +## Static Generated Serializers + +Use `@ForyStruct` static generated serializers for Android application classes. They are generated by +javac during the app build and work without runtime bytecode generation. + +### Install The Annotation Processor + +Add `fory-annotation-processor` to the annotation processor path of the module that compiles your +Android model classes: + +```xml + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.apache.fory + fory-annotation-processor + ${fory.version} + + + + + + +``` + +Then annotate Android model classes with `@ForyStruct`. + +Static generated serializers are required on Android when a serialized class uses Fory type-use +annotations, for example: + +```java +import java.util.List; +import org.apache.fory.annotation.ForyStruct; +import org.apache.fory.annotation.UInt8Type; + +@ForyStruct +public class ImageBlock { + public List<@UInt8Type Integer> pixels; +} +``` + +Without the generated static descriptors, Android reflection may not expose the nested type-use +metadata needed for annotations such as `@Ref`, `@Int8Type`, `@UInt8Type`, `@Float16Type`, or +`@BFloat16Type`. Serialization for those classes will not have the schema information Fory needs. + +See [Static Generated Serializers](static-generated-serializers.md) for setup instructions. + +## Object Model Requirements + +Android serializers use public Android APIs. For application classes, prefer: + +- accessible no-argument constructors, or records with supported constructors. +- public, protected, or package-private serialized fields. +- non-private getters and setters for private serialized fields. +- `@ForyStruct` static generated serializers for Android model classes. + +Final fields in ordinary classes are not suitable for generated read/copy methods. Use records for +constructor-based immutable values. + +## Unsupported Features + +The following JVM features are not supported on Android: + +- Runtime serializer code generation and async compilation. +- Lambda and `SerializedLambda` serialization. +- Native-address serialization APIs and native-address `MemoryBuffer` wrapping. +- Raw unsafe memory copy APIs. +- `java/fory-format` row-format APIs. + +## ByteBuffer + +`BaseFory#deserialize(ByteBuffer)` supports heap, direct, and read-only buffers on Android by copying +the remaining bytes into a Fory-owned heap buffer. The caller buffer position and limit are not +changed. + +Raw direct-buffer address wrapping is a JVM-only fast path and is not used on Android. + +## Collections, Maps, And Proxies + +Common JDK collection and map implementations are supported on Android. In xlang mode, collection and +map serialization uses the xlang protocol and does not encode Java wrapper/view internals. + +`java.lang.reflect.Proxy` serialization is supported for normal proxy usage. Do not invoke, log, or +use a proxy as a map/set key while it is still being deserialized; the invocation handler may not be +ready yet. diff --git a/versioned_docs/version-1.3.0/guide/java/basic-serialization.md b/versioned_docs/version-1.3.0/guide/java/basic-serialization.md new file mode 100644 index 00000000000..168b50b1b12 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/java/basic-serialization.md @@ -0,0 +1,132 @@ +--- +title: Basic Serialization +sidebar_position: 1 +id: basic_serialization +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 page covers the Java xlang quickstart. Xlang mode is the default Java wire format and is the +right first choice for cross-language payloads. + +## Create a Fory Instance + +For a single-threaded xlang Fory instance, set the mode explicitly: + +```java +import org.apache.fory.Fory; + +Fory fory = Fory.builder() + .withXlang(true) + .requireClassRegistration(true) + .build(); +``` + +For a thread-safe Fory instance, build `ThreadSafeFory` from the same builder: + +```java +import org.apache.fory.ThreadSafeFory; + +ThreadSafeFory fory = Fory.builder() + .withXlang(true) + .requireClassRegistration(true) + .buildThreadSafeFory(); +``` + +Default Java xlang mode also defaults to compatible schema mode, so independently deployed services +can add and remove fields when their schema metadata remains compatible. Use +`withCompatible(false)` only when every reader and writer always uses the same schema and you want +faster serialization and smaller size. Use the `compatible=false` opt-out only after verifying that every language uses the same xlang schema, or when native types are generated from Fory schema IDL. + +## Register Custom Types + +Register application classes with the same type identity on every peer. Numeric IDs are compact and +fast, while name registration is easier to coordinate across independently owned services. + +```java +import org.apache.fory.annotation.ForyField; + +public class User { + @ForyField(id = 0) + public String name; + + @ForyField(id = 1) + public int age; +} + +Fory fory = Fory.builder() + .withXlang(true) + .requireClassRegistration(true) + .build(); + +fory.register(User.class, "example", "User"); +``` + +Use field IDs for long-lived schemas so field identity is stable even if Java field names change. +See [Schema Metadata](schema-metadata.md) for Java annotations, nullability, reference tracking, and +enum metadata. + +## Serialize And Deserialize + +```java +User user = new User(); +user.name = "Alice"; +user.age = 30; + +byte[] bytes = fory.serialize(user); +User decoded = fory.deserialize(bytes, User.class); +``` + +When xlang bytes cross languages, every peer must register the same type identity and compatible +field metadata. The shared rules live in [Xlang](../xlang/index.md), while Java-specific API calls +are in [Xlang Serialization](xlang-serialization.md). + +## Use Native Serialization For Java-Only Traffic + +For same-language Java/JVM traffic, native mode is usually the better fit: + +```java +Fory fory = Fory.builder() + .withXlang(false) + .build(); +``` + +Native mode supports the broad Java object serialization surface, including JDK serialization hooks, +object copy, and native-mode zero-copy buffers. See [Native Serialization](native-serialization.md). + +## Common Options + +- `withRefTracking(true)` preserves shared references and circular references. +- `requireClassRegistration(true)` keeps the default registered-type policy. +- Compatible mode is enabled by default for native-mode and xlang payloads. Use + `withCompatible(false)` only when every reader and writer uses the same schema and you want faster + serialization and smaller size. For xlang payloads, use the `compatible=false` opt-out only after verifying that every language uses the same schema, or when native types are generated from Fory schema IDL. +- `withAsyncCompilation(true)` enables asynchronous serializer compilation where supported. + +## Best Practices + +1. **Reuse Fory instances**: Creating Fory is expensive, always reuse instances +2. **Use appropriate thread safety**: Choose between single-thread and thread-safe based on your needs +3. **Register classes**: Keep type identity stable across every xlang peer +4. **Configure reference tracking**: Enable it only when the object graph needs identity or cycles + +## Related Topics + +- [Configuration](configuration.md) - All ForyBuilder options +- [Native Serialization](native-serialization.md) - Java-only serialization features +- [Schema Metadata](schema-metadata.md) - Field IDs, nullability, reference tracking, and enum IDs +- [Xlang Serialization](xlang-serialization.md) - Java xlang interoperability +- [Troubleshooting](troubleshooting.md) - Common API usage issues diff --git a/versioned_docs/version-1.3.0/guide/java/compression.md b/versioned_docs/version-1.3.0/guide/java/compression.md new file mode 100644 index 00000000000..587b2c1f7c4 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/java/compression.md @@ -0,0 +1,126 @@ +--- +title: Compression +sidebar_position: 10 +id: compression +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 page covers compression options for reducing serialized data size. + +## Integer Compression + +`ForyBuilder#withIntCompressed`/`ForyBuilder#withLongCompressed` can be used to compress int/long for smaller size. Normally compressing int is enough. + +Both compression options are enabled by default. If the serialized size is not important (for example, you use FlatBuffers for serialization before, which doesn't compress anything), then you should disable compression. If your data are all numbers, the compression may bring 80% performance regression. + +### Int Compression + +For int compression, Fory uses 1~5 bytes for encoding. The first bit in every byte indicates whether there is a next byte. If the first bit is set, then the next byte will be read until the first bit of the next byte is unset. + +### Long Compression + +For long compression, Fory supports two encodings: + +#### SLI (Small Long as Int) Encoding (Default) + +- If long is in `[-1073741824, 1073741823]`, encode as 4 bytes int: `| little-endian: ((int) value) << 1 |` +- Otherwise write as 9 bytes: `| 0b1 | little-endian 8bytes long |` + +#### PVL (Progressive Variable-length Long) Encoding + +- First bit in every byte indicates whether there is a next byte. If first bit is set, then next byte will be read until first bit of next byte is unset. +- Negative numbers will be converted to positive numbers by `(v << 1) ^ (v >> 63)` to reduce cost of small negative numbers. + +If a number is of `long` type but can't be represented by smaller bytes mostly, the compression won't get good enough results—not worthy compared to performance cost. Maybe you should try to disable long compression if you find it didn't bring much space savings. + +## Array Compression + +Fory supports SIMD-accelerated compression for primitive arrays (`int[]` and `long[]`) when array values can fit in smaller data types. This feature is available on Java 16+ and uses the Vector API for optimal performance. + +### How Array Compression Works + +Array compression analyzes arrays to determine if values can be stored using fewer bytes: + +- **`int[]` → `byte[]`**: When all values are in range [-128, 127] (75% size reduction) +- **`int[]` → `short[]`**: When all values are in range [-32768, 32767] (50% size reduction) +- **`long[]` → `int[]`**: When all values fit in integer range (50% size reduction) + +### Configuration and Registration + +To enable array compression, you must explicitly register the serializers: + +```java +Fory fory = Fory.builder() + .withXlang(false) + // Enable int array compression + .withIntArrayCompressed(true) + // Enable long array compression + .withLongArrayCompressed(true) + .build(); + +// You must explicitly register compressed array serializers +CompressedArraySerializers.registerSerializers(fory); +``` + +Compressed array serializers are included in `fory-core` and use the Java 16+ Vector API when it is available. + +## String Compression + +String compression can be enabled via `ForyBuilder#withStringCompressed(true)`. This is disabled by default. + +## Configuration Summary + +| Option | Description | Default | +| ------------------- | --------------------------------------------- | ------- | +| `compressInt` | Enable int compression | `true` | +| `compressLong` | Enable long compression | `true` | +| `compressIntArray` | Enable SIMD int array compression (Java 16+) | `false` | +| `compressLongArray` | Enable SIMD long array compression (Java 16+) | `false` | +| `compressString` | Enable string compression | `false` | + +## Performance Considerations + +1. **Disable compression for numeric-heavy data**: If your data is mostly numbers, compression overhead may not be worth it +2. **Array compression requires Java 16+**: Uses Vector API for SIMD acceleration +3. **Long compression may not help large values**: If most longs can't fit in smaller representations, disable it +4. **String compression has overhead**: Only enable if strings are highly compressible + +## Example Configuration + +```java +// For mostly numeric data - disable compression +Fory fory = Fory.builder() + .withXlang(false) + .withIntCompressed(false) + .withLongCompressed(false) + .build(); + +// For mixed data with arrays - enable array compression +Fory fory = Fory.builder() + .withXlang(false) + .withIntCompressed(true) + .withLongCompressed(true) + .withIntArrayCompressed(true) + .withLongArrayCompressed(true) + .build(); +CompressedArraySerializers.registerSerializers(fory); +``` + +## Related Topics + +- [Configuration](configuration.md) - All ForyBuilder options +- [Advanced Features](advanced-features.md) - Memory management diff --git a/versioned_docs/version-1.3.0/guide/java/configuration.md b/versioned_docs/version-1.3.0/guide/java/configuration.md new file mode 100644 index 00000000000..7b3ce60bf63 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/java/configuration.md @@ -0,0 +1,119 @@ +--- +title: Configuration +sidebar_position: 4 +id: configuration +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 page documents all configuration options available through `ForyBuilder`. + +## ForyBuilder Options + +| Option Name | Description | Default Value | +| ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | +| `timeRefIgnored` | Whether to ignore reference tracking of all time types registered in `TimeSerializers` and subclasses of those types when ref tracking is enabled. If ignored, ref tracking of every time type can be enabled by invoking `Fory#registerSerializer(Class, Serializer)`. For example, `fory.registerSerializer(Date.class, new DateSerializer(fory.getConfig(), true))`. Note that enabling ref tracking should happen before serializer codegen of any types which contain time fields. Otherwise, those fields will still skip ref tracking. | `true` | +| `compressInt` | Enables or disables int compression for smaller size. | `true` | +| `compressLong` | Enables or disables long compression for smaller size. | `true` | +| `compressIntArray` | Enables or disables SIMD-accelerated compression for int arrays when values can fit in smaller data types. Requires Java 16+. | `false` | +| `compressLongArray` | Enables or disables SIMD-accelerated compression for long arrays when values can fit in smaller data types. Requires Java 16+. | `false` | +| `compressString` | Enables or disables string compression for smaller size. | `false` | +| `classLoader` | The classloader is fixed per `Fory` instance because Fory caches class metadata. To use a different loader, create a new `Fory` or `ThreadSafeFory` configured with that loader, or rely on the thread context classloader before first class resolution. | `Thread.currentThread().getContextClassLoader()` | +| `compatible` | Schema evolution mode. `true`: readers can tolerate compatible field additions, removals, and reordering. `false`: use only when every reader and writer always uses the same class schema and you want faster serialization and smaller size. When unset, compatible mode is enabled in both xlang and native mode. [See more](schema-evolution.md). | `true` | +| `checkClassVersion` | Checks the class-version hash for intentional same-schema payloads. Fory disables this check when compatible mode is enabled because compatible mode carries schema metadata for evolution. | `false` | +| `checkJdkClassSerializable` | Enables or disables checking of `Serializable` interface for classes under `java.*`. If a class under `java.*` is not `Serializable`, Fory will throw an `UnsupportedOperationException`. | `true` | +| `registerGuavaTypes` | Whether to pre-register Guava types such as `RegularImmutableMap`/`RegularImmutableList`. These types are not public API, but seem pretty stable. | `true` | +| `requireClassRegistration` | Disabling may allow unknown classes to be deserialized, potentially causing security risks. | `true` | +| `maxDepth` | Set max depth for deserialization, when depth exceeds, an exception will be thrown. This can be used to refuse deserialization DDOS attack. | `50` | +| `maxTypeFields` | Maximum fields accepted in one received remote struct metadata body. | `512` | +| `maxTypeMetaBytes` | Maximum encoded body bytes accepted for one received TypeDef or TypeMeta body, excluding the 8-byte header and any extended-size varint. | `4096` | +| `maxSchemaVersionsPerType` | Maximum accepted remote metadata versions for one logical type. | `10` | +| `maxAverageSchemaVersionsPerType` | Average accepted remote metadata versions across all accepted remote types. The effective global floor is `8192` metadata entries. | `3` | +| `suppressClassRegistrationWarnings` | Whether to suppress class registration warnings. The warnings can be used for security audit, but may be annoying, this suppression will be enabled by default. | `true` | +| `metaShareEnabled` | Enables or disables meta share mode. | `true` if compatible mode is enabled, otherwise false. | +| `scopedMetaShareEnabled` | Scoped meta share focuses on a single serialization process. Metadata created or identified during this process is exclusive to it and is not shared with by other serializations. | `true` if compatible mode is enabled, otherwise false. | +| `metaCompressor` | Set a compressor for meta compression. Note that the passed MetaCompressor should be thread-safe. By default, a `Deflater` based compressor `DeflaterMetaCompressor` will be used. Users can pass other compressor such as `zstd` for better compression rate. | `DeflaterMetaCompressor` | +| `deserializeUnknownClass` | Enables or disables deserialization/skipping of data for non-existent or unknown classes. | `true` if compatible mode is enabled, otherwise false. | +| `codeGenEnabled` | Disabling may result in faster initial serialization but slower subsequent serializations. When unset, codegen defaults to enabled on ordinary JVMs and disabled on Android and GraalVM native image. Explicit `withCodegen(true)` on Android or GraalVM native image is accepted, but final build configuration forces interpreter serializers and emits a warning. If a build-time `@ForyStruct` static serializer is available, ordinary JVM `withCodegen(false)` and Android use it instead of the interpreter object serializer. | `true` on ordinary JVMs; `false` on Android and GraalVM native image | +| `asyncCompilationEnabled` | If enabled, serialization uses interpreter mode first and switches to JIT serialization after async serializer JIT for a class is finished. This option is forced off on Android and GraalVM native image because runtime code generation is unavailable there. | `false` | +| `copyRef` | When disabled, the copy performance will be better. But fory deep copy will ignore circular and shared reference. Same reference of an object graph will be copied into different objects in one `Fory#copy`. | `false` | +| `serializeEnumByName` | When enabled, Fory serializes enum names instead of numeric enum tags. Without this option, Fory writes declaration ordinals by default, or explicit stable ids when the enum is configured with `@ForyEnumId`. | `false` | + +## Example Configuration + +```java +Fory fory = Fory.builder() + .withXlang(false) + // enable reference tracking for shared/circular reference. + // Disable it will have better performance if no duplicate reference. + .withRefTracking(false) + // compress int for smaller size + .withIntCompressed(true) + // compress long for smaller size + .withLongCompressed(true) + // Optional: use `compatible=false` only when + // every reader and writer always uses the same class schema. + // .withCompatible(false) + // enable async multi-threaded compilation. + .withAsyncCompilation(true) + .build(); +``` + +## Compatible Mode + +Compatible mode is enabled by default for both xlang and native mode. Keep this default when classes +may evolve independently, when services deploy separately, or when xlang schemas are written by hand +in different languages. + +Use `withCompatible(false)` only when the class schema used to deserialize every payload is always +the same as the class schema used to serialize it and you want faster serialization and smaller size. +For xlang payloads, call `withCompatible(false)` only after verifying that every language uses the same schema, or when native types are generated from Fory schema IDL. + +## Security + +Keep class registration enabled for production and any untrusted payload source: + +```java +Fory fory = Fory.builder() + .requireClassRegistration(true) + .withMaxDepth(50) + .build(); +``` + +Security-related options: + +- `requireClassRegistration(true)` restricts deserialization to registered classes. +- `withMaxDepth(...)` rejects unexpectedly deep object graphs. +- `withMaxTypeFields(...)` and `withMaxTypeMetaBytes(...)` bound the field count + and encoded body size of one received remote metadata body. +- `withMaxSchemaVersionsPerType(...)` and + `withMaxAverageSchemaVersionsPerType(...)` bound accepted remote metadata versions without + changing registration, dynamic loading, or schema-evolution semantics. +- `withDeserializeUnknownClass(false)` avoids materializing unknown classes from metadata. +- `checkJdkClassSerializable(true)` keeps the JDK serializability check for `java.*` classes. +- Class registration warnings can be useful during security audits; use + `suppressClassRegistrationWarnings(false)` when you need to surface unexpected types. + +Use `requireClassRegistration(false)` only for trusted payloads, and pair it with a `TypeChecker` +allow list when dynamic class loading is required. + +## Related Topics + +- [Schema Metadata](schema-metadata.md) - `@ForyField`, `@Ignore`, integer encoding annotations, `serializeEnumByName`, and `@ForyEnumId` +- [Schema Evolution](schema-evolution.md) - Compatible mode and meta sharing +- [Compression](compression.md) - Int, long, and array compression details +- [Type Registration](type-registration.md) - Class registration options +- [Static Generated Serializers](static-generated-serializers.md) - Annotation-processor static generated serializers for `@ForyStruct`, `codegen=false`, and Android diff --git a/versioned_docs/version-1.3.0/guide/java/custom-serializers.md b/versioned_docs/version-1.3.0/guide/java/custom-serializers.md new file mode 100644 index 00000000000..10bd77b0d39 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/java/custom-serializers.md @@ -0,0 +1,227 @@ +--- +title: Custom Serializers +sidebar_position: 11 +id: custom_serializers +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 page covers the current Java custom serializer API. + +## Constructor Inputs + +Custom serializers should not retain `Fory`. + +- Use `Config` when the serializer only depends on immutable configuration and can be shared. +- Use `TypeResolver` when the serializer needs type metadata, generics, or nested dynamic dispatch. +- If a serializer retains `TypeResolver`, it is usually not shareable and should not implement + `Shareable`. + +## Basic Serializer + +Use `WriteContext` and `ReadContext` for per-operation state. Only get the buffer into a local variable +when you perform multiple reads or writes. + +```java +import org.apache.fory.config.Config; +import org.apache.fory.context.ReadContext; +import org.apache.fory.context.WriteContext; +import org.apache.fory.memory.MemoryBuffer; +import org.apache.fory.serializer.Serializer; +import org.apache.fory.serializer.Shareable; + +public final class FooSerializer extends Serializer implements Shareable { + public FooSerializer(Config config) { + super(config, Foo.class); + } + + @Override + public void write(WriteContext writeContext, Foo value) { + writeContext.getBuffer().writeInt64(value.f1); + writeContext.writeString(value.f2); + } + + @Override + public Foo read(ReadContext readContext) { + MemoryBuffer buffer = readContext.getBuffer(); + Foo foo = new Foo(); + foo.f1 = buffer.readInt64(); + foo.f2 = readContext.readString(buffer); + return foo; + } +} +``` + +Register it with a `Config`-based constructor when the serializer is shareable: + +```java +Fory fory = Fory.builder().withXlang(false).build(); +fory.registerSerializer(Foo.class, new FooSerializer(fory.getConfig())); +``` + +## Nested Objects + +If your serializer needs to write or read nested objects, use the context helpers instead of +retaining `Fory`: + +```java +import org.apache.fory.config.Config; +import org.apache.fory.context.ReadContext; +import org.apache.fory.context.WriteContext; +import org.apache.fory.serializer.Serializer; + +public final class EnvelopeSerializer extends Serializer { + public EnvelopeSerializer(Config config) { + super(config, Envelope.class); + } + + @Override + public void write(WriteContext writeContext, Envelope value) { + writeContext.writeRef(value.header); + writeContext.writeRef(value.payload); + } + + @Override + public Envelope read(ReadContext readContext) { + Envelope envelope = new Envelope(); + envelope.header = (Header) readContext.readRef(); + envelope.payload = readContext.readRef(); + return envelope; + } +} +``` + +This serializer can implement `Shareable` because it retains no Fory-instance-local mutable state. + +## Collection Serializers + +For Java collections, extend `CollectionSerializer` or `CollectionLikeSerializer`. + +- Use `CollectionSerializer` for real `Collection` implementations. +- Use `CollectionLikeSerializer` for collection-shaped types that do not implement `Collection`. +- Keep `supportCodegenHook == true` when the collection can use the standard element codegen path. +- Set `supportCodegenHook == false` only when you need to fully control element IO. + +Example: + +```java +import java.util.ArrayList; +import java.util.Collection; +import org.apache.fory.context.ReadContext; +import org.apache.fory.context.WriteContext; +import org.apache.fory.memory.MemoryBuffer; +import org.apache.fory.resolver.TypeResolver; +import org.apache.fory.serializer.collection.CollectionSerializer; + +public final class CustomCollectionSerializer> + extends CollectionSerializer { + public CustomCollectionSerializer(TypeResolver typeResolver, Class type) { + super(typeResolver, type, true); + } + + @Override + public Collection onCollectionWrite(WriteContext writeContext, T value) { + writeContext.getBuffer().writeVarUInt32Small7(value.size()); + return value; + } + + @Override + public T onCollectionRead(Collection collection) { + return (T) collection; + } + + @Override + public Collection newCollection(ReadContext readContext) { + MemoryBuffer buffer = readContext.getBuffer(); + int numElements = buffer.readVarUInt32Small7(); + setNumElements(numElements); + return new ArrayList(numElements); + } +} +``` + +## Map Serializers + +For Java maps, extend `MapSerializer` or `MapLikeSerializer`. + +```java +import java.util.LinkedHashMap; +import java.util.Map; +import org.apache.fory.memory.MemoryBuffer; +import org.apache.fory.context.ReadContext; +import org.apache.fory.context.WriteContext; +import org.apache.fory.resolver.TypeResolver; +import org.apache.fory.serializer.collection.MapSerializer; + +public final class CustomMapSerializer> extends MapSerializer { + public CustomMapSerializer(TypeResolver typeResolver, Class type) { + super(typeResolver, type, true); + } + + @Override + public Map onMapWrite(WriteContext writeContext, T value) { + writeContext.getBuffer().writeVarUInt32Small7(value.size()); + return value; + } + + @Override + public T onMapRead(Map map) { + return (T) map; + } + + @Override + public Map newMap(ReadContext readContext) { + MemoryBuffer buffer = readContext.getBuffer(); + int numElements = buffer.readVarUInt32Small7(); + setNumElements(numElements); + return new LinkedHashMap(numElements); + } +} +``` + +## Registration + +```java +Fory fory = Fory.builder().withXlang(false).build(); + +fory.registerSerializer(Foo.class, new FooSerializer(fory.getConfig())); +fory.registerSerializer( + CustomMap.class, new CustomMapSerializer<>(fory.getTypeResolver(), CustomMap.class)); +fory.registerSerializer( + CustomCollection.class, + new CustomCollectionSerializer<>(fory.getTypeResolver(), CustomCollection.class)); +``` + +If you want Fory to construct the serializer lazily, register a factory: + +```java +fory.registerSerializer( + CustomMap.class, resolver -> new CustomMapSerializer<>(resolver, CustomMap.class)); +``` + +## Shareability + +Implement the `Shareable` marker interface when the serializer can be safely reused across +equivalent Fory instances and concurrent operations. A shareable serializer must not retain operation +state, Fory-instance-local mutable state, or mutable scratch buffers shared across calls. Consumers can +check shareability via `serializer instanceof Shareable`. + +In practice: + +- `Config`-only serializers are often shareable. +- `TypeResolver`-based serializers are usually not shareable. +- Operation state belongs in `WriteContext`, `ReadContext`, and `CopyContext`, not in serializer + fields. diff --git a/versioned_docs/version-1.3.0/guide/java/graalvm-support.md b/versioned_docs/version-1.3.0/guide/java/graalvm-support.md new file mode 100644 index 00000000000..1f2503fe1ee --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/java/graalvm-support.md @@ -0,0 +1,329 @@ +--- +title: GraalVM Support +sidebar_position: 14 +id: graalvm_support +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. +--- + +## GraalVM Native Image + +GraalVM `native image` compiles Java code into native executables ahead-of-time, resulting in faster startup and lower memory usage. However, native images don't support runtime JIT compilation or reflection without explicit configuration. + +Apache Fory™ works excellently with GraalVM native image by using **codegen instead of reflection**. All serializer code is generated at build time, eliminating the need for reflection configuration files in most cases. + +## How It Works + +Fory generates serialization code at GraalVM build time when you: + +1. Create Fory as a **static** field +2. **Register** all classes in a static initializer +3. Call `fory.ensureSerializersCompiled()` to compile serializers +4. Configure the class to initialize at build time via `native-image.properties` + +**The main benefit**: You don't need to configure [reflection json](https://www.graalvm.org/latest/reference-manual/native-image/metadata/#specifying-reflection-metadata-in-json) or [serialization json](https://www.graalvm.org/latest/reference-manual/native-image/metadata/#serialization) for most serializable classes. + +Note: Fory's `asyncCompilationEnabled` option is automatically disabled for GraalVM native image since runtime JIT is not supported. + +## Basic Usage + +### Step 0: Add the GraalVM Support Dependency + +Add `fory-graalvm-feature` to your application dependencies when building a native image: + +```xml + + org.apache.fory + fory-graalvm-feature + ${fory.version} + +``` + +This dependency already ships GraalVM feature metadata in `META-INF/native-image`, so adding it +automatically enables `org.apache.fory.graalvm.feature.ForyGraalVMFeature` during native-image +builds. + +### Step 1: Create Fory and Register Classes + +```java +import org.apache.fory.Fory; + +public class Example { + // Must be static field + static Fory fory; + + static { + fory = Fory.builder().withXlang(false).build(); + fory.register(MyClass.class); + fory.register(AnotherClass.class); + // Compile all serializers at build time + fory.ensureSerializersCompiled(); + } + + public static void main(String[] args) { + byte[] bytes = fory.serialize(new MyClass()); + MyClass obj = (MyClass) fory.deserialize(bytes); + } +} +``` + +### Step 2: Configure Build-Time Initialization + +Create `resources/META-INF/native-image/your-group/your-artifact/native-image.properties`: + +```properties +Args = --initialize-at-build-time=com.example.Example +``` + +## What `fory-graalvm-feature` Handles + +After you add the `fory-graalvm-feature` dependency, Fory automatically registers the extra +GraalVM metadata needed by advanced cases such as: + +- **Private constructors** (classes without accessible no-arg constructor) +- **Private inner classes/records** +- **Dynamic proxy serialization** + +This removes the need for manual `reflect-config.json` in most applications. Your own +`native-image.properties` still only needs to configure your build-time initialized bootstrap +class, for example: + +```properties +Args = --initialize-at-build-time=com.example.Example +``` + +| Scenario | Without Feature | With Feature | +| ------------------------------- | ------------------------- | --------------- | +| Public classes with no-arg ctor | Works | Works | +| Private constructors | Needs reflect-config.json | Auto-registered | +| Private inner records | Needs reflect-config.json | Auto-registered | +| Dynamic proxies | Needs manual config | Auto-registered | + +### Example with Private Record + +```java +public class Example { + // Private inner record - requires ForyGraalVMFeature + private record PrivateRecord(int id, String name) {} + + static Fory fory; + + static { + fory = Fory.builder().withXlang(false).build(); + fory.register(PrivateRecord.class); + fory.ensureSerializersCompiled(); + } +} +``` + +### Example with Dynamic Proxy + +```java +import org.apache.fory.platform.GraalvmSupport; + +public class ProxyExample { + public interface MyService { + String execute(); + } + + public interface Audited { + String traceId(); + } + + static Fory fory; + + static { + fory = Fory.builder().withXlang(false).build(); + // Register the exact interface list used by Proxy.newProxyInstance(...) + GraalvmSupport.registerProxySupport(MyService.class, Audited.class); + fory.ensureSerializersCompiled(); + } +} +``` + +Use `registerProxySupport(MyService.class)` for a single-interface proxy. For proxies that implement +multiple interfaces, pass the full interface list in the same order used to create the proxy. With +`fory-graalvm-feature` on the classpath, this replaces manual `proxy-config.json` entries for those +registered proxy shapes. + +## Thread-Safe Fory + +For multi-threaded applications, use `ThreadLocalFory`: + +```java +import org.apache.fory.Fory; +import org.apache.fory.ThreadLocalFory; +import org.apache.fory.ThreadSafeFory; + +public class ThreadSafeExample { + public record Foo(int f1, String f2, List f3) {} + + static ThreadSafeFory fory; + + static { + fory = new ThreadLocalFory(builder -> { + Fory f = builder.build(); + f.register(Foo.class); + f.ensureSerializersCompiled(); + return f; + }); + } + + public static void main(String[] args) { + Foo foo = new Foo(10, "abc", List.of("str1", "str2")); + byte[] bytes = fory.serialize(foo); + Foo result = (Foo) fory.deserialize(bytes); + } +} +``` + +## Troubleshooting + +### "Type is instantiated reflectively but was never registered" + +If you see this error: + +``` +Type com.example.MyClass is instantiated reflectively but was never registered +``` + +**Solution**: Register the class with Fory (don't add to reflect-config.json): + +```java +fory.register(MyClass.class); +fory.ensureSerializersCompiled(); +``` + +If the class has a private constructor, either: + +1. Make sure `fory-graalvm-feature` is already on the native-image classpath, or +2. Create a `reflect-config.json` for that specific class + +## Framework Integration + +For framework developers integrating Fory: + +1. Provide a configuration file for users to list serializable classes +2. Load those classes and call `fory.register(Class)` for each +3. Call `fory.ensureSerializersCompiled()` after all registrations +4. Configure your integration class for build-time initialization + +## Benchmark + +Performance comparison between Fory and GraalVM JDK Serialization: + +| Type | Compression | Speed | Size | +| ------ | ----------- | ---------- | ---- | +| Struct | Off | 46x faster | 43% | +| Struct | On | 24x faster | 31% | +| Pojo | Off | 12x faster | 56% | +| Pojo | On | 12x faster | 48% | + +See [Benchmark.java](https://github.com/apache/fory/blob/main/integration_tests/graalvm_tests/src/main/java/org/apache/fory/graalvm/Benchmark.java) for benchmark code. + +### Struct Benchmark + +#### Class Fields + +```java +public class Struct implements Serializable { + public int f1; + public long f2; + public float f3; + public double f4; + public int f5; + public long f6; + public float f7; + public double f8; + public int f9; + public long f10; + public float f11; + public double f12; +} +``` + +#### Benchmark Results + +No compression: + +``` +Benchmark repeat number: 400000 +Object type: class org.apache.fory.graalvm.Struct +Compress number: false +Fory size: 76.0 +JDK size: 178.0 +Fory serialization took mills: 49 +JDK serialization took mills: 2254 +Compare speed: Fory is 45.70x speed of JDK +Compare size: Fory is 0.43x size of JDK +``` + +Compress number: + +``` +Benchmark repeat number: 400000 +Object type: class org.apache.fory.graalvm.Struct +Compress number: true +Fory size: 55.0 +JDK size: 178.0 +Fory serialization took mills: 130 +JDK serialization took mills: 3161 +Compare speed: Fory is 24.16x speed of JDK +Compare size: Fory is 0.31x size of JDK +``` + +### Pojo Benchmark + +#### Class Fields + +```java +public class Foo implements Serializable { + int f1; + String f2; + List f3; + Map f4; +} +``` + +#### Benchmark Results + +No compression: + +``` +Benchmark repeat number: 400000 +Object type: class org.apache.fory.graalvm.Foo +Compress number: false +Fory size: 541.0 +JDK size: 964.0 +Fory serialization took mills: 1663 +JDK serialization took mills: 16266 +Compare speed: Fory is 12.19x speed of JDK +Compare size: Fory is 0.56x size of JDK +``` + +Compress number: + +``` +Benchmark repeat number: 400000 +Object type: class org.apache.fory.graalvm.Foo +Compress number: true +Fory size: 459.0 +JDK size: 964.0 +Fory serialization took mills: 1289 +JDK serialization took mills: 15069 +Compare speed: Fory is 12.11x speed of JDK +Compare size: Fory is 0.48x size of JDK +``` diff --git a/versioned_docs/version-1.3.0/guide/java/grpc-support.md b/versioned_docs/version-1.3.0/guide/java/grpc-support.md new file mode 100644 index 00000000000..2239444fa1e --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/java/grpc-support.md @@ -0,0 +1,398 @@ +--- +title: gRPC Support +sidebar_position: 17 +id: grpc_support +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. +--- + +Fory can generate Java gRPC service companions for schemas that define +services. The generated service code uses normal grpc-java channels, servers, +deadlines, status codes, interceptors, and transport security, while request +and response objects are serialized with Fory instead of protobuf. + +Use this mode when both sides of the RPC are generated from the same Fory IDL, +protobuf IDL, or FlatBuffers IDL and you want gRPC transport semantics with +Fory payload encoding. Use standard protobuf gRPC code generation when your API +must be consumed by generic protobuf clients, reflection tools, or components +that expect protobuf message bytes. + +For Scala generated grpc-java companions, see +[Scala gRPC Support](../scala/grpc-support.md). For Kotlin coroutine stubs and +service bases, see [Kotlin gRPC Support](../kotlin/grpc-support.md). + +## Add Dependencies + +The generated Java service files compile against grpc-java. Fory Java artifacts +do not add gRPC as a hard dependency, so add grpc-java dependencies in your +application build and align the version with the rest of your service stack. + +Maven: + +```xml + + + org.apache.fory + fory-core + ${fory.version} + + + io.grpc + grpc-api + ${grpc.version} + + + io.grpc + grpc-stub + ${grpc.version} + + + io.grpc + grpc-netty-shaded + ${grpc.version} + + +``` + +Gradle: + +```kotlin +dependencies { + implementation("org.apache.fory:fory-core:$foryVersion") + implementation("io.grpc:grpc-api:$grpcVersion") + implementation("io.grpc:grpc-stub:$grpcVersion") + implementation("io.grpc:grpc-netty-shaded:$grpcVersion") +} +``` + +## Define a Service + +Service definitions can come from Fory IDL, protobuf IDL, or FlatBuffers +`rpc_service` definitions. A Fory IDL service looks like this: + +```protobuf +package demo.greeter; + +message HelloRequest { + string name = 1; +} + +message HelloReply { + string reply = 1; +} + +service Greeter { + rpc SayHello (HelloRequest) returns (HelloReply); +} +``` + +Generate Java model and gRPC companion code with `--grpc`: + +```bash +foryc service.fdl --java_out=./generated/java --grpc +``` + +For this schema, the Java generator emits: + +| File | Purpose | +| ------------------------ | -------------------------------------------- | +| `HelloRequest.java` | Fory model type for the request | +| `HelloReply.java` | Fory model type for the response | +| `GreeterForyModule.java` | Fory registration module for generated types | +| `GreeterGrpc.java` | grpc-java service base class, stubs, codecs | + +## Implement a Server + +Extend the generated `GreeterGrpc.GreeterImplBase` class and register it with a +standard grpc-java `Server`. + +```java +package demo.greeter; + +import io.grpc.Server; +import io.grpc.ServerBuilder; +import io.grpc.stub.StreamObserver; + +final class GreeterService extends GreeterGrpc.GreeterImplBase { + @Override + public void sayHello( + HelloRequest request, StreamObserver responseObserver) { + HelloReply reply = new HelloReply(); + reply.setReply("Hello, " + request.getName()); + responseObserver.onNext(reply); + responseObserver.onCompleted(); + } +} + +public final class GreeterServer { + public static void main(String[] args) throws Exception { + Server server = + ServerBuilder.forPort(50051) + .addService(new GreeterService()) + .build() + .start(); + server.awaitTermination(); + } +} +``` + +Generated request and response types are registered by the generated code, so +service implementations do not perform manual serializer registration. + +## Create a Client + +Use the generated stubs with an ordinary grpc-java channel: + +```java +package demo.greeter; + +import io.grpc.ManagedChannel; +import io.grpc.ManagedChannelBuilder; + +public final class GreeterClient { + public static void main(String[] args) { + ManagedChannel channel = + ManagedChannelBuilder.forAddress("localhost", 50051) + .usePlaintext() + .build(); + try { + GreeterGrpc.GreeterBlockingStub stub = + GreeterGrpc.newBlockingStub(channel); + + HelloRequest request = new HelloRequest(); + request.setName("Fory"); + HelloReply reply = stub.sayHello(request); + System.out.println(reply.getReply()); + } finally { + channel.shutdownNow(); + } + } +} +``` + +For asynchronous calls, use `GreeterGrpc.newStub(channel)`. For future-based +unary calls, use `GreeterGrpc.newFutureStub(channel)`. + +## Streaming RPCs + +Fory service definitions can use the same gRPC streaming shapes: + +```protobuf +service Greeter { + rpc SayHello (HelloRequest) returns (HelloReply); + rpc LotsOfReplies (HelloRequest) returns (stream HelloReply); + rpc LotsOfGreetings (stream HelloRequest) returns (HelloReply); + rpc Chat (stream HelloRequest) returns (stream HelloReply); +} +``` + +Generated Java service methods follow grpc-java conventions: + +| IDL shape | Server method shape | Client method shape | +| ----------------------------------------- | ------------------------------------------------------ | -------------------------------------- | +| `rpc A (Req) returns (Res)` | `void a(Req request, StreamObserver responses)` | blocking, async, and future unary stub | +| `rpc A (Req) returns (stream Res)` | `void a(Req request, StreamObserver responses)` | blocking iterator or async observer | +| `rpc A (stream Req) returns (Res)` | `StreamObserver a(StreamObserver responses)` | async request observer | +| `rpc A (stream Req) returns (stream Res)` | `StreamObserver a(StreamObserver responses)` | async request observer | + +Server implementations can use the generated streaming method shapes directly: + +```java +package demo.greeter; + +import io.grpc.stub.StreamObserver; +import java.util.ArrayList; +import java.util.List; + +final class GreeterService extends GreeterGrpc.GreeterImplBase { + @Override + public void lotsOfReplies( + HelloRequest request, StreamObserver responseObserver) { + HelloReply first = new HelloReply(); + first.setReply("Hello, " + request.getName()); + responseObserver.onNext(first); + + HelloReply second = new HelloReply(); + second.setReply("Welcome, " + request.getName()); + responseObserver.onNext(second); + responseObserver.onCompleted(); + } + + @Override + public StreamObserver lotsOfGreetings( + StreamObserver responseObserver) { + List names = new ArrayList<>(); + return new StreamObserver<>() { + @Override + public void onNext(HelloRequest request) { + names.add(request.getName()); + } + + @Override + public void onError(Throwable error) { + responseObserver.onError(error); + } + + @Override + public void onCompleted() { + HelloReply reply = new HelloReply(); + reply.setReply(String.join(", ", names)); + responseObserver.onNext(reply); + responseObserver.onCompleted(); + } + }; + } + + @Override + public StreamObserver chat( + StreamObserver responseObserver) { + return new StreamObserver<>() { + @Override + public void onNext(HelloRequest request) { + HelloReply reply = new HelloReply(); + reply.setReply("Hello, " + request.getName()); + responseObserver.onNext(reply); + } + + @Override + public void onError(Throwable error) { + responseObserver.onError(error); + } + + @Override + public void onCompleted() { + responseObserver.onCompleted(); + } + }; + } +} +``` + +Generated clients return the standard grpc-java call shapes: + +```java +package demo.greeter; + +import io.grpc.stub.StreamObserver; +import java.util.Iterator; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +final class StreamingClient { + private final GreeterGrpc.GreeterBlockingStub blockingStub; + private final GreeterGrpc.GreeterStub asyncStub; + + StreamingClient( + GreeterGrpc.GreeterBlockingStub blockingStub, + GreeterGrpc.GreeterStub asyncStub) { + this.blockingStub = blockingStub; + this.asyncStub = asyncStub; + } + + void run() throws InterruptedException { + Iterator replies = + blockingStub.lotsOfReplies(newRequest("Fory")); + while (replies.hasNext()) { + System.out.println(replies.next().getReply()); + } + + CountDownLatch greetingsDone = new CountDownLatch(1); + StreamObserver greetings = + asyncStub.lotsOfGreetings(new StreamObserver<>() { + @Override + public void onNext(HelloReply reply) { + System.out.println(reply.getReply()); + } + + @Override + public void onError(Throwable error) { + greetingsDone.countDown(); + } + + @Override + public void onCompleted() { + greetingsDone.countDown(); + } + }); + greetings.onNext(newRequest("Ada")); + greetings.onNext(newRequest("Grace")); + greetings.onCompleted(); + greetingsDone.await(5, TimeUnit.SECONDS); + + CountDownLatch chatDone = new CountDownLatch(1); + StreamObserver chat = + asyncStub.chat(new StreamObserver<>() { + @Override + public void onNext(HelloReply reply) { + System.out.println(reply.getReply()); + } + + @Override + public void onError(Throwable error) { + chatDone.countDown(); + } + + @Override + public void onCompleted() { + chatDone.countDown(); + } + }); + chat.onNext(newRequest("Fory")); + chat.onCompleted(); + chatDone.await(5, TimeUnit.SECONDS); + } + + private static HelloRequest newRequest(String name) { + HelloRequest request = new HelloRequest(); + request.setName(name); + return request; + } +} +``` + +The generated descriptors preserve the exact IDL service and method names for +the gRPC path. + +## gRPC Runtime Behavior + +The generated service code only replaces request and response serialization. +All normal gRPC operational features still belong to grpc-java: + +- Deadlines and cancellations +- TLS and authentication +- Name resolution and load balancing +- Client and server interceptors +- Status codes and metadata +- Channel pooling and lifecycle management + +## Troubleshooting + +### Missing `io.grpc` or Guava Classes + +Add the grpc-java dependencies shown above. Generated Fory service files import +grpc-java APIs, but Fory Java artifacts intentionally do not depend on gRPC. + +### `UNIMPLEMENTED` + +Confirm that the generated service implementation is registered with +`ServerBuilder.addService(...)`, and that the client and server were generated +from the same package, service, and method names. + +### Protobuf Clients Cannot Decode the Service + +Fory gRPC companions do not use protobuf wire encoding for messages. Use a +Fory-generated client for Fory-generated services, or provide a separate +protobuf service endpoint for generic protobuf clients. diff --git a/versioned_docs/version-1.3.0/guide/java/index.md b/versioned_docs/version-1.3.0/guide/java/index.md new file mode 100644 index 00000000000..bf09b4e8610 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/java/index.md @@ -0,0 +1,253 @@ +--- +title: Java Serialization Guide +sidebar_position: 0 +id: serialization_index +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. +--- + +Apache Fory™ provides blazingly fast Java object serialization with JIT compilation and zero-copy techniques. Java supports both xlang mode and native mode. Xlang mode is the default cross-language wire format and uses compatible schema evolution. Native mode is the Java-only wire format for same-language object serialization, JDK serialization replacement behavior, framework replacement, and Java-native object graph features. + +## Features + +### High Performance + +- **JIT Code Generation**: Highly-extensible JIT framework generates serializer code at runtime using async multi-threaded compilation, delivering 20-170x speedup through: + - Inlining variables to reduce memory access + - Inlining method calls to eliminate virtual dispatch overhead + - Minimizing conditional branching + - Eliminating hash lookups +- **Zero-Copy**: Direct memory access without intermediate buffer copies; row format supports random access and partial serialization +- **Variable-Length Encoding**: Optimized compression for integers, longs +- **Meta Sharing**: Cached class metadata reduces redundant type information +- **SIMD Acceleration**: Java Vector API support for array operations (Java 16+) + +### Drop-in Replacement + +- **100% JDK Serialization Compatible**: Supports `writeObject`/`readObject`/`writeReplace`/`readResolve`/`readObjectNoData`/`Externalizable` +- **Java 8+ Support**: Works across all modern Java versions including Java 17+ records +- **GraalVM Native Image**: AOT compilation support without reflection configuration +- **Android API 26+ Support**: Core object serialization works on Android without runtime code generation. + +### Advanced Features + +- **Reference Tracking**: Automatic handling of shared and circular references +- **Schema Evolution**: Forward/backward compatibility for class schema changes +- **Polymorphism**: Full support for inheritance hierarchies and interfaces +- **Deep Copy**: Efficient deep cloning of complex object graphs with reference preservation +- **Security**: Class registration and configurable deserialization policies + +## Installation + +### Maven + +```xml + + org.apache.fory + fory-core + 1.3.0 + +``` + +### Gradle + +```kotlin +implementation("org.apache.fory:fory-core:1.3.0") +``` + +### JDK25+ + +On JDK25+, open `java.lang.invoke` to Fory. Use `ALL-UNNAMED` when Fory is on +the classpath: + +```bash +--add-opens=java.base/java.lang.invoke=ALL-UNNAMED +``` + +Use the Fory core module name when Fory is on the module path: + +```bash +--add-opens=java.base/java.lang.invoke=org.apache.fory.core +``` + +## Quick Start + +Note that Fory creation is not cheap, the **Fory instances should be reused between serializations** instead of creating it every time. You should keep Fory as a static global variable, or instance variable of some singleton object or limited objects. + +### Single-Thread Usage + +```java +import java.util.List; +import java.util.Arrays; + +import org.apache.fory.*; +import org.apache.fory.config.*; + +public class Example { + public static void main(String[] args) { + SomeClass object = new SomeClass(); + // Note that Fory instances should be reused between + // multiple serializations of different objects. + Fory fory = Fory.builder() + .withXlang(true) + .requireClassRegistration(true) + .build(); + // Registering types can reduce class name serialization overhead, but not mandatory. + // If class registration enabled, all custom types must be registered. + // Registration order must be consistent if id is not specified + fory.register(SomeClass.class); + byte[] bytes = fory.serialize(object); + System.out.println(fory.deserialize(bytes)); + } +} +``` + +### Multi-Thread Usage + +```java +import org.apache.fory.*; +import org.apache.fory.config.*; + +public class Example { + public static void main(String[] args) { + SomeClass object = new SomeClass(); + ThreadSafeFory fory = Fory.builder() + .withXlang(true) + .buildThreadSafeFory(); + fory.register(SomeClass.class, 1); + byte[] bytes = fory.serialize(object); + System.out.println(fory.deserialize(bytes)); + } +} +``` + +### Fory Instance Reuse Pattern + +```java +import org.apache.fory.*; +import org.apache.fory.config.*; + +public class Example { + private static final ThreadSafeFory fory = Fory.builder() + .withXlang(true) + .buildThreadSafeFory(); + + static { + fory.register(SomeClass.class, 1); + } + + public static void main(String[] args) { + SomeClass object = new SomeClass(); + byte[] bytes = fory.serialize(object); + System.out.println(fory.deserialize(bytes)); + } +} +``` + +## Xlang Mode And Native Mode + +Use xlang mode for cross-language payloads and schemas shared with non-Java implementations. It is the default Java wire mode, and Java examples that use it set `.withXlang(true)` explicitly so the mode choice is visible. + +Use native mode for Java-only traffic. Native mode is selected with `.withXlang(false)` and owns Java-specific object behavior such as JDK serialization hooks, `Externalizable`, dynamic object graphs, object copy, and Java native-mode zero-copy buffers. It is optimized for the JVM type system and supports a broader Java object surface than xlang mode. Compatible mode is enabled by default. Set `.withCompatible(false)` only when every reader and writer uses the same class schema and you want faster serialization and smaller size. If you are replacing JDK serialization, Kryo, FST, Hessian, or Java-only Protocol Buffers payloads, start with native mode. + +See [Native Serialization](native-serialization.md) for Java-only serialization details and [Xlang Serialization](xlang-serialization.md) for Java xlang registration and interoperability rules. + +## Thread Safety + +Fory provides two thread-safe Fory instance styles: + +### `buildThreadSafeFory` + +This is the default choice. It uses a fixed-size shared `ThreadPoolFory` sized to +`4 * availableProcessors()` and is the preferred instance form for virtual-thread workloads: + +```java +ThreadSafeFory fory = Fory.builder() + .withXlang(true) + .withRefTracking(false) + .withAsyncCompilation(true) + .buildThreadSafeFory(); +``` + +See more details in [Virtual Threads](virtual-threads.md). + +### ThreadLocalFory + +Use `buildThreadLocalFory()` only when you explicitly want one `Fory` instance per long-lived +platform thread, or when you want to pin that choice regardless of JDK version: + +```java +ThreadSafeFory fory = Fory.builder() + .withXlang(true) + .buildThreadLocalFory(); +fory.register(SomeClass.class, 1); +byte[] bytes = fory.serialize(object); +System.out.println(fory.deserialize(bytes)); +``` + +### `buildThreadSafeForyPool` + +Use `buildThreadSafeForyPool(poolSize)` when you want to set that fixed shared pool size +explicitly. It eagerly creates `poolSize` `Fory` instances, keeps them in shared fixed slots, and +then lets any caller borrow one through a thread-agnostic fast path. Calls only block when every +pooled instance is already in use; the pool does not key cached instances by thread identity: + +```java +ThreadSafeFory fory = Fory.builder() + .withXlang(true) + .withRefTracking(false) + .withAsyncCompilation(true) + .buildThreadSafeForyPool(poolSize); +``` + +### Builder Methods + +```java +// Single-thread Fory +Fory fory = Fory.builder() + .withXlang(true) + .withRefTracking(false) + .withAsyncCompilation(true) + .build(); + +// Thread-safe Fory (thread-safe Fory backed by a pool of Fory instances) +ThreadSafeFory fory = Fory.builder() + .withXlang(true) + .withRefTracking(false) + .withAsyncCompilation(true) + .buildThreadSafeFory(); + +// Explicit thread-local Fory instance +ThreadSafeFory threadLocalFory = Fory.builder() + .withXlang(true) + .buildThreadLocalFory(); +``` + +## Next Steps + +- [Configuration](configuration.md) - Learn about ForyBuilder options +- [Schema Metadata](schema-metadata.md) - `@ForyField`, `@Ignore`, integer encoding annotations, `serializeEnumByName`, and `@ForyEnumId` +- [Basic Serialization](basic-serialization.md) - Detailed serialization patterns +- [Object Copy](object-copy.md) - Deep-copy Java object graphs in memory +- [Compression](compression.md) - Integer, long, and array compression options +- [Virtual Threads](virtual-threads.md) - Virtual-thread usage and pool sizing guidance +- [gRPC Support](grpc-support.md) - Fory payloads over grpc-java +- [Type Registration](type-registration.md) - Class registration and security +- [Custom Serializers](custom-serializers.md) - Implement custom serializers +- [Xlang Serialization](xlang-serialization.md) - Serialize data for other languages +- [Native Serialization](native-serialization.md) - Java-only serialization features +- [Static Generated Serializers](static-generated-serializers.md) - Annotation-processor static generated serializers for `@ForyStruct` +- [GraalVM Support](graalvm-support.md) - Build-time serializer compilation for native images diff --git a/versioned_docs/version-1.3.0/guide/java/native-serialization.md b/versioned_docs/version-1.3.0/guide/java/native-serialization.md new file mode 100644 index 00000000000..efe79c15546 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/java/native-serialization.md @@ -0,0 +1,401 @@ +--- +title: Native Serialization +sidebar_position: 3 +id: native_serialization +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. +--- + +Java native serialization is the Java-only wire format selected with `withXlang(false)`. Use it +when every writer and reader is a Java/JVM process and the payload should follow the JVM type system +instead of the portable xlang type system. Native serialization is the right starting point for +Java/JVM-only replacements of JDK serialization, Kryo, FST, Hessian, or Java-only Protocol Buffers +payloads. + +Native serialization in this page means Fory's `xlang=false` wire mode. It is separate from GraalVM +native image support, which is covered in [GraalVM Support](graalvm-support.md). + +Use [Xlang Serialization](xlang-serialization.md), the default Java mode, when bytes must be read by +non-Java Fory implementations. + +## When To Use Native Serialization + +Use native serialization when: + +- A payload is produced and consumed only by Java/JVM applications. +- The object model uses Java-specific types, JDK collections, wrapper types, inheritance, + interfaces, or polymorphism that do not need a cross-language schema. +- Existing classes rely on JDK serialization hooks such as `writeObject`, `readObject`, + `writeReplace`, `readResolve`, `readObjectNoData`, or `Externalizable`. +- You need Java object copy through `Fory.copy(...)`. +- Large primitive arrays or binary payloads should use native-mode out-of-band buffers. +- You are replacing Java-only serialization frameworks and want the broadest Java object surface. + +Use xlang serialization instead when the payload must be read by Python, C++, Go, +Rust, JavaScript/TypeScript, C#, Swift, Dart, Scala, Kotlin, or another +non-Java implementation. + +## Create a Native-Mode Fory Instance + +```java +import org.apache.fory.Fory; + +Fory fory = Fory.builder() + .withXlang(false) + .requireClassRegistration(true) + .withRefTracking(true) + .build(); + +byte[] bytes = fory.serialize(object); +Object decoded = fory.deserialize(bytes); +``` + +Create and reuse a `Fory` or `ThreadSafeFory` instance for each configuration. Fory creation is not +cheap because Fory caches class metadata, serializers, and generated code. + +```java +import org.apache.fory.Fory; +import org.apache.fory.ThreadSafeFory; + +ThreadSafeFory fory = Fory.builder() + .withXlang(false) + .requireClassRegistration(true) + .withRefTracking(true) + .buildThreadSafeFory(); + +fory.register(Order.class, 100); +``` + +Register classes and serializers during startup before concurrent serialization starts. Use a +separate Fory instance when class loader, registration, security, schema evolution, or reference-tracking +settings differ. + +## Schema Evolution + +Native serialization defaults to compatible mode, so readers can tolerate schema changes during +rolling deployments when the schema metadata remains compatible: + +```java +Fory fory = Fory.builder() + .withXlang(false) + .build(); +``` + +Compatible mode lets readers tolerate added, removed, or reordered fields when the schema metadata +remains compatible. It also enables metadata sharing by default. See [Schema Evolution](schema-evolution.md) +for field IDs, class version checks, meta sharing, and unknown-class handling. + +For faster serialization and smaller size, set `.withCompatible(false)` only when +every reader and writer always uses the same class schema. + +## Registration And Security + +Class registration is enabled by default. Keep it enabled for service boundaries and register +application classes explicitly: + +```java +Fory fory = Fory.builder() + .withXlang(false) + .requireClassRegistration(true) + .build(); + +fory.register(Order.class, 100); +fory.register(LineItem.class, 101); +``` + +Explicit numeric IDs avoid registration-order drift. If you use `fory.register(MyClass.class)` +without an ID, every writer and reader must register classes in the same order. Name-based +registration is also available when type ID coordination is harder: + +```java +fory.register(Order.class, "com.example", "Order"); +``` + +Disable class registration only in trusted environments. If you need dynamic class loading, install +a `TypeChecker` or `AllowListChecker` so deserialization can reject unexpected classes: + +```java +import org.apache.fory.Fory; +import org.apache.fory.resolver.AllowListChecker; + +AllowListChecker checker = new AllowListChecker(AllowListChecker.CheckLevel.STRICT); +checker.allowClass("com.example.*"); + +Fory fory = Fory.builder() + .withXlang(false) + .requireClassRegistration(false) + .withTypeChecker(checker) + .withMaxDepth(100) + .build(); +``` + +Use `withMaxDepth(...)` to cap object graph depth for untrusted or externally supplied payloads. +See [Type Registration](type-registration.md) for the full security configuration. + +## Java Object Surface + +Native serialization owns the Java-specific object surface: + +- POJOs, records, enums, primitive arrays, object arrays, and common JDK collections. +- Inheritance, interfaces, polymorphic fields, shared references, and circular object graphs. +- Java wrapper and collection behavior that does not have to map to a portable xlang type. +- JDK serialization hooks for classes that require Java serialization compatibility. +- Custom serializers registered with `registerSerializer(...)` or `registerSerializerAndType(...)`. + +For ordinary application classes, Fory can use generated serializers and avoid JDK +`ObjectOutputStream` semantics. Classes that require JDK serialization hooks may use the Java +serialization-compatible path; prefer a Fory custom serializer for hot classes when the hook-based +path is too expensive. + +## JDK Serialization Hooks + +Java native mode supports the JDK serialization hooks that are part of many existing Java object +models: + +- `writeObject` and `readObject` +- `writeReplace` and `readResolve` +- `readObjectNoData` +- `Externalizable` + +```java +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; + +public class MyClass implements Serializable { + private void writeObject(ObjectOutputStream out) throws IOException { + // Custom serialization logic. + } + + private void readObject(ObjectInputStream in) throws IOException { + // Custom deserialization logic. + } + + private Object writeReplace() { + return this; + } + + private Object readResolve() { + return this; + } +} +``` + +Fory native payloads are not JDK `ObjectOutputStream` payloads. The hooks are honored for +Java-object compatibility, but new payloads should be written and read by Fory. + +## Migrating From Java Serialization Frameworks + +When replacing JDK serialization, Kryo, FST, Hessian, or a Java-only Protocol Buffers pipeline: + +1. Start with `.withXlang(false)` because the data is Java-only. +2. Keep `requireClassRegistration(true)` and register application classes with explicit IDs. +3. Keep compatible mode enabled when writer and reader deployments roll independently. +4. Enable `.withRefTracking(true)` only when identity or circular references matter. +5. Add custom serializers for hot classes that would otherwise use expensive JDK serialization hooks. +6. Keep old and new byte streams separated when possible. + +When an application must read data that may be either JDK `ObjectOutputStream` bytes or Fory +native-mode bytes, `JavaSerializer.serializedByJDK` can identify the JDK payload before falling +back to Fory: + +```java +import java.io.ByteArrayInputStream; +import java.io.ObjectInputStream; +import org.apache.fory.serializer.JavaSerializer; + +if (JavaSerializer.serializedByJDK(bytes)) { + ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(bytes)); + return objectInputStream.readObject(); +} +return fory.deserialize(bytes); +``` + +Use this bridge only at boundaries that actually accept both formats. Native-mode Fory payloads +should otherwise be written and read by Fory directly. + +## Object Graphs And Reference Tracking + +Native mode supports shared references and circular references when reference tracking is enabled: + +```java +Fory fory = Fory.builder() + .withXlang(false) + .withRefTracking(true) + .build(); +``` + +Disable reference tracking only for value-shaped graphs where identity and cycles are not part of +the data model: + +```java +Fory fory = Fory.builder() + .withXlang(false) + .withRefTracking(false) + .build(); +``` + +Reference tracking is a semantic choice. Turning it off can improve performance and reduce payload +size, but repeated references deserialize as distinct objects and cycles are unsupported. + +## Object Copy + +Fory can deep-copy Java objects without materializing a byte array. For full copy semantics, custom +copy hooks, and troubleshooting, see [Object Copy](object-copy.md). + +```java +Fory fory = Fory.builder() + .withXlang(false) + .withRefCopy(true) + .build(); + +MyClass copy = fory.copy(original); +``` + +`withRefCopy(true)` controls reference preservation for copy operations. It is separate from +`withRefTracking(...)`, which controls serialization and deserialization. + +## Zero-Copy Serialization + +Native mode supports out-of-band `BufferObject` payloads for large binary values and primitive +arrays: + +```java +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; +import org.apache.fory.Fory; +import org.apache.fory.memory.MemoryBuffer; +import org.apache.fory.serializer.BufferObject; + +Fory fory = Fory.builder() + .withXlang(false) + .build(); + +List value = Arrays.asList("str", new byte[1000], new int[100], new double[100]); +Collection bufferObjects = new ArrayList<>(); +byte[] bytes = fory.serialize(value, bufferObject -> !bufferObjects.add(bufferObject)); +List buffers = bufferObjects.stream() + .map(BufferObject::toBuffer) + .collect(Collectors.toList()); + +Object decoded = fory.deserialize(bytes, buffers); +``` + +The callback returns `false` for buffers that should be sent out-of-band. The main byte array still +contains the root object graph and references the buffers in callback order. + +Use this when the transport can carry the main payload and buffers separately. If the stream is +stored or sent as one byte array, omit the callback and let Fory keep buffer contents in-band. + +Native serialization also supports byte arrays, `MemoryBuffer`, `ByteBuffer`, `OutputStream`, +`ForyInputStream`, and `ForyReadableChannel` APIs. Choose the API that matches the boundary you +already own; avoid copying through `byte[]` when a buffer or stream is already available. + +## Class Loaders + +```java +ClassLoader loader = Thread.currentThread().getContextClassLoader(); + +Fory fory = Fory.builder() + .withXlang(false) + .withClassLoader(loader) + .build(); +``` + +Each `Fory` instance is tied to one class loader because class metadata and serializers are cached. +Build a separate Fory instance for each application, plugin, or tenant class loader instead of switching +loaders on an existing instance. + +## Performance Guidelines + +- Reuse `Fory` or `ThreadSafeFory` instances instead of rebuilding them per request. +- Register classes with explicit numeric IDs for compact type metadata and stable deployments. +- Use `.withCompatible(false)` only when every reader and writer always uses the same class schema + and the application wants faster serialization and smaller size. +- Disable reference tracking for value-shaped graphs with no identity or cycles. +- Use async compilation on ordinary JVMs when startup latency can tolerate interpreter-first + serialization: + + ```java + Fory fory = Fory.builder() + .withXlang(false) + .withAsyncCompilation(true) + .build(); + ``` + +- Keep runtime code generation enabled on ordinary JVMs. Use static generated serializers for + GraalVM native image and Android flows. +- Use zero-copy out-of-band buffers for large primitive arrays or binary fields when the transport + supports split payloads. +- Replace expensive JDK serialization hooks with Fory custom serializers for hot classes when the + object contract allows it. + +## Native And Xlang Comparison + +| Requirement | Use native serialization | Use xlang serialization | +| -------------------------------------- | ------------------------ | ----------------------- | +| Java/JVM-only payloads | Yes | Optional | +| Non-Java readers or writers | No | Yes | +| Broad Java object surface | Yes | Limited to xlang types | +| JDK serialization hooks | Yes | No | +| Java object copy | Yes | No | +| Portable type mapping across languages | No | Yes | +| Compatible schema evolution by default | Yes | Yes | +| Same-schema performance optimization | Yes | No | + +## Troubleshooting + +### A non-Java implementation cannot read the payload + +The writer is using native serialization. Rebuild the writer with `.withXlang(true)` and align type +registration with every peer. + +### A class is rejected during deserialization + +Keep class registration enabled and register the class on both writer and reader. If dynamic class +loading is intentional, use `requireClassRegistration(false)` only with an allow-listing +`TypeChecker`. + +### A rolling deployment fails after a field change + +Native serialization defaults to compatible mode. Keep that default when writer and reader versions +can differ, and add stable field metadata for long-lived schemas. + +### Object identity is not preserved + +Enable `.withRefTracking(true)` for serialization and deserialization. For `Fory.copy(...)`, enable +`.withRefCopy(true)`. + +### A migrated boundary receives both JDK and Fory bytes + +Use `JavaSerializer.serializedByJDK(...)` only at the mixed-format boundary, then route JDK bytes to +`ObjectInputStream` and Fory native bytes to `fory.deserialize(...)`. + +## Related Topics + +- [Basic Serialization](basic-serialization.md) - Xlang-first Java quickstart +- [Xlang Serialization](xlang-serialization.md) - Cross-language Java payloads +- [Configuration](configuration.md) - Java builder options +- [Schema Evolution](schema-evolution.md) - compatible mode and same-schema optimization +- [Type Registration](type-registration.md) - Registration and security +- [Object Copy](object-copy.md) - Deep-copy semantics +- [Custom Serializers](custom-serializers.md) - Custom Java serializers +- [Static Generated Serializers](static-generated-serializers.md) - Build-time generated serializers +- [GraalVM Support](graalvm-support.md) - Native-image platform support diff --git a/versioned_docs/version-1.3.0/guide/java/object-copy.md b/versioned_docs/version-1.3.0/guide/java/object-copy.md new file mode 100644 index 00000000000..a34c02177b5 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/java/object-copy.md @@ -0,0 +1,373 @@ +--- +title: Object Copy +sidebar_position: 9 +id: object_copy +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 page covers in-memory Java object graph copying with `Fory#copy(Object)`. + +`Fory.copy` is a deep-copy operation for Java object graphs. It does not serialize to bytes first. +Instead, it uses Fory's type system and serializers to create a copied object graph in +memory. + +## When to Use Object Copy + +Use object copy when you want a detached in-memory clone of an existing Java object graph. + +Typical use cases: + +- Clone request or response models before mutation +- Duplicate cached state for optimistic updates +- Copy graphs that contain collections, maps, arrays, or nested beans +- Preserve shared references and circular references during cloning + +Use serialization instead when you need bytes for transport, storage, or cross-process exchange. + +| Operation | `Fory.copy` | `serialize` / `deserialize` | +| ------------------------ | ------------------- | ----------------------------------------- | +| Result | Java object graph | Binary payload plus reconstructed objects | +| Main use | In-memory deep copy | Transport, persistence, interoperability | +| Copy ref option | `withRefCopy(...)` | `withRefTracking(...)` | +| Cross-language payload | No | Yes, in xlang mode | +| Intermediate byte buffer | No | Yes | + +## Quick Start + +For general-purpose object graphs, enable `withRefCopy(true)` so shared references and cycles are +handled correctly: + +```java +import org.apache.fory.Fory; + +public class Example { + public static void main(String[] args) { + Fory fory = Fory.builder() + .withXlang(false) + .withRefCopy(true) + .build(); + + Order original = new Order(); + Order copied = fory.copy(original); + } +} +``` + +`copy(null)` returns `null`. + +## Reference Semantics + +The most important copy option is `ForyBuilder#withRefCopy(boolean)`. + +### `withRefCopy(true)` + +This is the safe default for general object graphs. Shared references remain shared in the copied +graph, and circular references can be copied correctly. + +```java +import org.apache.fory.Fory; + +public class Example { + static final class Address { + String city; + } + + static final class Pair { + Address left; + Address right; + } + + public static void main(String[] args) { + Fory fory = Fory.builder() + .withXlang(false) + .withRefCopy(true) + .build(); + + Address address = new Address(); + address.city = "Shanghai"; + + Pair pair = new Pair(); + pair.left = address; + pair.right = address; + + Pair copied = fory.copy(pair); + System.out.println(copied.left == copied.right); // true + } +} +``` + +### `withRefCopy(false)` + +Disable copy ref tracking only when you know the graph is tree-like and does not rely on shared or +cyclic references. This can be faster, but repeated references are copied into different objects. + +```java +import org.apache.fory.Fory; + +public class Example { + static final class Address { + String city; + } + + static final class Pair { + Address left; + Address right; + } + + public static void main(String[] args) { + Fory fory = Fory.builder() + .withXlang(false) + .withRefCopy(false) + .build(); + + Address address = new Address(); + Pair pair = new Pair(); + pair.left = address; + pair.right = address; + + Pair copied = fory.copy(pair); + System.out.println(copied.left == copied.right); // false + } +} +``` + +If you disable `withRefCopy` and the graph contains a cycle, copy can fail with stack overflow. + +## `withRefCopy` vs `withRefTracking` + +These two options control different operations: + +- `withRefCopy(true)` affects `Fory.copy(...)` +- `withRefTracking(true)` affects serialization and deserialization + +Enabling one does not automatically enable the other. If your application both serializes and +copies graphs with shared or circular references, configure both options explicitly. + +```java +Fory fory = Fory.builder().withXlang(false) + .withRefTracking(true) + .withRefCopy(true) + .build(); +``` + +## Immutable vs Mutable Values + +Fory may reuse the original instance for immutable values. For mutable values, it creates a new +object graph. + +In practice, this means: + +- `String`, boxed primitives, enums, and many immutable JDK value types may be returned as-is +- Primitive arrays, string arrays, collections, maps, beans, dates, and other mutable structures + are copied into distinct objects + +Do not use object identity alone to decide whether copy succeeded. Use the mutability contract of +the value you are copying. + +## Class Registration + +If class registration is required, register copied classes before calling `copy`. + +```java +import org.apache.fory.Fory; + +public class Example { + public static void main(String[] args) { + Fory fory = Fory.builder().withXlang(false) + .requireClassRegistration(true) + .withRefCopy(true) + .build(); + + fory.register(Order.class); + Order copied = fory.copy(new Order()); + } +} +``` + +This follows the same registration rules as other Fory operations: if the Fory instance requires class +registration, copied concrete types must be registered first. + +## Thread-Safe Copy + +`ThreadSafeFory` also supports `copy(...)`. + +For general multi-threaded usage: + +```java +import org.apache.fory.Fory; +import org.apache.fory.ThreadSafeFory; + +public class Example { + public static void main(String[] args) { + ThreadSafeFory fory = Fory.builder() + .withXlang(false) + .withRefCopy(true) + .buildThreadSafeFory(); + + Order copied = fory.copy(new Order()); + } +} +``` + +The same API also works for `buildThreadLocalFory()` and `buildThreadSafeForyPool(poolSize)`. + +## Built-In Coverage + +Fory already provides copy support for many common Java platform types, including: + +- Primitive values and boxed primitives +- Strings and primitive arrays +- Common JDK collections and maps +- Java time and date/time values +- Beans, records, and nested object graphs + +If Fory already knows how to serialize a mutable type, it may still need an explicit copy +implementation in that serializer. For mutable serializers, the default `Serializer.copy(...)` +throws `UnsupportedOperationException` unless the serializer overrides it. + +## Custom Copy with `ForyCopyable` + +If a type needs custom copy logic, implement `ForyCopyable`. + +This is the simplest approach when the class itself should control how nested fields are copied: + +```java +import java.util.ArrayList; +import java.util.List; +import org.apache.fory.ForyCopyable; +import org.apache.fory.context.CopyContext; + +public final class Node implements ForyCopyable { + private String name; + private final List neighbors = new ArrayList<>(); + + @Override + public Node copy(CopyContext copyContext) { + Node copied = new Node(); + copyContext.reference(this, copied); + copied.name = name; + for (Node neighbor : neighbors) { + copied.neighbors.add(copyContext.copyObject(neighbor)); + } + return copied; + } +} +``` + +Guidelines: + +- Call `copyContext.reference(origin, copy)` immediately after creating a composite mutable object + if the type can participate in cycles or shared-reference graphs +- Use `copyContext.copyObject(...)` for nested values instead of manually duplicating nested copy + logic +- Keep copy logic consistent with the normal Java semantics of the type + +## Custom Copy in a Serializer + +When a type already uses a custom serializer, override `Serializer.copy(...)` for mutable values. + +```java +import org.apache.fory.config.Config; +import org.apache.fory.context.CopyContext; +import org.apache.fory.context.ReadContext; +import org.apache.fory.context.WriteContext; +import org.apache.fory.serializer.Serializer; + +public final class EnvelopeSerializer extends Serializer { + public EnvelopeSerializer(Config config) { + super(config, Envelope.class); + } + + @Override + public Envelope copy(CopyContext copyContext, Envelope value) { + Envelope copied = new Envelope(); + copyContext.reference(value, copied); + copied.header = copyContext.copyObject(value.header); + copied.payload = copyContext.copyObject(value.payload); + return copied; + } + + @Override + public void write(WriteContext writeContext, Envelope value) { + throw new UnsupportedOperationException("omitted"); + } + + @Override + public Envelope read(ReadContext readContext) { + throw new UnsupportedOperationException("omitted"); + } +} +``` + +Use this approach when copy behavior belongs with a serializer rather than the domain class. + +## Best Practices + +- Reuse `Fory` or `ThreadSafeFory` instances instead of rebuilding them for each copy +- Enable `withRefCopy(true)` unless you are certain the graph is acyclic and does not rely on + shared references +- Treat `withRefCopy(false)` as a performance optimization for tree-like data, not as a default +- Test custom copy implementations with both shared-reference and cyclic graphs +- Keep mutable custom serializer copy paths explicit and do not rely on fallback behavior + +## Troubleshooting + +### Stack Overflow or Copy Failure on Cyclic Graphs + +If copy fails on a cyclic object graph, enable `withRefCopy(true)`: + +```java +Fory fory = Fory.builder().withXlang(false) + .withRefCopy(true) + .build(); +``` + +Disabling copy ref tracking is only safe for acyclic graphs. + +### Shared References Are Not Preserved + +If the same source object is copied into multiple distinct target objects, `withRefCopy` is +disabled. Turn it on: + +```java +Fory fory = Fory.builder().withXlang(false) + .withRefCopy(true) + .build(); +``` + +`withRefTracking(true)` alone does not change `Fory.copy(...)` behavior. + +### `Copy for ... is not supported` + +This means the mutable serializer for that type does not implement `copy(...)`. + +Fix it by either: + +- Implementing `ForyCopyable` on the class, or +- Overriding `Serializer.copy(CopyContext, T)` in the registered serializer + +### Registration Errors + +If your Fory instance uses `requireClassRegistration(true)`, make sure the copied concrete types are +registered before calling `copy(...)`. + +## Related Topics + +- [Basic Serialization](basic-serialization.md) - Fory instance creation and core APIs +- [Configuration](configuration.md) - Builder options including `withRefCopy` +- [Custom Serializers](custom-serializers.md) - Serializer design and registration +- [Virtual Threads](virtual-threads.md) - Thread-safe Fory guidance diff --git a/versioned_docs/version-1.3.0/guide/java/row-format.md b/versioned_docs/version-1.3.0/guide/java/row-format.md new file mode 100644 index 00000000000..477f9ec1364 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/java/row-format.md @@ -0,0 +1,194 @@ +--- +title: Row Format +sidebar_position: 12 +id: row_format +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. +--- + +Apache Fory™ provides a random-access row format that enables reading nested fields from binary data without full deserialization. This drastically reduces overhead when working with large objects where only partial data access is needed. + +## Overview + +Row format is a cache-friendly binary random access format that supports: + +- **Zero-copy access**: Read fields directly from binary without allocating objects +- **Partial deserialization**: Access only the fields you need +- **Skipping serialization**: Skip serialization of fields you don't need +- **Cross-language compatibility**: Works across Python, Java, C++, and other languages +- **Column format conversion**: Can convert to Apache Arrow columnar format automatically + +## Basic Usage + +```java +public class Bar { + String f1; + List f2; +} + +public class Foo { + int f1; + List f2; + Map f3; + List f4; +} + +RowEncoder encoder = Encoders.bean(Foo.class); + +// Create large dataset +Foo foo = new Foo(); +foo.f1 = 10; +foo.f2 = IntStream.range(0, 1_000_000).boxed().collect(Collectors.toList()); +foo.f3 = IntStream.range(0, 1_000_000).boxed().collect(Collectors.toMap(i -> "k" + i, i -> i)); +List bars = new ArrayList<>(1_000_000); +for (int i = 0; i < 1_000_000; i++) { + Bar bar = new Bar(); + bar.f1 = "s" + i; + bar.f2 = LongStream.range(0, 10).boxed().collect(Collectors.toList()); + bars.add(bar); +} +foo.f4 = bars; + +// Encode to row format (cross-language compatible with Python/C++) +BinaryRow binaryRow = encoder.toRow(foo); + +// Zero-copy random access without full deserialization +BinaryArray f2Array = binaryRow.getArray(1); // Access f2 list +BinaryArray f4Array = binaryRow.getArray(3); // Access f4 list +BinaryRow bar10 = f4Array.getStruct(10); // Access 11th Bar +long value = bar10.getArray(1).getInt64(5); // Access 6th element of bar.f2 + +// Name-based access without repeated schema lookups +Schema schema = encoder.schema(); +Schema.Int32Field f1 = schema.int32Field("f1"); +Schema.ArrayField f4 = schema.arrayField("f4"); +int f1Value = f1.get(binaryRow); +ArrayData f4ByName = f4.get(binaryRow); + +// Partial deserialization - only deserialize what you need +RowEncoder barEncoder = Encoders.bean(Bar.class); +Bar bar1 = barEncoder.fromRow(f4Array.getStruct(10)); // Deserialize 11th Bar only +Bar bar2 = barEncoder.fromRow(f4Array.getStruct(20)); // Deserialize 21st Bar only + +// Full deserialization when needed +Foo newFoo = encoder.fromRow(binaryRow); +``` + +Cache the returned `Schema.*Field` handles in user code and reuse them for all rows with the same +schema. Calling `schema.int32Field("f1")` creates a typed handle by resolving the field name to an +ordinal, accepting Java lower-camel field names for bean-derived schemas, validating the expected +row-format type, and storing the resolved ordinal. Later calls such as `f1.get(binaryRow)` go +straight to the ordinal row getter without another schema map lookup or typed handle construction. + +## Key Benefits + +| Feature | Description | +| ----------------------- | ------------------------------------------------------ | +| Zero-Copy Access | Read nested fields without deserializing entire object | +| Memory Efficiency | Memory-map large datasets directly from disk | +| Cross-Language | Binary format compatible between Java, Python, C++ | +| Partial Deserialization | Deserialize only specific elements you need | +| High Performance | Skip unnecessary data parsing for analytics workloads | + +## When to Use Row Format + +Row format is ideal for: + +- **Analytics workloads**: When you only need to access specific fields +- **Large datasets**: When full deserialization is too expensive +- **Memory-mapped files**: Working with data larger than RAM +- **Data pipelines**: Processing data without full object reconstruction +- **Cross-language data sharing**: When data needs to be accessed from multiple languages + +## Cross-Language Compatibility + +Row format works seamlessly across languages. The same binary data can be accessed from: + +### Python + +```python +import pyfory +import pyarrow as pa +from dataclasses import dataclass +from typing import List, Dict + +@dataclass +class Bar: + f1: str + f2: List[pa.int64] + +@dataclass +class Foo: + f1: pa.int32 + f2: List[pa.int32] + f3: Dict[str, pa.int32] + f4: List[Bar] + +encoder = pyfory.encoder(Foo) +binary: bytes = encoder.to_row(foo).to_bytes() + +# Zero-copy access +foo_row = pyfory.RowData(encoder.schema, binary) +print(foo_row.f2[100000]) +print(foo_row.f4[100000].f1) +``` + +### C++ + +```cpp +#include "fory/encoder/row_encoder.h" +#include "fory/row/writer.h" + +struct Bar { + std::string f1; + std::vector f2; + FORY_STRUCT(Bar, f1, f2); +}; + +struct Foo { + int32_t f1; + std::vector f2; + std::map f3; + std::vector f4; + FORY_STRUCT(Foo, f1, f2, f3, f4); +}; + +fory::row::encoder::RowEncoder encoder; +encoder.encode(foo); +auto row = encoder.get_writer().to_row(); + +// Zero-copy random access +auto f2_array = row->get_array(1); +auto f4_array = row->get_array(3); +auto bar10 = f4_array->get_struct(10); +int64_t value = bar10->get_array(1)->get_int64(5); +std::string str = bar10->get_string(0); +``` + +## Performance Comparison + +| Operation | Object Format | Row Format | +| -------------------- | ----------------------------- | ------------------------------- | +| Full deserialization | Allocates all objects | Zero allocation | +| Single field access | Full deserialization required | Direct offset read | +| Memory usage | Full object graph in memory | Only accessed fields | +| Suitable for | Small objects, full access | Large objects, selective access | + +## Related Topics + +- [Xlang Serialization](xlang-serialization.md) - xlang mode +- [Advanced Features](advanced-features.md) - Zero-copy serialization +- [Row Format Specification](https://fory.apache.org/docs/specification/row_format_spec) - Protocol details diff --git a/versioned_docs/version-1.3.0/guide/java/schema-evolution.md b/versioned_docs/version-1.3.0/guide/java/schema-evolution.md new file mode 100644 index 00000000000..6f056f45253 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/java/schema-evolution.md @@ -0,0 +1,270 @@ +--- +title: Schema Evolution +sidebar_position: 6 +id: schema_evolution +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 page covers schema evolution, meta sharing, and handling non-existent/unknown classes. + +## Handling Class Schema Evolution + +In many systems, the schema of a class used for serialization may change over time. For instance, fields within a class may be added or removed. When serialization and deserialization processes use different versions of jars, the schema of the class being deserialized may differ from the one used during serialization. + +### Default Mode + +Fory defaults to compatible mode in both Java native mode (`xlang=false`) and xlang mode. This default is safer for independently deployed services because writer and reader schemas can diverge during rolling upgrades or across language implementations. + +For payloads whose reader and writer schemas never differ, see +[Same-Schema Optimization](#same-schema-optimization). + +### Compatible Mode + +Compatible mode is enabled by default, so deserialization can tolerate added, removed, or reordered +fields when metadata remains compatible. + +In this compatible mode, deserialization can handle schema changes such as missing or extra fields, allowing it to succeed even when the serialization and deserialization processes have different class schemas. + +Compatible readers also tolerate selected scalar field type changes when the value is lossless. A +matched field can read between `boolean`, `String`, numeric scalars, and `BigDecimal` when the value +has the same logical value after conversion. For example, `"true"` and `"false"` can be read as +booleans, `"123"` can be read as a numeric field that can hold `123`, numbers and decimals can be +read as canonical strings, and numeric widening or narrowing succeeds only when no precision or range +is lost. Numeric strings use finite ASCII decimal syntax. Nullable and boxed fields still compose with +these conversions, but reference-tracked scalar type changes are incompatible. Invalid strings and +lossy conversions fail during deserialization. + +Extra writer fields with no matching local field are skipped. A field that matches by tag ID or name +but has an incompatible schema is not treated as missing; deserialization fails instead. + +```java +Fory fory = Fory.builder().withXlang(false) + .build(); + +byte[] bytes = fory.serialize(object); +System.out.println(fory.deserialize(bytes)); +``` + +This compatible mode involves serializing class metadata into the serialized output. Despite Fory's use of sophisticated compression techniques to minimize overhead, there is still some additional space cost associated with class metadata. + +## Meta Sharing + +To further reduce metadata costs, Fory introduces a class metadata sharing mechanism, which allows the metadata to be sent to the deserialization process only once. + +Fory supports sharing type metadata (class name, field name, final field type information, etc.) between multiple serializations in a context (ex. TCP connection). This information will be sent to the peer during the first serialization in the context. Based on this metadata, the peer can rebuild the same deserializer, which avoids transmitting metadata for subsequent serializations and reduces network traffic pressure while supporting type forward/backward compatibility automatically. + +### Using Meta Sharing + +```java +// Fory.builder() +// .withXlang(false) +// .withRefTracking(false) +// // share meta across serialization. +// .withMetaShare(true) + +// Not thread-safe fory. +MetaWriteContext writeContext = xxx; +fory.setMetaWriteContext(writeContext); +byte[] bytes = fory.serialize(o); + +// Not thread-safe fory. +MetaReadContext readContext = xxx; +fory.setMetaReadContext(readContext); +fory.deserialize(bytes); +``` + +### Thread-Safe Meta Sharing + +```java +// Thread-safe fory +byte[] serialized = fory.execute( + f -> { + f.setMetaWriteContext(writeContext); + return f.serialize(beanA); + } +); + +// Thread-safe fory +Object newObj = fory.execute( + f -> { + f.setMetaReadContext(readContext); + return f.deserialize(serialized); + } +); +``` + +**Note**: `MetaWriteContext` and `MetaReadContext` are not thread-safe and cannot be reused across +instances of Fory or multiple threads. In cases of multi-threading, a separate pair of meta +contexts must be created for each Fory instance. If you need a different classloader, create a +separate `Fory` or `ThreadSafeFory` configured with that loader instead of switching loaders on an +existing instance. + +For more details, please refer to the [Meta Sharing specification](https://fory.apache.org/docs/specification/java_serialization_spec#meta-share). + +## Deserialize Unknown Classes + +Fory supports deserializing non-existent or unknown classes. This feature can be enabled by `ForyBuilder#deserializeUnknownClass(true)`. + +When enabled and metadata sharing is enabled, Fory will store the deserialized data of this type in a lazy subclass of Map. By using the lazy map implemented by Fory, the rebalance cost of filling map during deserialization can be avoided, which further improves performance. + +If this data is sent to another process and the class exists in this process, the data will be deserialized into the object of this type without losing any information. + +If metadata sharing is not enabled, the new class data is skipped and Fory returns an `UnknownEmptyStruct` marker object. + +## Copy/Map Object from One Type to Another + +Fory supports mapping objects from one type to another type. + +**Notes:** + +1. This mapping will execute a deep copy. All mapped fields are serialized into binary and deserialized from that binary to map into another type. +2. All struct types must be registered with the same ID, otherwise Fory cannot map to the correct struct type. Be careful when you use `Fory#register(Class)`, because Fory will allocate an auto-grown ID which might be inconsistent if you register classes with different order between Fory instances. + +```java +public class StructMappingExample { + static class Struct1 { + int f1; + String f2; + + public Struct1(int f1, String f2) { + this.f1 = f1; + this.f2 = f2; + } + } + + static class Struct2 { + int f1; + String f2; + double f3; + } + + static ThreadSafeFory fory1 = Fory.builder().withXlang(false) + .buildThreadSafeFory(); + static ThreadSafeFory fory2 = Fory.builder().withXlang(false) + .buildThreadSafeFory(); + + static { + fory1.register(Struct1.class); + fory2.register(Struct2.class); + } + + public static void main(String[] args) { + Struct1 struct1 = new Struct1(10, "abc"); + Struct2 struct2 = (Struct2) fory2.deserialize(fory1.serialize(struct1)); + Assert.assertEquals(struct2.f1, struct1.f1); + Assert.assertEquals(struct2.f2, struct1.f2); + struct1 = (Struct1) fory1.deserialize(fory2.serialize(struct2)); + Assert.assertEquals(struct1.f1, struct2.f1); + Assert.assertEquals(struct1.f2, struct2.f2); + } +} +``` + +## Deserialize POJO into Another Type + +Fory allows you to serialize one POJO and deserialize it into a different POJO. The different POJO means schema inconsistency, so use compatible mode. + +```java +public class DeserializeIntoType { + static class Struct1 { + int f1; + String f2; + + public Struct1(int f1, String f2) { + this.f1 = f1; + this.f2 = f2; + } + } + + static class Struct2 { + int f1; + String f2; + double f3; + } + + static ThreadSafeFory fory = Fory.builder().withXlang(false) + .buildThreadSafeFory(); + + public static void main(String[] args) { + Struct1 struct1 = new Struct1(10, "abc"); + byte[] data = fory.serialize(struct1); + Struct2 struct2 = fory.deserialize(data, Struct2.class); + } +} +``` + +## Same-Schema Optimization + +Use `ForyBuilder#withCompatible(false)` only when the class schema used to deserialize every payload +is always the same as the class schema used to serialize it, and you want faster serialization and smaller size. +For xlang payloads, call `withCompatible(false)` only after verifying that every language uses the same schema, or when native types are generated from Fory schema IDL. + +```java +Fory fory = Fory.builder() + .withXlang(false) + .withCompatible(false) + .build(); +``` + +### Per-Class Opt-Out + +`@ForyStruct` can set a per-class evolution policy: + +- `Evolution.INHERIT`: follow the Fory instance's compatible/meta-share configuration. This is the + default. +- `Evolution.ENABLED`: require schema evolution metadata for this class. Registration or type + resolution fails if the Fory instance cannot emit that metadata. +- `Evolution.DISABLED`: force fixed-schema `STRUCT/NAMED_STRUCT` encoding even when compatible + metadata is otherwise enabled. + +Use `@ForyStruct(evolution = Evolution.DISABLED)` only for same-schema classes. The boolean shorthand +`@ForyStruct(evolving = false)` is also supported as a same-schema opt-out. + +```java +import org.apache.fory.annotation.ForyStruct; +import org.apache.fory.annotation.ForyStruct.Evolution; + +@ForyStruct(evolution = Evolution.DISABLED) +public class SameSchemaMessage { + public int id; + public String name; +} +``` + +## Configuration + +| Option | Description | Default | +| ------------------------- | -------------------------------------------------------------------------------------------------- | ------------------------- | +| `compatibleMode` | Controls whether Fory writes schema evolution metadata; same-schema mode requires matching schemas | `COMPATIBLE` | +| `checkClassVersion` | Check schema hashes for same-schema payloads | `false` | +| `metaShareEnabled` | Enable meta sharing | `true` if Compatible mode | +| `scopedMetaShareEnabled` | Scoped meta share per serialization | `true` if Compatible mode | +| `deserializeUnknownClass` | Handle non-existent or unknown classes | `true` if Compatible mode | +| `metaCompressor` | Compressor for meta compression | `DeflaterMetaCompressor` | + +## Best Practices + +1. **Use COMPATIBLE mode for evolving schemas**: When classes may change between versions +2. **Enable meta sharing for network communication**: Reduces bandwidth for repeated serializations +3. **Use consistent type IDs for struct mapping**: Ensure same registration order or explicit IDs +4. **Consider space overhead**: Compatible mode adds metadata, balance with your requirements + +## Related Topics + +- [Configuration](configuration.md) - All ForyBuilder options +- [Xlang Serialization](xlang-serialization.md) - xlang mode +- [Troubleshooting](troubleshooting.md) - Common schema issues diff --git a/versioned_docs/version-1.3.0/guide/java/schema-metadata.md b/versioned_docs/version-1.3.0/guide/java/schema-metadata.md new file mode 100644 index 00000000000..5bf45718c21 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/java/schema-metadata.md @@ -0,0 +1,716 @@ +--- +title: Schema Metadata +sidebar_position: 7 +id: schema_metadata +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 page explains how to configure field-level metadata for serialization in Java. + +## Overview + +Apache Fory™ provides field-level configuration through annotations: + +- **`@ForyField`**: Configure field metadata (id, dynamic) +- **`@Nullable`**: Mark a field type or nested type position as nullable +- **`@Ref`**: Enable field or nested-element reference tracking +- **`@Ignore`**: Exclude fields from serialization +- **Integer type annotations**: Control integer encoding (varint, fixed, tagged, unsigned) + +This enables: + +- **Tag IDs**: Assign compact numeric IDs to reduce struct field meta size overhead for compatible mode +- **Nullability**: Control whether fields can be null +- **Reference Tracking**: Enable reference tracking for shared objects +- **Field Skipping**: Exclude fields from serialization +- **Encoding Control**: Specify how integers are encoded +- **Polymorphism Control**: Control type info writing for struct fields + +## Basic Syntax + +Use annotations on fields: + +```java +import org.apache.fory.annotation.ForyField; +import org.apache.fory.annotation.Nullable; + +public class Person { + @ForyField(id = 0) + private String name; + + @ForyField(id = 1) + private int age; + + @Nullable + @ForyField(id = 2) + private String nickname; +} +``` + +## The `@ForyField` Annotation + +Use `@ForyField` to configure field-level metadata: + +```java +public class User { + @ForyField(id = 0) + private long id; + + @ForyField(id = 1) + private String name; + + @Nullable + @ForyField(id = 2) + private String email; + + @ForyField(id = 3) + private List<@Ref User> friends; + + @ForyField(id = 4, dynamic = ForyField.Dynamic.TRUE) + private Object data; +} +``` + +### Parameters + +| Parameter | Type | Default | Description | +| --------- | --------- | ------- | -------------------------------------- | +| `id` | `int` | `-1` | Non-negative field tag ID, or no ID | +| `dynamic` | `Dynamic` | `AUTO` | Control polymorphism for struct fields | + +Use `@Nullable` on the field type or nested type position for nullable schema +metadata and `@Ref` for reference tracking. `@ForyField` does not carry either +setting. + +## Field ID (`id`) + +Assigns a numeric ID to a field to minimize struct field meta size overhead for compatible mode: + +```java +public class User { + @ForyField(id = 0) + private long id; + + @ForyField(id = 1) + private String name; + + @ForyField(id = 2) + private int age; +} +``` + +**Benefits**: + +- Smaller serialized size (numeric IDs vs field names in metadata) +- Reduced struct field meta overhead +- Allows renaming fields without breaking binary compatibility + +**Recommendation**: It is recommended to configure field IDs for compatible mode since it reduces serialization cost. + +**Notes**: + +- IDs must be unique within a class +- IDs must be >= 0 when configured +- If not specified, the annotation default `-1` is ignored and field name is used in metadata + (larger overhead) + +**Without field IDs** (field names used in metadata): + +```java +public class User { + private long id; + private String name; +} +``` + +## Nullable Fields (`@Nullable`) + +Use `@Nullable` for fields that can be `null`: + +```java +public class Record { + // Nullable string field + @Nullable + @ForyField(id = 0) + private String optionalName; + + // Nullable Integer field (boxed type) + @Nullable + @ForyField(id = 1) + private Integer optionalCount; + + // Non-nullable field (default) + @ForyField(id = 2) + private String requiredName; +} +``` + +**Notes**: + +- Xlang fields are non-nullable by default. +- When a field is non-nullable, Fory skips writing the null flag. +- Boxed types (`Integer`, `Long`, etc.) that can be null should use `@Nullable`. + +## Reference Tracking (`@Ref`) + +Enable reference tracking for fields that may be shared or circular: + +```java +public class RefOuter { + // Both fields may point to the same inner object + @Nullable + @ForyField(id = 0) + @Ref + private RefInner inner1; + + @Nullable + @ForyField(id = 1) + @Ref + private RefInner inner2; +} + +public class CircularRef { + @ForyField(id = 0) + private String name; + + // Self-referencing field for circular references + @Nullable + @ForyField(id = 1) + @Ref + private CircularRef selfRef; +} +``` + +**Use Cases**: + +- Enable for fields that may be circular or shared +- When the same object is referenced from multiple fields + +**Notes**: + +- Fields without `@Ref` do not use field-wrapper reference tracking +- Avoid `@Ref` when values are not shared or circular, so Fory can skip the reference flag +- Reference tracking only takes effect when global ref tracking is enabled + +## Dynamic (Polymorphism Control) + +Controls polymorphism behavior for struct fields in cross-language serialization: + +```java +public class Container { + // AUTO: Interface/abstract types are dynamic, concrete types are not + @ForyField(id = 0, dynamic = ForyField.Dynamic.AUTO) + private Animal animal; // Interface - type info written + + // FALSE: No type info written, uses declared type's serializer + @ForyField(id = 1, dynamic = ForyField.Dynamic.FALSE) + private Dog dog; // Concrete - no type info + + // TRUE: Type info written to support subtypes + @ForyField(id = 2, dynamic = ForyField.Dynamic.TRUE) + private Object data; // Force polymorphic +} +``` + +**Options**: + +| Value | Description | +| ------- | ------------------------------------------------------------------- | +| `AUTO` | Auto-detect: interface/abstract are dynamic, concrete types are not | +| `FALSE` | No type info written, uses declared type's serializer directly | +| `TRUE` | Type info written to support subtypes | + +## Skipping Fields + +### Using `@Ignore` + +Exclude fields from serialization: + +```java +import org.apache.fory.annotation.Ignore; + +public class User { + @ForyField(id = 0) + private long id; + + @ForyField(id = 1) + private String name; + + @Ignore + private String password; // Not serialized + + @Ignore + private Object internalState; // Not serialized +} +``` + +### Using `transient` + +Java's `transient` keyword also excludes fields: + +```java +public class User { + @ForyField(id = 0) + private long id; + + private transient String password; // Not serialized + private transient Object cache; // Not serialized +} +``` + +## Integer Type Annotations + +Fory provides annotations to control integer encoding for cross-language compatibility. +Integer schema annotations are Java type-use annotations. Put them on the field type, after any +field modifiers and alongside `@ForyField` when both are present. + +### Signed 32-bit Integer (`@Int32Type`) + +```java +import org.apache.fory.annotation.Int32Type; +import org.apache.fory.config.Int32Encoding; + +public class MyStruct { + // Variable-length encoding (default) - compact for small values + private @Int32Type(encoding = Int32Encoding.VARINT) int compactId; + + // Fixed 4-byte encoding - consistent size + private @Int32Type(encoding = Int32Encoding.FIXED) int fixedId; +} +``` + +### Signed 64-bit Integer (`@Int64Type`) + +```java +import org.apache.fory.annotation.Int64Type; +import org.apache.fory.config.Int64Encoding; + +public class MyStruct { + // Variable-length encoding (default) + private @Int64Type(encoding = Int64Encoding.VARINT) long compactId; + + // Fixed 8-byte encoding + private @Int64Type(encoding = Int64Encoding.FIXED) long fixedTimestamp; + + // Tagged encoding (4 bytes for small values, 9 bytes otherwise) + private @Int64Type(encoding = Int64Encoding.TAGGED) long taggedValue; +} +``` + +### Unsigned Integers + +```java +import org.apache.fory.annotation.UInt8Type; +import org.apache.fory.annotation.UInt16Type; +import org.apache.fory.annotation.UInt32Type; +import org.apache.fory.annotation.UInt64Type; +import org.apache.fory.config.Int32Encoding; +import org.apache.fory.config.Int64Encoding; + +public class UnsignedStruct { + // Unsigned 8-bit [0, 255] + private @UInt8Type int flags; + + // Unsigned 16-bit [0, 65535] + private @UInt16Type int port; + + // Unsigned 32-bit with varint encoding (default) + private @UInt32Type(encoding = Int32Encoding.VARINT) long compactCount; + + // Unsigned 32-bit with fixed encoding + private @UInt32Type(encoding = Int32Encoding.FIXED) long fixedCount; + + // Unsigned 64-bit with various encodings + private @UInt64Type(encoding = Int64Encoding.VARINT) long varintU64; + + private @UInt64Type(encoding = Int64Encoding.FIXED) long fixedU64; + + private @UInt64Type(encoding = Int64Encoding.TAGGED) long taggedU64; +} +``` + +### Encoding Summary + +| Annotation | Type ID | Encoding | Size | +| -------------------------------- | ------- | -------- | ------------ | +| `@Int32Type(encoding = VARINT)` | 5 | varint | 1-5 bytes | +| `@Int32Type(encoding = FIXED)` | 4 | fixed | 4 bytes | +| `@Int64Type(encoding = VARINT)` | 7 | varint | 1-10 bytes | +| `@Int64Type(encoding = FIXED)` | 6 | fixed | 8 bytes | +| `@Int64Type(encoding = TAGGED)` | 8 | tagged | 4 or 9 bytes | +| `@UInt8Type` | 9 | fixed | 1 byte | +| `@UInt16Type` | 10 | fixed | 2 bytes | +| `@UInt32Type(encoding = VARINT)` | 12 | varint | 1-5 bytes | +| `@UInt32Type(encoding = FIXED)` | 11 | fixed | 4 bytes | +| `@UInt64Type(encoding = VARINT)` | 14 | varint | 1-10 bytes | +| `@UInt64Type(encoding = FIXED)` | 13 | fixed | 8 bytes | +| `@UInt64Type(encoding = TAGGED)` | 15 | tagged | 4 or 9 bytes | + +**When to Use**: + +- `varint`: Best for values that are often small (default) +- `fixed`: Best for values that use full range (e.g., timestamps, hashes) +- `tagged`: Good balance between size and performance +- Unsigned types: For cross-language compatibility with Rust, Go, C++ + +Unsigned Java scalar carriers are `int`/`Integer` for `@UInt8Type` and +`@UInt16Type`, and `long`/`Long` for `@UInt32Type` and `@UInt64Type`. +Annotating `byte` with `@UInt8Type` is invalid because Java `byte` cannot +represent the unsigned range. + +Integer annotations can also be applied to nested generic type arguments: + +```java +import java.util.List; +import java.util.Map; +import org.apache.fory.annotation.Int64Type; +import org.apache.fory.annotation.UInt32Type; +import org.apache.fory.config.Int32Encoding; +import org.apache.fory.config.Int64Encoding; + +public class NestedStruct { + private Map< + @UInt32Type(encoding = Int32Encoding.FIXED) Long, + List<@Int64Type(encoding = Int64Encoding.TAGGED) Long>> + values; +} +``` + +Specialized unsigned list carriers use the `list` schema by default, so +their element annotations are preserved in list metadata. Add `@ArrayType` only +when the field should use dense `array` schema. + +Primitive unsigned arrays can use scalar element annotations for dense +`array` metadata: + +```java +import org.apache.fory.annotation.UInt32Type; + +public class IdBatch { + private @UInt32Type int[] ids; +} +``` + +## Complete Example + +```java +import org.apache.fory.Fory; +import org.apache.fory.annotation.ForyField; +import org.apache.fory.annotation.Ignore; +import org.apache.fory.annotation.Int64Type; +import org.apache.fory.annotation.Nullable; +import org.apache.fory.annotation.UInt64Type; +import org.apache.fory.config.Int64Encoding; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class Document { + // Fields with tag IDs (recommended for compatible mode) + @ForyField(id = 0) + private String title; + + @ForyField(id = 1) + private int version; + + // Nullable field + @Nullable + @ForyField(id = 2) + private String description; + + // Collection fields + @ForyField(id = 3) + private List tags; + + @ForyField(id = 4) + private Map metadata; + + @ForyField(id = 5) + private Set categories; + + // Integer with different encodings + @ForyField(id = 6) + private @UInt64Type(encoding = Int64Encoding.VARINT) long viewCount; // varint encoding + + @ForyField(id = 7) + private @UInt64Type(encoding = Int64Encoding.FIXED) long fileSize; // fixed encoding + + @ForyField(id = 8) + private @UInt64Type(encoding = Int64Encoding.TAGGED) long checksum; // tagged encoding + + // Reference-tracked field for shared/circular references + @Nullable + @ForyField(id = 9) + @Ref + private Document parent; + + // Ignored field (not serialized) + private transient Object cache; + + // Getters and setters... +} + +// Usage +public class Main { + public static void main(String[] args) { + Fory fory = Fory.builder() + .withXlang(true) + .withRefTracking(true) + .build(); + + fory.register(Document.class, 100); + + Document doc = new Document(); + doc.setTitle("My Document"); + doc.setVersion(1); + doc.setDescription("A sample document"); + + // Serialize + byte[] data = fory.serialize(doc); + + // Deserialize + Document decoded = (Document) fory.deserialize(data); + } +} +``` + +## Cross-Language Compatibility + +When serializing data to be read by other languages (Python, Rust, C++, Go), use field IDs and matching type annotations: + +```java +public class CrossLangData { + // Use field IDs for cross-language compatibility + @ForyField(id = 0) + private @Int32Type(encoding = Int32Encoding.VARINT) int intVar; + + @ForyField(id = 1) + private @UInt64Type(encoding = Int64Encoding.FIXED) long longFixed; + + @ForyField(id = 2) + private @UInt64Type(encoding = Int64Encoding.TAGGED) long longTagged; + + @Nullable + @ForyField(id = 3) + private String optionalValue; +} +``` + +## Schema Evolution + +Compatible mode supports schema evolution. It is recommended to configure field IDs to reduce serialization cost: + +```java +// Version 1 +public class DataV1 { + @ForyField(id = 0) + private long id; + + @ForyField(id = 1) + private String name; +} + +// Version 2: Added new field +public class DataV2 { + @ForyField(id = 0) + private long id; + + @ForyField(id = 1) + private String name; + + @Nullable + @ForyField(id = 2) + private String email; // New field +} +``` + +Data serialized with V1 can be deserialized with V2 (new field will be `null`). + +Alternatively, field IDs can be omitted (field names will be used in metadata with larger overhead): + +```java +public class Data { + private long id; + private String name; +} +``` + +## Enum Metadata + +Java enums are serialized by numeric tag in xlang mode. The default tag is the declaration ordinal. +When an enum needs stable ids that do not depend on declaration order, annotate exactly one id +source with `@ForyEnumId`, or annotate every enum constant with explicit tag values. + +```java +import org.apache.fory.annotation.ForyEnumId; + +enum Status { + Unknown(10), + Running(20), + Finished(30); + + private final int id; + + Status(int id) { + this.id = id; + } + + @ForyEnumId + public int getId() { + return id; + } +} +``` + +Java also supports annotating one enum instance field with `@ForyEnumId`, or annotating every enum +constant directly, such as `@ForyEnumId(10) Unknown`. + +`@ForyEnumId` supports exactly three configuration styles: + +1. Annotate one enum instance field and store the numeric id there. +2. Annotate one zero-argument public instance method such as `getId()`. +3. Annotate every enum constant directly with an explicit value such as `@ForyEnumId(10) Unknown`. + +Validation rules: + +1. Use exactly one of those three styles for a given enum. +2. Field and method annotations must leave `value()` at its default `-1`. +3. Enum-constant annotations must appear on every constant once any constant uses `@ForyEnumId`. +4. All ids must be non-negative, unique, and fit in Java `int`. + +Lookup behavior: + +1. Without `@ForyEnumId`, Fory writes the declaration ordinal. +2. With `@ForyEnumId`, Fory writes the configured stable numeric tag instead. +3. Small dense tags use an array lookup internally; sparse larger tags fall back to a map. + +Use `serializeEnumByName(true)` only for Java native-mode peers that should match enum constants by +name instead of numeric tag: + +```java +Fory fory = Fory.builder() + .withXlang(false) + .serializeEnumByName(true) + .build(); +``` + +This Java native-mode option does not change xlang enum encoding; xlang uses numeric enum tags. Prefer +`@ForyEnumId` for cross-language payloads or any schema where numeric wire ids must stay stable. + +## Native Mode vs Xlang Mode + +Field configuration behaves differently depending on the serialization mode: + +### Native Mode (Java-only) + +Native mode has **relaxed default values** for maximum compatibility: + +- **Nullable**: Reference types are nullable by default +- **Ref tracking**: Enabled by default for object references (except `String`, boxed types, and time types) +- **Polymorphism**: All non-final classes support polymorphism by default + +In native mode, you typically **don't need to configure field annotations** unless you want to: + +- Reduce serialized size by using field IDs +- Optimize performance by disabling unnecessary ref tracking +- Control integer encoding for specific fields + +```java +// Native mode: works without any annotations +public class User { + private long id; + private String name; + private List tags; // Nullable and ref-tracked by default +} +``` + +### Xlang Mode (Cross-language) + +Xlang mode has **stricter default values** due to type system differences between languages: + +- **Nullable**: Fields are non-nullable by default +- **Ref tracking**: Disabled by default unless the field type uses `@Ref` +- **Polymorphism**: Concrete types are non-polymorphic by default + +In xlang mode, you **need to configure fields** when: + +- A field can be null (use `@Nullable`) +- A field needs reference tracking for shared/circular objects (use `@Ref`) +- Integer types need specific encoding for cross-language compatibility +- You want to reduce metadata size (use field IDs) + +```java +// Xlang mode: explicit configuration required for nullable/ref fields +public class User { + @ForyField(id = 0) + private long id; + + @ForyField(id = 1) + private String name; + + @Nullable + @ForyField(id = 2) // Must declare @Nullable + private String email; + + @Nullable + @ForyField(id = 3) + @Ref // Must declare @Ref for shared objects + private User friend; +} +``` + +### Default Values Summary + +| Option | Native Mode Default | Xlang Mode Default | +| ---------- | ------------------------ | --------------------------------- | +| `nullable` | `true` (reference types) | `false` | +| `ref` | `true` | `false` | +| `dynamic` | `true` (non-final) | `AUTO` (concrete types are final) | + +## Best Practices + +1. **Configure field IDs**: Recommended for compatible mode to reduce serialization cost +2. **Use `@Nullable` for nullable fields**: Required for fields that can be null +3. **Enable ref tracking for shared objects**: Use `@Ref` when objects are shared or circular +4. **Use `@Ignore` or `transient` for sensitive data**: Passwords, tokens, internal state +5. **Choose appropriate encoding**: `varint` for small values, `fixed` for full-range values +6. **Keep IDs stable**: Once assigned, don't change field IDs +7. **Configure unsigned types for cross-language compatibility**: When interoperating with unsigned numbers in Rust, Go, C++ + +## Annotations Reference + +| Annotation | Description | +| ----------------------------- | -------------------------------------- | +| `@ForyField(id = N)` | Field tag ID to reduce metadata size | +| `@Nullable` | Mark field or nested type as nullable | +| `@Ref` | Enable reference tracking | +| `@ForyField(dynamic = ...)` | Control polymorphism for struct fields | +| `@Ignore` | Exclude field from serialization | +| `@Int32Type(encoding = ...)` | 32-bit signed integer encoding | +| `@Int64Type(encoding = ...)` | 64-bit signed integer encoding | +| `@UInt8Type` | Unsigned 8-bit integer | +| `@UInt16Type` | Unsigned 16-bit integer | +| `@UInt32Type(encoding = ...)` | Unsigned 32-bit integer encoding | +| `@UInt64Type(encoding = ...)` | Unsigned 64-bit integer encoding | + +## Related Topics + +- [Basic Serialization](basic-serialization.md) - Getting started with Fory serialization +- [Configuration](configuration.md) - `ForyBuilder` options +- [Schema Evolution](schema-evolution.md) - Compatible mode and schema evolution +- [Xlang Serialization](xlang-serialization.md) - Interoperability with Python, Rust, C++, Go diff --git a/versioned_docs/version-1.3.0/guide/java/static-generated-serializers.md b/versioned_docs/version-1.3.0/guide/java/static-generated-serializers.md new file mode 100644 index 00000000000..fa14f6bd244 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/java/static-generated-serializers.md @@ -0,0 +1,165 @@ +--- +title: Static Generated Serializers +sidebar_position: 8 +id: static_generated_serializers +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. +--- + +Static generated serializers are Java serializers generated by javac during your application build. +They are useful when runtime code generation is disabled or unavailable. + +Use them when: + +- you run on Android. +- you run an ordinary JVM with `ForyBuilder#withCodegen(false)` and want generated serializers. +- your Android model classes use Fory type-use annotations such as `@Ref`, `@UInt8Type`, or + `@Float16Type`. + +For GraalVM native images, follow [GraalVM Support](graalvm-support.md) instead. + +## Install The Annotation Processor + +Add `fory-annotation-processor` to the annotation processor path of the module that compiles your +serializable classes: + +```xml + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.apache.fory + fory-annotation-processor + ${fory.version} + + + + + + +``` + +The generated serializers depend on `fory-core` at runtime. Applications opt in by adding the +annotation processor; `fory-core` does not depend on it. + +## Annotate Classes + +Annotate each serializable class with `@ForyStruct`: + +```java +import org.apache.fory.annotation.ForyStruct; + +@ForyStruct +public class Order { + public long id; + public String note; + + public Order() {} +} +``` + +The processor generates serializer classes in the same Java package as the annotated class. For +`Order`, the generated classes are: + +- `Order_ForySerializer` for xlang mode. +- `Order_ForyNativeSerializer` for Java native mode. + +For a static nested type such as `Outer.Inner`, the generated top-level classes are +`Outer_Inner_ForySerializer` and `Outer_Inner_ForyNativeSerializer`. + +## Field Debug Tracing + +Add `@ForyDebug` next to `@ForyStruct` when you need generated serializers to include field-level +debug tracing hooks. The generated code prints those traces only when +`ENABLE_FORY_DEBUG_OUTPUT=1`. + +```java +import org.apache.fory.annotation.ForyDebug; +import org.apache.fory.annotation.ForyStruct; + +@ForyStruct +@ForyDebug +public class DebugOrder { + public long id; + public String note; + + public DebugOrder() {} +} +``` + +## Runtime Use + +Fory uses static generated serializers when they are available on: + +- Android. +- ordinary JVMs with `ForyBuilder#withCodegen(false)`. +- compatible-mode reads when the target struct has a generated serializer. + +On an ordinary JVM with `codegen=true`, Fory continues to prefer runtime-generated serializers. + +Fory resolves generated serializers from the registered target class name. Application code +should not reference generated serializer classes directly. + +## Field Access Rules + +Generated serializers must be able to access serialized fields or their accessors at compile time. + +- Public, protected, and package-private fields can be accessed directly when Java package access + allows same-package generated serializers to use them. +- Private serialized fields must have accessible non-private getter and setter methods, or be + excluded with `transient` or Fory `@Ignore`. +- Public, protected, and package-private getter/setter methods are accepted when they are accessible + from the generated serializer package. +- Final fields are not supported for normal mutable classes because generated read and copy methods + must assign fields. Use records for constructor-based immutable values. + +For records, generated serializers use public record accessors and construct values through the +canonical record constructor. Ignored record components are skipped by serialization and copy, and +their constructor arguments use Java default values during generated read/copy. + +## Type-Use Annotations On Android + +On Android, static generated serializers are required when a class uses Fory type-use annotations on +nested types: + +```java +import java.util.List; +import org.apache.fory.annotation.ForyStruct; +import org.apache.fory.annotation.UInt8Type; + +@ForyStruct +public class ImageBlock { + public List<@UInt8Type Integer> pixels; +} +``` + +Without the generated serializer metadata, Android may not expose enough nested type information for +Fory to preserve annotations such as `@Ref`, `@Int8Type`, `@UInt8Type`, `@Float16Type`, or +`@BFloat16Type`. + +The annotation processor emits generated consumer R8/ProGuard rules under `META-INF/proguard/` for +the exact serializer constructors used by Fory. Android applications should not add broad generated +serializer keep rules by hand. + +## Compatible Reads + +Static generated serializers support both normal serialization and compatible-mode reads. Compatible +reads match remote fields to local fields, skip fields that no longer exist locally, and keep Java +default values for fields that are missing from the remote payload. diff --git a/versioned_docs/version-1.3.0/guide/java/troubleshooting.md b/versioned_docs/version-1.3.0/guide/java/troubleshooting.md new file mode 100644 index 00000000000..2795f837622 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/java/troubleshooting.md @@ -0,0 +1,215 @@ +--- +title: Troubleshooting +sidebar_position: 18 +id: troubleshooting +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 page covers common issues and their solutions. + +## Class Inconsistency and Class Version Check + +If you explicitly disabled compatible mode and get a strange serialization error, it may be caused by class inconsistency between the serialization peer and deserialization peer. + +In such cases, you can invoke `ForyBuilder#withClassVersionCheck` with `withCompatible(false)` to validate it. If deserialization throws `org.apache.fory.exception.ClassNotCompatibleException`, the classes are inconsistent. Remove the `withCompatible(false)` override unless every reader and writer always uses the same class schema. + +```java +// Enable class version check to diagnose issues +Fory fory = Fory.builder() + .withCompatible(false) + .withClassVersionCheck(true) + .build(); + +// If ClassNotCompatibleException is thrown, remove withCompatible(false). +``` + +**Note**: compatible mode is the default for both xlang and native mode. Use `withCompatible(false)` only if every reader and writer always uses the same class schema and you want faster serialization and smaller size. + +## Using Wrong API for Deserialization + +Use `serialize` with one of the `deserialize` overloads: + +| Serialization API | Deserialization API | +| ----------------- | ------------------- | +| `Fory#serialize` | `Fory#deserialize` | + +**Wrong usage example:** + +```java +// Wrong: deserialize with an incompatible target class +byte[] bytes = fory.serialize(struct1); +Struct2 result = fory.deserialize(bytes, Struct2.class); // May throw ClassCastException +``` + +**Correct usage:** + +```java +byte[] bytes = fory.serialize(object); +Object result = fory.deserialize(bytes); + +byte[] typedBytes = fory.serialize(object); +MyClass typedResult = fory.deserialize(typedBytes, MyClass.class); +``` + +## Deserialize POJO into Another Type + +If you want to serialize one POJO and deserialize it into a different POJO type, use compatible mode: + +```java +public class DeserializeIntoType { + static class Struct1 { + int f1; + String f2; + + public Struct1(int f1, String f2) { + this.f1 = f1; + this.f2 = f2; + } + } + + static class Struct2 { + int f1; + String f2; + double f3; + } + + static ThreadSafeFory fory = Fory.builder() + .buildThreadSafeFory(); + + public static void main(String[] args) { + Struct1 struct1 = new Struct1(10, "abc"); + byte[] data = fory.serialize(struct1); + Struct2 struct2 = fory.deserialize(data, Struct2.class); + } +} +``` + +## Common Error Messages + +### "Class not registered" + +**Cause**: Class registration is required but the class wasn't registered. + +**Solution**: Register the class before serialization: + +```java +fory.register(MyClass.class); +// or with explicit ID +fory.register(MyClass.class, 100); +``` + +### "ClassNotCompatibleException" + +**Cause**: Class schema differs between serialization and deserialization. + +**Solution**: Keep compatible mode enabled: + +```java +Fory fory = Fory.builder() + .build(); +``` + +### "Max depth exceeded" + +**Cause**: Object graph is too deep, possibly indicating a circular reference attack. + +**Solution**: Increase max depth if legitimate, or check for malicious data: + +```java +Fory fory = Fory.builder() + .withMaxDepth(100) // Increase from default 50 + .build(); +``` + +### "Serializer not found" + +**Cause**: No serializer registered for the type. + +**Solution**: Register a custom serializer: + +```java +fory.registerSerializer(MyClass.class, new MyClassSerializer(fory.getTypeResolver())); +``` + +### JDK25+ access errors + +On JDK25+, if an error names `java.base/java.lang.invoke`, open `java.lang.invoke` to Fory. Use +`ALL-UNNAMED` when Fory is on the classpath: + +```bash +--add-opens=java.base/java.lang.invoke=ALL-UNNAMED +``` + +Use the Fory core module name when Fory is on the module path: + +```bash +--add-opens=java.base/java.lang.invoke=org.apache.fory.core +``` + +Fory does not require application package opens for private-field access. + +## Performance Issues + +### Slow Initial Serialization + +**Cause**: JIT compilation happening on first serialization. + +**Solution**: Enable async compilation: + +```java +Fory fory = Fory.builder() + .withAsyncCompilation(true) + .build(); +``` + +### High Memory Usage + +**Cause**: Large object graphs or reference tracking overhead. + +**Solutions**: + +- Disable reference tracking if not needed: `.withRefTracking(false)` +- Use custom memory allocator for pooling +- Consider row format for large datasets + +### Large Serialized Size + +**Cause**: Metadata overhead or uncompressed data. + +**Solutions**: + +- Enable compression: `.withIntCompressed(true)`, `.withLongCompressed(true)` +- Use compatible mode only when needed +- Register classes to avoid class name serialization + +## Debugging Tips + +1. **Enable class version check** to diagnose schema issues +2. **Check API pairing** - ensure serialize/deserialize APIs match +3. **Verify registration order** - must be consistent across peers +4. **Enable logging** to see internal operations: + +```bash +FORY_LOG_LEVEL=INFO mvn test -Dtest=org.apache.fory.TestClass#testMethod +``` + +## Related Topics + +- [Configuration](configuration.md) - All ForyBuilder options +- [Schema Evolution](schema-evolution.md) - Compatible mode details +- [Type Registration](type-registration.md) - Registration best practices +- [Native Serialization](native-serialization.md) - Java-only serialization features diff --git a/versioned_docs/version-1.3.0/guide/java/type-registration.md b/versioned_docs/version-1.3.0/guide/java/type-registration.md new file mode 100644 index 00000000000..e1c7c03c955 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/java/type-registration.md @@ -0,0 +1,124 @@ +--- +title: Type Registration +sidebar_position: 5 +id: type_registration +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 page covers class registration mechanisms and security configurations. + +## Class Registration + +`ForyBuilder#requireClassRegistration` can be used to disable class registration. This will allow deserializing objects of unknown types, which is more flexible but **may be insecure if the classes contain malicious code**. + +**Do not disable class registration unless you can ensure your environment is secure**. Malicious code in `init/equals/hashCode` can be executed when deserializing unknown/untrusted types when this option is disabled. + +Class registration can not only reduce security risks, but also avoid classname serialization cost. + +### Register by ID + +You can register class with API `Fory#register`: + +```java +Fory fory = xxx; +fory.register(SomeClass.class); +fory.register(SomeClass1.class, 1); +``` + +Note that class registration order is important. Serialization and deserialization peers should have the same registration order. +Register classes and custom serializers before the first top-level `serialize`, `deserialize`, or +`copy` call on a `Fory` instance. Fory freezes registration at that point so serializer lookups can use +the finalized registration state. + +Internal type IDs 0-32 are reserved for built-in xlang types. Java native built-ins start at +`Types.NONE + 1`, and user IDs are encoded as `(user_id << 8) | internal_type_id`. + +### Register by Name + +Register class by ID has better performance and smaller space overhead. But in some cases, +management for a bunch of type IDs is complex. In such cases, registering class by name using API +`register(Class cls, String name)` is recommended. Use `.` inside the name to add a namespace +prefix: + +```java +fory.register(Foo.class, "demo.Foo"); +``` + +If there are no duplicate names for types, use a name without a namespace prefix to reduce +serialized size. + +**Do not use this API to register class since it will increase serialized size a lot compared to registering class by ID.** + +## Security Configuration + +### Type Checker + +If you invoke `ForyBuilder#requireClassRegistration(false)` to disable class registration check, you can configure `org.apache.fory.resolver.TypeChecker` by `ForyBuilder#withTypeChecker` or `TypeResolver#setTypeChecker` to control which classes are allowed for serialization. + +For example, you can allow classes started with `org.example.*`: + +```java +Fory fory = Fory.builder().withXlang(false) + .requireClassRegistration(false) + .withTypeChecker((typeResolver, className) -> className.startsWith("org.example.")) + .build(); +``` + +### AllowListChecker + +Fory provides a `org.apache.fory.resolver.AllowListChecker` which is an allowed/disallowed list based checker to simplify the customization of class check mechanism: + +```java +AllowListChecker checker = new AllowListChecker(AllowListChecker.CheckLevel.STRICT); +checker.allowClass("org.example.*"); +ThreadSafeFory fory = Fory.builder().withXlang(false) + .requireClassRegistration(false) + .withTypeChecker(checker) + .buildThreadSafeFory(); +``` + +`withTypeChecker` installs the checker on every created Fory instance immediately, which also avoids the +generic startup warning emitted when class registration is disabled without any checker. You can +still use `TypeResolver#setTypeChecker` or `ThreadSafeFory#setTypeChecker` later if you need to +replace the checker after build time. + +## Limit Max Deserialization Depth + +Fory provides `ForyBuilder#withMaxDepth` to limit max deserialization depth. The default max depth is 50. + +If max depth is reached, Fory will throw `ForyException`. This can be used to prevent malicious data from causing stack overflow or other issues. + +```java +Fory fory = Fory.builder() + .withXlang(false) + .withMaxDepth(100) // Set custom max depth + .build(); +``` + +## Best Practices + +1. **Always enable class registration in production**: Use `requireClassRegistration(true)` +2. **Use ID-based registration for performance**: Numeric IDs are faster than string names +3. **Maintain consistent registration order**: Same order on both serialization and deserialization sides +4. **Set appropriate max depth**: Prevent stack overflow attacks +5. **Use AllowListChecker for fine-grained control**: When you need flexible class filtering + +## Related Topics + +- [Configuration](configuration.md) - ForyBuilder security options +- [Custom Serializers](custom-serializers.md) - Register custom serializers +- [Troubleshooting](troubleshooting.md) - Common registration issues diff --git a/versioned_docs/version-1.3.0/guide/java/virtual-threads.md b/versioned_docs/version-1.3.0/guide/java/virtual-threads.md new file mode 100644 index 00000000000..1dbadbf4cfa --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/java/virtual-threads.md @@ -0,0 +1,127 @@ +--- +title: Virtual Threads +sidebar_position: 13 +id: virtual_threads +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. +--- + +Apache Fory Java uses `buildThreadSafeFory()` for virtual-thread workloads. It builds a fixed-size +shared `ThreadPoolFory` sized to `4 * availableProcessors()`. If you need a different fixed pool +size, use `buildThreadSafeForyPool(poolSize)`. + +## Use Binary Input/Output APIs + +When you use virtual threads, always use Fory's binary input/output APIs: + +- `serialize(Object)` or `serialize(MemoryBuffer, Object)` +- `deserialize(byte[])` or `deserialize(MemoryBuffer)` + +Typical usage: + +```java +ThreadSafeFory fory = Fory.builder().withXlang(false) + .requireClassRegistration(false) + .buildThreadSafeFory(); + +byte[] bytes = fory.serialize(request); +Object value = fory.deserialize(bytes); +``` + +## Do Not Use Stream APIs For Large Virtual-Thread Counts + +Do not use stream or channel based APIs for virtual-thread-heavy workloads: + +- `serialize(OutputStream, Object)` +- `deserialize(ForyInputStream)` +- `deserialize(ForyReadableChannel)` + +Those APIs keep a pooled `Fory` instance occupied for the whole blocking call. With many virtual +threads, that means many `Fory` instances stay busy while waiting on I/O. Each `Fory` instance +typically uses around `30~50 KB` of memory, so holding many of them during blocking I/O adds up +quickly. + +Use stream APIs with virtual threads only when you have at most several hundred virtual threads and +the extra retained `Fory` memory is still acceptable. + +## Why Binary APIs Are The Right Fit + +Serialization and deserialization are CPU work. Fory is fast, so this CPU time is usually short +compared with network transfer time. + +In most cases, you do not need to overlap network transfer with Fory deserialization. Fory +deserialization is usually less than `1/10` of network transfer time, so optimizing the transport +path matters much more than trying to stream one object graph through Fory. + +Most RPC systems also already work with framed byte messages instead of Java object streams. For +example, gRPC uses length-delimited frames, which matches Fory's binary APIs naturally. + +A good virtual-thread pattern is: + +1. Read one framed message into bytes. +2. Call `fory.deserialize(bytes)`. +3. Produce the response object. +4. Call `fory.serialize(response)`. +5. Write the response bytes as the next framed chunk. + +## Recommended Pattern + +```java +byte[] requestBytes = readOneFrame(channel); +Request request = (Request) fory.deserialize(requestBytes); + +Response response = handle(request); +byte[] responseBytes = fory.serialize(response); +writeOneFrame(channel, responseBytes); +``` + +This keeps Fory on the fast CPU-bound part and keeps blocking I/O outside the serializer. + +## Huge Payloads: Chunked Length-Delimited Streaming + +For most cases, the normal framed-byte pattern above is enough. Only consider chunked streaming for +very large payloads when you want to overlap transport with serialization and deserialization. + +Even in that case, do not use Fory's stream APIs. Instead, split one large payload into multiple +sub object graphs, serialize each sub object graph to a `byte[]`, then write: + +1. frame length +2. chunk bytes + +On deserialization in virtual threads: + +1. read the frame length +2. read exactly that many bytes +3. call `fory.deserialize(chunkBytes)` + +This lets the transport move data chunk by chunk while Fory still works on complete binary frames. + +```java +for (Object chunk : splitIntoSubGraphs(largePayload)) { + byte[] bytes = fory.serialize(chunk); + writeFrame(output, bytes); +} + +while (hasMoreFrames(input)) { + int length = readLength(input); + byte[] bytes = readBytes(input, length); + Object chunk = fory.deserialize(bytes); + consumeChunk(chunk); +} +``` + +Length-delimited framing is common, and gRPC also uses length-delimited frames instead of Java +object streams, so this pattern fits typical RPC and virtual-thread transports well. diff --git a/versioned_docs/version-1.3.0/guide/java/xlang-serialization.md b/versioned_docs/version-1.3.0/guide/java/xlang-serialization.md new file mode 100644 index 00000000000..08d8e2440cb --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/java/xlang-serialization.md @@ -0,0 +1,283 @@ +--- +title: Xlang Serialization +sidebar_position: 2 +id: xlang_serialization +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. +--- + +Apache Fory™ xlang serialization is the Java wire mode for payloads that must be read by Python, +Rust, Go, JavaScript/TypeScript, C++, C#, Swift, Dart, Scala, Kotlin, or another non-Java Fory implementation. Java defaults to +xlang mode with compatible schema evolution, but examples set the mode explicitly so the payload +contract is visible in code. + +## Create an Xlang Fory Instance + +Use one long-lived `Fory` or `ThreadSafeFory` instance per configuration. Creating a Fory instance is +expensive because Fory caches type metadata and generated serializers. + +```java +import org.apache.fory.Fory; + +Fory fory = Fory.builder() + .withXlang(true) + .requireClassRegistration(true) + .withRefTracking(true) + .build(); +``` + +`withRefTracking(true)` is required only when the cross-language data model includes shared object +identity or cycles. Disable it for value-shaped schemas. + +Use [Native Serialization](native-serialization.md) instead when every writer and reader is Java +and the payload should preserve Java-specific object behavior. + +## Register Types + +Types must be registered with consistent IDs or names across all languages. Fory supports two +registration methods. + +### Register by ID (Recommended for Performance) + +```java +public record Person(String name, int age) {} + +// Numeric ID registration is compact and fast. +fory.register(Person.class, 1); + +Person person = new Person("Alice", 30); +byte[] bytes = fory.serialize(person); +// bytes can be deserialized by Python, Rust, Go, etc. +``` + +Benefits: faster serialization and smaller binary size. + +Trade-off: every service must coordinate IDs so the same logical type uses the same number. + +### Register by Name (Recommended for Flexibility) + +```java +public record Person(String name, int age) {} + +// Namespace/type-name registration is easier to coordinate across teams. +fory.register(Person.class, "example", "Person"); + +Person person = new Person("Alice", 30); +byte[] bytes = fory.serialize(person); +// bytes can be deserialized by Python, Rust, Go, etc. +``` + +Benefits: less risk of numeric ID conflicts and easier management across independently owned +services. + +Trade-off: the payload includes string identity, so it is larger than ID-based registration. + +The Java API also supports a single string type name, such as +`fory.register(Person.class, "example.Person")`. Use the same logical identity on every peer. + +## Java To Python Example + +### Java (Serializer) + +```java +import org.apache.fory.Fory; + +public record Person(String name, int age) {} + +public class Example { + public static void main(String[] args) { + Fory fory = Fory.builder() + .withXlang(true) + .withRefTracking(true) + .build(); + + // Register with the same logical name used by Python. + fory.register(Person.class, "example.Person"); + + Person person = new Person("Bob", 25); + byte[] bytes = fory.serialize(person); + + // Send bytes to Python by your service transport. + } +} +``` + +### Python (Deserializer) + +```python +import pyfory +from dataclasses import dataclass + +@dataclass +class Person: + name: str + age: pyfory.Int32 + +fory = pyfory.Fory(xlang=True, ref=True) + +# Register with the same name as Java. +fory.register_type(Person, name="example.Person") + +person = fory.deserialize(bytes_from_java) +print(f"{person.name}, {person.age}") # Output: Bob, 25 +``` + +## Handling Circular and Shared References + +Xlang mode supports circular and shared references when reference tracking is enabled: + +```java +public class Node { + public String value; + public Node next; + public Node parent; +} + +Fory fory = Fory.builder() + .withXlang(true) + .withRefTracking(true) + .build(); + +fory.register(Node.class, "example.Node"); + +Node node1 = new Node(); +node1.value = "A"; +Node node2 = new Node(); +node2.value = "B"; +node1.next = node2; +node2.parent = node1; + +byte[] bytes = fory.serialize(node1); +// Python/Rust/Go can correctly deserialize this with circular references preserved +``` + +## Type Mapping Considerations + +Not all Java types have equivalents in other languages. When using xlang mode: + +- Use primitive types (`int`, `long`, `double`, `String`) for maximum compatibility. +- Use standard collections (`List`, `Map`, `Set`) instead of language-specific collections. +- Use reduced-precision carriers (`Float16`, `BFloat16`, `Float16List`, `BFloat16List`) for + 16-bit float payloads. +- Treat `Float16[]`, `BFloat16[]`, `Float16List`, and `BFloat16List` as `list` carriers by + default; use `@ArrayType` when the schema must be `array` or `array`. +- Avoid Java-specific types like `Optional`, `BigDecimal`, and `EnumSet` unless every target language + has an agreed mapping. +- See [Type Mapping Guide](../../specification/xlang_type_mapping.md) for the complete + compatibility matrix. + +### Lists and Dense Arrays + +Java primitive arrays are dense `array` carriers, except plain `byte[]`, +which defaults to `bytes`. General Java collections and Fory primitive-list +carriers such as `Int32List`, `Float16List`, and `BFloat16List` use +`list` unless the field has explicit `@ArrayType` metadata. + +| Fory schema | Java field shape | +| ----------------- | ------------------------------------------ | +| `list` | `List` or `Int32List` | +| `array` | `boolean[]` | +| `array` | `@Int8Type byte[]` type-use | +| `array` | `short[]` | +| `array` | `int[]` | +| `array` | `long[]` | +| `array` | `@UInt8Type byte[]` type-use | +| `array` | `@UInt16Type short[]` type-use | +| `array` | `@UInt32Type int[]` type-use | +| `array` | `@UInt64Type long[]` type-use | +| `array` | `Float16Array` or `@Float16Type short[]` | +| `array` | `BFloat16Array` or `@BFloat16Type short[]` | +| `array` | `float[]` | +| `array` | `double[]` | + +Prefer type-use syntax for primitive-array annotations: + +```java +private @UInt32Type int[] ids; +private @BFloat16Type short[] values; +``` + +### Compatible Types + +```java +public record UserData( + String name, // compatible + int age, // compatible + List tags, // compatible + Map scores // compatible +) {} +``` + +### Problematic Types + +```java +public record UserData( + Optional name, // not cross-language compatible + BigDecimal balance, // limited support + EnumSet statuses // Java-specific collection +) {} +``` + +## Performance Considerations + +Xlang mode has additional overhead compared to Java native mode: + +- **Type metadata encoding**: Adds extra bytes per type +- **Type resolution**: Requires name/ID lookup during deserialization + +**For best performance**: + +- Use **ID-based registration** when possible (smaller encoding) +- **Disable reference tracking** if you don't need circular references (`withRefTracking(false)`) +- **Use native mode** (`withXlang(false)`) when only Java serialization is needed + +## Best Practices + +1. Use explicit type IDs or namespace/type names for every user type. +2. Keep compatible mode for independently deployed services. +3. Test payloads through every peer before relying on a schema in production. +4. Use native serialization for Java-only traffic that needs Java-specific object behavior. + +## Troubleshooting + +### "Type not registered" errors + +- Verify type is registered with same ID/name on both sides +- Check if type name has typos or case differences + +### "Type mismatch" errors + +- Ensure field types are compatible across languages +- Review [Type Mapping Guide](../../specification/xlang_type_mapping.md) + +### Data corruption or unexpected values + +- Verify both sides use xlang payloads +- Ensure both sides have compatible Fory versions + +## See Also + +- [Xlang Serialization Specification](../../specification/xlang_serialization_spec.md) +- [Type Mapping Reference](../../specification/xlang_type_mapping.md) +- [Python Xlang Serialization Guide](../python/xlang-serialization.md) +- [Rust Xlang Serialization Guide](../rust/xlang-serialization.md) + +## Related Topics + +- [Schema Evolution](schema-evolution.md) - Compatible mode +- [Type Registration](type-registration.md) - Registration methods +- [Native Serialization](native-serialization.md) - Java-only serialization features +- [Row Format](row-format.md) - Cross-language row format diff --git a/versioned_docs/version-1.3.0/guide/javascript/_category_.json b/versioned_docs/version-1.3.0/guide/javascript/_category_.json new file mode 100644 index 00000000000..2434e92ab88 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/javascript/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "JavaScript", + "position": 6, + "collapsible": true, + "collapsed": true +} diff --git a/versioned_docs/version-1.3.0/guide/javascript/basic-serialization.md b/versioned_docs/version-1.3.0/guide/javascript/basic-serialization.md new file mode 100644 index 00000000000..1ef847e5072 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/javascript/basic-serialization.md @@ -0,0 +1,244 @@ +--- +title: Basic Serialization +sidebar_position: 1 +id: basic_serialization +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 guide covers the core serialization APIs in Apache Fory JavaScript. + +## Create a `Fory` Instance + +```ts +import Fory from "@apache-fory/core"; + +const fory = new Fory(); +``` + +Create one instance, register your schemas, and reuse it. Fory caches the generated serializers after the first `register` call, so recreating it on every request wastes that work. + +## Define a Schema with `Type.struct` + +The most common path is to define a schema and register it. + +```ts +import Fory, { Type } from "@apache-fory/core"; + +const accountType = Type.struct( + { typeName: "example.account" }, + { + id: Type.int64(), + owner: Type.string(), + active: Type.bool(), + nickname: Type.string().setNullable(true), + }, +); + +const fory = new Fory(); +const { serialize, deserialize } = fory.register(accountType); +``` + +## Serialize and Deserialize + +```ts +const bytes = serialize({ + id: 42n, + owner: "Alice", + active: true, + nickname: null, +}); + +const value = deserialize(bytes); +console.log(value); +// { id: 42n, owner: 'Alice', active: true, nickname: null } +``` + +The returned `bytes` value is a `Uint8Array`/platform buffer and can be sent over the network or written to storage. + +## Root-Level Dynamic Serialization + +`Fory` can also serialize dynamic root values without first binding a schema-specific serializer. + +```ts +const fory = new Fory(); + +const bytes = fory.serialize( + new Map([ + ["name", "Alice"], + ["age", 30], + ]), +); + +const value = fory.deserialize(bytes); +``` + +This is convenient for dynamic payloads, but explicit schemas are usually better for stable interfaces and cross-language contracts. + +## Primitive Values + +```ts +const fory = new Fory(); + +fory.deserialize(fory.serialize(true)); +// true + +fory.deserialize(fory.serialize("hello")); +// 'hello' + +fory.deserialize(fory.serialize(123)); +// 123 + +fory.deserialize(fory.serialize(123n)); +// 123n + +fory.deserialize(fory.serialize(new Date("2021-10-20T09:13:00Z"))); +// Date +``` + +### Number and `bigint` + +JavaScript `number` is a 64-bit float, which cannot exactly represent all 64-bit integers. For cross-language contracts or anywhere exact integer sizes matter, use explicit field types in your schema: + +- `Type.int32()` — 32-bit integer; use JavaScript `number` +- `Type.int64()` — 64-bit integer; use JavaScript `bigint` +- `Type.float32()` / `Type.float64()` — floating-point + +Dynamic root serialization (calling `fory.serialize(someNumber)` without a schema) will infer a type, but the inferred type is not guaranteed by the API. Use a schema for any stable contract. + +## Arrays, Maps, and Sets + +```ts +const inventoryType = Type.struct("example.inventory", { + tags: Type.list(Type.string()), + counts: Type.map(Type.string(), Type.int32()), + labels: Type.set(Type.string()), +}); + +const fory = new Fory({ ref: true }); +const { serialize, deserialize } = fory.register(inventoryType); + +const bytes = serialize({ + tags: ["hot", "new"], + counts: new Map([ + ["apple", 3], + ["pear", 8], + ]), + labels: new Set(["featured", "seasonal"]), +}); + +const value = deserialize(bytes); +``` + +## Nested Structs + +```ts +const addressType = Type.struct("example.address", { + city: Type.string(), + country: Type.string(), +}); + +const userType = Type.struct("example.user", { + name: Type.string(), + address: Type.struct("example.address", { + city: Type.string(), + country: Type.string(), + }), +}); + +const fory = new Fory(); +const { serialize, deserialize } = fory.register(userType); + +const bytes = serialize({ + name: "Alice", + address: { city: "Hangzhou", country: "CN" }, +}); + +const user = deserialize(bytes); +``` + +If a nested value can be missing, mark it nullable: + +```ts +const wrapperType = Type.struct("example.wrapper", { + child: Type.struct("example.child", { + name: Type.string(), + }).setNullable(true), +}); +``` + +## Decorator-Based Registration + +TypeScript decorators are also supported. + +```ts +import Fory, { Type } from "@apache-fory/core"; + +@Type.struct("example.user") +class User { + @Type.int64() + id!: bigint; + + @Type.string() + name!: string; +} + +const fory = new Fory(); +const { serialize, deserialize } = fory.register(User); + +const user = new User(); +user.id = 1n; +user.name = "Alice"; + +const copy = deserialize(serialize(user)); +console.log(copy instanceof User); // true +``` + +## Nullability + +Field nullability is explicit in schema-based structs. + +```ts +const nullableType = Type.struct("example.optional_user", { + name: Type.string(), + email: Type.string().setNullable(true), +}); +``` + +If a field is not marked nullable and you try to write `null`, serialization throws. + +## Debugging Generated Code + +You can inspect generated serializer code with `hooks.afterCodeGenerated`. + +```ts +const fory = new Fory({ + hooks: { + afterCodeGenerated(code) { + console.log(code); + return code; + }, + }, +}); +``` + +This is useful when debugging schema behavior, field ordering, or generated fast paths. + +## Related Topics + +- [Type Registration](type-registration.md) +- [Supported Types](supported-types.md) +- [References](references.md) diff --git a/versioned_docs/version-1.3.0/guide/javascript/configuration.md b/versioned_docs/version-1.3.0/guide/javascript/configuration.md new file mode 100644 index 00000000000..058bccf4b3b --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/javascript/configuration.md @@ -0,0 +1,126 @@ +--- +title: Configuration +sidebar_position: 2 +id: configuration +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. +--- + +Fory JavaScript is an xlang-only implementation. `new Fory()` writes xlang payloads and uses compatible +schema evolution by default. There is no native-mode switch in the JavaScript API. + +## Basic Configuration + +```ts +import Fory from "@apache-fory/core"; + +const fory = new Fory(); +``` + +Create one `Fory` instance per application area and reuse it. Registration generates and caches +serializer code for each schema. + +## Constructor Options + +```ts +import Fory from "@apache-fory/core"; +import hps from "@apache-fory/hps"; + +const fory = new Fory({ + ref: true, + compatible: true, + maxDepth: 100, + maxTypeFields: 512, + maxTypeMetaBytes: 4096, + maxSchemaVersionsPerType: 10, + maxAverageSchemaVersionsPerType: 3, + hps, +}); +``` + +| Option | Default | Description | +| --------------------------------- | ------- | ------------------------------------------------------------------------------------- | +| `ref` | `false` | Enable reference tracking for shared or circular object graphs | +| `compatible` | `true` | Allow field additions/removals without breaking existing messages | +| `maxDepth` | `50` | Maximum nesting depth. Must be `>= 2`. Increase for deeply nested structures | +| `maxTypeFields` | `512` | Maximum fields accepted in one received remote struct metadata body | +| `maxTypeMetaBytes` | `4096` | Maximum encoded body bytes accepted for one received TypeMeta body | +| `maxSchemaVersionsPerType` | `10` | Maximum accepted remote metadata versions for one logical type | +| `maxAverageSchemaVersionsPerType` | `3` | Average accepted remote metadata versions across accepted remote types | +| `useSliceString` | `false` | Optional string-reading optimization for Node.js. Leave at default unless benchmarked | +| `hps` | unset | Optional fast string helper from `@apache-fory/hps` (Node.js 20+) | +| `hooks.afterCodeGenerated` | unset | Callback to inspect the generated serializer code, useful for debugging | + +## Reference Tracking + +Global reference tracking must be enabled before field-level reference metadata can take effect: + +```ts +const fory = new Fory({ ref: true }); +``` + +Then mark reference-tracked fields in the schema, for example with +`Type.struct("example.node").setTrackingRef(true)`. See [References](references.md) and +[Schema Metadata](schema-metadata.md). + +## Compatible Schema Evolution + +Compatible mode is the default. To use faster serialization and smaller size: + +```ts +const fory = new Fory({ compatible: false }); +``` + +Use compatible mode for rolling upgrades, independently deployed services, and +cross-language payloads. Use `compatible: false` only when every reader and +writer always uses the same struct schema and you want faster serialization and +smaller size. For individual structs, `evolving: false` applies the same opt-out +to that struct. For cross-language payloads, set `compatible: false` only after +verifying that every language uses the same schema, or when native types are +generated from Fory schema IDL. See [Schema Evolution](schema-evolution.md). + +## Optional HPS String Path + +`@apache-fory/hps` provides an optional Node.js string fast path: + +```ts +import hps from "@apache-fory/hps"; + +const fory = new Fory({ hps }); +``` + +Leave this unset unless you run on Node.js 20+ and have benchmarked your workload. + +## Security + +Security-related configuration: + +- Register only the expected schemas before deserializing untrusted payloads. +- Set `maxDepth` for the maximum nesting depth your service accepts. +- Keep `maxTypeFields` and `maxTypeMetaBytes` at their defaults unless the data + is not malicious and a trusted peer sends larger remote metadata. +- Keep `maxSchemaVersionsPerType` and + `maxAverageSchemaVersionsPerType` at their defaults unless the data is not + malicious and a trusted peer sends many remote schema versions. +- Prefer explicit `Type.struct(...)` schemas over `Type.any()` for untrusted input. +- Pass `hps` only from the official package version you deploy with Fory. + +## Related Topics + +- [Basic Serialization](basic-serialization.md) +- [Schema Metadata](schema-metadata.md) +- [Schema Evolution](schema-evolution.md) +- [References](references.md) diff --git a/versioned_docs/version-1.3.0/guide/javascript/grpc-support.md b/versioned_docs/version-1.3.0/guide/javascript/grpc-support.md new file mode 100644 index 00000000000..2cbc68dbee3 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/javascript/grpc-support.md @@ -0,0 +1,323 @@ +--- +title: gRPC Support +sidebar_position: 25 +id: grpc_support +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. +--- + +Fory can generate JavaScript service companions for schemas that define +services. The generated service code uses normal gRPC transports while request +and response objects are serialized with Fory instead of protobuf. + +Use this mode when both RPC peers are generated from the same Fory IDL, +protobuf IDL, or FlatBuffers IDL and both sides expect Fory-encoded message +bodies. Use normal protobuf gRPC generation for APIs that must be consumed by +generic protobuf clients, reflection tools, or components that expect protobuf +message bytes. + +Use `--grpc` for Node.js server and client code. Use `--grpc-web` for browser +clients that call a gRPC-Web compatible server or proxy. + +## Add Dependencies + +The generated model file depends on `@apache-fory/core`. + +Node.js gRPC companions import `@grpc/grpc-js`: + +```bash +npm install @apache-fory/core @grpc/grpc-js +``` + +Browser gRPC-Web companions import `grpc-web`: + +```bash +npm install @apache-fory/core grpc-web +``` + +Fory does not add gRPC packages as hard dependencies. Add only the transport +package used by your application. + +## Define a Service + +Service definitions can come from Fory IDL, protobuf IDL, or FlatBuffers +`rpc_service` definitions. A Fory IDL service looks like this: + +```protobuf +package demo.greeter; + +message HelloRequest { + string name = 1; +} + +message HelloReply { + string reply = 1; +} + +service Greeter { + rpc SayHello (HelloRequest) returns (HelloReply); +} +``` + +Generate Node.js gRPC bindings: + +```bash +foryc service.fdl --javascript_out=./generated/javascript --grpc +``` + +Generate browser gRPC-Web bindings: + +```bash +foryc service.fdl --javascript_out=./generated/javascript --grpc-web +``` + +Generate both: + +```bash +foryc service.fdl --javascript_out=./generated/javascript --grpc --grpc-web +``` + +For `service.fdl`, JavaScript output contains: + +| File | Purpose | +| --------------------- | --------------------------------------------- | +| `service.ts` | Interfaces, enums, unions, and schema helpers | +| `service_grpc.ts` | Node.js `@grpc/grpc-js` server/client code | +| `service_grpc_web.ts` | Browser `grpc-web` clients | + +The generated model file exports `registerXxxTypes(fory)` for custom `Fory` +instances and default root helpers such as `serializeHelloRequest` and +`deserializeHelloRequest`. Generated gRPC companions import those helpers +automatically. + +## Implement a Node.js Server + +```ts +import * as grpc from "@grpc/grpc-js"; +import { + GreeterHandlers, + addGreeterService, +} from "./generated/javascript/service_grpc"; + +const greeter: GreeterHandlers = { + sayHello(call, callback) { + callback(null, { + reply: `Hello, ${call.request.name}`, + }); + }, +}; + +const server = new grpc.Server(); +addGreeterService(server, greeter); +server.bindAsync( + "0.0.0.0:50051", + grpc.ServerCredentials.createInsecure(), + (error, port) => { + if (error) { + throw error; + } + server.start(); + console.log(`listening on ${port}`); + }, +); +``` + +## Create a Node.js Client + +```ts +import * as grpc from "@grpc/grpc-js"; +import { createGreeterClient } from "./generated/javascript/service_grpc"; + +const client = createGreeterClient( + "localhost:50051", + grpc.credentials.createInsecure(), +); + +client.sayHello({ name: "Fory" }, (error, reply) => { + if (error) { + throw error; + } + console.log(reply.reply); +}); +``` + +Use normal `@grpc/grpc-js` metadata, call options, credentials, deadlines, and +interceptors with the generated client and server. + +## Create a Browser Client + +```ts +import { createGreeterWebClient } from "./generated/javascript/service_grpc_web"; + +const client = createGreeterWebClient("https://api.example.com", { + wireFormat: "grpcweb", +}); + +client.sayHello({ name: "Fory" }, null, (error, reply) => { + if (error) { + console.error(error.message); + return; + } + console.log(reply.reply); +}); +``` + +For unary calls, the generated promise client is also available: + +```ts +import { createGreeterWebPromiseClient } from "./generated/javascript/service_grpc_web"; + +const client = createGreeterWebPromiseClient("https://api.example.com"); +const reply = await client.sayHello({ name: "Fory" }); +console.log(reply.reply); +``` + +## Streaming RPCs + +Node.js companions support all gRPC streaming shapes: + +```protobuf +service Greeter { + rpc SayHello (HelloRequest) returns (HelloReply); + rpc LotsOfReplies (HelloRequest) returns (stream HelloReply); + rpc LotsOfGreetings (stream HelloRequest) returns (HelloReply); + rpc Chat (stream HelloRequest) returns (stream HelloReply); +} +``` + +Browser gRPC-Web companions support unary and server-streaming methods. gRPC-Web +does not support client-streaming or bidirectional methods; the compiler rejects +those shapes for `--grpc-web`. + +Node.js server implementations use the normal `@grpc/grpc-js` streaming call +objects: + +```ts +const greeter: GreeterHandlers = { + sayHello(call, callback) { + callback(null, { reply: `Hello, ${call.request.name}` }); + }, + + lotsOfReplies(call) { + call.write({ reply: `Hello, ${call.request.name}` }); + call.write({ reply: `Welcome, ${call.request.name}` }); + call.end(); + }, + + lotsOfGreetings(call, callback) { + const names: string[] = []; + call.on("data", (request) => { + names.push(request.name); + }); + call.on("end", () => { + callback(null, { reply: `Hello, ${names.join(", ")}` }); + }); + }, + + chat(call) { + call.on("data", (request) => { + call.write({ reply: `Hello, ${request.name}` }); + }); + call.on("end", () => { + call.end(); + }); + }, +}; +``` + +Node.js clients use the generated methods that match the RPC shape: + +```ts +const replies = client.lotsOfReplies({ name: "Fory" }); +replies.on("data", (reply) => { + console.log(reply.reply); +}); + +const greetings = client.lotsOfGreetings((error, reply) => { + if (error) { + throw error; + } + console.log(reply.reply); +}); +greetings.write({ name: "Alice" }); +greetings.write({ name: "Bob" }); +greetings.end(); + +const chat = client.chat(); +chat.on("data", (reply) => { + console.log(reply.reply); +}); +chat.write({ name: "Alice" }); +chat.write({ name: "Bob" }); +chat.end(); +``` + +For services with server-streaming methods, the generated gRPC-Web companion +defaults to `grpcwebtext` wire format. Unary-only services default to +`grpcweb`. You can choose the format explicitly: + +```ts +const client = createGreeterWebClient("https://api.example.com", { + wireFormat: "grpcwebtext", +}); +``` + +Browser clients can consume server-streaming RPCs with the callback client: + +```ts +const stream = client.lotsOfReplies({ name: "Fory" }); + +stream.on("data", (reply) => { + console.log(reply.reply); +}); +stream.on("error", (error) => { + console.error(error.message); +}); +stream.on("end", () => { + console.log("stream ended"); +}); +``` + +## gRPC Runtime Behavior + +Generated service code only replaces request and response serialization. Normal +gRPC operational features still belong to the transport package: + +- TLS and credentials +- Metadata and status codes +- Deadlines and cancellation +- Client and server interceptors +- Load balancing and deployment-specific proxy configuration + +## Troubleshooting + +### Missing gRPC Packages + +Add `@grpc/grpc-js` for Node.js companions or `grpc-web` for browser +companions. `@apache-fory/core` intentionally does not depend on either +transport package. + +### gRPC-Web Client-Streaming or Bidirectional RPCs Are Rejected + +gRPC-Web does not support client-streaming or bidirectional streaming. Generate +Node.js companions with `--grpc` for those shapes, or expose unary and +server-streaming methods to browser clients. + +### Protobuf Clients Cannot Decode the Service + +Fory gRPC companions do not use protobuf wire encoding for messages. Use a +Fory-generated client for Fory-generated services, or provide a separate +protobuf service endpoint for generic protobuf clients. diff --git a/versioned_docs/version-1.3.0/guide/javascript/index.md b/versioned_docs/version-1.3.0/guide/javascript/index.md new file mode 100644 index 00000000000..9ba58f29fe8 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/javascript/index.md @@ -0,0 +1,176 @@ +--- +title: JavaScript Serialization Guide +sidebar_position: 0 +id: index +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. +--- + +Apache Fory JavaScript lets you serialize JavaScript and TypeScript objects to +bytes and deserialize them back, including across services written in Java, +Python, C++, Go, Rust, C#, Swift, Dart, Scala, Kotlin, and other +Fory-supported languages. + +## Why Fory JavaScript? + +- **Xlang**: serialize in JavaScript/TypeScript, deserialize in any supported + Fory library without writing glue code +- **Fast**: serializer code is generated and cached the first time you register a schema, not on every call +- **Reference-aware**: shared references and circular object graphs are supported when enabled +- **Explicit schemas**: field types, nullability, and polymorphism are declared once with `Type.*` builders or TypeScript decorators +- **Safe defaults**: configurable depth checks reject unexpectedly deep payloads +- **Modern types**: `bigint`, typed arrays, `Map`, `Set`, `Date`, `float16`, and `bfloat16` are supported + +## Installation + +Install the JavaScript packages from npm: + +```bash +npm install @apache-fory/core +``` + +Optional Node.js string fast-path support is available through `@apache-fory/hps`: + +```bash +npm install @apache-fory/core @apache-fory/hps +``` + +`@apache-fory/hps` depends on Node.js 20+ and is optional. If it is unavailable, Fory still works correctly; omit `hps` from the configuration. + +## Quick Start + +```ts +import Fory, { Type } from "@apache-fory/core"; + +const userType = Type.struct( + { typeName: "example.user" }, + { + id: Type.int64(), + name: Type.string(), + age: Type.int32(), + }, +); + +const fory = new Fory(); +const { serialize, deserialize } = fory.register(userType); + +const bytes = serialize({ + id: 1n, + name: "Alice", + age: 30, +}); + +const user = deserialize(bytes); +console.log(user); +// { id: 1n, name: 'Alice', age: 30 } +``` + +## Generated Code from Fory IDL + +You can also define schemas in Fory IDL and generate TypeScript model files: + +```protobuf +package example; + +message Person { + string name = 1; + int32 age = 2; +} +``` + +Generate JavaScript/TypeScript code: + +```bash +foryc person.fdl --javascript_out=./generated +``` + +The generated model file exports TypeScript interfaces, enums, unions, a +registration helper, and root serialization helpers: + +```ts +import { + Person, + deserializePerson, + registerPersonTypes, + serializePerson, +} from "./generated/person"; + +const bytes = serializePerson({ name: "Alice", age: 30 }); +const person: Person = deserializePerson(bytes); +``` + +When you manage your own `Fory` instance, register the generated schema module +before serializing values with that instance: + +```ts +import Fory from "@apache-fory/core"; +import { registerPersonTypes } from "./generated/person"; + +const fory = new Fory(); +const { person } = registerPersonTypes(fory); + +const bytes = person.serialize({ name: "Alice", age: 30 }); +const copy = person.deserialize(bytes); +``` + +## How it works + +Fory is schema-driven. You describe the shape of your data once with `Type.*` builders (or TypeScript decorators), then call `fory.register(schema)`. This returns a `{ serialize, deserialize }` pair that is fast to call repeatedly. + +```ts +// 1. Define the schema +const personType = Type.struct("example.person", { + name: Type.string(), + email: Type.string().setNullable(true), +}); + +// 2. Register once +const fory = new Fory(); +const { serialize, deserialize } = fory.register(personType); + +// 3. Use as many times as needed +const bytes = serialize({ name: "Alice", email: null }); +const person = deserialize(bytes); +``` + +Create one `Fory` instance per application and reuse it — creating a new one for every request wastes the work of schema registration. + +## Configuration + +Fory JavaScript is xlang-only. `new Fory()` uses compatible schema evolution by default. Configure +reference tracking, maximum read depth, and optional Node.js string acceleration through constructor +options; see [Configuration](configuration.md). + +## Documentation + +| Topic | Description | +| --------------------------------------------- | ------------------------------------------------------- | +| [Basic Serialization](basic-serialization.md) | Core APIs and everyday usage | +| [Configuration](configuration.md) | Fory options, compatible mode, limits, and HPS | +| [Type Registration](type-registration.md) | Numeric IDs, names, decorators, and schema registration | +| [Schema Metadata](schema-metadata.md) | Type builders, field options, and decorators | +| [Supported Types](supported-types.md) | Primitive, collection, time, enum, and struct mappings | +| [References](references.md) | Shared references and circular object graphs | +| [Schema Evolution](schema-evolution.md) | Compatible mode and evolving structs | +| [Xlang Serialization](xlang-serialization.md) | Interop guidance and mapping rules | +| [Fory IDL Compiler](../../compiler/index.md) | Generate TypeScript models from `.fdl` schemas | +| [gRPC Support](grpc-support.md) | Node.js gRPC and browser gRPC-Web generated clients | +| [Troubleshooting](troubleshooting.md) | Common issues, limits, and debugging tips | + +## Related Resources + +- [Xlang Serialization Specification](../../specification/xlang_serialization_spec.md) +- [Xlang Type Mapping](../../specification/xlang_type_mapping.md) diff --git a/versioned_docs/version-1.3.0/guide/javascript/references.md b/versioned_docs/version-1.3.0/guide/javascript/references.md new file mode 100644 index 00000000000..91e1d487d2f --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/javascript/references.md @@ -0,0 +1,111 @@ +--- +title: References +sidebar_position: 50 +id: references +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. +--- + +By default Fory treats every value as a separate copy — if the same object appears in two fields it gets serialized twice, and after deserialization you get two independent copies. Enable reference tracking when: + +- the same object instance is referenced from multiple places in the graph +- your data contains a circular structure (e.g. a node that points to itself) +- object identity must be preserved after a round trip + +Leave reference tracking off for plain tree-shaped data; it adds a small overhead. + +## Step 1: Enable Reference Tracking on the `Fory` Instance + +```ts +const fory = new Fory({ ref: true }); +``` + +## Step 2: Mark the Fields That Can Have Shared or Circular References + +For each field whose value may be shared or circular, call `.setTrackingRef(true)` on the field's schema: + +```ts +const nodeType = Type.struct("example.node", { + value: Type.string(), + next: Type.struct("example.node").setNullable(true).setTrackingRef(true), +}); +``` + +You need **both** the global flag and the field-level flag. Missing either one results in values being copied rather than referenced. + +## Circular self-reference example + +```ts +import Fory, { Type } from "@apache-fory/core"; + +const nodeType = Type.struct("example.node", { + name: Type.string(), + selfRef: Type.struct("example.node").setNullable(true).setTrackingRef(true), +}); + +const fory = new Fory({ ref: true }); +const { serialize, deserialize } = fory.register(nodeType); + +const node: any = { name: "root", selfRef: null }; +node.selfRef = node; + +const copy = deserialize(serialize(node)); +console.log(copy.selfRef === copy); // true +``` + +## Shared nested reference example + +```ts +const innerType = Type.struct(501, { + value: Type.string(), +}); + +const outerType = Type.struct(502, { + left: Type.struct(501).setNullable(true).setTrackingRef(true), + right: Type.struct(501).setNullable(true).setTrackingRef(true), +}); + +const fory = new Fory({ ref: true }); +const { serialize, deserialize } = fory.register(outerType); + +const shared = { value: "same-object" }; +const copy = deserialize(serialize({ left: shared, right: shared })); +console.log(copy.left === copy.right); // true +``` + +## When to enable it + +Enable reference tracking when: + +- the same object instance is reused in multiple fields +- your graph can be cyclic +- identity preservation matters after deserialization + +Leave it disabled when: + +- the data is a plain tree +- you want the lowest overhead +- object identity does not matter + +## Xlang Note + +Reference tracking is part of the Fory binary protocol and works across languages. Both sides must enable reference tracking and mark the same fields as reference-tracked for the behavior to be consistent. + +## Related Topics + +- [Basic Serialization](basic-serialization.md) +- [Schema Evolution](schema-evolution.md) +- [Xlang Serialization](xlang-serialization.md) diff --git a/versioned_docs/version-1.3.0/guide/javascript/schema-evolution.md b/versioned_docs/version-1.3.0/guide/javascript/schema-evolution.md new file mode 100644 index 00000000000..cd653992d5f --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/javascript/schema-evolution.md @@ -0,0 +1,116 @@ +--- +title: Schema Evolution +sidebar_position: 60 +id: schema_evolution +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. +--- + +Schema evolution lets different versions of your service exchange messages safely — a v2 writer can produce a message a v1 reader still understands, and vice versa. + +## Compatible Mode + +Compatible mode is the default. It writes extra field metadata so readers can skip unknown fields and +tolerate missing ones. Keep it for independent deployments, rolling upgrades, and xlang services. +For payloads whose reader and writer schemas never differ, see +[When to Use Each Mode](#when-to-use-each-mode). + +Compatible readers also tolerate selected scalar field type changes when the conversion is lossless. +A matched field can read between `boolean`, `string`, numeric scalars, and `Decimal` when the +converted value has the same logical value. For example, `"true"`, `"false"`, `"1"`, and `"0"` can +be read as booleans, exact finite ASCII numeric strings can be read as numeric fields that can hold +them, numbers and decimals can be read as canonical strings, and numeric widening or narrowing +succeeds only when no precision or range is lost. Invalid strings and lossy conversions fail during +deserialization. Nullable fields still compose with these conversions, but reference-tracked scalar +type changes are incompatible. + +## Default Compatible Mode + +```ts +const fory = new Fory(); +``` + +Use this when: + +- services deploy schema changes independently +- older readers may see newer payloads +- newer readers may see older payloads from before a field was added + +## Example + +Writer schema: + +```ts +const writerType = Type.struct( + { typeId: 1001 }, + { + name: Type.string(), + age: Type.int32(), + }, +); +``` + +Reader schema with fewer fields: + +```ts +const readerType = Type.struct( + { typeId: 1001 }, + { + name: Type.string(), + }, +); +``` + +With compatible mode, the reader ignores fields it does not know about, and fills unknown fields with default values. + +## When to Use Each Mode + +| Requirement | Same-schema opt-out | Compatible mode | +| -------------------------------------------- | ------------------- | --------------- | +| Every reader and writer uses the same schema | works | works | +| Independent deployments | unsafe | recommended | +| Best size and speed for same-schema data | yes | no | +| Rolling upgrades | unsafe | recommended | + +Set `compatible: false` for xlang payloads only after verifying that every +language uses the same schema, or when native types are generated from Fory +schema IDL. + +## Same-Schema Per-Struct Opt-Out + +You can disable evolution metadata for a specific struct even inside a `compatible: true` instance: + +```ts +const fixedType = Type.struct( + { typeId: 1002, evolving: false }, + { + name: Type.string(), + }, +); +``` + +`evolving: false` can be faster and smaller for that struct. Use it only when every reader and +writer always uses the same struct schema. If one side writes with `evolving: false` and the other +reads expecting compatible metadata, deserialization will fail. + +## Xlang Requirement + +Compatible mode only protects you from schema differences in the _fields_ of a type. You still need the same type identity (same numeric ID or same `typeName`) on every side. See [Xlang Serialization](xlang-serialization.md). + +## Related Topics + +- [Type Registration](type-registration.md) +- [Xlang Serialization](xlang-serialization.md) diff --git a/versioned_docs/version-1.3.0/guide/javascript/schema-metadata.md b/versioned_docs/version-1.3.0/guide/javascript/schema-metadata.md new file mode 100644 index 00000000000..86b1ec33bfb --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/javascript/schema-metadata.md @@ -0,0 +1,165 @@ +--- +title: Schema Metadata +sidebar_position: 35 +id: schema_metadata +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. +--- + +JavaScript schema metadata is declared with `Type.*` builders or TypeScript decorators. Metadata +defines type identity, field types, nullability, reference tracking, dynamic fields, and per-struct +schema evolution behavior. + +## Type Identity + +Structs and enums can use a numeric ID or a name. Pick one identity strategy for a type and use it +consistently in every implementation that reads or writes the payload. + +```ts +import { Type } from "@apache-fory/core"; + +const byId = Type.struct( + { typeId: 1001 }, + { + id: Type.int64(), + name: Type.string(), + }, +); + +const byName = Type.struct( + { typeName: "example.user" }, + { + id: Type.int64(), + name: Type.string(), + }, +); +``` + +Use `.` inside `typeName` to add a namespace prefix. + +## Decorator Metadata + +Decorators keep the schema next to a TypeScript class declaration: + +```ts +@Type.struct({ typeName: "example.user" }) +class User { + @Type.int64() + id!: bigint; + + @Type.string() + name!: string; +} +``` + +The decorator metadata is equivalent to the builder metadata registered with `fory.register(...)`. + +## Field Types + +Use explicit scalar builders for stable contracts: + +```ts +Type.int8(); +Type.int16(); +Type.int32(); +Type.int64(); // JavaScript value is bigint +Type.uint32(); +Type.uint64(); // JavaScript value is bigint +Type.float16(); +Type.bfloat16(); +Type.float32(); +Type.float64(); +Type.string(); +Type.binary(); +``` + +Use collection builders for nested values: + +```ts +Type.list(Type.string()); +Type.map(Type.string(), Type.int32()); +Type.set(Type.string()); +Type.int32Array(); +Type.float64Array(); +``` + +## Nullability + +Fields are non-nullable unless the schema says otherwise: + +```ts +const userType = Type.struct("example.user", { + name: Type.string(), + email: Type.string().setNullable(true), +}); +``` + +Passing `null` to a non-nullable field throws. + +## Reference Tracking + +When the same object instance can appear in multiple fields, or when an object graph can be +circular, enable global reference tracking and mark reference-tracked fields: + +```ts +import Fory, { Type } from "@apache-fory/core"; + +const fory = new Fory({ ref: true }); + +const nodeType = Type.struct("example.node", { + next: Type.struct("example.node").setNullable(true).setTrackingRef(true), +}); +``` + +Field-level reference metadata has no effect unless `new Fory({ ref: true })` is also set. + +## Dynamic Fields + +Use `Type.any()` when a field can hold values with different concrete Fory types: + +```ts +const eventType = Type.struct("example.event", { + kind: Type.string(), + payload: Type.any(), +}); +``` + +For a struct field with a declared type, `.setDynamic(Dynamic.FALSE)` always treats values as the +declared type and `.setDynamic(Dynamic.TRUE)` always writes the concrete type. The default +`Dynamic.AUTO` is appropriate for most fields. + +## Per-Struct Schema Evolution + +JavaScript uses compatible schema evolution by default. For a same-schema struct that should omit +evolution metadata, set `evolving: false`: + +```ts +const fixedType = Type.struct( + { typeId: 1002, evolving: false }, + { + name: Type.string(), + }, +); +``` + +Use `evolving: false` only when every reader and writer always uses the same struct schema. + +## Related Topics + +- [Configuration](configuration.md) +- [Type Registration](type-registration.md) +- [Supported Types](supported-types.md) +- [Schema Evolution](schema-evolution.md) diff --git a/versioned_docs/version-1.3.0/guide/javascript/supported-types.md b/versioned_docs/version-1.3.0/guide/javascript/supported-types.md new file mode 100644 index 00000000000..f820276316a --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/javascript/supported-types.md @@ -0,0 +1,177 @@ +--- +title: Supported Types +sidebar_position: 40 +id: supported_types +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 page lists the JavaScript and TypeScript types supported by Fory, and explains when you need to be deliberate about type choices for cross-language compatibility. + +## Primitive and Scalar Types + +| JavaScript value | Fory schema | Notes | +| ---------------- | ------------------------------------------------------------------------------------- | ---------------------------------------------------- | +| `boolean` | `Type.bool()` | | +| `number` | `Type.int8()` / `Type.int16()` / `Type.int32()` / `Type.float32()` / `Type.float64()` | Pick the width that matches the peer language | +| `bigint` | `Type.int64()` / `Type.uint64()` | Use `bigint` for 64-bit integers | +| `string` | `Type.string()` | | +| `Uint8Array` | `Type.binary()` | Binary blob | +| `Date` | `Type.timestamp()` | Serializes/deserializes as `Date` | +| `Date` | `Type.date()` | Date without time; deserializes as `Date` | +| duration (ms) | `Type.duration()` | Exposed as a numeric millisecond value in JavaScript | +| `number` | `Type.float16()` | Half-precision float | +| `number` | `Type.bfloat16()` | Brain floating point | + +## Integer Types + +JavaScript `number` is a 64-bit float. It cannot safely represent all 64-bit integers (integers above `Number.MAX_SAFE_INTEGER` lose precision). Use explicit schemas to match the width expected by the peer language: + +```ts +Type.int8(); // -128 to 127 +Type.int16(); // -32,768 to 32,767 +Type.int32(); // variable-length int32; default for semantic int32 +Type.int32({ encoding: "fixed" }); +Type.int64(); // variable-length int64; use with bigint +Type.int64({ encoding: "fixed" }); +Type.int64({ encoding: "tagged" }); +Type.uint8(); +Type.uint16(); +Type.uint32(); // variable-length uint32 +Type.uint32({ encoding: "fixed" }); +Type.uint64(); // variable-length uint64; use with bigint +Type.uint64({ encoding: "fixed" }); +Type.uint64({ encoding: "tagged" }); +``` + +**Rule of thumb**: anything that maps to a 64-bit integer in another language should use `Type.int64()` or `Type.uint64()` on the JavaScript side and be passed as a `bigint` value. + +## Floating-Point Types + +```ts +Type.float16(); +Type.float32(); +Type.float64(); +Type.bfloat16(); +``` + +`float16` and `bfloat16` are useful when interoperating with languages or payloads that use reduced-precision numeric formats. + +## Arrays and Typed Arrays + +### Lists + +```ts +Type.list(Type.string()); +Type.list( + Type.struct("example.item", { + id: Type.int64(), + }), +); +``` + +These map to JavaScript arrays and use the Fory `list` schema. + +## Optimized Numeric Arrays + +For dense arrays of bools and numbers, use the element-specific array builders. They are more compact and map to native typed arrays where JavaScript has one: + +```ts +Type.boolArray(); // boolean[] in JS +Type.int16Array(); // Int16Array +Type.int32Array(); // Int32Array +Type.int64Array(); // BigInt64Array +Type.float32Array(); // Float32Array +Type.float64Array(); // Float64Array +Type.float16Array(); // number[] +Type.bfloat16Array(); // BFloat16Array +``` + +Use `Type.list(elementType)` for non-numeric, struct, nullable-element, or ref-tracked ordered collections. + +## Maps and Sets + +```ts +Type.map(Type.string(), Type.int32()); +Type.set(Type.string()); +``` + +These map to JavaScript `Map` and `Set` values. + +## Structs + +```ts +Type.struct("example.user", { + id: Type.int64(), + name: Type.string(), + tags: Type.list(Type.string()), +}); +``` + +Structs can be declared inline, by decorators, or nested within other schemas. + +## Enums + +```ts +Type.enum("example.color", { + Red: 1, + Green: 2, + Blue: 3, +}); +``` + +Fory encodes enum values by their ordinal position in the object (not their value). Both sides must declare enum members in the same order. When interoperating with another language, make sure the member order matches, not just the values. + +## Nullable fields + +Use `.setNullable(true)` when a field may be `null`. + +```ts +Type.string().setNullable(true); +``` + +## Dynamic Fields + +Use `Type.any()` when a field can hold values of different concrete types. + +```ts +const eventType = Type.struct("example.event", { + kind: Type.string(), + payload: Type.any(), +}); +``` + +Explicit field schemas are preferable when the type is known — `Type.any()` is harder to keep aligned across languages. + +## Reference-Tracked Fields + +When the same object instance can appear in multiple fields, or when your graph is circular, opt individual fields into reference tracking: + +```ts +Type.struct("example.node").setTrackingRef(true).setNullable(true); +``` + +This requires `new Fory({ ref: true })`. See [References](references.md). + +## Extension Types + +For types that need completely custom encoding, use `Type.ext(...)` and pass a custom serializer to `fory.register(...)`. This is an advanced use case; the standard `Type.struct` covers most scenarios. + +## Related Topics + +- [Basic Serialization](basic-serialization.md) +- [References](references.md) +- [Xlang Serialization](xlang-serialization.md) diff --git a/versioned_docs/version-1.3.0/guide/javascript/troubleshooting.md b/versioned_docs/version-1.3.0/guide/javascript/troubleshooting.md new file mode 100644 index 00000000000..bba32aeef76 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/javascript/troubleshooting.md @@ -0,0 +1,90 @@ +--- +title: Troubleshooting +sidebar_position: 90 +id: troubleshooting +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 page covers common problems when using Fory JavaScript. + +## Cannot deserialize a non-cross-language payload + +Fory JavaScript only reads Fory cross-language payloads. If the producer is a Java or Go service using a native-mode format, the JavaScript side cannot decode it. + +Fix: switch the producer to xlang payloads. Java and Go use xlang by default; use compatible mode +unless every peer uses the same schema. + +## `maxDepth must be an integer >= 2` + +This means you passed an invalid `maxDepth` value. It must be a positive integer of at least 2. + +```ts +new Fory({ maxDepth: 100 }); +``` + +Increase this only if your data is legitimately deeply nested. + +## `Field "..." is not nullable` + +You are passing `null` to a field that was not declared nullable. Fix: add `.setNullable(true)` to the field schema: + +```ts +const userType = Type.struct("example.user", { + name: Type.string(), + email: Type.string().setNullable(true), // ← this field can be null +}); +``` + +## Objects are not the same instance after deserialization + +Fory does not preserve object identity by default. Two fields pointing to the same object will become two independent copies. + +Fix: enable **both** of these: + +1. `new Fory({ ref: true })` on the instance +2. `.setTrackingRef(true)` on the specific fields + +See [References](references.md). + +## Large integers come back as `bigint` + +This is expected. Fory uses `bigint` for any 64-bit integer field (`Type.int64()`, `Type.uint64()`). If you need a `number`, use a smaller integer type like `Type.int32()` — but only if the value actually fits in 32 bits. + +## Inspecting Generated Serializer Code + +If you need to debug what Fory is doing under the hood, inspect the generated serializer code with a hook: + +```ts +const fory = new Fory({ + hooks: { + afterCodeGenerated(code) { + console.log(code); + return code; + }, + }, +}); +``` + +## `@apache-fory/hps` Install Fails + +`@apache-fory/hps` is an optional Node.js accelerator. If it fails to install (e.g. on a platform without native module support), just remove it from your dependencies. Fory still works correctly without it. + +## Related Topics + +- [Basic Serialization](basic-serialization.md) +- [References](references.md) +- [Xlang Serialization](xlang-serialization.md) diff --git a/versioned_docs/version-1.3.0/guide/javascript/type-registration.md b/versioned_docs/version-1.3.0/guide/javascript/type-registration.md new file mode 100644 index 00000000000..940d4f1ea4a --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/javascript/type-registration.md @@ -0,0 +1,164 @@ +--- +title: Type Registration +sidebar_position: 30 +id: type_registration +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. +--- + +Every struct and enum you serialize must be registered with the `Fory` instance before use. Registration tells Fory how to identify the type in a message and how to encode and decode it. + +## Registering Structs + +You can identify a struct with a numeric ID or with a name. Pick one strategy and use it consistently across all languages that share the same messages. + +### Register by numeric ID + +Smaller wire representation. Good when a small team can coordinate IDs. + +```ts +const userType = Type.struct( + { typeId: 1001 }, + { + id: Type.int64(), + name: Type.string(), + }, +); + +const fory = new Fory(); +const { serialize, deserialize } = fory.register(userType); +``` + +The same number must be used in every peer that reads or writes this type. + +### Register by name + +Easier to coordinate across teams. Slightly larger metadata in the message. + +```ts +const userType = Type.struct( + { typeName: "example.user" }, + { + id: Type.int64(), + name: Type.string(), + }, +); + +const fory = new Fory(); +const { serialize, deserialize } = fory.register(userType); +``` + +Use `.` inside `typeName` to add a namespace prefix. Fory splits the namespace from +the final type-name segment. + +> **Do not mix strategies for the same type across peers.** If one side uses a numeric ID and the other uses a name, deserialization will fail. + +## Registering with Decorators + +```ts +@Type.struct({ typeId: 1001 }) +class User { + @Type.int64() + id!: bigint; + + @Type.string() + name!: string; +} + +const fory = new Fory(); +const { serialize, deserialize } = fory.register(User); +``` + +Decorator-based registration is convenient when you want your TypeScript class declaration and schema to live together. + +## Registering Enums + +Fory JavaScript supports both plain JavaScript enum-like objects and TypeScript enums. + +### JavaScript object enum + +```ts +const Color = { + Red: 1, + Green: 2, + Blue: 3, +}; + +const fory = new Fory(); +const colorSerde = fory.register(Type.enum("example.color", Color)); +``` + +### TypeScript enum + +```ts +enum Status { + Pending = "pending", + Active = "active", +} + +const fory = new Fory(); +fory.register(Type.enum("example.status", Status)); +``` + +## Registration Scope + +Registration is per `Fory` instance. If you create two instances, you need to register schemas in both. + +## What `register` Returns + +`fory.register(schema)` returns a bound serializer pair: + +```ts +const { serialize, deserialize } = fory.register(orderType); + +// serialize returns Uint8Array bytes +const bytes = serialize({ id: 1n, total: 99.99 }); + +// deserialize returns the decoded value +const order = deserialize(bytes); +``` + +Store and reuse this pair — it is the fast path. + +## Field Metadata + +Field nullability, reference tracking, dynamic field behavior, numeric widths, and per-struct +schema-evolution metadata are covered in [Schema Metadata](schema-metadata.md). + +## Choosing IDs vs Names + +Use **numeric IDs** when: + +- you want the smallest possible message size +- your organization can keep IDs stable and globally unique +- services are tightly coordinated + +Use **names** when: + +- teams define types independently +- schemas are already identified by package/module name +- slightly larger metadata overhead is acceptable + +## Xlang + +For a message to round-trip between JavaScript and another language, both sides must use the same identity for a given type: same numeric ID, or same `typeName`. Use `.` inside `typeName` to add a namespace prefix. See [Xlang Serialization](xlang-serialization.md). + +## Related Topics + +- [Basic Serialization](basic-serialization.md) +- [Schema Metadata](schema-metadata.md) +- [Schema Evolution](schema-evolution.md) +- [Xlang Serialization](xlang-serialization.md) diff --git a/versioned_docs/version-1.3.0/guide/javascript/xlang-serialization.md b/versioned_docs/version-1.3.0/guide/javascript/xlang-serialization.md new file mode 100644 index 00000000000..9442fe2647d --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/javascript/xlang-serialization.md @@ -0,0 +1,155 @@ +--- +title: Xlang Serialization +sidebar_position: 20 +id: xlang_serialization +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. +--- + +Fory JavaScript serializes to the same binary format as the Java, Python, C++, +Go, Rust, C#, Swift, Dart, Scala, and Kotlin Fory implementations. You can write a +message in JavaScript and read it in Java, or any other direction, without a +conversion layer. + +Things to keep in mind: + +- Fory JavaScript reads and writes cross-language payloads only; it does not support any native-mode format. +- JavaScript does not support out-of-band mode. + +## Requirements for a Successful Round Trip + +For a message to survive a round trip between JavaScript and another language: + +1. **Same type identity** on both sides — same numeric ID, or same `typeName`. +2. **Compatible field types** — a `Type.int32()` field in JavaScript matches Java `int`, Go `int32`, C# `int`. +3. **Same nullability** — if one side marks a field nullable, the other should too. +4. Compatible schema evolution on both sides. JavaScript enables it by default. +5. **Same reference tracking config** if your data has shared or circular references. + +## Step-by-Step: JavaScript to Another Peer + +1. Define the JavaScript schema with the same type name or numeric ID used by the peer. +2. Register the schema in both peers. +3. Match field types, nullability, and schema-evolution settings. +4. Test a real payload end-to-end before shipping. + +JavaScript side: + +```ts +import Fory, { Type } from "@apache-fory/core"; + +const messageType = Type.struct( + { typeName: "example.message" }, + { + id: Type.int64(), + content: Type.string(), + }, +); + +const fory = new Fory(); +const { serialize } = fory.register(messageType); + +const bytes = serialize({ + id: 1n, + content: "hello from JavaScript", +}); +``` + +On the other side, register the same `example.message` type (same name or same numeric ID) using the peer language's API: + +- [Java guide](../java/index.md) +- [Python guide](../python/index.md) +- [Go guide](../go/index.md) +- [Rust guide](../rust/index.md) + +## Field Naming + +Fory matches fields by name. When models are defined in multiple languages, keep field names consistent — or at minimum use a naming scheme that maps unambiguously across languages (e.g. `snake_case` everywhere). + +With the default compatible schema evolution, field order differences are tolerated, but the names +themselves must still match. + +## Numeric Types + +JavaScript `number` is a 64-bit float, which does not map cleanly to every integer type in other languages. Use explicit schema types: + +- `Type.int32()` for 32-bit integers (Java `int`, Go `int32`, C# `int`) +- `Type.int64()` with `bigint` values for 64-bit integers (Java `long`, Go `int64`) +- `Type.float32()` or `Type.float64()` for floating-point values + +## Lists and Dense Arrays + +Use `Type.list(T)` for ordinary JavaScript `Array` values and Fory +`list` schema. Dense bool/numeric vectors use the explicit array builders +listed below. + +| Fory schema | JavaScript/TypeScript schema builder | +| ----------------- | ------------------------------------ | +| `list` | `Type.list(Type.int32())` | +| `array` | `Type.boolArray()` | +| `array` | `Type.int8Array()` | +| `array` | `Type.int16Array()` | +| `array` | `Type.int32Array()` | +| `array` | `Type.int64Array()` | +| `array` | `Type.uint8Array()` | +| `array` | `Type.uint16Array()` | +| `array` | `Type.uint32Array()` | +| `array` | `Type.uint64Array()` | +| `array` | `Type.float16Array()` | +| `array` | `Type.bfloat16Array()` | +| `array` | `Type.float32Array()` | +| `array` | `Type.float64Array()` | + +## Date and Time + +- `Type.timestamp()` — a point in time; round-trips as a JavaScript `Date` +- `Type.date()` — a date without time; deserializes as `Date` +- `Type.duration()` — exposed as a numeric millisecond value in JavaScript + +## Polymorphic Fields + +`Type.any()` lets a field hold different concrete types, but it is harder to keep in sync across languages. Prefer explicit field schemas whenever possible. + +```ts +const wrapperType = Type.struct( + { typeId: 3001 }, + { + payload: Type.any(), + }, +); +``` + +## Enums + +Enum member **order** must match across languages. Fory encodes enums by ordinal position, not by value. + +```ts +const Color = { Red: 1, Green: 2, Blue: 3 }; +const fory = new Fory(); +fory.register(Type.enum({ typeId: 210 }, Color)); +``` + +Use the same type ID or type name in every peer. + +## Safety Limits + +The `maxDepth` option bounds nested payloads. It does not change the binary format; it only controls what the local `Fory` instance accepts. + +## Related Topics + +- [Supported Types](supported-types.md) +- [Schema Evolution](schema-evolution.md) +- [Xlang Serialization Specification](../../specification/xlang_serialization_spec.md) diff --git a/versioned_docs/version-1.3.0/guide/kotlin/_category_.json b/versioned_docs/version-1.3.0/guide/kotlin/_category_.json new file mode 100644 index 00000000000..18540b48012 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/kotlin/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "Kotlin", + "position": 11, + "collapsible": true, + "collapsed": true +} diff --git a/versioned_docs/version-1.3.0/guide/kotlin/android-support.md b/versioned_docs/version-1.3.0/guide/kotlin/android-support.md new file mode 100644 index 00000000000..3f8c66b67fe --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/kotlin/android-support.md @@ -0,0 +1,122 @@ +--- +title: Android Support +sidebar_position: 7 +id: android_support +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. +--- + +Apache Fory Kotlin supports Kotlin/JVM and Android. Android support is built on +the existing Fory Java implementation plus Kotlin serializers from +`fory-kotlin`. Kotlin schema serializers are generated by `fory-kotlin-ksp` at +build time. + +Use this page for Android setup and release-build constraints. Use +[Static Generated Serializers](static-generated-serializers.md) for the Kotlin +KSP serializer model itself. If your Android project also contains Java +`@ForyStruct` classes, use the Java annotation processor documented in +[Java Static Generated Serializers](../java/static-generated-serializers.md). + +## Dependencies + +Add `fory-kotlin` to the Android module that uses Fory. Add +`fory-kotlin-ksp` to the module that compiles Kotlin `@ForyStruct` model +classes. + +```kotlin +plugins { + id("com.android.application") + id("org.jetbrains.kotlin.android") + id("com.google.devtools.ksp") +} + +dependencies { + implementation("org.apache.fory:fory-kotlin:") + ksp("org.apache.fory:fory-kotlin-ksp:") +} +``` + +For Android library modules, apply KSP in the library module that owns the +annotated Kotlin classes. The generated serializers and generated consumer R8 +rules must be packaged with that library artifact. + +## Fory Setup + +Create the Fory instance with `ForyKotlin.builder().withXlang(true)`, then register application classes +through the Kotlin `register` extension or the normal Fory Java registration +APIs. + +```kotlin +import org.apache.fory.kotlin.ForyKotlin +import org.apache.fory.kotlin.register + +val fory = ForyKotlin.builder() + .withXlang(true) + .requireClassRegistration(true) + .build() + +fory.register("example.User") +``` + +Do not reference generated serializer classes from application code. Fory +resolves generated serializers from the registered target class. + +## Xlang Schema Mode + +Android Kotlin structs that participate in Fory cross-language schema +serialization should use KSP generated serializers. Generated serializers avoid +using runtime reflection as the source of Kotlin schema metadata and call the +same Fory Java serializer infrastructure used by other generated serializers. + +Kotlin KSP generated serializers are xlang/schema serializers only. They do not +replace Java native object serializers and do not preserve concrete JVM +collection implementation identity. For example, a Kotlin `List` field +is schema `list`; deserialization only guarantees a value assignable to +the declared field type. + +## Minified Release Builds + +Validate Fory Android behavior with a minified release build. Debug builds do +not prove that generated serializers, generated constructor entry points, or +Kotlin metadata survive R8. + +KSP emits generated consumer R8/ProGuard rules under `META-INF/proguard/` for +the generated serializer constructors and Kotlin metadata required by Fory. +Android apps should not need broad user-written keep rules for generated Kotlin +serializers. If a custom packaging setup drops generated `META-INF/proguard/` +resources, fix that packaging path instead of adding broad keep rules for every +generated serializer. + +The Apache Fory repository validates this path with +`integration_tests/android_tests`, including release-minified instrumented +tests. + +## Java Models In Android Apps + +Kotlin KSP only processes Kotlin source. If your Android app contains Java +classes annotated with `@ForyStruct`, configure the Java +`fory-annotation-processor` for those Java sources. + +Static generated Java serializers are also important on Android when Java model +classes use Fory type-use annotations on nested types, such as +`List<@UInt8Type Integer>`. See +[Java Static Generated Serializers](../java/static-generated-serializers.md) +for that path. + +## Unsupported Targets + +`fory-kotlin` and `fory-kotlin-ksp` target Kotlin/JVM and Android only. +Kotlin/Native and Kotlin/JS are not supported. diff --git a/versioned_docs/version-1.3.0/guide/kotlin/configuration.md b/versioned_docs/version-1.3.0/guide/kotlin/configuration.md new file mode 100644 index 00000000000..061f14a7f8d --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/kotlin/configuration.md @@ -0,0 +1,151 @@ +--- +title: Configuration +sidebar_position: 1 +id: configuration +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 page covers Kotlin-specific Fory instance configuration and creation. + +## Xlang Setup + +Fory Kotlin follows the Java builder default: xlang mode with compatible schema +evolution. Use this path for cross-language Kotlin payloads, schema IDL +generated Kotlin models, and KSP-generated xlang serializers. + +```kotlin +import org.apache.fory.kotlin.ForyKotlin + +val fory = ForyKotlin.builder() + .withXlang(true) + .requireClassRegistration(true) + .build() +``` + +## Native Mode Setup + +For same-language Kotlin/JVM payloads that need native JVM object behavior, use +native mode explicitly: + +```kotlin +import org.apache.fory.kotlin.ForyKotlin + +val fory = ForyKotlin.builder().withXlang(false) + .requireClassRegistration(true) + .build() +``` + +## Thread Safety + +Fory instance creation is not cheap. Instances should be shared between multiple serializations. + +### Single-Thread Usage + +```kotlin +import org.apache.fory.Fory +import org.apache.fory.kotlin.ForyKotlin + +object ForyHolder { + val fory: Fory = ForyKotlin.builder() + .withXlang(true) + .requireClassRegistration(true) + .build() +} +``` + +### Multi-Thread Usage + +For multi-threaded applications, use `ThreadSafeFory`: + +```kotlin +import org.apache.fory.ThreadSafeFory +import org.apache.fory.kotlin.ForyKotlin + +object ForyHolder { + val fory: ThreadSafeFory = ForyKotlin.builder() + .withXlang(true) + .requireClassRegistration(true) + .buildThreadSafeFory() +} +``` + +### Using Builder Methods + +```kotlin +// Thread-safe Fory +val fory: ThreadSafeFory = ForyKotlin.builder() + .withXlang(true) + .requireClassRegistration(true) + .buildThreadSafeFory() +``` + +## Configuration + +All configuration options from Fory Java are available. See [Java Configuration](../java/configuration.md) for the complete list. + +Common options for Kotlin native-mode payloads: + +```kotlin +import org.apache.fory.kotlin.ForyKotlin + +val fory = ForyKotlin.builder().withXlang(false) + // Enable reference tracking for circular references + .withRefTracking(true) + // Same-schema optimization. Use only when every reader and writer + // always uses the same Kotlin/JVM schema. + .withCompatible(false) + // Enable async compilation for better startup performance + .withAsyncCompilation(true) + // Compression options + .withIntCompressed(true) + .withLongCompressed(true) + .build() +``` + +## Compatible Mode + +Compatible mode is enabled by default through the Java builder in both xlang and native mode. Keep +this default when models may evolve independently, when services deploy separately, or when xlang +schemas are written by hand in different languages. + +Use `withCompatible(false)` only when the class schema used to deserialize every payload is always +the same as the class schema used to serialize it and you want faster serialization and smaller size. +For xlang payloads, call `withCompatible(false)` only after verifying that every language uses the same schema, or when native types are generated from Fory schema IDL. + +## Security + +Kotlin uses the Java configuration surface. Keep class registration enabled for production +and any untrusted payload source: + +```kotlin +val fory = ForyKotlin.builder() + .requireClassRegistration(true) + .withMaxDepth(50) + .withMaxTypeFields(512) + .withMaxTypeMetaBytes(4096) + .build() +``` + +Security-related configuration: + +- Keep `requireClassRegistration(true)` and register application classes or generated modules. +- Use `withMaxDepth(...)` to reject unexpectedly deep object graphs. +- Keep `withMaxTypeFields(...)`, `withMaxTypeMetaBytes(...)`, and the remote schema-version limits + at their defaults unless the data is not malicious and a trusted peer sends larger metadata or + many schema versions. +- Follow [Java Configuration](../java/configuration.md#security) for allow-listing and unknown-class + controls. diff --git a/versioned_docs/version-1.3.0/guide/kotlin/default-values.md b/versioned_docs/version-1.3.0/guide/kotlin/default-values.md new file mode 100644 index 00000000000..e707c70db6b --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/kotlin/default-values.md @@ -0,0 +1,133 @@ +--- +title: Default Values +sidebar_position: 4 +id: default_values +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. +--- + +Fory supports Kotlin data class default values during native-mode deserialization when compatible mode is enabled. This feature enables forward/backward compatibility when data class schemas evolve. + +## Overview + +When a Kotlin data class has parameters with default values, Fory can: + +1. **Detect default values** using Kotlin reflection +2. **Apply default values** during deserialization when fields are missing from serialized data +3. **Support schema evolution** by allowing new fields with defaults to be added without breaking existing serialized data + +## Usage + +This feature is available when: + +- Compatible mode is enabled for the native-mode Fory instance. This is the default with + `withXlang(false)`. +- The Fory instance is built through `ForyKotlin.builder()` or `Fory.builder().withModule(ForyKotlin)` + with native mode enabled +- A field is missing from the serialized data but exists in the target class with a default value + +## Example + +```kotlin +import org.apache.fory.kotlin.ForyKotlin + +// Original data class +data class User(val name: String, val age: Int) + +// Evolved data class with new field and default value +data class UserV2(val name: String, val age: Int, val email: String = "default@example.com") + +fun main() { + val fory = ForyKotlin.builder().withXlang(false) + .build() + fory.register(User::class.java) + fory.register(UserV2::class.java) + + // Serialize with old schema + val oldUser = User("John", 30) + val serialized = fory.serialize(oldUser) + + // Deserialize with new schema - missing field gets default value + val newUser = fory.deserialize(serialized) as UserV2 + println(newUser) // UserV2(name=John, age=30, email=default@example.com) +} +``` + +## Supported Default Value Types + +The following types are supported for default values: + +- **Primitive types**: `Int`, `Long`, `Double`, `Float`, `Boolean`, `Byte`, `Short`, `Char` +- **Unsigned types**: `UInt`, `ULong`, `UByte`, `UShort` +- **String**: `String` +- **Collections**: `List`, `Set`, `Map` (with default instances) +- **Custom objects**: Any object that can be instantiated via reflection + +## Complex Default Values + +Default values can be complex expressions: + +```kotlin +data class ConfigV1(val name: String) + +data class ConfigV2( + val name: String, + val settings: Map = mapOf("default" to "value"), + val tags: List = listOf("default"), + val enabled: Boolean = true, + val retryCount: Int = 3 +) + +val fory = ForyKotlin.builder().withXlang(false) + .build() + +val original = ConfigV1("myConfig") +val serialized = fory.serialize(original) + +val deserialized = fory.deserialize(serialized) as ConfigV2 +// deserialized.name == "myConfig" +// deserialized.settings == mapOf("default" to "value") +// deserialized.tags == listOf("default") +// deserialized.enabled == true +// deserialized.retryCount == 3 +``` + +## Nullable Fields with Defaults + +Nullable fields with default values are also supported: + +```kotlin +data class PersonV1(val name: String) + +data class PersonV2( + val name: String, + val nickname: String? = null, + val age: Int? = null +) + +val original = PersonV1("John") +val serialized = fory.serialize(original) + +val deserialized = fory.deserialize(serialized) as PersonV2 +// deserialized.name == "John" +// deserialized.nickname == null (default) +// deserialized.age == null (default) +``` + +## Related Topics + +- [Schema Evolution](../java/schema-evolution.md) - Forward/backward compatibility in Java +- [Configuration](configuration.md) - Setting up Fory with compatible mode diff --git a/versioned_docs/version-1.3.0/guide/kotlin/grpc-support.md b/versioned_docs/version-1.3.0/guide/kotlin/grpc-support.md new file mode 100644 index 00000000000..c18eabebfa9 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/kotlin/grpc-support.md @@ -0,0 +1,278 @@ +--- +title: Kotlin gRPC Support +sidebar_position: 6 +id: grpc_support +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. +--- + +Fory IDL can generate Kotlin coroutine gRPC companions. The generated gRPC +files use normal grpc-java and grpc-kotlin APIs, while each request and response +message is serialized with Fory. + +Use this mode when both RPC peers are generated from the same Fory IDL, +protobuf IDL, or FlatBuffers IDL and both sides expect Fory-encoded message +bodies. Use normal protobuf gRPC generation for APIs that must be consumed by +generic protobuf clients, reflection tools, or components that expect protobuf +message bytes. + +## Add Dependencies + +Add Fory Kotlin, KSP, grpc-java, grpc-kotlin, coroutines, and one grpc-java +transport to the application or service module that compiles the generated +source. + +```kotlin +plugins { + id("com.google.devtools.ksp") version "" +} + +dependencies { + implementation("org.apache.fory:fory-kotlin:") + ksp("org.apache.fory:fory-kotlin-ksp:") + + implementation("io.grpc:grpc-api:") + implementation("io.grpc:grpc-stub:") + implementation("io.grpc:grpc-kotlin-stub:") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:") + + runtimeOnly("io.grpc:grpc-netty-shaded:") +} +``` + +Use a different grpc-java transport if your application already standardizes on +one. Generated Kotlin Fory gRPC does not require `grpc-protobuf` for payload +encoding. + +## Define a Service + +Service definitions can come from Fory IDL, protobuf IDL, or FlatBuffers +`rpc_service` definitions. A Fory IDL service looks like this: + +```protobuf +package demo.greeter; + +message HelloRequest { + string name = 1; +} + +message HelloReply { + string reply = 1; +} + +service Greeter { + rpc SayHello (HelloRequest) returns (HelloReply); +} +``` + +Generate Kotlin model and gRPC companion code with `--grpc`: + +```bash +foryc service.fdl --kotlin_out=./generated/kotlin --grpc +``` + +For this schema, the Kotlin generator emits: + +| File | Purpose | +| ------------------- | -------------------------------------------- | +| `HelloRequest.kt` | Fory model type for the request | +| `HelloReply.kt` | Fory model type for the response | +| `ServiceForyModule` | Fory registration module for generated types | +| `GreeterGrpcKt.kt` | Coroutine service base, stubs, and codecs | + +Run KSP when compiling the generated model files so the schema serializers are +available at runtime. Generated request and response types are registered by +the generated schema module used by the service companion, so service +implementations do not perform manual serializer registration. + +## Implement a Server + +Implement the generated coroutine base class and register it with a normal +grpc-java server. + +```kotlin +import demo.greeter.GreeterGrpcKt +import demo.greeter.HelloReply +import demo.greeter.HelloRequest +import io.grpc.ServerBuilder + +class GreeterService : GreeterGrpcKt.GreeterCoroutineImplBase() { + override suspend fun sayHello(request: HelloRequest): HelloReply = + HelloReply(reply = "Hello, ${request.name}") +} + +val server = ServerBuilder + .forPort(50051) + .addService(GreeterService()) + .build() + .start() +``` + +Unimplemented generated methods fail with gRPC `UNIMPLEMENTED`. Exceptions +thrown by your service method follow grpc-kotlin server behavior. + +## Create a Client + +Construct the generated coroutine stub directly from a grpc-java channel. + +```kotlin +import demo.greeter.GreeterGrpcKt +import demo.greeter.HelloRequest +import io.grpc.ManagedChannelBuilder + +val channel = ManagedChannelBuilder + .forAddress("localhost", 50051) + .usePlaintext() + .build() + +val stub = GreeterGrpcKt.GreeterCoroutineStub(channel) +val reply = stub.sayHello(HelloRequest(name = "Fory")) +``` + +Channel construction, shutdown, deadlines, credentials, interceptors, load +balancing, retries, and server lifecycle stay normal grpc-java/grpc-kotlin +responsibilities. + +## Streaming RPCs + +Fory service definitions can use the same gRPC streaming shapes: + +```protobuf +service Greeter { + rpc SayHello (HelloRequest) returns (HelloReply); + rpc LotsOfReplies (HelloRequest) returns (stream HelloReply); + rpc LotsOfGreetings (stream HelloRequest) returns (HelloReply); + rpc Chat (stream HelloRequest) returns (stream HelloReply); +} +``` + +Streaming RPCs use `kotlinx.coroutines.flow.Flow`. + +| IDL shape | Server method | Client method | +| ----------------------------------------- | ----------------------------------------- | ----------------------------------------- | +| `rpc A (Req) returns (Res)` | `suspend fun a(request: Req): Res` | `suspend fun a(request: Req): Res` | +| `rpc A (Req) returns (stream Res)` | `fun a(request: Req): Flow` | `fun a(request: Req): Flow` | +| `rpc A (stream Req) returns (Res)` | `suspend fun a(requests: Flow): Res` | `suspend fun a(requests: Flow): Res` | +| `rpc A (stream Req) returns (stream Res)` | `fun a(requests: Flow): Flow` | `fun a(requests: Flow): Flow` | + +The generated method path keeps the exact service and method names from the +schema, for example `/demo.greeter.Greeter/SayHello`. + +Server implementations can return or consume `Flow` values directly: + +```kotlin +import demo.greeter.GreeterGrpcKt +import demo.greeter.HelloReply +import demo.greeter.HelloRequest +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.toList + +class GreeterService : GreeterGrpcKt.GreeterCoroutineImplBase() { + override fun lotsOfReplies(request: HelloRequest): Flow = flow { + emit(HelloReply(reply = "Hello, ${request.name}")) + emit(HelloReply(reply = "Welcome, ${request.name}")) + } + + override suspend fun lotsOfGreetings( + requests: Flow + ): HelloReply { + val names = requests.toList().joinToString(", ") { it.name } + return HelloReply(reply = names) + } + + override fun chat(requests: Flow): Flow = + requests.map { request -> + HelloReply(reply = "Hello, ${request.name}") + } +} +``` + +Generated clients expose the matching coroutine and Flow APIs: + +```kotlin +import demo.greeter.HelloRequest +import kotlinx.coroutines.flow.flowOf + +stub.lotsOfReplies(HelloRequest(name = "Fory")).collect { reply -> + println(reply.reply) +} + +val summary = stub.lotsOfGreetings( + flowOf( + HelloRequest(name = "Ada"), + HelloRequest(name = "Grace"), + ) +) +println(summary.reply) + +stub.chat( + flowOf( + HelloRequest(name = "Fory"), + HelloRequest(name = "RPC"), + ) +).collect { reply -> + println(reply.reply) +} +``` + +## gRPC Runtime Behavior + +The generated service code only replaces request and response serialization. +All normal gRPC operational features still belong to grpc-java and +grpc-kotlin: + +- Deadlines and cancellations +- TLS and authentication +- Name resolution and load balancing +- Client and server interceptors +- Status codes and metadata +- Channel pooling and lifecycle management + +## Interoperability + +Generated Kotlin service companions use Fory binary payloads inside gRPC +frames. They are interoperable with other Fory gRPC companions generated from +the same schema, such as Java, Go, Python, and Rust companions. Generic +protobuf gRPC clients cannot decode these payloads. + +Direct union request and response types are supported for Fory IDL services. +For protobuf input, use the protobuf service shapes accepted by the protobuf +frontend; protobuf `oneof` fields are translated into Fory union fields inside +messages. + +## Troubleshooting + +### Generated service file is missing + +Pass `--grpc` together with `--kotlin_out`. Schemas without service definitions +only generate model files and the schema module. + +### Serializer class not found at runtime + +Ensure KSP runs for the generated Kotlin model sources and that +`fory-kotlin-ksp` uses the same Fory version as `fory-kotlin`. + +### gRPC classes are unresolved + +Add grpc-java and grpc-kotlin dependencies to the application module. Fory +Kotlin artifacts do not add those dependencies automatically. + +### A protobuf client cannot read responses + +Fory gRPC uses Fory binary protocol payloads, not protobuf wire-format messages. +Use generated Fory gRPC companions on both sides for the same service schema. diff --git a/versioned_docs/version-1.3.0/guide/kotlin/index.md b/versioned_docs/version-1.3.0/guide/kotlin/index.md new file mode 100644 index 00000000000..8eff5555ec1 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/kotlin/index.md @@ -0,0 +1,135 @@ +--- +title: Kotlin Serialization Guide +sidebar_position: 0 +id: serialization_index +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. +--- + +Apache Fory™ Kotlin provides optimized serializers for Kotlin types, built on top of Fory Java. It supports xlang mode for cross-language payloads and native mode for Kotlin/JVM-only object serialization. Most standard Kotlin types work out of the box with the default Fory Java implementation, while Fory Kotlin adds additional support for Kotlin-specific types. + +Supported types include: + +- `data class` serialization +- Unsigned primitives: `UByte`, `UShort`, `UInt`, `ULong` +- Unsigned arrays: `UByteArray`, `UShortArray`, `UIntArray`, `ULongArray` +- Stdlib types: `Pair`, `Triple`, `Result` +- Ranges: `IntRange`, `LongRange`, `CharRange`, and progressions +- Collections: `ArrayDeque`, empty collections (`emptyList`, `emptyMap`, `emptySet`) +- `kotlin.time.Duration`, `kotlin.text.Regex`, `kotlin.uuid.Uuid` + +## Features + +Fory Kotlin inherits all features from Fory Java, plus Kotlin-specific optimizations: + +- **High Performance**: JIT code generation, zero-copy, 20-170x faster than traditional serialization +- **Kotlin Type Support**: Optimized serializers for data classes, unsigned types, ranges, and stdlib types +- **Default Value Support**: Automatic handling of Kotlin data class default parameters during schema evolution +- **Static Xlang Serializers**: KSP-generated schema serializers for Kotlin/JVM and Android xlang mode +- **Schema IDL Generation**: Fory compiler output for Kotlin models, sealed unions, and schema modules +- **Kotlin gRPC Support**: Coroutine service companions that use Fory payload serialization +- **Schema Evolution**: Forward/backward compatibility for class schema changes + +See [Java Features](../java/index.md#features) for complete feature list. + +## Installation + +### Maven + +```xml + + org.apache.fory + fory-kotlin + 1.3.0 + +``` + +### Gradle + +```kotlin +implementation("org.apache.fory:fory-kotlin:1.3.0") +``` + +### JDK25+ + +Kotlin uses the Fory Java core when running. On JDK25+, open `java.lang.invoke` +to Fory. Use `ALL-UNNAMED` when Fory is on the classpath: + +```bash +--add-opens=java.base/java.lang.invoke=ALL-UNNAMED +``` + +Use the Fory core module name when Fory is on the module path: + +```bash +--add-opens=java.base/java.lang.invoke=org.apache.fory.core +``` + +## Quick Start + +```kotlin +import org.apache.fory.ThreadSafeFory +import org.apache.fory.kotlin.ForyKotlin + +data class Person(val name: String, val id: Long, val github: String) +data class Point(val x: Int, val y: Int, val z: Int) + +fun main() { + // Create Fory instance (should be reused). Kotlin follows the Java default: + // xlang mode with compatible schema evolution. + val fory: ThreadSafeFory = ForyKotlin.builder() + .withXlang(true) + .requireClassRegistration(true) + .buildThreadSafeFory() + + fory.register(Person::class.java) + fory.register(Point::class.java) + + val p = Person("Shawn Yang", 1, "https://github.com/chaokunyang") + println(fory.deserialize(fory.serialize(p))) + println(fory.deserialize(fory.serialize(Point(1, 2, 3)))) +} +``` + +## Xlang Mode And Native Mode + +Use xlang mode for cross-language payloads and schemas shared with other Fory implementations. Xlang mode is the default Kotlin wire mode through the JVM builder, and Kotlin examples that use it set `.withXlang(true)` explicitly so the mode choice is visible. + +Use native mode for Kotlin/JVM-only traffic. Native mode is selected with `.withXlang(false)` and inherits the JVM native-mode object serialization path from Fory Java while adding Kotlin-specific serializers for data classes, unsigned values, ranges, stdlib types, and generated serializers. It is optimized for JVM and Kotlin type systems and is the right path for same-language Kotlin/JVM framework replacement payloads. Compatible mode is enabled by default. Set `.withCompatible(false)` only when every reader and writer uses the same Kotlin/JVM schema and you want faster serialization and smaller size. + +See [Configuration](configuration.md) for Kotlin builder setup and [Java Native Serialization](../java/native-serialization.md) for the full JVM native-mode behavior. + +## Built on Fory Java + +Fory Kotlin is built on top of Fory Java. Most configuration options, features, and concepts from Fory Java apply directly to Kotlin. Refer to the Java documentation for: + +- [Configuration](../java/configuration.md) - All ForyBuilder options +- [Basic Serialization](../java/basic-serialization.md) - Serialization patterns and APIs +- [Type Registration](../java/type-registration.md) - Class registration and security +- [Schema Evolution](../java/schema-evolution.md) - Forward/backward compatibility +- [Custom Serializers](../java/custom-serializers.md) - Implement custom serializers +- [Compression](../java/compression.md) - Int, long, and string compression +- [Troubleshooting](../java/troubleshooting.md) - Common issues and solutions + +## Kotlin-Specific Documentation + +- [Configuration](configuration.md) - Kotlin-specific Fory setup requirements +- [Type Serialization](type-serialization.md) - Serializing Kotlin types +- [Schema Metadata](schema-metadata.md) - Kotlin annotations, nullability, references, and integer metadata +- [Default Values](default-values.md) - Kotlin data class default values support +- [Static Generated Serializers](static-generated-serializers.md) - KSP xlang/schema serializer generation +- [Kotlin gRPC Support](grpc-support.md) - Coroutine stubs and service bases for Fory IDL services +- [Android Support](android-support.md) - Android setup, R8 behavior, and release-build validation diff --git a/versioned_docs/version-1.3.0/guide/kotlin/schema-metadata.md b/versioned_docs/version-1.3.0/guide/kotlin/schema-metadata.md new file mode 100644 index 00000000000..5c8ab0008dc --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/kotlin/schema-metadata.md @@ -0,0 +1,124 @@ +--- +title: Schema Metadata +sidebar_position: 3 +id: schema_metadata +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. +--- + +Kotlin schema metadata is used by the KSP-generated xlang serializers. Reuse the Java Fory +annotations for schema concepts, and use Kotlin type-use annotations only when you need Kotlin +specific integer encoding metadata. + +## Struct Fields + +Annotate Kotlin schema classes with `@ForyStruct` and constructor properties with +`@ForyField(id = N)`: + +```kotlin +import org.apache.fory.annotation.ForyField +import org.apache.fory.annotation.ForyStruct +import org.apache.fory.kotlin.Fixed +import org.apache.fory.kotlin.VarInt + +@ForyStruct +data class User( + @ForyField(id = 1) + val id: @Fixed UInt, + + @ForyField(id = 2) + val score: @VarInt Long, + + @ForyField(id = 3) + val tags: List, +) +``` + +Use `@ForyField(id = 1)` on constructor properties. `@field:ForyField(id = 1)` is also accepted +for field-backed properties. Do not use `@get:ForyField` or `@set:ForyField`; accessors are not +schema fields and the processor rejects them. + +## Nullability + +Use Kotlin `?` to describe nullable schema positions. Nullability is preserved inside collections +and maps: + +```kotlin +@ForyStruct +data class NullabilityExample( + @ForyField(id = 1) + val names: List, + + @ForyField(id = 2) + val optionalNames: List, + + @ForyField(id = 3) + val nullableList: List?, +) +``` + +Do not use Fory `@Nullable` in hand-written constructor-based Kotlin structs. The KSP processor +reads nullability from Kotlin source and rejects conflicting nullable annotations. + +## Reference Tracking + +Kotlin generated serializers preserve `@Ref` metadata for fields, list elements, and map values: + +```kotlin +import org.apache.fory.annotation.Ref + +@ForyStruct +data class Node( + @ForyField(id = 1) + val children: List<@Ref Node>, + + @ForyField(id = 2) + @Ref + val parent: Node?, +) +``` + +Global reference tracking still comes from Fory configuration. See +[Configuration](configuration.md). + +## Integer Encoding + +Kotlin type-use encoding annotations map to Fory xlang integer encodings: + +| Annotation | Valid Kotlin types | +| ---------- | ------------------------------ | +| `@Fixed` | `Int`, `Long`, `UInt`, `ULong` | +| `@VarInt` | `Int`, `Long`, `UInt`, `ULong` | +| `@Tagged` | `Long`, `ULong` | + +Without an annotation, xlang `Int`, `Long`, `UInt`, and `ULong` use varint encoding. + +## Collections And Dense Arrays + +Collection declarations carry schema shape, not JVM implementation identity. `List` is +encoded as `list` and `Map` is encoded as `map`. + +Dense primitive and unsigned array fields are supported, including `BooleanArray`, `ByteArray`, +`IntArray`, `LongArray`, `FloatArray`, `DoubleArray`, `UByteArray`, `UShortArray`, `UIntArray`, and +`ULongArray`. `ByteArray` is encoded as Fory `binary` unless the type use is annotated with Java +`@ArrayType`. + +## Related Topics + +- [Static Generated Serializers](static-generated-serializers.md) +- [Configuration](configuration.md) +- [Default Values](default-values.md) +- [Android Support](android-support.md) diff --git a/versioned_docs/version-1.3.0/guide/kotlin/static-generated-serializers.md b/versioned_docs/version-1.3.0/guide/kotlin/static-generated-serializers.md new file mode 100644 index 00000000000..08c3f27f8cd --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/kotlin/static-generated-serializers.md @@ -0,0 +1,319 @@ +--- +title: Static Generated Serializers +sidebar_position: 5 +id: static_generated_serializers +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. +--- + +Use `fory-kotlin-ksp` when Kotlin classes must participate in Fory +cross-language schema serialization. The processor generates Kotlin source +serializers at build time. Those serializers call the existing Fory Java +implementation, including `WriteContext`, `ReadContext`, and `MemoryBuffer`; there is +no Kotlin-only protocol. + +Static generated Kotlin serializers are for Kotlin/JVM and Android xlang/schema +mode. They are not Java native object serializers and do not preserve JVM object +graph implementation details such as the exact concrete collection class. + +## Add KSP + +Add `fory-kotlin` to the application classpath and run `fory-kotlin-ksp` as a KSP processor in +the module that compiles your `@ForyStruct` Kotlin classes. + +```kotlin +plugins { + id("com.google.devtools.ksp") version "" +} + +dependencies { + implementation("org.apache.fory:fory-kotlin:") + ksp("org.apache.fory:fory-kotlin-ksp:") +} +``` + +For Android, configure KSP in the Android module or library module that owns +the Kotlin model classes. + +## Define A Struct + +Reuse the Java Fory annotations for schema concepts. Use Kotlin type-use +annotations only when you need to override integer encoding. + +```kotlin +import org.apache.fory.annotation.ForyField +import org.apache.fory.annotation.ForyStruct +import org.apache.fory.kotlin.Fixed +import org.apache.fory.kotlin.VarInt + +@ForyStruct +data class User( + @ForyField(id = 1) + val id: @Fixed UInt, + + @ForyField(id = 2) + val score: @VarInt Long, + + @ForyField(id = 3) + val tags: List, +) +``` + +Use `@ForyField(id = 1)` on constructor properties. `@field:ForyField(id = 1)` +is also accepted for field-backed properties. Do not use `@get:ForyField` or +`@set:ForyField`; accessors are not schema fields and the processor rejects +them. + +## Supported Structs + +The processor generates serializers for public or internal, concrete, +non-generic classes in named packages. A supported class must have a primary +constructor whose serialized parameters are `val` or `var` properties. `data +class` is the common case, but it is not required. + +Internal Kotlin struct classes are supported when KSP runs in the same Kotlin +module that owns the struct. The generated Kotlin serializer is also internal, +so it can call the internal constructor and expose the internal type in +overrides while still producing a JVM class that Fory Java can load. +Application code outside that Kotlin module still cannot refer to the internal +struct directly, so registration must happen from code that can see the class. + +The processor rejects these declarations: + +- `private` struct classes. +- local, anonymous, or nested `@ForyStruct` classes. +- Kotlin `object` declarations. +- interfaces, abstract classes, and sealed classes as serializer targets. +- generic `@ForyStruct` classes. +- private constructor properties. +- private or protected primary constructors. + +Kotlin default constructor arguments are supported for compatible reads. A +struct can have up to 12 defaulted constructor fields. + +Constructor-based generated serializers support wide primary constructors. +Compatible reads track remote field presence in generated side state instead of +using constructor bit masks. + +## Nullability + +Use Kotlin `?` to describe nullable schema positions. Nullability is preserved +inside collections and maps. + +```kotlin +@ForyStruct +data class NullabilityExample( + @ForyField(id = 1) + val a: List, + + @ForyField(id = 2) + val b: List, + + @ForyField(id = 3) + val c: List?, + + @ForyField(id = 4) + val d: List?, +) +``` + +Do not use Fory `@Nullable` in hand-written constructor-based Kotlin structs. +The KSP processor rejects it so the schema is always read from Kotlin source +nullability. Compiler-generated Kotlin IDL sources follow the same rule and use +Kotlin `?` for nullable fields. + +## References + +Kotlin generated serializers preserve `@Ref` metadata for fields, list +elements, and map values. Constructor-owned reads construct Kotlin values +through primary constructors. Schema IDL classes that need reference +publication are emitted as mutable no-arg classes, and their KSP-generated +serializers publish the instance before reading fields. In both shapes, KSP owns +field descriptors, nested nullability, and `@Ref` metadata. + +## Collections + +Collection declarations carry schema shape, not JVM implementation identity. +For example, `List` is encoded as `list` and +`Map` is encoded as `map`. + +Deserialization only guarantees that the result is assignable to the declared +field type. Fory does not preserve whether the original concrete value was an +`ArrayList`, `LinkedList`, `Collections.unmodifiableList`, synchronized +collection wrapper, or another JVM-specific collection implementation. + +Supported collection declarations include Kotlin and Java list, set, and map +types. Mutable collection interface fields are deserialized to mutable +implementations assignable to the declared type. Sorted collections without an +explicit comparator, such as `TreeSet` and `ConcurrentSkipListSet`, are accepted +only for non-null scalar or string elements. Concurrent map declarations are +accepted only with non-null values because JVM concurrent map implementations +reject null entries. + +`Set<*>`, `Map<*, T>`, `Map<*, *>`, and raw Java collections are rejected. +`List<*>` and `Map` are accepted and use dynamic nullable values. + +## Dense Arrays + +Kotlin dense primitive and unsigned array fields are supported: + +- `BooleanArray` +- `ByteArray` +- `ShortArray` +- `IntArray` +- `LongArray` +- `FloatArray` +- `DoubleArray` +- `UByteArray` +- `UShortArray` +- `UIntArray` +- `ULongArray` + +Dense arrays with unambiguous Kotlin carriers are supported in fields, +collection elements, map values, and union cases. `array` and +`array` use the Java core `Float16Array` and `BFloat16Array` carriers. + +`ByteArray` is encoded as Fory `binary` unless the `ByteArray` type use is +annotated with Java `@ArrayType`. Generated Kotlin IDL uses +`@ArrayType ByteArray` for `array`, including nested collection and map +positions. + +`@ArrayType` is also supported on top-level `List` fields when `T` is a +non-null boolean or numeric dense-array element type. In that case the field is +encoded as dense `array` schema, and generated reads convert decoded JVM list +elements back to the declared Kotlin element carrier. + +## Integer Encoding + +Kotlin type-use encoding annotations map to Fory xlang integer encodings: + +| Annotation | Valid Kotlin types | +| ---------- | ------------------------------ | +| `@Fixed` | `Int`, `Long`, `UInt`, `ULong` | +| `@VarInt` | `Int`, `Long`, `UInt`, `ULong` | +| `@Tagged` | `Long`, `ULong` | + +Without an annotation, xlang `Int`, `Long`, `UInt`, and `ULong` use varint +encoding. This is required by xlang mode and is not controlled by Java native +mode numeric compression options. + +## Duration + +Xlang `duration` maps to `kotlin.time.Duration`. Infinite Kotlin durations +cannot be represented by the xlang duration payload and fail during +serialization. + +## Sealed Unions + +KSP generates serializers for top-level sealed classes annotated with +`@ForyUnion`. Each schema case is a nested class annotated with `@ForyCase` and +one constructor property named `value`. `Unknown(UnknownCase)` is marked with +`@ForyUnknownCase` as the Fory-owned forward-compatibility carrier. It is +omitted from the schema case table because the marker only selects the carrier +and does not add a schema entry. A typed union must declare at least one +non-`Unknown` case: + +```kotlin +package example + +import org.apache.fory.annotation.ForyCase +import org.apache.fory.annotation.ForyUnion +import org.apache.fory.annotation.ForyUnknownCase +import org.apache.fory.type.union.UnknownCase + +@ForyUnion +sealed class Animal { + @ForyUnknownCase + data class Unknown(val value: UnknownCase) : Animal() + + @ForyCase(id = 0) + data class Dog(val value: example.Dog) : Animal() +} +``` + +When a generated Kotlin union case name matches the payload type simple name, +packaged output keeps the case name and qualifies the payload type. If a target +output mode cannot express a legal qualifier for a conflict, the IDL compiler +appends `Case` to the generated case class name. + +Generated schema modules register sealed unions through `KotlinSerializers.registerUnion`. +Fory discovers the generated `_ForySerializer` automatically, so +callers do not pass a serializer instance. + +## Register Classes + +Register Kotlin struct classes with the Kotlin `register` extension. You +choose the xlang namespace and type name; generated serializers do not choose +IDs or names for you. + +```kotlin +import org.apache.fory.kotlin.ForyKotlin +import org.apache.fory.kotlin.register + +val fory = ForyKotlin.builder() + .withXlang(true) + .requireClassRegistration(true) + .build() + +fory.register("example.User") +``` + +`ForyKotlin.builder()` installs the Kotlin serializer bootstrap for the Fory +instance. The `fory.register(...)` extension registers your xlang schema type +name and resolves the generated serializer from the target class. + +Do not register or reference generated serializer classes in application code. +Fory resolves them from the registered target class. + +Generated Schema IDL modules use the same path. They call +`KotlinSerializers.registerType`, `registerSerializer`, `registerEnum`, and +`registerUnion` as appropriate and never emit Java files. + +## Generated Names + +The generated serializer is emitted in the same package as the target class. +Its name is `_ForySerializer`. For nested binary names, `$` is encoded +as `_`; source underscores are encoded as `_u_`. + +These names are an implementation detail. They matter for diagnostics and +Android shrinking, but user code should only register target classes. + +If a constructor-owned Kotlin xlang struct is registered but its KSP generated +serializer is missing, Fory fails with a configuration error. Compile generated +IDL sources with KSP before registering generated Kotlin classes. + +## Android And R8 + +Android apps should not need user-written keep rules for generated Kotlin +serializers. KSP emits generated consumer R8/ProGuard rules under +`META-INF/proguard/` for the generated serializer constructors used by Fory and +the Kotlin metadata needed to detect required Kotlin generated serializers. + +For library modules, package the generated `META-INF/proguard/` resources into +the produced artifact. For Android application modules, make sure your KSP +setup includes generated resources in the minified variant. + +See [Android Support](android-support.md) for Android Gradle setup and +release-minified validation guidance. + +## Native Object Mode + +Kotlin KSP generated serializers are only for xlang/schema mode. They do not +replace Fory Java native object serializers and do not preserve JVM object graph +identity. If you use Fory with `withXlang(false)`, Fory uses the normal Java and +Kotlin serializers instead. + +Kotlin/Native and Kotlin/JS are not supported by this module. diff --git a/versioned_docs/version-1.3.0/guide/kotlin/type-serialization.md b/versioned_docs/version-1.3.0/guide/kotlin/type-serialization.md new file mode 100644 index 00000000000..cb8471eee4b --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/kotlin/type-serialization.md @@ -0,0 +1,192 @@ +--- +title: Type Serialization +sidebar_position: 2 +id: type_serialization +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 page covers serialization of Kotlin-specific JVM types in native mode. For +cross-language Kotlin models, use the xlang path described in +[Static Generated Serializers](static-generated-serializers.md). + +When compatible mode is enabled, Kotlin readers use the JVM compatible-read rules for selected +scalar field type changes. A matched field can read between `Boolean`, `String`, numeric scalars, +and `java.math.BigDecimal` when the converted value has the same logical value. For example, +`"true"` and `"false"` can be read as booleans, `"123"` can be read as a numeric field that can hold +`123`, numbers and decimals can be read as canonical strings, and numeric widening or narrowing +succeeds only when no precision or range is lost. Numeric strings use finite ASCII decimal syntax. +Invalid strings and lossy conversions fail during deserialization. Nullable and boxed fields still +compose with these conversions, but reference-tracked scalar type changes are incompatible. + +## Setup + +All examples assume the following setup: + +```kotlin +import org.apache.fory.kotlin.ForyKotlin + +val fory = ForyKotlin.builder().withXlang(false) + .requireClassRegistration(false) + .build() +``` + +## Data Class + +```kotlin +data class Person(val name: String, val age: Int, val id: Long) + +fory.register(Person::class.java) + +val p = Person("John", 30, 1L) +println(fory.deserialize(fory.serialize(p))) +``` + +## Unsigned Primitives + +Kotlin unsigned types are fully supported: + +```kotlin +val uByte: UByte = 255u +val uShort: UShort = 65535u +val uInt: UInt = 4294967295u +val uLong: ULong = 18446744073709551615u + +println(fory.deserialize(fory.serialize(uByte))) +println(fory.deserialize(fory.serialize(uShort))) +println(fory.deserialize(fory.serialize(uInt))) +println(fory.deserialize(fory.serialize(uLong))) +``` + +## Unsigned Arrays + +```kotlin +val uByteArray = ubyteArrayOf(1u, 2u, 255u) +val uShortArray = ushortArrayOf(1u, 2u, 65535u) +val uIntArray = uintArrayOf(1u, 2u, 4294967295u) +val uLongArray = ulongArrayOf(1u, 2u, 18446744073709551615u) + +println(fory.deserialize(fory.serialize(uByteArray)).contentToString()) +println(fory.deserialize(fory.serialize(uShortArray)).contentToString()) +println(fory.deserialize(fory.serialize(uIntArray)).contentToString()) +println(fory.deserialize(fory.serialize(uLongArray)).contentToString()) +``` + +## Stdlib Types + +### Pair and Triple + +```kotlin +val pair = Pair("key", 42) +val triple = Triple("a", "b", "c") + +println(fory.deserialize(fory.serialize(pair))) +println(fory.deserialize(fory.serialize(triple))) +``` + +### Result + +```kotlin +val success: Result = Result.success(42) +val failure: Result = Result.failure(Exception("error")) + +println(fory.deserialize(fory.serialize(success))) +println(fory.deserialize(fory.serialize(failure))) +``` + +## Ranges and Progressions + +```kotlin +val intRange = 1..10 +val longRange = 1L..100L +val charRange = 'a'..'z' + +println(fory.deserialize(fory.serialize(intRange))) +println(fory.deserialize(fory.serialize(longRange))) +println(fory.deserialize(fory.serialize(charRange))) + +// Progressions +val intProgression = 1..10 step 2 +val longProgression = 1L..100L step 10 + +println(fory.deserialize(fory.serialize(intProgression))) +println(fory.deserialize(fory.serialize(longProgression))) +``` + +## Collections + +### ArrayDeque + +```kotlin +val deque = ArrayDeque() +deque.addFirst("first") +deque.addLast("last") + +println(fory.deserialize(fory.serialize(deque))) +``` + +### Empty Collections + +```kotlin +val emptyList = emptyList() +val emptySet = emptySet() +val emptyMap = emptyMap() + +println(fory.deserialize(fory.serialize(emptyList))) +println(fory.deserialize(fory.serialize(emptySet))) +println(fory.deserialize(fory.serialize(emptyMap))) +``` + +## Duration + +```kotlin +import kotlin.time.Duration +import kotlin.time.Duration.Companion.hours +import kotlin.time.Duration.Companion.minutes + +val duration: Duration = 2.hours + 30.minutes + +println(fory.deserialize(fory.serialize(duration))) +``` + +## Regex + +```kotlin +val regex = Regex("[a-zA-Z]+") + +println(fory.deserialize(fory.serialize(regex))) +``` + +## UUID (Kotlin 2.0+) + +```kotlin +import kotlin.uuid.Uuid + +val uuid = Uuid.random() + +println(fory.deserialize(fory.serialize(uuid))) +``` + +## Types Working Out of the Box + +The following types work with the default Fory Java implementation: + +- **Primitives**: `Byte`, `Boolean`, `Int`, `Short`, `Long`, `Char`, `Float`, `Double` +- **String**: `String` +- **Collections**: `ArrayList`, `HashMap`, `HashSet`, `LinkedHashSet`, `LinkedHashMap` +- **Arrays**: `Array`, `BooleanArray`, `ByteArray`, `CharArray`, `DoubleArray`, `FloatArray`, `IntArray`, `LongArray`, `ShortArray` + +Use `ForyKotlin.builder()` for Kotlin-specific types such as unsigned values, ranges, and `Duration`. diff --git a/versioned_docs/version-1.3.0/guide/python/_category_.json b/versioned_docs/version-1.3.0/guide/python/_category_.json new file mode 100644 index 00000000000..b37a319075d --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/python/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "Python", + "position": 2, + "collapsible": true, + "collapsed": true +} diff --git a/versioned_docs/version-1.3.0/guide/python/basic-serialization.md b/versioned_docs/version-1.3.0/guide/python/basic-serialization.md new file mode 100644 index 00000000000..eaa997b25e9 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/python/basic-serialization.md @@ -0,0 +1,112 @@ +--- +title: Basic Serialization +sidebar_position: 1 +id: basic_serialization +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 page covers the Python xlang quickstart. `pyfory.Fory()` defaults to xlang mode with +compatible schema evolution; examples set `xlang=True` explicitly so the mode choice is visible. + +## Basic Object Serialization + +Serialize and deserialize Python objects with a simple API: + +```python +import pyfory + +fory = pyfory.Fory(xlang=True) + +# Serialize xlang-compatible values +data = fory.dumps({"name": "Alice", "age": 30, "scores": [95, 87, 92]}) + +# Deserialize back to Python object +obj = fory.loads(data) +print(obj) # {'name': 'Alice', 'age': 30, 'scores': [95, 87, 92]} +``` + +**Note**: `dumps()`/`loads()` are aliases for `serialize()`/`deserialize()`. Both APIs are identical, use whichever feels more intuitive. + +## Custom Class Serialization + +Use dataclasses and type annotations for stable xlang payloads: + +```python +import pyfory +from dataclasses import dataclass +from typing import List, Dict + +@dataclass +class Person: + name: str + age: pyfory.Int32 + scores: List[pyfory.Int32] + metadata: Dict[str, str] + +fory = pyfory.Fory(xlang=True, ref=True) +fory.register(Person, name="example.Person") +person = Person("Bob", 25, [88, 92, 85], {"team": "engineering"}) +data = fory.serialize(person) +result = fory.deserialize(data) +print(result) # Person(name='Bob', age=25, ...) +``` + +## Reference Tracking & Circular References + +Handle repeated references safely when the payload uses xlang-compatible types: + +```python +import pyfory + +f = pyfory.Fory(xlang=True, ref=True) + +shared = ["shared"] +value = [shared, shared] + +data = f.serialize(value) +result = f.deserialize(data) +assert result[0] is result[1] +``` + +For arbitrary Python object graphs, local classes, functions, and methods, use +[Native Serialization](native-serialization.md). + +## Performance Tips + +1. **Disable `ref=True` if not needed**: Reference tracking has overhead +2. **Use type_id instead of name**: Integer IDs are faster than string names +3. **Reuse Fory instances**: Create once, use many times +4. **Enable Cython**: Make sure `ENABLE_FORY_CYTHON_SERIALIZATION=1` + +```python +# Good: Reuse instance +fory = pyfory.Fory(xlang=True) +for obj in objects: + data = fory.dumps(obj) + +# Bad: Create new instance each time +for obj in objects: + fory = pyfory.Fory(xlang=True) # Wasteful! + data = fory.dumps(obj) +``` + +## Related Topics + +- [Configuration](configuration.md) - Fory parameters +- [Type Registration](type-registration.md) - Registration patterns +- [Native Serialization](native-serialization.md) - Functions and lambdas +- [Out-of-Band Serialization](out-of-band.md) - Buffer callback APIs diff --git a/versioned_docs/version-1.3.0/guide/python/configuration.md b/versioned_docs/version-1.3.0/guide/python/configuration.md new file mode 100644 index 00000000000..fdd6459fea2 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/python/configuration.md @@ -0,0 +1,287 @@ +--- +title: Configuration +sidebar_position: 4 +id: configuration +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 page covers Python Fory instance configuration. `pyfory.Fory()` defaults to xlang mode with +compatible schema evolution. Native mode is selected explicitly with `xlang=False` and also defaults +to compatible schema evolution. + +## Fory Class + +The main serialization interface: + +```python +class Fory: + def __init__( + self, + xlang: bool = True, + ref: bool = False, + strict: bool = True, + compatible: Optional[bool] = None, + max_depth: int = 50, + max_type_fields: int = 512, + max_type_meta_bytes: int = 4096, + max_schema_versions_per_type: int = 10, + max_average_schema_versions_per_type: int = 3, + policy: DeserializationPolicy = None, + field_nullable: bool = False, + meta_compressor=None, + ) +``` + +## ThreadSafeFory Class + +Thread-safe serialization interface using a pooled wrapper: + +```python +class ThreadSafeFory: + def __init__( + self, fory_factory=None, **kwargs + ) +``` + +## Parameters + +| Parameter | Type | Default | Description | +| -------------------------------------- | ------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `xlang` | `bool` | `True` | Use xlang mode. Set `False` for Python native mode. | +| `ref` | `bool` | `False` | Enable reference tracking for shared/circular references. Disable for better performance if your data has no shared references. | +| `strict` | `bool` | `True` | Require type registration for security. Keep this enabled for production unless a policy owns trust decisions. | +| `compatible` | `bool \| None` | `None` | Schema evolution mode. `None` enables compatible mode in both xlang and native mode. Set `False` only when every reader and writer uses the same schema. | +| `max_depth` | `int` | `50` | Maximum deserialization depth for security, preventing stack overflow attacks. | +| `max_type_fields` | `int` | `512` | Maximum fields accepted in one received remote struct metadata body. | +| `max_type_meta_bytes` | `int` | `4096` | Maximum encoded body bytes accepted for one received TypeDef body, excluding the 8-byte header and any extended-size varint. | +| `max_schema_versions_per_type` | `int` | `10` | Maximum accepted remote metadata versions for one logical type. | +| `max_average_schema_versions_per_type` | `int` | `3` | Average accepted remote metadata versions across accepted remote types. The effective global floor is `8192` schemas. | +| `policy` | `DeserializationPolicy \| None` | `None` | Deserialization policy used for security checks. Strongly recommended when `strict=False`. | +| `field_nullable` | `bool` | `False` | Treat dataclass fields as nullable by default. | +| `meta_compressor` | `Any` | `None` | Optional metadata compressor used for compatible-mode metadata encoding. | +| `fory_factory` | `Callable \| None` | `None` | `ThreadSafeFory` factory hook. When set, `ThreadSafeFory` creates instances via this callback; otherwise it forwards `**kwargs` to `Fory` construction. | + +## Key Methods + +```python +# Serialization (serialize/deserialize are identical to dumps/loads) +data: bytes = fory.serialize(obj) +obj = fory.deserialize(data) + +# Alternative API (aliases) +data: bytes = fory.dumps(obj) +obj = fory.loads(data) + +# Type registration by id +fory.register(MyClass, type_id=123) +fory.register(MyClass, type_id=123, serializer=custom_serializer) + +# Type registration by name +fory.register(MyClass, name="my.package.MyClass") +fory.register(MyClass, name="my.package.MyClass", serializer=custom_serializer) +``` + +## Xlang And Native Mode Comparison + +| Feature | Native mode (`xlang=False`) | Xlang mode (default) | +| ------------------- | ---------------------------------------------- | -------------------------------------------------------------------------------- | +| Use case | Python-only applications | Multi-language systems | +| Compatibility | Python only | Java, C++, Go, Rust, JavaScript/TypeScript, C#, Swift, Dart, Scala, Kotlin, etc. | +| Supported types | Python object surface | Cross-language compatible types | +| Functions/lambdas | Supported with trusted dynamic deserialization | Not allowed | +| Local classes | Supported with trusted dynamic deserialization | Not allowed | +| Dynamic classes | Supported with trusted dynamic deserialization | Not allowed | +| Schema mode default | Compatible | Compatible | + +## Xlang Mode + +Xlang mode is the default and restricts payloads to types compatible across Fory implementations: + +```python +import pyfory + +fory = pyfory.Fory(xlang=True, ref=True) +fory.register(MyDataClass, name="com.example.MyDataClass") +data = fory.serialize(MyDataClass(field1="value", field2=42)) +``` + +Use `compatible=False` for xlang payloads only when every reader and writer always uses the same schema and you want faster serialization and smaller size. Use it only after verifying that every language uses that schema, or when native types are generated from Fory schema IDL. + +## Native Mode + +```python +import pyfory + +fory = pyfory.Fory(xlang=False, ref=True, strict=False) +``` + +Native mode supports Python-specific object features such as functions, local classes, methods, +`__reduce__`, and `__getstate__`. Compatible mode is still enabled by default. Set +`compatible=False` only when every reader and writer always uses the same Python +class schema and you want faster serialization and smaller size. + +## Compatible Mode + +Compatible mode is enabled by default for both xlang and native mode. Keep this default when Python +classes may evolve independently, when services deploy separately, or when xlang schemas are written +by hand in different languages. + +For xlang payloads, set `compatible=False` only after verifying that every language uses the same schema, or when native types are generated from Fory schema IDL. + +## Example Configurations + +### Xlang Service + +```python +import pyfory + +fory = pyfory.Fory( + xlang=True, + ref=False, + strict=True, + max_depth=20, +) + +fory.register(UserModel, name="example.User") +``` + +### Native Mode With Dynamic Types + +```python +import pyfory + +fory = pyfory.Fory( + xlang=False, + ref=True, + strict=False, + max_depth=1000, +) +``` + +Use `strict=False` only for trusted data, preferably with a `policy=` deserialization policy. + +## Security + +Treat native-mode bytes from untrusted sources the same way you would treat untrusted pickle bytes. +Native mode can reconstruct Python objects, import modules, invoke reduction hooks, and rebuild +dynamic classes or functions when `strict=False`. + +### Production Configuration + +Keep `strict=True` for production payloads unless the whole data source is trusted and a +`DeserializationPolicy` owns the remaining trust decisions: + +```python +import pyfory + +fory = pyfory.Fory( + xlang=True, + ref=False, + strict=True, + max_depth=50, + max_type_fields=512, + max_type_meta_bytes=4096, + max_schema_versions_per_type=10, + max_average_schema_versions_per_type=3, +) + +fory.register(UserModel, name="example.User") +fory.register(OrderModel, name="example.Order") +``` + +Use dynamic native-mode deserialization (`strict=False`) only for trusted Python-only payloads: + +```python +import pyfory + +fory = pyfory.Fory( + xlang=False, + ref=True, + strict=False, + max_depth=100, +) +``` + +Received remote metadata is also limited: + +- `max_type_fields` limits the number of fields accepted in one received struct metadata body. +- `max_type_meta_bytes` limits the encoded body bytes accepted for one received TypeDef body. +- `max_schema_versions_per_type` limits accepted remote metadata versions for one logical type. +- `max_average_schema_versions_per_type` limits the average across accepted remote types. + +These limits do not change `strict`, `policy`, dynamic loading, unknown-class handling, or +schema-evolution semantics. + +### DeserializationPolicy + +When `strict=False` is necessary, use `DeserializationPolicy` to restrict the dynamic types and +hooks accepted during deserialization: + +```python +import pyfory +from pyfory import DeserializationPolicy + +dangerous_modules = {"subprocess", "os", "__builtin__"} + +class SafeDeserializationPolicy(DeserializationPolicy): + def validate_class(self, cls, is_local, **kwargs): + if cls.__module__ in dangerous_modules: + raise ValueError(f"Blocked dangerous class: {cls.__module__}.{cls.__name__}") + + def intercept_reduce_call(self, callable_obj, args, **kwargs): + if getattr(callable_obj, "__name__", "") == "Popen": + raise ValueError("Blocked attempt to invoke subprocess.Popen") + return None + + def intercept_setstate(self, obj, state, **kwargs): + if isinstance(state, dict) and "password" in state: + state["password"] = "***REDACTED***" + return None + +policy = SafeDeserializationPolicy() +fory = pyfory.Fory(xlang=False, ref=True, strict=False, policy=policy) +``` + +Available policy hooks include: + +Reference validation hooks reject by raising exceptions and otherwise leave deserialized references +unchanged. + +| Hook | Description | +| -------------------------------------------- | --------------------------------------------------- | +| `validate_class(cls, is_local)` | Validate or block class types | +| `validate_module(module_name, is_local)` | Validate or block module imports | +| `validate_function(func, is_local)` | Validate or block function references | +| `validate_method(method, is_local)` | Validate or block method references | +| `intercept_reduce_call(callable_obj, args)` | Intercept `__reduce__` invocations | +| `inspect_reduced_object(obj)` | Inspect or replace objects created via `__reduce__` | +| `intercept_setstate(obj, state)` | Sanitize state before `__setstate__` | +| `authorize_instantiation(cls, args, kwargs)` | Control class instantiation | + +### Security Checklist + +- Keep `strict=True` for untrusted data. +- Register all expected application types before deserialization. +- Use `DeserializationPolicy` when `strict=False` is necessary. +- Keep `max_depth` low enough to reject unexpectedly deep payloads. +- Do not treat xlang/native mode choice as a security control. + +## Related Topics + +- [Basic Serialization](basic-serialization.md) - Using configured Fory +- [Type Registration](type-registration.md) - Registration patterns +- [Native Serialization](native-serialization.md) - Python-only object serialization diff --git a/versioned_docs/version-1.3.0/guide/python/custom-serializers.md b/versioned_docs/version-1.3.0/guide/python/custom-serializers.md new file mode 100644 index 00000000000..a3499a74bca --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/python/custom-serializers.md @@ -0,0 +1,138 @@ +--- +title: Custom Serializers +sidebar_position: 10 +id: custom_serializers +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. +--- + +Implement custom serialization logic for specialized types. + +## Implementing Custom Serializers + +Implement `write/read` once for both Python native and xlang modes: + +```python +import pyfory +from pyfory.serializer import Serializer +from dataclasses import dataclass + +@dataclass +class Foo: + f1: int + f2: str + +class FooSerializer(Serializer): + def __init__(self, type_resolver, cls): + super().__init__(type_resolver, cls) + + def write(self, write_context, obj: Foo): + # Custom serialization logic + write_context.write_varint32(obj.f1) + write_context.write_string(obj.f2) + + def read(self, read_context): + # Custom deserialization logic + f1 = read_context.read_varint32() + f2 = read_context.read_string() + return Foo(f1, f2) + +f = pyfory.Fory(xlang=False) +f.register(Foo, type_id=100, serializer=FooSerializer(f.type_resolver, Foo)) + +# Now Foo uses your custom serializer +data = f.dumps(Foo(42, "hello")) +result = f.loads(data) +print(result) # Foo(f1=42, f2='hello') +``` + +## Buffer API + +### Write Methods + +```python +# Integers +buffer.write_int8(value) +buffer.write_int16(value) +buffer.write_int32(value) +buffer.write_int64(value) + +# Variable-length integers +buffer.write_varint32(value) +buffer.write_varint64(value) + +# Floating point +buffer.write_float32(value) +buffer.write_float64(value) + +# Strings and bytes +buffer.write_string(value) +buffer.write_bytes(value) + +# Boolean +buffer.write_bool(value) +``` + +### Read Methods + +```python +# Integers +value = buffer.read_int8() +value = buffer.read_int16() +value = buffer.read_int32() +value = buffer.read_int64() + +# Variable-length integers +value = buffer.read_varint32() +value = buffer.read_varint64() + +# Floating point +value = buffer.read_float32() +value = buffer.read_float64() + +# Strings and bytes +value = buffer.read_string() +value = buffer.read_bytes(length) + +# Boolean +value = buffer.read_bool() +``` + +## When to Use Custom Serializers + +- External types from other packages +- Types with special serialization requirements +- Existing data format compatibility +- Performance-critical custom encoding +- Types that don't work well with automatic serialization + +## Registering Custom Serializers + +```python +fory = pyfory.Fory(xlang=False) + +# Register with type_id +fory.register(MyClass, type_id=100, serializer=MySerializer(fory.type_resolver, MyClass)) + +# Register with name (for xlang) +fory.register(MyClass, name="com.example.MyClass", serializer=MySerializer(fory.type_resolver, MyClass)) +``` + +## Related Topics + +- [Type Registration](type-registration.md) - Registration patterns +- [Configuration](configuration.md) - Fory parameters +- [Xlang Serialization](xlang-serialization.md) - type registration and schema rules for xlang diff --git a/versioned_docs/version-1.3.0/guide/python/grpc-support.md b/versioned_docs/version-1.3.0/guide/python/grpc-support.md new file mode 100644 index 00000000000..0c5921cc67d --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/python/grpc-support.md @@ -0,0 +1,320 @@ +--- +title: gRPC Support +sidebar_position: 13 +id: grpc_support +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. +--- + +Fory can generate Python gRPC service companions for schemas that define +services. The generated modules use `grpcio` for transport and use Fory to +serialize request and response objects. + +Use this mode when every RPC peer is generated from the same Fory IDL, protobuf +IDL, or FlatBuffers IDL and you want gRPC transport semantics with Fory payload +encoding. Use standard protobuf gRPC code generation when clients or tools must +consume protobuf message bytes directly. + +Python gRPC generation defaults to the `grpc.aio` AsyncIO API. Generated +servicer bases use `async def` methods, generated stubs are used with +`grpc.aio.Channel` instances, and streaming RPCs use async iterables. Synchronous +`grpcio` companions are still available with `--grpc-python-mode=sync`. + +## Install Dependencies + +Install `grpcio` alongside `pyfory`. The generated companion imports `grpc` and, +in the default mode, `grpc.aio`, but `pyfory` does not add gRPC as a hard +dependency. + +```bash +pip install pyfory grpcio +``` + +## Define a Service + +Service definitions can come from Fory IDL, protobuf IDL, or FlatBuffers +`rpc_service` definitions. A Fory IDL service looks like this: + +```protobuf +package demo.greeter; + +message HelloRequest { + string name = 1; +} + +message HelloReply { + string reply = 1; +} + +service Greeter { + rpc SayHello (HelloRequest) returns (HelloReply); +} +``` + +Generate Python model and gRPC companion code with `--grpc`: + +```bash +foryc service.fdl --python_out=./generated/python --grpc +``` + +For this schema, the Python generator emits: + +| File | Purpose | +| ---------------------- | --------------------------------------------- | +| `demo_greeter.py` | Fory dataclasses and registration helpers | +| `demo_greeter_grpc.py` | `grpc.aio` stub, servicer base, and registrar | + +The module name is derived from the Fory package by replacing dots with +underscores. A schema with no package uses `generated.py` and +`generated_grpc.py`. + +## Implement an Async Server + +Subclass the generated servicer and register it with a `grpc.aio` server. +Generated Python method names use snake_case, while the gRPC wire path keeps the +original IDL method name. + +```python +import asyncio + +import grpc.aio + +import demo_greeter +import demo_greeter_grpc + + +class Greeter(demo_greeter_grpc.GreeterServicer): + async def say_hello(self, request, context): + return demo_greeter.HelloReply(reply=f"Hello, {request.name}") + + +async def serve(): + server = grpc.aio.server() + demo_greeter_grpc.add_servicer(Greeter(), server) + server.add_insecure_port("[::]:50051") + await server.start() + await server.wait_for_termination() + + +if __name__ == "__main__": + asyncio.run(serve()) +``` + +Generated request and response types are serialized by the generated companion, +so service implementations do not perform manual Fory registration. + +## Create an Async Client + +Use the generated stub with a `grpc.aio` channel. Production clients usually +pass a TLS/auth-configured channel: + +```python +import asyncio + +import grpc +import grpc.aio + +import demo_greeter +import demo_greeter_grpc + + +async def main(): + credentials = grpc.ssl_channel_credentials() + async with grpc.aio.secure_channel("api.example.com:443", credentials) as channel: + stub = demo_greeter_grpc.GreeterStub(channel) + reply = await stub.say_hello(demo_greeter.HelloRequest(name="Fory")) + print(reply.reply) + + +if __name__ == "__main__": + asyncio.run(main()) +``` + +For local tests and development, an insecure channel can be used explicitly: + +```python +# Test-only channel. Use a TLS/auth-configured grpc.aio.Channel in production. +async with grpc.aio.insecure_channel("localhost:50051") as channel: + stub = demo_greeter_grpc.GreeterStub(channel) +``` + +`grpcio` still owns channel options, credentials, deadlines, metadata, retries, +and interceptors. + +## Streaming RPCs + +Fory service definitions can use unary, server-streaming, client-streaming, and +bidirectional streaming RPC shapes: + +```protobuf +service Greeter { + rpc SayHello (HelloRequest) returns (HelloReply); + rpc LotsOfReplies (HelloRequest) returns (stream HelloReply); + rpc LotsOfGreetings (stream HelloRequest) returns (HelloReply); + rpc Chat (stream HelloRequest) returns (stream HelloReply); +} +``` + +Default Python gRPC output follows `grpc.aio` conventions: + +| IDL shape | Servicer method shape | Stub method shape | +| ----------------------------------------- | ----------------------------------------------- | -------------------------------------- | +| `rpc A (Req) returns (Res)` | `async def` returns one response object | awaitable returns one response object | +| `rpc A (Req) returns (stream Res)` | `async def` yields response objects | returns an async iterator of responses | +| `rpc A (stream Req) returns (Res)` | consumes an async iterator and returns response | accepts an async iterator of requests | +| `rpc A (stream Req) returns (stream Res)` | consumes and yields async iterators | accepts and returns async iterators | + +Servicer methods use snake_case names, while generated descriptors preserve the +exact IDL service and method names for the gRPC path. + +Server implementations use async methods and async iteration: + +```python +class Greeter(demo_greeter_grpc.GreeterServicer): + async def lots_of_replies(self, request, context): + yield demo_greeter.HelloReply(reply=f"Hello, {request.name}") + yield demo_greeter.HelloReply(reply=f"Welcome, {request.name}") + + async def lots_of_greetings(self, request_iterator, context): + names = [] + async for request in request_iterator: + names.append(request.name) + return demo_greeter.HelloReply(reply=", ".join(names)) + + async def chat(self, request_iterator, context): + async for request in request_iterator: + yield demo_greeter.HelloReply(reply=f"Hello, {request.name}") +``` + +Generated clients use `grpc.aio` streaming call shapes: + +```python +credentials = grpc.ssl_channel_credentials() +async with grpc.aio.secure_channel("api.example.com:443", credentials) as channel: + stub = demo_greeter_grpc.GreeterStub(channel) + + async for reply in stub.lots_of_replies( + demo_greeter.HelloRequest(name="Fory") + ): + print(reply.reply) + + async def greeting_requests(): + yield demo_greeter.HelloRequest(name="Ada") + yield demo_greeter.HelloRequest(name="Grace") + + summary = await stub.lots_of_greetings(greeting_requests()) + print(summary.reply) + + async def chat_requests(): + yield demo_greeter.HelloRequest(name="Fory") + yield demo_greeter.HelloRequest(name="RPC") + + async for reply in stub.chat(chat_requests()): + print(reply.reply) +``` + +## Sync Mode + +Use sync mode for existing synchronous `grpcio` applications or environments +that do not run an asyncio event loop. Generate sync companions explicitly: + +```bash +foryc service.fdl --python_out=./generated/python --grpc --grpc-python-mode=sync +``` + +Sync mode emits the same `_grpc.py` filename and public names, but the +servicer methods use regular `def`, and applications use `grpc.server(...)` and +standard `grpc.Channel` instances. + +Unary sync server example: + +```python +from concurrent import futures + +import grpc + +import demo_greeter +import demo_greeter_grpc + + +class Greeter(demo_greeter_grpc.GreeterServicer): + def say_hello(self, request, context): + return demo_greeter.HelloReply(reply=f"Hello, {request.name}") + + +server = grpc.server(futures.ThreadPoolExecutor(max_workers=8)) +demo_greeter_grpc.add_servicer(Greeter(), server) +server.add_insecure_port("[::]:50051") +server.start() +server.wait_for_termination() +``` + +Unary sync client example: + +```python +import grpc + +import demo_greeter +import demo_greeter_grpc + + +with grpc.insecure_channel("localhost:50051") as channel: + stub = demo_greeter_grpc.GreeterStub(channel) + reply = stub.say_hello(demo_greeter.HelloRequest(name="Fory")) + print(reply.reply) +``` + +Sync streaming follows the normal `grpcio` iterator and generator conventions. + +## gRPC Runtime Behavior + +The generated service companion only supplies Fory serialization callbacks. +Operational behavior remains standard `grpcio` behavior: + +- Deadlines and cancellations +- TLS and authentication credentials +- Client and server interceptors +- Status codes, details, and metadata +- Async event loop, channel, and server lifecycle in default mode +- Thread pool sizing for synchronous servers in sync mode + +## Troubleshooting + +### `ModuleNotFoundError: No module named 'grpc'` + +Install `grpcio` in the environment that runs the generated service module: + +```bash +pip install grpcio +``` + +### `TypeError: Unsupported gRPC servicer type` + +Pass an instance of the generated servicer subclass to +`demo_greeter_grpc.add_servicer(...)`. If the schema contains multiple services, +the generated registrar accepts only the matching generated servicer types. + +### `UNIMPLEMENTED` + +Confirm that the generated servicer was registered with the server, and that the +client and server were generated from the same package, service, and method +names. + +### Protobuf Clients Cannot Decode the Service + +Fory gRPC companions do not use protobuf wire encoding for messages. Use a +Fory-generated client for Fory-generated services, or provide a separate +protobuf service endpoint for generic protobuf clients. diff --git a/versioned_docs/version-1.3.0/guide/python/index.md b/versioned_docs/version-1.3.0/guide/python/index.md new file mode 100644 index 00000000000..5b74d7de654 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/python/index.md @@ -0,0 +1,170 @@ +--- +title: Python Serialization Guide +sidebar_position: 0 +id: serialization_index +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. +--- + +**Apache Fory™** is a blazing fast multi-language serialization framework powered by **JIT compilation** and **zero-copy** techniques, providing up to **ultra-fast performance** while maintaining ease of use and safety. + +`pyfory` provides the Python implementation of Apache Fory™, offering xlang mode for cross-language payloads, native mode for Python-only object serialization, and advanced row-format capabilities for data processing tasks. + +## Key Features + +### Flexible Serialization Modes + +- **Xlang mode**: Default cross-language wire format with compatible schema evolution +- **Python native mode**: Same-language mode and drop-in replacement for pickle/cloudpickle +- **Row Format**: Zero-copy row format for analytics workloads + +### Versatile Serialization Features + +- **Reference tracking** for shared xlang schema objects and Python native-mode circular graphs +- **Polymorphism support** for customized types with automatic type dispatching +- **Schema evolution** support for backward/forward compatibility when using dataclasses in xlang mode +- **Out-of-band buffer support** for zero-copy serialization of large data structures like NumPy arrays and Pandas DataFrames, compatible with pickle protocol 5 + +### Blazing Fast Performance + +- **Extremely fast performance** compared to other serialization frameworks +- **Runtime code generation** and **Cython-accelerated** core implementation for optimal performance + +### Compact Data Size + +- **Compact object graph protocol** with minimal space overhead—up to 3× size reduction compared to pickle/cloudpickle +- **Meta packing and sharing** to minimize type forward/backward compatibility space overhead + +### Security & Safety + +- **Strict mode** prevents deserialization of untrusted types by type registration and checks. +- **Reference tracking** for handling circular references safely + +## Installation + +### Basic Installation + +```bash +pip install pyfory +``` + +### Optional Dependencies + +```bash +# Install with row format support (requires Apache Arrow) +pip install pyfory[format] + +# Install from source for development +git clone https://github.com/apache/fory.git +cd fory/python +pip install -e ".[dev,format]" +``` + +### Requirements + +- **Python**: 3.8 or higher +- **OS**: Linux, macOS, Windows + +## Thread Safety + +`pyfory` provides `ThreadSafeFory` for thread-safe serialization using a pooled wrapper: + +```python +import pyfory +import threading +from dataclasses import dataclass + +@dataclass +class Person: + name: str + age: int + +# Create a thread-safe xlang Fory instance +fory = pyfory.ThreadSafeFory(xlang=True, ref=True) +fory.register(Person) + +# Use in multiple threads safely +def serialize_in_thread(thread_id): + person = Person(name=f"User{thread_id}", age=25 + thread_id) + data = fory.serialize(person) + result = fory.deserialize(data) + print(f"Thread {thread_id}: {result}") + +threads = [threading.Thread(target=serialize_in_thread, args=(i,)) for i in range(10)] +for t in threads: t.start() +for t in threads: t.join() +``` + +**Key Features:** + +- **Instance Pool**: Maintains a pool of `Fory` instances protected by a lock for thread safety +- **Shared Configuration**: All registrations must be done upfront and are applied to all instances +- **Same API**: Drop-in replacement for `Fory` class with identical methods +- **Registration Safety**: Prevents registration after first use to ensure consistency + +**When to Use:** + +- **Multi-threaded Applications**: Web servers, concurrent workers, parallel processing +- **Shared Fory Instances**: When multiple threads need to serialize/deserialize data +- **Thread Pools**: Applications using thread pools or concurrent.futures + +## Quick Start + +```python +import pyfory +from dataclasses import dataclass + +@dataclass +class Person: + name: str + age: int + +# Create an xlang Fory instance +fory = pyfory.Fory(xlang=True, ref=True) +fory.register(Person) + +person = Person("Alice", 30) +data = fory.serialize(person) +result = fory.deserialize(data) +print(result) # Person(name='Alice', age=30) +``` + +## Xlang Mode And Native Mode + +Use xlang mode for cross-language payloads and dataclass schemas shared with other Fory implementations. Xlang mode is the default Python wire mode, and Python examples that use it set `xlang=True` explicitly so the mode choice is visible. + +Use native mode for Python-only traffic. Native mode is selected with `xlang=False` and owns pickle/cloudpickle-style behavior such as functions, lambdas, classes, methods, `__reduce__`, `__getstate__`, and out-of-band pickle protocol 5 buffers. It is optimized for Python's type system and supports a broader Python object surface than xlang mode, so use it when replacing pickle or cloudpickle. Compatible mode is enabled by default. Set `compatible=False` only when every reader and writer uses the same Python class schema and you want faster serialization and smaller size. + +See [Native Serialization](native-serialization.md) for Python-only serialization details and [Xlang Serialization](xlang-serialization.md) for Python xlang registration and interoperability rules. + +## Next Steps + +- [Basic Serialization](basic-serialization.md) - Basic usage patterns +- [Xlang Serialization](xlang-serialization.md) - xlang mode +- [Native Serialization](native-serialization.md) - Python-only serialization +- [Configuration](configuration.md) - Fory parameters, modes, and security +- [Type Registration](type-registration.md) - User-defined type registration +- [Custom Serializers](custom-serializers.md) - Extend serialization behavior +- [Row Format](row-format.md) - Zero-copy row format +- [gRPC Support](grpc-support.md) - Fory payloads over grpcio + +## Links + +- **Documentation**: https://fory.apache.org/docs/guide/python/ +- **GitHub**: https://github.com/apache/fory +- **PyPI**: https://pypi.org/project/pyfory/ +- **Issues**: https://github.com/apache/fory/issues +- **Slack**: https://join.slack.com/t/fory-project/shared_invite/zt-36g0qouzm-kcQSvV_dtfbtBKHRwT5gsw diff --git a/versioned_docs/version-1.3.0/guide/python/native-serialization.md b/versioned_docs/version-1.3.0/guide/python/native-serialization.md new file mode 100644 index 00000000000..e513ca7fe82 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/python/native-serialization.md @@ -0,0 +1,342 @@ +--- +title: Native Serialization +sidebar_position: 3 +id: native_serialization +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. +--- + +Python native serialization is the Python-only wire mode selected with `xlang=False`. Use it when +every writer and reader is Python and the payload should follow Python's object model instead of +the portable xlang type system. + +Use [Xlang Serialization](xlang-serialization.md), the default Python mode, when bytes must be read +by Java, C++, Go, Rust, JavaScript/TypeScript, C#, Swift, Dart, Scala, Kotlin, +or another non-Python Fory implementation. + +## When To Use Native Serialization + +Use native serialization when: + +- A payload is produced and consumed only by Python applications. +- You are replacing `pickle` or `cloudpickle` for Python-only object graphs. +- The data model includes functions, lambdas, local classes, methods, or Python reduction hooks. +- The graph can contain shared objects or cycles that need Python reference tracking. +- You need pickle protocol 5-style out-of-band buffers for large Python data objects. + +Native mode can serialize Python-specific values such as global functions, local functions, lambdas, +local classes, methods, and objects customized with `__getstate__`, `__setstate__`, `__reduce__`, +or `__reduce_ex__`. Those values are not valid xlang payloads. + +## Create a Native-Mode Fory Instance + +Create `Fory` with `xlang=False`: + +```python +import pyfory +fory = pyfory.Fory(xlang=False, ref=False, strict=True) +``` + +Keep `strict=True` for registered, trusted type surfaces. Use `strict=False` only when native-mode +payloads need dynamic Python types such as functions, local classes, or objects reconstructed by +reduction hooks. + +## Common Usage + +```python +import pyfory + +fory = pyfory.Fory(xlang=False, ref=True, strict=False) + +data = fory.dumps({"name": "Alice", "age": 30, "scores": [95, 87, 92]}) +print(fory.loads(data)) + +from dataclasses import dataclass + +@dataclass +class Person: + name: str + age: int + +person = Person("Bob", 25) +data = fory.dumps(person) +print(fory.loads(data)) # Person(name='Bob', age=25) +``` + +Use `dumps`/`loads` for pickle-style APIs, or `serialize`/`deserialize` when matching the xlang +API shape in code that switches modes explicitly. + +## Security And Dynamic Types + +Native mode can reconstruct Python objects that execute import and construction logic during +deserialization. Treat untrusted native-mode bytes the same way you would treat untrusted pickle +bytes. + +- Keep `strict=True` when deserializing data that should contain only registered or built-in types. +- Use `strict=False` only for trusted payloads that require dynamic Python classes or functions. +- Provide a `policy=` deserialization policy when dynamic types are required but the accepted type + surface should still be restricted. +- Do not use xlang/native mode choice as a security control. Apply strict mode, policies, + registration, and resource limits based on the payload source. + +## References And Cycles + +Enable `ref=True` when object identity, shared references, or cycles must round-trip: + +```python +import pyfory + +fory = pyfory.Fory(xlang=False, ref=True, strict=True) + +node = {} +node["self"] = node +data = fory.dumps(node) +decoded = fory.loads(data) +assert decoded["self"] is decoded +``` + +Disable reference tracking for value-shaped payloads that do not need identity preservation. It +keeps the payload smaller and the hot path simpler. + +## Pickle And Cloudpickle Replacement + +Native mode is the Python mode to choose when the existing boundary uses `pickle` or +`cloudpickle`. It supports richer Python values than JSON and xlang mode, including Python +functions, local classes, closures, and reduction hooks. + +Use xlang mode instead when the payload crosses language boundaries or the data model should be a +portable schema shared with other Fory implementations. + +## Serialize Global Functions + +Capture and serialize functions defined at module level. Fory deserializes and returns the same +function object: + +```python +import pyfory + +fory = pyfory.Fory(xlang=False, ref=True, strict=False) + +def my_global_function(x): + return 10 * x + +data = fory.dumps(my_global_function) +print(fory.loads(data)(10)) # 100 +``` + +## Serialize Local Functions/Lambdas + +Serialize functions with closures and lambda expressions. Fory captures the closure variables +automatically: + +```python +import pyfory + +fory = pyfory.Fory(xlang=False, ref=True, strict=False) + +# Local functions with closures +def my_function(): + local_var = 10 + def local_func(x): + return x * local_var + return local_func + +data = fory.dumps(my_function()) +print(fory.loads(data)(10)) # 100 + +# Lambdas +data = fory.dumps(lambda x: 10 * x) +print(fory.loads(data)(10)) # 100 +``` + +## Serialize Global Classes/Methods + +Serialize class objects, instance methods, class methods, and static methods: + +```python +from dataclasses import dataclass +import pyfory +fory = pyfory.Fory(xlang=False, ref=True, strict=False) + +@dataclass +class Person: + name: str + age: int + + def f(self, x): + return self.age * x + + @classmethod + def g(cls, x): + return 10 * x + + @staticmethod + def h(x): + return 10 * x + +# Serialize global class +print(fory.loads(fory.dumps(Person))("Bob", 25)) # Person(name='Bob', age=25) + +# Serialize instance method +print(fory.loads(fory.dumps(Person("Bob", 20).f))(10)) # 200 + +# Serialize class method +print(fory.loads(fory.dumps(Person.g))(10)) # 100 + +# Serialize static method +print(fory.loads(fory.dumps(Person.h))(10)) # 100 +``` + +## Serialize Local Classes/Methods + +Serialize classes defined inside functions along with their methods: + +```python +from dataclasses import dataclass +import pyfory +fory = pyfory.Fory(xlang=False, ref=True, strict=False) + +def create_local_class(): + class LocalClass: + def f(self, x): + return 10 * x + + @classmethod + def g(cls, x): + return 10 * x + + @staticmethod + def h(x): + return 10 * x + return LocalClass + +# Serialize local class +data = fory.dumps(create_local_class()) +print(fory.loads(data)().f(10)) # 100 + +# Serialize local class instance method +data = fory.dumps(create_local_class()().f) +print(fory.loads(data)(10)) # 100 + +# Serialize local class method +data = fory.dumps(create_local_class().g) +print(fory.loads(data)(10)) # 100 + +# Serialize local class static method +data = fory.dumps(create_local_class().h) +print(fory.loads(data)(10)) # 100 +``` + +## Custom Python Object Hooks + +Native mode respects common Python customization hooks: + +```python +import pyfory + +class SessionToken: + def __init__(self, value): + self.value = value + + def __getstate__(self): + return {"value": self.value} + + def __setstate__(self, state): + self.value = state["value"] + +fory = pyfory.Fory(xlang=False, strict=False) +token = fory.loads(fory.dumps(SessionToken("abc"))) +print(token.value) # abc +``` + +Use these hooks for Python-only payloads. For xlang payloads, model the data as dataclasses with +portable field annotations instead. + +## Out-of-Band Buffers + +Python native mode can use pickle protocol 5-style out-of-band buffers for large binary payloads +and data structures backed by external memory: + +```python +import pickle +import pyfory + +data = b"Large binary data" +pickle_buffer = pickle.PickleBuffer(data) + +buffer_objects = [] +fory = pyfory.Fory(xlang=False, ref=True, strict=False) +serialized = fory.dumps(pickle_buffer, buffer_callback=buffer_objects.append) +buffers = [obj.getbuffer() for obj in buffer_objects] +decoded = fory.loads(serialized, buffers=buffers) +assert bytes(decoded.raw()) == data +``` + +Use this when the payload stays in Python and large buffers should avoid extra copies. See +[Out-of-Band Serialization](out-of-band.md). + +## Native And Xlang Comparison + +| Requirement | Use native serialization | Use xlang serialization | +| ------------------------------------------ | ------------------------ | ----------------------- | +| Python-only payloads | Yes | Optional | +| Non-Python readers or writers | No | Yes | +| Functions, lambdas, local classes | Yes | No | +| `__reduce__` / `__getstate__` object hooks | Yes | No | +| Pickle/cloudpickle replacement | Yes | No | +| Portable type mapping across languages | No | Yes | + +## Performance Comparison + +```python +import pyfory +import pickle +import timeit + +fory = pyfory.Fory(xlang=False, ref=True, strict=False) + +obj = {f"key{i}": f"value{i}" for i in range(10000)} +print(f"Fory: {timeit.timeit(lambda: fory.dumps(obj), number=1000):.3f}s") +print(f"Pickle: {timeit.timeit(lambda: pickle.dumps(obj), number=1000):.3f}s") +``` + +## Troubleshooting + +### Another language cannot read the payload + +The writer is using native serialization. Rebuild it with `xlang=True`, register portable schemas +on every peer, and avoid Python-only values such as lambdas or local classes. + +### A dynamic class or function fails to deserialize + +Use `strict=False` for trusted payloads and provide a deserialization `policy=` when only selected +dynamic types should be accepted. + +### A cycle does not round-trip + +Create the `Fory` instance with `ref=True`. + +### A value depends on pickle hooks + +Keep the payload in native mode. Xlang mode does not execute Python `__reduce__`, +`__reduce_ex__`, `__getstate__`, or `__setstate__` object reconstruction hooks. + +## Related Topics + +- [Xlang Serialization](xlang-serialization.md) - Cross-language Python payloads +- [Configuration](configuration.md) - Python `Fory` options +- [Out-of-Band Serialization](out-of-band.md) - Zero-copy buffer support +- [Configuration](configuration.md#security) - Deserialization policies diff --git a/versioned_docs/version-1.3.0/guide/python/numpy-integration.md b/versioned_docs/version-1.3.0/guide/python/numpy-integration.md new file mode 100644 index 00000000000..b91c910ec84 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/python/numpy-integration.md @@ -0,0 +1,103 @@ +--- +title: NumPy & Pandas +sidebar_position: 11 +id: numpy_integration +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. +--- + +Fory natively supports numpy arrays and pandas DataFrame with optimized serialization. + +## NumPy Array Serialization + +Large arrays use zero-copy when possible: + +```python +import pyfory +import numpy as np + +f = pyfory.Fory(xlang=False) + +# Numpy arrays are supported natively +arrays = { + 'matrix': np.random.rand(1000, 1000), + 'vector': np.arange(10000), + 'bool_mask': np.random.choice([True, False], size=5000) +} + +data = f.serialize(arrays) +result = f.deserialize(data) + +# Zero-copy for compatible array types +assert np.array_equal(arrays['matrix'], result['matrix']) +``` + +## Pandas DataFrames + +Fory can serialize Pandas DataFrames efficiently: + +```python +import pyfory +import pandas as pd +import numpy as np + +f = pyfory.Fory(xlang=False, ref=False, strict=False) + +df = pd.DataFrame({ + 'a': np.arange(1000, dtype=np.float64), + 'b': np.arange(1000, dtype=np.int64), + 'c': ['text'] * 1000 +}) + +data = f.serialize(df) +result = f.deserialize(data) + +assert df.equals(result) +``` + +## Zero-Copy with Out-of-Band Buffers + +For maximum performance with large arrays, use out-of-band serialization: + +```python +import pyfory +import numpy as np + +f = pyfory.Fory(xlang=False, ref=False, strict=False) + +# Large array +array = np.random.rand(10000, 1000) + +# Out-of-band for zero-copy +buffer_objects = [] +data = f.serialize(array, buffer_callback=buffer_objects.append) +buffers = [obj.getbuffer() for obj in buffer_objects] + +result = f.deserialize(data, buffers=buffers) +assert np.array_equal(array, result) +``` + +## Supported Array Types + +- `np.ndarray` (all dtypes) +- `np.matrix` +- Structured arrays +- Record arrays + +## Related Topics + +- [Out-of-Band Serialization](out-of-band.md) - Zero-copy buffers +- [Basic Serialization](basic-serialization.md) - Standard usage diff --git a/versioned_docs/version-1.3.0/guide/python/out-of-band.md b/versioned_docs/version-1.3.0/guide/python/out-of-band.md new file mode 100644 index 00000000000..3feea3425f6 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/python/out-of-band.md @@ -0,0 +1,179 @@ +--- +title: Out-of-Band Serialization +sidebar_position: 9 +id: out_of_band +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. +--- + +Fory supports pickle5-compatible out-of-band buffer serialization for efficient zero-copy handling of large data structures. + +## Overview + +Out-of-band serialization separates metadata from the actual data buffers, allowing for: + +- **Zero-copy transfers** when sending data over networks or IPC using `memoryview` +- **Improved performance** for large datasets +- **Pickle5 compatibility** using `pickle.PickleBuffer` +- **Flexible stream support** - write to any writable object (files, BytesIO, sockets, etc.) + +## Basic Out-of-Band Serialization + +```python +import pyfory +import numpy as np + +fory = pyfory.Fory(xlang=False, ref=False, strict=False) + +# Large numpy array +array = np.arange(10000, dtype=np.float64) + +# Serialize with out-of-band buffers +buffer_objects = [] +serialized_data = fory.serialize(array, buffer_callback=buffer_objects.append) + +# Convert buffer objects to memoryview for zero-copy transmission +# For contiguous buffers (bytes, numpy arrays), this is zero-copy +# For non-contiguous data, a copy may be created to ensure contiguity +buffers = [obj.getbuffer() for obj in buffer_objects] + +# Deserialize with out-of-band buffers (accepts memoryview, bytes, or Buffer) +deserialized_array = fory.deserialize(serialized_data, buffers=buffers) + +assert np.array_equal(array, deserialized_array) +``` + +## Out-of-Band with Pandas DataFrames + +```python +import pyfory +import pandas as pd +import numpy as np + +fory = pyfory.Fory(xlang=False, ref=False, strict=False) + +# Create a DataFrame with numeric columns +df = pd.DataFrame({ + 'a': np.arange(1000, dtype=np.float64), + 'b': np.arange(1000, dtype=np.int64), + 'c': ['text'] * 1000 +}) + +# Serialize with out-of-band buffers +buffer_objects = [] +serialized_data = fory.serialize(df, buffer_callback=buffer_objects.append) +buffers = [obj.getbuffer() for obj in buffer_objects] + +# Deserialize +deserialized_df = fory.deserialize(serialized_data, buffers=buffers) + +assert df.equals(deserialized_df) +``` + +## Selective Out-of-Band Serialization + +Control which buffers go out-of-band by providing a callback that returns `True` to keep data in-band or `False` to send it out-of-band: + +```python +import pyfory +import numpy as np + +fory = pyfory.Fory(xlang=False, ref=True, strict=False) + +arr1 = np.arange(1000, dtype=np.float64) +arr2 = np.arange(2000, dtype=np.float64) +data = [arr1, arr2] + +buffer_objects = [] +counter = 0 + +def selective_callback(buffer_object): + global counter + counter += 1 + # Only send even-numbered buffers out-of-band + if counter % 2 == 0: + buffer_objects.append(buffer_object) + return False # Out-of-band + return True # In-band + +serialized = fory.serialize(data, buffer_callback=selective_callback) +buffers = [obj.getbuffer() for obj in buffer_objects] +deserialized = fory.deserialize(serialized, buffers=buffers) +``` + +## Pickle5 Compatibility + +Fory's out-of-band serialization is fully compatible with pickle protocol 5: + +```python +import pyfory +import pickle + +fory = pyfory.Fory(xlang=False, ref=False, strict=False) + +# PickleBuffer objects are automatically supported +data = b"Large binary data" +pickle_buffer = pickle.PickleBuffer(data) + +# Serialize with buffer callback for out-of-band handling +buffer_objects = [] +serialized = fory.serialize(pickle_buffer, buffer_callback=buffer_objects.append) +buffers = [obj.getbuffer() for obj in buffer_objects] + +# Deserialize with buffers +deserialized = fory.deserialize(serialized, buffers=buffers) +assert bytes(deserialized.raw()) == data +``` + +## Writing Buffers to Different Streams + +The `BufferObject.write_to()` method accepts any writable stream object: + +```python +import pyfory +import numpy as np +import io + +fory = pyfory.Fory(xlang=False, ref=False, strict=False) + +array = np.arange(1000, dtype=np.float64) + +# Collect out-of-band buffers +buffer_objects = [] +serialized = fory.serialize(array, buffer_callback=buffer_objects.append) + +# Write to different stream types +for buffer_obj in buffer_objects: + # Write to BytesIO (in-memory stream) + bytes_stream = io.BytesIO() + buffer_obj.write_to(bytes_stream) + + # Write to file + with open('/tmp/buffer_data.bin', 'wb') as f: + buffer_obj.write_to(f) + + # Get zero-copy memoryview (for contiguous buffers) + mv = buffer_obj.getbuffer() + assert isinstance(mv, memoryview) +``` + +**Note**: For contiguous memory buffers (like bytes, numpy arrays), `getbuffer()` returns a zero-copy `memoryview`. For non-contiguous data, a copy may be created to ensure contiguity. + +## Related Topics + +- [NumPy Integration](numpy-integration.md) - NumPy array serialization +- [Basic Serialization](basic-serialization.md) - Standard serialization +- [Configuration](configuration.md) - Fory parameters diff --git a/versioned_docs/version-1.3.0/guide/python/row-format.md b/versioned_docs/version-1.3.0/guide/python/row-format.md new file mode 100644 index 00000000000..a9f7219e41d --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/python/row-format.md @@ -0,0 +1,191 @@ +--- +title: Row Format +sidebar_position: 12 +id: row_format +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. +--- + +Apache Fory™ provides a random-access row format that enables reading nested fields from binary data without full deserialization. + +## Overview + +Row format drastically reduces overhead when working with large objects where only partial data access is needed. It also supports memory-mapped files for ultra-low memory footprint. + +**Key Benefits:** + +| Feature | Description | +| ----------------------- | ------------------------------------------------------ | +| Zero-Copy Access | Read nested fields without deserializing entire object | +| Memory Efficiency | Memory-map large datasets directly from disk | +| Cross-Language | Binary format compatible between Python, Java, C++ | +| Partial Deserialization | Deserialize only specific elements you need | +| High Performance | Skip unnecessary data parsing for analytics workloads | + +## Basic Usage + +```python +from dataclasses import dataclass +from typing import Dict, List + +import pyfory + +@dataclass +class Bar: + f1: str + f2: List[pyfory.Int64] + +@dataclass +class Foo: + f1: pyfory.Int32 + f2: List[pyfory.Int32] + f3: Dict[str, pyfory.Int32] + f4: List[Bar] + +# Create encoder for row format +encoder = pyfory.encoder(Foo) + +# Create large dataset +foo = Foo( + f1=10, + f2=list(range(1_000_000)), + f3={f"k{i}": i for i in range(1_000_000)}, + f4=[Bar(f1=f"s{i}", f2=list(range(10))) for i in range(1_000_000)] +) + +# Encode to row format +binary: bytes = encoder.to_row(foo).to_bytes() + +# Zero-copy access - no full deserialization needed! +foo_row = pyfory.RowData(encoder.schema, binary) +print(foo_row.f2[100000]) # Access 100,000th element directly +print(foo_row.f4[100000].f1) # Access nested field directly +print(foo_row.f4[200000].f2[5]) # Access deeply nested field directly +``` + +## PyArrow Schema Conversion + +Row format can convert PyArrow schemas through `pyfory.format` when the +`format` optional dependency is installed: + +```python +import pyarrow as pa +from pyfory.format import from_arrow_schema, to_arrow_schema + +arrow_schema = pa.schema( + [ + pa.field("id", pa.int32(), nullable=False), + pa.field("scores", pa.list_(pa.float64())), + ] +) + +fory_schema = from_arrow_schema(arrow_schema) +roundtrip_arrow_schema = to_arrow_schema(fory_schema) +``` + +This PyArrow conversion surface is separate from cross-language dense-array +field annotations. In object serialization, `pyfory.PyArray[T]` means the +standard-library Python `array.array` carrier, not PyArrow. + +## Cross-Language Compatibility + +Row format works seamlessly across languages. The same binary data can be accessed from Java and C++. + +### Java + +```java +public class Bar { + String f1; + List f2; +} + +public class Foo { + int f1; + List f2; + Map f3; + List f4; +} + +RowEncoder encoder = Encoders.bean(Foo.class); + +// Encode to row format (cross-language compatible with Python) +BinaryRow binaryRow = encoder.toRow(foo); + +// Zero-copy random access without full deserialization +BinaryArray f2Array = binaryRow.getArray(1); // Access f2 list +BinaryArray f4Array = binaryRow.getArray(3); // Access f4 list +BinaryRow bar10 = f4Array.getStruct(10); // Access 11th Bar +long value = bar10.getArray(1).getInt64(5); // Access 6th element of bar.f2 + +// Partial deserialization - only deserialize what you need +RowEncoder barEncoder = Encoders.bean(Bar.class); +Bar bar1 = barEncoder.fromRow(f4Array.getStruct(10)); // Deserialize 11th Bar only +Bar bar2 = barEncoder.fromRow(f4Array.getStruct(20)); // Deserialize 21st Bar only +``` + +### C++ + +```cpp +#include "fory/encoder/row_encoder.h" +#include "fory/row/writer.h" + +struct Bar { + std::string f1; + std::vector f2; + FORY_STRUCT(Bar, f1, f2); +}; + +struct Foo { + int32_t f1; + std::vector f2; + std::map f3; + std::vector f4; + FORY_STRUCT(Foo, f1, f2, f3, f4); +}; + +fory::row::encoder::RowEncoder encoder; +encoder.encode(foo); +auto row = encoder.get_writer().to_row(); + +// Zero-copy random access without full deserialization +auto f2_array = row->get_array(1); // Access f2 list +auto f4_array = row->get_array(3); // Access f4 list +auto bar10 = f4_array->get_struct(10); // Access 11th Bar +int64_t value = bar10->get_array(1)->get_int64(5); // Access 6th element of bar.f2 +std::string str = bar10->get_string(0); // Access bar.f1 +``` + +## Installation + +Row format requires Apache Arrow: + +```bash +pip install pyfory[format] +``` + +## When to Use Row Format + +- **Analytics workloads**: When you only need to access specific fields +- **Large datasets**: When full deserialization is too expensive +- **Memory-mapped files**: Working with data larger than RAM +- **Data pipelines**: Processing data without full object reconstruction +- **Cross-language data sharing**: When data needs to be accessed from multiple languages + +## Related Topics + +- [Xlang Serialization](xlang-serialization.md) - xlang mode +- [Basic Serialization](basic-serialization.md) - Object serialization +- [Row Format Specification](https://fory.apache.org/docs/specification/row_format_spec) - Protocol details diff --git a/versioned_docs/version-1.3.0/guide/python/schema-evolution.md b/versioned_docs/version-1.3.0/guide/python/schema-evolution.md new file mode 100644 index 00000000000..a66562b3e79 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/python/schema-evolution.md @@ -0,0 +1,122 @@ +--- +title: Schema Evolution +sidebar_position: 8 +id: schema_evolution +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. +--- + +Apache Fory™ supports schema evolution in compatible mode, allowing fields to be added or removed +while maintaining compatibility. Compatible mode is enabled by default in both xlang and native mode. + +Compatible readers also tolerate selected scalar field type changes when the value is lossless. A +matched field can read between `bool`, `str`, numeric scalars, and `Decimal` when the converted value +has the same logical value. For example, `"true"`, `"false"`, `"0"`, and `"1"` can be read as +booleans; `"123"` can be read as a numeric field that can hold `123`; numbers and decimals can be +read as canonical strings; and numeric widening or narrowing succeeds only when no precision or range +is lost. + +Scalar conversion is only applied to matched compatible fields, not to root values or collection +elements. String-to-number conversion accepts finite ASCII decimal literals without whitespace, a +leading `+`, Unicode digits, underscores, or special values such as `NaN` and `Infinity`. Invalid +strings, out-of-range values, and lossy conversions fail with `pyfory.error.ForyInvalidDataError` +during deserialization. +Optional and nullable fields still compose with these conversions, but reference-tracked scalar type +changes are incompatible. + +## Default Compatible Mode + +```python +import pyfory + +f = pyfory.Fory() +native_f = pyfory.Fory(xlang=False) +``` + +`pyfory.dataclass` also supports `slots=True`: + +```python +@pyfory.dataclass(slots=True) +class SlotMessage: + id: int +``` + +## Schema Evolution Example + +```python +import pyfory +from dataclasses import dataclass + +# Version 1: Original class +@dataclass +class User: + name: str + age: pyfory.Int32 + +f = pyfory.Fory(xlang=True) +f.register(User, name="User") +data = f.dumps(User("Alice", 30)) + +# Version 2: Add new field (backward compatible) +@dataclass +class User: + name: str + age: pyfory.Int32 + email: str = "unknown@example.com" # New field with default + +# Can still deserialize old data +user = f.loads(data) +print(user.email) # "unknown@example.com" +``` + +## Supported Changes + +- **Add new fields**: With default values +- **Remove fields**: Old data with extra fields will be skipped +- **Reorder fields**: Fields are matched by name, not position + +## Same-Schema Class Optimization + +Use `compatible=False` only when the class schema used to deserialize every payload is always the same +as the class schema used to serialize it, and you want faster serialization and smaller size. For xlang payloads, set `compatible=False` only after verifying that every language uses the same schema, or when native types are generated from Fory schema IDL. + +```python +f = pyfory.Fory(xlang=False, compatible=False) +``` + +For one dataclass, you can opt out of evolution metadata with `pyfory.dataclass(evolving=False)`: + +```python +import pyfory + +@pyfory.dataclass(evolving=False) +class SameSchemaMessage: + id: int + name: str +``` + +## Best Practices + +1. **Always provide default values** for new fields +2. **Use name for cross-language compatibility** +3. **Test schema changes** before deploying +4. **Document schema versions** for your team + +## Related Topics + +- [Configuration](configuration.md) - Compatible mode settings +- [Xlang Serialization](xlang-serialization.md) - Schema evolution across languages +- [Type Registration](type-registration.md) - Registration patterns diff --git a/versioned_docs/version-1.3.0/guide/python/schema-metadata.md b/versioned_docs/version-1.3.0/guide/python/schema-metadata.md new file mode 100644 index 00000000000..d956cf341f5 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/python/schema-metadata.md @@ -0,0 +1,529 @@ +--- +title: Schema Metadata +sidebar_position: 7 +id: schema_metadata +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 page explains how to configure field-level metadata for serialization in Python. + +## Overview + +Apache Fory™ provides field-level configuration through: + +- **`pyfory.field()`**: Configure field metadata (id, nullable, ref, ignore, dynamic) +- **Type annotations**: Control integer encoding (varint, fixed, tagged) +- **`Optional[T]`**: Mark fields as nullable + +This enables: + +- **Tag IDs**: Assign compact numeric IDs to reduce struct field meta size overhead +- **Nullability**: Control whether fields can be null +- **Reference Tracking**: Enable reference tracking for shared objects +- **Field Skipping**: Exclude fields from serialization +- **Encoding Control**: Specify how integers are encoded (varint, fixed, tagged) +- **Polymorphism**: Control whether type info is written for struct fields + +## Basic Syntax + +Use `@dataclass` decorator with type annotations and `pyfory.field()`: + +```python +from dataclasses import dataclass +from typing import Optional +import pyfory + +@dataclass +class Person: + name: str = pyfory.field(id=0) + age: pyfory.Int32 = pyfory.field(id=1, default=0) + nickname: Optional[str] = pyfory.field(id=2, nullable=True, default=None) +``` + +## The `pyfory.field()` Function + +Use `pyfory.field()` to configure field-level metadata: + +```python +@dataclass +class User: + id: pyfory.Int64 = pyfory.field(id=0, default=0) + name: str = pyfory.field(id=1, default="") + email: Optional[str] = pyfory.field(id=2, nullable=True, default=None) + friends: List["User"] = pyfory.field(id=3, ref=True, default_factory=list) + _cache: dict = pyfory.field(ignore=True, default_factory=dict) +``` + +### Parameters + +| Parameter | Type | Default | Description | +| ----------------- | -------- | --------- | ------------------------------------ | +| `id` | `int` | omitted | Non-negative field tag ID | +| `nullable` | `bool` | `False` | Whether the field can be null | +| `ref` | `bool` | `False` | Enable reference tracking | +| `ignore` | `bool` | `False` | Exclude field from serialization | +| `dynamic` | `bool` | `None` | Control whether type info is written | +| `default` | Any | `MISSING` | Default value for the field | +| `default_factory` | Callable | `MISSING` | Factory function for default value | + +## Field ID (`id`) + +Assigns a numeric ID to a field to minimize struct field meta size overhead: + +```python +@dataclass +class User: + id: pyfory.Int64 = pyfory.field(id=0, default=0) + name: str = pyfory.field(id=1, default="") + age: pyfory.Int32 = pyfory.field(id=2, default=0) +``` + +**Benefits**: + +- Smaller serialized size (numeric IDs vs field names in metadata) +- Reduced struct field meta overhead +- Allows renaming fields without breaking binary compatibility + +**Recommendation**: It is recommended to configure field IDs for compatible mode since it reduces serialization cost. + +**Notes**: + +- IDs must be unique within a class +- IDs must be >= 0 +- If not specified, field name is used in metadata (larger overhead) + +**Without field IDs** (field names used in metadata): + +```python +@dataclass +class User: + id: pyfory.Int64 = 0 + name: str = "" +``` + +## Nullable Fields (`nullable`) + +Use `nullable=True` for fields that can be `None`: + +```python +from typing import Optional + +@dataclass +class Record: + # Nullable string field + optional_name: Optional[str] = pyfory.field(id=0, nullable=True, default=None) + + # Nullable integer field + optional_count: Optional[pyfory.Int32] = pyfory.field(id=1, nullable=True, default=None) +``` + +**Notes**: + +- `Optional[T]` fields must have `nullable=True` +- Non-optional fields default to `nullable=False` + +## Reference Tracking (`ref`) + +Enable reference tracking for fields that may be shared. Circular Python object +graphs require Python native mode with global reference tracking enabled. + +```python +@dataclass +class RefOuter: + # Both fields may point to the same inner object + inner1: Optional[RefInner] = pyfory.field(id=0, ref=True, nullable=True, default=None) + inner2: Optional[RefInner] = pyfory.field(id=1, ref=True, nullable=True, default=None) + + +@dataclass +class CircularRef: + name: str = pyfory.field(id=0, default="") + # Self-referencing field for circular references + self_ref: Optional["CircularRef"] = pyfory.field(id=1, ref=True, nullable=True, default=None) +``` + +**Use Cases**: + +- Enable for fields that may be circular or shared +- When the same object is referenced from multiple fields + +**Notes**: + +- Global `Fory(ref=True)` must be enabled. +- Field-level `ref=True` and global `ref=True` must both be enabled for schema + fields. + +## Skipping Fields (`ignore`) + +Exclude fields from serialization: + +```python +@dataclass +class User: + id: pyfory.Int64 = pyfory.field(id=0, default=0) + name: str = pyfory.field(id=1, default="") + # Not serialized + _cache: dict = pyfory.field(ignore=True, default_factory=dict) + _internal_state: str = pyfory.field(ignore=True, default="") +``` + +## Dynamic Fields (`dynamic`) + +Control whether type information is written for struct fields. This is essential for polymorphism support: + +```python +from abc import ABC, abstractmethod + +class Shape(ABC): + @abstractmethod + def area(self) -> float: + pass + +@dataclass +class Circle(Shape): + radius: float = 0.0 + + def area(self) -> float: + return 3.14159 * self.radius * self.radius + +@dataclass +class Container: + # Abstract class: dynamic is always True (type info written) + shape: Shape = pyfory.field(id=0) + + # Force type info for concrete type (support subtypes) + circle: Circle = pyfory.field(id=1, dynamic=True) + + # Skip type info for concrete type (use declared type directly) + fixed_circle: Circle = pyfory.field(id=2, dynamic=False) +``` + +**Default Behavior**: + +| Mode | Abstract Class | Concrete Object Types | Numeric/str/time Types | +| ----------- | -------------- | --------------------- | ---------------------- | +| Native mode | `True` | `True` | `False` | +| Xlang mode | `True` | `False` | `False` | + +**Notes**: + +- **Abstract classes**: `dynamic` is always `True` (type info must be written) +- **Native mode**: `dynamic` defaults to `True` for object types, `False` for numeric/str/time types +- **Xlang mode**: `dynamic` defaults to `False` for concrete types +- Use `dynamic=True` when a concrete field may hold subclass instances +- Use `dynamic=False` for performance optimization when type is known + +## Integer Type Annotations + +Fory provides type annotations to control integer encoding: + +Use these markers directly in Python type annotations. Field values remain +ordinary Python `int` or `float` values, and Fory serializes them with the +requested xlang numeric width and encoding. + +### Signed Integers + +```python +@dataclass +class SignedIntegers: + byte_val: pyfory.Int8 = 0 # 8-bit signed + short_val: pyfory.Int16 = 0 # 16-bit signed + int_val: pyfory.Int32 = 0 # 32-bit signed (varint encoding) + long_val: pyfory.Int64 = 0 # 64-bit signed (varint encoding) +``` + +### Unsigned Integers + +```python +@dataclass +class UnsignedIntegers: + # Fixed-size encoding + u8_val: pyfory.UInt8 = 0 # 8-bit unsigned (fixed) + u16_val: pyfory.UInt16 = 0 # 16-bit unsigned (fixed) + + # Variable-length encoding (default for u32/u64) + u32_var: pyfory.UInt32 = 0 # 32-bit unsigned (varint) + u64_var: pyfory.UInt64 = 0 # 64-bit unsigned (varint) + + # Explicit fixed-size encoding + u32_fixed: pyfory.FixedUInt32 = 0 # 32-bit unsigned (fixed 4 bytes) + u64_fixed: pyfory.FixedUInt64 = 0 # 64-bit unsigned (fixed 8 bytes) + + # Tagged encoding (includes type tag) + u64_tagged: pyfory.TaggedUInt64 = 0 # 64-bit unsigned (tagged) +``` + +### Floating Point + +```python +@dataclass +class FloatingPoint: + float_val: pyfory.Float32 = 0.0 # 32-bit float + double_val: pyfory.Float64 = 0.0 # 64-bit double +``` + +### Encoding Summary + +| Type | Encoding | Size | +| --------------------- | -------- | ---------- | +| `pyfory.Int8` | fixed | 1 byte | +| `pyfory.Int16` | fixed | 2 bytes | +| `pyfory.Int32` | varint | 1-5 bytes | +| `pyfory.Int64` | varint | 1-10 bytes | +| `pyfory.FixedInt32` | fixed | 4 bytes | +| `pyfory.FixedInt64` | fixed | 8 bytes | +| `pyfory.TaggedInt64` | tagged | 1-9 bytes | +| `pyfory.UInt8` | fixed | 1 byte | +| `pyfory.UInt16` | fixed | 2 bytes | +| `pyfory.UInt32` | varint | 1-5 bytes | +| `pyfory.UInt64` | varint | 1-10 bytes | +| `pyfory.FixedUInt32` | fixed | 4 bytes | +| `pyfory.FixedUInt64` | fixed | 8 bytes | +| `pyfory.TaggedUInt64` | tagged | 1-9 bytes | +| `pyfory.Float32` | fixed | 4 bytes | +| `pyfory.Float64` | fixed | 8 bytes | + +**When to Use**: + +- `varint`: Best for values that are often small (default for int32/int64/uint32/uint64) +- `fixed`: Best for values that use full range (e.g., timestamps, hashes) +- `tagged`: When type information needs to be preserved (int64/uint64 only) + +## Nested Container Type Annotations + +Integer encoding aliases can be used inside declared collection schemas. Fory uses the declared +field schema for every nested element, key, and value in both pure Python and Cython modes: + +```python +from dataclasses import dataclass, field +from typing import Dict, List +import pyfory + + +@dataclass +class Counters: + values: Dict[pyfory.FixedInt32, List[pyfory.TaggedInt64]] = field(default_factory=dict) +``` + +For `values`, map keys are written as fixed-width int32 values and each nested list element is +written as tagged int64. Runtime type inference is used only for dynamic or unknown container +schemas. + +In compatible mode, readers consume field bytes using the remote schema metadata. Python assigns the +decoded value only when it can safely satisfy the local declared schema. Scalar conversion and +integer encoding adaptation apply only to the immediate matched field schema. Nested collection +elements, map keys, and map values must keep exact nullability, reference-tracking, and type shape +metadata, except for user-type family normalization such as named and unnamed struct metadata. + +## Complete Example + +```python +from dataclasses import dataclass +from typing import Optional, List, Dict, Set +import pyfory + + +@dataclass +class Document: + # Fields with tag IDs (recommended for compatible mode) + title: str = pyfory.field(id=0, default="") + version: pyfory.Int32 = pyfory.field(id=1, default=0) + + # Nullable field + description: Optional[str] = pyfory.field(id=2, nullable=True, default=None) + + # Collection fields + tags: List[str] = pyfory.field(id=3, default_factory=list) + metadata: Dict[str, str] = pyfory.field(id=4, default_factory=dict) + categories: Set[str] = pyfory.field(id=5, default_factory=set) + + # Unsigned integers with different encodings + view_count: pyfory.UInt64 = pyfory.field(id=6, default=0) # varint encoding + file_size: pyfory.FixedUInt64 = pyfory.field(id=7, default=0) # fixed encoding + checksum: pyfory.TaggedUInt64 = pyfory.field(id=8, default=0) # tagged encoding + + # Reference-tracked field for shared/circular references + parent: Optional["Document"] = pyfory.field(id=9, ref=True, nullable=True, default=None) + + # Ignored field (not serialized) + _cache: dict = pyfory.field(ignore=True, default_factory=dict) + + +def main(): + fory = pyfory.Fory(xlang=True, ref=True) + fory.register_type(Document, type_id=100) + + doc = Document( + title="My Document", + version=1, + description="A sample document", + tags=["tag1", "tag2"], + metadata={"key": "value"}, + categories={"cat1"}, + view_count=42, + file_size=1024, + checksum=123456789, + parent=None, + ) + + # Serialize + data = fory.serialize(doc) + + # Deserialize + decoded = fory.deserialize(data) + assert decoded.title == doc.title + assert decoded.version == doc.version + + +if __name__ == "__main__": + main() +``` + +## Cross-Language Compatibility + +When serializing data to be read by other languages (Java, Rust, C++, Go), use field IDs and matching type annotations: + +```python +@dataclass +class CrossLangData: + # Use field IDs for cross-language compatibility + int_var: pyfory.Int32 = pyfory.field(id=0, default=0) + long_fixed: pyfory.FixedUInt64 = pyfory.field(id=1, default=0) + long_tagged: pyfory.TaggedUInt64 = pyfory.field(id=2, default=0) + optional_value: Optional[str] = pyfory.field(id=3, nullable=True, default=None) +``` + +## Schema Evolution + +Compatible mode supports schema evolution. It is recommended to configure field IDs to reduce serialization cost: + +```python +# Version 1 +@dataclass +class DataV1: + id: pyfory.Int64 = pyfory.field(id=0, default=0) + name: str = pyfory.field(id=1, default="") + + +# Version 2: Added new field +@dataclass +class DataV2: + id: pyfory.Int64 = pyfory.field(id=0, default=0) + name: str = pyfory.field(id=1, default="") + email: Optional[str] = pyfory.field(id=2, nullable=True, default=None) # New field +``` + +Data serialized with V1 can be deserialized with V2 (new field will be `None`). + +Alternatively, field IDs can be omitted (field names will be used in metadata with larger overhead): + +```python +@dataclass +class Data: + id: pyfory.Int64 = 0 + name: str = "" +``` + +## Native Mode vs Xlang Mode + +Field configuration behaves differently depending on the serialization mode: + +### Native Mode (Python-only) + +Native mode has **relaxed default values** for maximum compatibility: + +- **Nullable**: `str` and numeric types are non-nullable by default unless `Optional` is used +- **Ref tracking**: Enabled by default for object references (except `str` and numeric types) + +In native mode, you typically **don't need to configure field annotations** unless you want to: + +- Reduce serialized size by using field IDs +- Optimize performance by disabling unnecessary ref tracking + +```python +# Native mode: works without schema metadata +@dataclass +class User: + id: int = 0 + name: str = "" + tags: List[str] = None +``` + +### Xlang Mode (Cross-language) + +Xlang mode has **stricter default values** due to type system differences between languages: + +- **Nullable**: Fields are non-nullable by default (`nullable=False`) +- **Ref tracking**: Disabled by default (`ref=False`) + +In xlang mode, you **need to configure fields** when: + +- A field can be None (use `Optional[T]` with `nullable=True`) +- A field needs reference tracking for shared/circular objects (use `ref=True`) +- Integer types need specific encoding for cross-language compatibility +- You want to reduce metadata size (use field IDs) + +```python +# Xlang mode: explicit configuration required for nullable/ref fields +@dataclass +class User: + id: pyfory.Int64 = pyfory.field(id=0, default=0) + name: str = pyfory.field(id=1, default="") + email: Optional[str] = pyfory.field(id=2, nullable=True, default=None) # Must declare nullable + friend: Optional["User"] = pyfory.field(id=3, ref=True, nullable=True, default=None) # Must declare ref +``` + +### Default Values Summary + +| Option | Native Mode Default | Xlang Mode Default | +| ---------- | ----------------------------------------------------- | ------------------ | +| `nullable` | `False` for `str`/numeric; others nullable by default | `False` | +| `ref` | `True` (except `str` and numeric types) | `False` | +| `dynamic` | `True` (except numeric/str/time types) | `False` (concrete) | + +## Best Practices + +1. **Configure field IDs**: Recommended for compatible mode to reduce serialization cost +2. **Use `Optional[T]` with `nullable=True`**: Required for nullable fields in xlang mode +3. **Enable ref tracking for shared objects**: Use `ref=True` when objects are shared or circular +4. **Use `ignore=True` for sensitive data**: Passwords, tokens, internal state +5. **Choose appropriate encoding**: `varint` for small values, `fixed` for full-range values +6. **Keep IDs stable**: Once assigned, don't change field IDs + +## Options Reference + +| Configuration | Description | +| ------------------------------------------ | ------------------------------------ | +| `pyfory.field(id=N)` | Field tag ID to reduce metadata size | +| `pyfory.field(nullable=True)` | Mark field as nullable | +| `pyfory.field(ref=True)` | Enable reference tracking | +| `pyfory.field(ignore=True)` | Exclude field from serialization | +| `pyfory.field(dynamic=True)` | Force type info to be written | +| `pyfory.field(dynamic=False)` | Skip type info (use declared type) | +| `Optional[T]` | Type hint for nullable fields | +| `pyfory.Int32`, `pyfory.Int64` | Signed integers (varint encoding) | +| `pyfory.FixedInt32`, `pyfory.FixedInt64` | Fixed-size signed | +| `pyfory.TaggedInt64` | Tagged encoding for int64 | +| `pyfory.UInt32`, `pyfory.UInt64` | Unsigned integers (varint encoding) | +| `pyfory.FixedUInt32`, `pyfory.FixedUInt64` | Fixed-size unsigned | +| `pyfory.TaggedUInt64` | Tagged encoding for uint64 | + +## Related Topics + +- [Basic Serialization](basic-serialization.md) - Getting started with Fory serialization +- [Schema Evolution](schema-evolution.md) - Compatible mode and schema evolution +- [Xlang Serialization](xlang-serialization.md) - Interoperability with Java, Rust, C++, Go diff --git a/versioned_docs/version-1.3.0/guide/python/troubleshooting.md b/versioned_docs/version-1.3.0/guide/python/troubleshooting.md new file mode 100644 index 00000000000..8dc56c160c4 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/python/troubleshooting.md @@ -0,0 +1,195 @@ +--- +title: Troubleshooting +sidebar_position: 14 +id: troubleshooting +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 page covers common issues and their solutions. + +## Common Issues + +### ImportError with Format Features + +```python +# Solution: Install Row format support +pip install pyfory[format] + +# Or install from source with format support +pip install -e ".[format]" +``` + +### Slow Serialization Performance + +```python +# Check if Cython acceleration is enabled +import pyfory +print(pyfory.ENABLE_FORY_CYTHON_SERIALIZATION) # Should be True + +# If False, Cython extension may not be compiled correctly +# Reinstall with: pip install --force-reinstall --no-cache-dir pyfory +``` + +### Cross-Language Compatibility Issues + +```python +# Use explicit type registration with consistent naming +f = pyfory.Fory(xlang=True) +f.register(MyClass, name="com.package.MyClass") # Use same name in all languages +``` + +### Circular Reference Errors or Duplicate Data + +Registered xlang schema objects and Python native objects both require reference tracking when +object identity or cycles matter: + +```python +# Enable reference tracking for registered schema objects. +f = pyfory.Fory(ref=True) +``` + +For arbitrary Python object graphs with circular references, use Python native mode: + +```python +f = pyfory.Fory(xlang=False, ref=True, strict=False) + +# Example with circular reference +class Node: + def __init__(self, value): + self.value = value + self.next = None + +node1 = Node(1) +node2 = Node(2) +node1.next = node2 +node2.next = node1 # Circular reference + +data = f.dumps(node1) +result = f.loads(data) +assert result.next.next is result # Circular reference preserved +``` + +### Schema Evolution Not Working + +```python +# Keep compatible mode enabled. This is the default. +f = pyfory.Fory() + +# Version 1: Original class +@dataclass +class User: + name: str + age: pyfory.Int32 + +f.register(User, name="User") +data = f.dumps(User("Alice", 30)) + +# Version 2: Add new field (backward compatible) +@dataclass +class User: + name: str + age: pyfory.Int32 + email: str = "unknown@example.com" # New field with default + +# Can still deserialize old data +user = f.loads(data) +print(user.email) # "unknown@example.com" +``` + +### Type Registration Errors in Strict Mode + +```python +# Register all custom types before serialization +f = pyfory.Fory(strict=True) + +# Must register before use +f.register(MyClass, type_id=100) +f.register(AnotherClass, type_id=101) + +# Or disable strict mode (NOT recommended for production) +f = pyfory.Fory(strict=False) # Use only in trusted environments +``` + +## Debug Mode + +Set environment variable BEFORE importing pyfory to disable Cython for debugging: + +```python +import os +os.environ['ENABLE_FORY_CYTHON_SERIALIZATION'] = '0' +import pyfory # Now uses pure Python implementation + +# This is useful for: +# 1. Debugging protocol issues +# 2. Understanding serialization behavior +# 3. Development without recompiling Cython +``` + +## Error Handling + +Handle common serialization errors gracefully: + +```python +import pyfory +from pyfory.error import TypeUnregisteredError, TypeNotCompatibleError + +fory = pyfory.Fory(strict=True) + +try: + data = fory.dumps(my_object) +except TypeUnregisteredError as e: + print(f"Type not registered: {e}") + # Register the type and retry + fory.register(type(my_object), type_id=100) + data = fory.dumps(my_object) +except Exception as e: + print(f"Serialization failed: {e}") + +try: + obj = fory.loads(data) +except TypeNotCompatibleError as e: + print(f"Schema mismatch: {e}") + # Handle version mismatch +except Exception as e: + print(f"Deserialization failed: {e}") +``` + +## Development Setup + +```bash +git clone https://github.com/apache/fory.git +cd fory/python + +# Install dependencies +pip install -e ".[dev,format]" + +# Run tests +pytest -v -s . + +# Run specific test +pytest -v -s pyfory/tests/test_serializer.py + +# Format code +ruff format . +ruff check --fix . +``` + +## Related Topics + +- [Configuration](configuration.md) - Fory parameters +- [Type Registration](type-registration.md) - Registration best practices +- [Configuration](configuration.md#security) - Security configuration diff --git a/versioned_docs/version-1.3.0/guide/python/type-registration.md b/versioned_docs/version-1.3.0/guide/python/type-registration.md new file mode 100644 index 00000000000..9e5589595b2 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/python/type-registration.md @@ -0,0 +1,85 @@ +--- +title: Type Registration +sidebar_position: 5 +id: type_registration +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 page covers Python type registration APIs. Use [Configuration](configuration.md#security) for +strict-mode policy, max-depth limits, and trusted-data guidance. + +## Type Registration + +Register xlang classes by type name so other languages can resolve the same +schema identity: + +```python +from dataclasses import dataclass +import pyfory + +fory = pyfory.Fory(xlang=True, strict=True) + +@dataclass +class User: + name: str + age: pyfory.Int32 + +fory.register(User, name="example.User") +``` + +For Python native mode, numeric type IDs are the compact same-language +registration path: + +```python +import pyfory + +fory = pyfory.Fory(xlang=False, strict=True) +fory.register(MyClass, type_id=100) +``` + +## Registration Patterns + +Use the registration form that matches the payload contract: + +```python +# Xlang: stable name identity +fory.register(MyClass, name="com.example.MyClass") + +# Native mode: compact numeric identity +fory.register(MyClass, type_id=100) + +# Custom serializer +fory.register(MyClass, type_id=100, serializer=MySerializer(fory.type_resolver, MyClass)) + +# Batch registration +type_id = 100 +for model_class in [User, Order, Product, Invoice]: + fory.register(model_class, type_id=type_id) + type_id += 1 +``` + +## Strict Mode Relationship + +With `strict=True`, deserialization accepts only registered types. Register all +application classes before serializing or deserializing payloads, and keep the +same registration IDs or names on every peer that shares those payloads. + +## Related Topics + +- [Configuration](configuration.md) - Fory parameters +- [Configuration](configuration.md#security) - Strict mode, deserialization policies, and maximum read depth +- [Custom Serializers](custom-serializers.md) - Custom serialization diff --git a/versioned_docs/version-1.3.0/guide/python/xlang-serialization.md b/versioned_docs/version-1.3.0/guide/python/xlang-serialization.md new file mode 100644 index 00000000000..34cec4048d6 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/python/xlang-serialization.md @@ -0,0 +1,215 @@ +--- +title: Xlang Serialization +sidebar_position: 2 +id: xlang_serialization +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. +--- + +`pyfory` supports xlang object graph serialization, allowing you to serialize +data in Python and deserialize it in Java, C++, Go, Rust, +JavaScript/TypeScript, C#, Swift, Dart, Scala, Kotlin, or another supported +language. + +## Create an Xlang Fory Instance + +Python defaults to xlang mode with compatible schema evolution. Set the mode explicitly in xlang examples: + +```python +import pyfory +fory = pyfory.Fory(xlang=True, ref=False, strict=True) +``` + +## Xlang Example + +### Python (Serializer) + +```python +import pyfory +from dataclasses import dataclass + +f = pyfory.Fory(xlang=True, ref=True) + +# Register type for xlang compatibility +@dataclass +class Person: + name: str + age: pyfory.Int32 + +f.register(Person, name="example.Person") + +person = Person("Charlie", 35) +binary_data = f.serialize(person) +# binary_data can now be sent to Java, Go, etc. +``` + +### Java (Deserializer) + +```java +import org.apache.fory.*; + +public class Person { + public String name; + public int age; +} + +Fory fory = Fory.builder() + .withXlang(true) + .withRefTracking(true) + .build(); + +fory.register(Person.class, "example.Person"); +Person person = (Person) fory.deserialize(binaryData); +``` + +### Rust (Deserializer) + +```rust +use fory::Fory; +use fory::ForyStruct; + +#[derive(ForyStruct)] +struct Person { + name: String, + age: i32, +} + +let mut fory = Fory::builder().xlang(true).build(); + +fory.register_by_name::("example.Person"); +let person: Person = fory.deserialize(&binary_data)?; +``` + +## Type Annotations for Xlang + +Use pyfory type annotations for explicit xlang type mapping: + +Use these markers directly in Python type annotations. Field values remain +ordinary Python `int` or `float` values, and Fory serializes them with the +requested xlang numeric width and encoding. + +```python +from dataclasses import dataclass +from typing import Dict, List +import pyfory + +@dataclass +class TypedData: + int_value: pyfory.Int32 # 32-bit integer + long_value: pyfory.Int64 # 64-bit integer + float_value: pyfory.Float32 # 32-bit float + double_value: pyfory.Float64 # 64-bit float + values: Dict[pyfory.Int32, List[pyfory.Int64]] +``` + +Nested collection annotations are part of the field schema. Compatible-mode +reads consume bytes with the remote schema metadata, then assign only when the +decoded value safely satisfies the local schema. + +## Reduced-Precision Types + +`pyfory.Float16` and `pyfory.BFloat16` are reserved annotation markers for xlang +reduced-precision fields. They are not value wrapper classes; scalar values deserialize as native +Python `float`. + +Dense reduced-precision arrays use public dense wrappers with list-like sequence behavior. Construct them from Python +numeric values with `pyfory.Float16Array.from_values([...])` or +`pyfory.BFloat16Array.from_values([...])`. Use `from_buffer(...)` and `to_buffer()` only when you +already need packed little-endian `uint16` storage and want the raw-buffer fast path. + +## Type Mapping + +| Python marker/carrier | Java | Rust | Go | +| ---------------------- | -------------- | --------------- | --------------------- | +| `str` | `String` | `String` | `string` | +| `int` | `long` | `i64` | `int64` | +| `pyfory.Int32` | `int` | `i32` | `int32` | +| `pyfory.Int64` | `long` | `i64` | `int64` | +| `float` | `double` | `f64` | `float64` | +| `pyfory.Float32` | `float` | `f32` | `float32` | +| `pyfory.Float16` | `Float16` | `Float16` | `float16.Float16` | +| `pyfory.BFloat16` | `BFloat16` | `BFloat16` | `bfloat16.BFloat16` | +| `pyfory.Float16Array` | `Float16List` | `Vec` | `[]float16.Float16` | +| `pyfory.BFloat16Array` | `BFloat16List` | `Vec` | `[]bfloat16.BFloat16` | +| `list` | `List` | `Vec` | `[]T` | +| `dict` | `Map` | `HashMap` | `map[K]V` | + +### Lists and Dense Arrays + +Python `List[T]` maps to Fory `list`. Use `pyfory.Array[T]`, +`pyfory.NDArray[T]`, or `pyfory.PyArray[T]` only when the schema is the dense +one-dimensional `array` kind. + +| Fory schema | Python annotation and default carrier | +| ----------------- | -------------------------------------------------- | +| `list` | `List[pyfory.Int32]` | +| `array` | `pyfory.Array[bool]` -> `BoolArray` | +| `array` | `pyfory.Array[pyfory.Int8]` -> `Int8Array` | +| `array` | `pyfory.Array[pyfory.Int16]` -> `Int16Array` | +| `array` | `pyfory.Array[pyfory.Int32]` -> `Int32Array` | +| `array` | `pyfory.Array[pyfory.Int64]` -> `Int64Array` | +| `array` | `pyfory.Array[pyfory.UInt8]` -> `UInt8Array` | +| `array` | `pyfory.Array[pyfory.UInt16]` -> `UInt16Array` | +| `array` | `pyfory.Array[pyfory.UInt32]` -> `UInt32Array` | +| `array` | `pyfory.Array[pyfory.UInt64]` -> `UInt64Array` | +| `array` | `pyfory.Array[pyfory.Float16]` -> `Float16Array` | +| `array` | `pyfory.Array[pyfory.BFloat16]` -> `BFloat16Array` | +| `array` | `pyfory.Array[pyfory.Float32]` -> `Float32Array` | +| `array` | `pyfory.Array[pyfory.Float64]` -> `Float64Array` | + +The `pyfory.*Array` wrappers accept iterable constructors such as +`pyfory.Float32Array([1, 2, 3])` and expose list-like sequence behavior over +dense owned storage. + +`pyfory.Array[T]`, `pyfory.NDArray[T]`, and `pyfory.PyArray[T]` all describe +the same Fory `array` schema. They differ only in the Python carrier +contract: + +| Python field annotation | Value accepted for that field | Deserialized carrier | +| ----------------------- | ------------------------------------------------------- | -------------------- | +| `pyfory.Array[T]` | `pyfory.*Array`, `numpy.ndarray`, `array.array`, `list` | `pyfory.*Array` | +| `pyfory.NDArray[T]` | `numpy.ndarray` | `numpy.ndarray` | +| `pyfory.PyArray[T]` | Python `array.array` | Python `array.array` | + +In compatible mode, a writer and reader can use different Python carriers for +the same named field as long as both annotations lower to the same Fory +`array` schema. For example, a writer field declared as +`pyfory.Array[pyfory.Int32]` can be read by a Python class whose matching field +is declared as `pyfory.NDArray[pyfory.Int32]`, and the reader receives a NumPy +`int32` ndarray. The reverse pattern also works for `pyfory.PyArray[T]`; that +name always means Python `array.array`. + +PyArrow is a separate row/columnar format surface, not a `pyfory.PyArray` +carrier. Use `pyfory.format.from_arrow_schema(...)` and +`pyfory.format.to_arrow_schema(...)` to convert between PyArrow schemas and +Fory row-format schemas. + +## Differences from Python Native Mode + +The binary protocol and API are similar to `pyfory`'s Python native mode, but Python native mode can serialize any Python object—including global functions, local functions, lambdas, local classes, and types with customized serialization using `__getstate__/__reduce__/__reduce_ex__`, which are **not allowed** in xlang mode. + +## See Also + +- [Xlang Serialization Specification](../../specification/xlang_serialization_spec.md) +- [Type Mapping Reference](../../specification/xlang_type_mapping.md) +- [Java Xlang Serialization Guide](../java/xlang-serialization.md) +- [Rust Xlang Serialization Guide](../rust/xlang-serialization.md) + +## Related Topics + +- [Configuration](configuration.md) - xlang mode settings +- [Schema Evolution](schema-evolution.md) - Compatible mode +- [Type Registration](type-registration.md) - Registration patterns diff --git a/versioned_docs/version-1.3.0/guide/rust/_category_.json b/versioned_docs/version-1.3.0/guide/rust/_category_.json new file mode 100644 index 00000000000..e891086214a --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/rust/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "Rust", + "position": 3, + "collapsible": true, + "collapsed": true +} diff --git a/versioned_docs/version-1.3.0/guide/rust/basic-serialization.md b/versioned_docs/version-1.3.0/guide/rust/basic-serialization.md new file mode 100644 index 00000000000..8c2c2262075 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/rust/basic-serialization.md @@ -0,0 +1,195 @@ +--- +title: Basic Serialization +sidebar_position: 1 +id: basic_serialization +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 page covers basic object graph serialization and supported types. + +## Object Graph Serialization + +Apache Fory™ provides automatic serialization of complex object graphs, preserving the structure and relationships between objects. The `#[derive(ForyStruct)]` macro generates efficient serialization code at compile time, eliminating reflection overhead. + +**Key capabilities:** + +- Nested struct serialization with arbitrary depth +- Collection types (Vec, HashMap, HashSet, BTreeMap) +- Optional fields with `Option` +- Automatic handling of primitive types and strings +- Efficient binary encoding with variable-length integers + +```rust +use fory::{Fory, Error}; +use fory::ForyStruct; +use std::collections::HashMap; + +#[derive(ForyStruct, Debug, PartialEq)] +struct Person { + name: String, + age: i32, + address: Address, + hobbies: Vec, + metadata: HashMap, +} + +#[derive(ForyStruct, Debug, PartialEq)] +struct Address { + street: String, + city: String, + country: String, +} + +let mut fory = Fory::builder().xlang(true).build(); +fory.register_by_name::
("example.Address").unwrap(); +fory.register_by_name::("example.Person").unwrap(); + +let person = Person { + name: "John Doe".to_string(), + age: 30, + address: Address { + street: "123 Main St".to_string(), + city: "New York".to_string(), + country: "USA".to_string(), + }, + hobbies: vec!["reading".to_string(), "coding".to_string()], + metadata: HashMap::from([ + ("role".to_string(), "developer".to_string()), + ]), +}; + +let bytes = fory.serialize(&person).unwrap(); +let decoded: Person = fory.deserialize(&bytes)?; +assert_eq!(person, decoded); +``` + +## Supported Types + +### Primitive Types + +| Rust Type | Description | +| ------------------------- | --------------------------- | +| `bool` | Boolean | +| `i8`, `i16`, `i32`, `i64` | Signed integers | +| `f32`, `f64` | Floating point | +| `BFloat16` | 16-bit brain floating point | +| `String` | UTF-8 string | + +### Collections + +| Rust Type | Description | +| ---------------- | ------------------ | +| `Vec` | Dynamic array | +| `VecDeque` | Double-ended queue | +| `LinkedList` | Doubly-linked list | +| `HashMap` | Hash map | +| `BTreeMap` | Ordered map | +| `HashSet` | Hash set | +| `BTreeSet` | Ordered set | +| `BinaryHeap` | Binary heap | +| `Option` | Optional value | + +`Vec` is the dense carrier when the schema is `array`. + +### Smart Pointers + +| Rust Type | Description | +| ------------ | ---------------------------------------------------- | +| `Box` | Heap allocation | +| `Rc` | Reference counting (shared refs tracked) | +| `Arc` | Thread-safe reference counting (shared refs tracked) | +| `RcWeak` | Weak reference to `Rc` (breaks circular refs) | +| `ArcWeak` | Weak reference to `Arc` (breaks circular refs) | +| `RefCell` | Interior mutability (runtime borrow checking) | +| `Mutex` | Thread-safe interior mutability | + +### Date and Time + +| Rust Type | Description | +| ----------- | ------------------------------------------------------- | +| `Date` | Date without timezone, stored as epoch days | +| `Timestamp` | Point in time, stored as epoch seconds and nanos | +| `Duration` | Signed duration, stored as seconds and normalized nanos | + +The built-in carriers expose dependency-free constructors, accessors, conversions, and checked +arithmetic: + +```rust +use fory::{Date, Duration, Timestamp}; + +let date = Date::from_epoch_days(19_782); +assert_eq!(date.checked_add_days(1)?.epoch_days(), 19_783); + +let timestamp = Timestamp::from_epoch_millis(-1); +assert_eq!(timestamp.to_epoch_millis()?, -1); + +let duration = Duration::from_parts(1, 1_500_000_000)?; +assert_eq!(duration.to_millis()?, 2_500); +let later = timestamp.checked_add_duration(duration)?; +``` + +`chrono::NaiveDate`, `chrono::NaiveDateTime`, and `chrono::Duration` are supported when the Rust +`chrono` feature is enabled: + +```toml +[dependencies] +fory = { version = "1.3.0", features = ["chrono"] } +``` + +### Custom Types + +| Macro | Description | +| ----------------------- | -------------------------- | +| `#[derive(ForyStruct)]` | Object graph serialization | +| `#[derive(ForyRow)]` | Row-based serialization | + +## Serialization APIs + +```rust +use fory::{Fory, Reader}; + +let mut fory = Fory::builder().xlang(true).build(); +fory.register::(1)?; + +let obj = MyStruct { /* ... */ }; + +// Basic serialize/deserialize +let bytes = fory.serialize(&obj)?; +let decoded: MyStruct = fory.deserialize(&bytes)?; + +// Serialize to existing buffer +let mut buf: Vec = vec![]; +fory.serialize_to(&mut buf, &obj)?; + +// Deserialize from reader +let mut reader = Reader::new(&buf); +let decoded: MyStruct = fory.deserialize_from(&mut reader)?; +``` + +## Performance Tips + +- **Zero-Copy Deserialization**: Row format enables direct memory access without copying +- **Buffer Pre-allocation**: Minimizes memory allocations during serialization +- **Compact Encoding**: Variable-length encoding for space efficiency +- **Little-Endian**: Optimized for modern CPU architectures +- **Reference Deduplication**: Shared objects serialized only once + +## Related Topics + +- [Type Registration](type-registration.md) - Registering types +- [References](references.md) - Shared and circular references +- [Custom Serializers](custom-serializers.md) - Manual serialization diff --git a/versioned_docs/version-1.3.0/guide/rust/configuration.md b/versioned_docs/version-1.3.0/guide/rust/configuration.md new file mode 100644 index 00000000000..58bd0705670 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/rust/configuration.md @@ -0,0 +1,180 @@ +--- +title: Configuration +sidebar_position: 4 +id: configuration +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 page covers Rust Fory instance configuration. `Fory::builder().xlang(true).build()` selects xlang mode with +compatible schema evolution. Native mode is selected explicitly with `.xlang(false)` and also defaults to +compatible schema evolution. + +## Wire Modes + +Apache Fory™ supports two serialization modes: + +### Xlang Mode + +Xlang mode is selected with `.xlang(true)` and uses the cross-language wire +format. Compatible schema evolution is the xlang default and is recommended for +cross-language services because schemas can diverge more easily across +languages: + +```rust +let fory = Fory::builder().xlang(true).build(); +``` + +Use `.compatible(false)` for xlang payloads only when every reader and writer always uses the same schema and you want faster serialization and smaller size. Use it only after verifying that every language uses that schema, or when native types are generated from Fory schema IDL: + +```rust +let fory = Fory::builder().compatible(false).build(); +``` + +### Native Mode + +For Rust-only payloads, select native mode explicitly: + +```rust +let fory = Fory::builder().xlang(false).build(); +``` + +Compatible mode is enabled by default. Set `.compatible(false)` only when every reader and +writer always uses the same Rust schema and you want faster serialization and smaller size. + +## Configuration + +### Maximum Dynamic Object Nesting Depth + +Apache Fory™ provides protection against stack overflow from deeply nested dynamic objects during deserialization. By default, the maximum nesting depth is set to 5 levels for trait objects and containers. + +**Default configuration:** + +```rust +let fory = Fory::builder().build(); // max_dyn_depth = 5 +``` + +**Custom depth limit:** + +```rust +let fory = Fory::builder().max_dyn_depth(10).build(); // Allow up to 10 levels +``` + +**When to adjust:** + +- **Increase**: For legitimate deeply nested data structures +- **Decrease**: For stricter security requirements or shallow data structures + +**Protected types:** + +- `Box`, `Rc`, `Arc` +- `Box`, `Rc`, `Arc` (trait objects) +- `RcWeak`, `ArcWeak` +- Collection types (Vec, HashMap, HashSet) +- Nested struct types in Compatible mode + +Note: Static data types (non-dynamic types) are secure by nature and not subject to depth limits, as their structure is known at compile time. + +### Remote Schema Metadata Limits + +Compatible mode can receive remote metadata for schema evolution. These limits +bound metadata size and accepted schema versions: + +```rust +let fory = Fory::builder() + .max_type_fields(512) + .max_type_meta_bytes(4096) + .max_schema_versions_per_type(10) + .max_average_schema_versions_per_type(3) + .build(); +``` + +- `max_type_fields` defaults to `512` and limits fields in one received struct metadata body. +- `max_type_meta_bytes` defaults to `4096` and limits encoded body bytes in one received TypeDef or + TypeMeta body, excluding the 8-byte header and any extended-size varint. +- `max_schema_versions_per_type` defaults to `10` and limits accepted remote metadata versions for + one logical type. +- `max_average_schema_versions_per_type` defaults to `3` and limits the average across accepted + remote types. The effective global floor is `8192` schemas. + +### Explicit Xlang Examples + +Set `.xlang(true)` explicitly for xlang serialization examples: + +```rust +let fory = Fory::builder().xlang(true).build(); +``` + +## Builder Pattern + +```rust +use fory::Fory; + +// Default xlang configuration +let fory = Fory::builder().build(); + +// Native mode for Rust-only traffic +let fory = Fory::builder().xlang(false).build(); + +// Same-schema optimization for Rust-only payloads +let fory = Fory::builder().xlang(false).compatible(false).build(); + +// Custom depth limit +let fory = Fory::builder().max_dyn_depth(10).build(); + +// Combined configuration +let fory = Fory::builder() + .xlang(false) + .max_dyn_depth(10) + .build(); +``` + +## Configuration Summary + +| Option | Description | Default | +| --------------------------------------------- | ------------------------------------------------- | ------- | +| `compatible(bool)` | Enable schema evolution | `true` | +| `xlang(bool)` | Use xlang mode | `true` | +| `max_dyn_depth(u32)` | Maximum nesting depth for dynamic types | `5` | +| `max_type_fields(usize)` | Max fields in one received struct metadata body | `512` | +| `max_type_meta_bytes(usize)` | Max encoded bytes in one received metadata body | `4096` | +| `max_schema_versions_per_type(usize)` | Max remote metadata versions for one logical type | `10` | +| `max_average_schema_versions_per_type(usize)` | Average remote metadata versions across types | `3` | + +## Compatible Mode + +Compatible mode is enabled by default for both xlang and native mode. Keep this default when Rust +structs may evolve independently, when services deploy separately, or when xlang schemas are written +by hand in different languages. + +Use `.compatible(false)` only when the schema used to deserialize every payload is always the same as the schema used to serialize it and you want faster serialization and smaller size. For xlang payloads, use `.compatible(false)` only after verifying that every language uses the same schema, or when native types are generated from Fory schema IDL. + +## Security + +Security-related configuration: + +- Register application structs and trait-object implementations before deserializing untrusted + payloads. +- Use `max_dyn_depth(...)` to reject unexpectedly deep dynamic object graphs. +- Keep the remote schema metadata limits at their defaults unless the data is not malicious and a + trusted peer sends larger metadata or many schema versions. +- Prefer concrete typed fields over `dyn Any` or broad trait-object fields for untrusted input. + +## Related Topics + +- [Basic Serialization](basic-serialization.md) - Using configured Fory +- [Schema Evolution](schema-evolution.md) - Compatible mode details +- [Xlang Serialization](xlang-serialization.md) - xlang mode diff --git a/versioned_docs/version-1.3.0/guide/rust/custom-serializers.md b/versioned_docs/version-1.3.0/guide/rust/custom-serializers.md new file mode 100644 index 00000000000..e5c273e0ab8 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/rust/custom-serializers.md @@ -0,0 +1,176 @@ +--- +title: Custom Serializers +sidebar_position: 10 +id: custom_serializers +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. +--- + +For types that don't support `#[derive(ForyStruct)]`, implement the `Serializer` trait manually. + +## When to Use Custom Serializers + +- External types from other crates +- Types with special serialization requirements +- Existing data format compatibility +- Performance-critical custom encoding + +## Implementing the Serializer Trait + +```rust +use fory::{Error, Fory, ForyDefault, ReadContext, Serializer, TypeResolver, WriteContext}; +use std::any::Any; + +#[derive(Debug, PartialEq, Default)] +struct CustomType { + value: i32, + name: String, +} + +impl Serializer for CustomType { + fn fory_write_data(&self, context: &mut WriteContext) -> Result<(), Error> { + context.writer.write_i32(self.value); + context.writer.write_var_u32(self.name.len() as u32); + context.writer.write_utf8_string(&self.name); + Ok(()) + } + + fn fory_read_data(context: &mut ReadContext) -> Result { + let value = context.reader.read_i32()?; + let len = context.reader.read_var_u32()? as usize; + let name = context.reader.read_utf8_string(len)?; + Ok(Self { value, name }) + } + + fn fory_type_id_dyn(&self, type_resolver: &TypeResolver) -> Result { + Self::fory_get_type_id(type_resolver) + } + + fn as_any(&self) -> &dyn Any { + self + } +} + +// ForyDefault delegates to Default +impl ForyDefault for CustomType { + fn fory_default() -> Self { + Self::default() + } +} +``` + +> **Note**: When implementing `ForyDefault` manually, ensure your type also implements `Default` if you use `Self::default()`. +> Alternatively, you can construct a default instance directly in `fory_default()`. +> +> **Tip**: If your type supports `#[derive(ForyStruct)]`, you can use `#[fory(generate_default)]` to automatically generate both `ForyDefault` and `Default` implementations. + +## Manual Serializers and Arc Any + +If a manually registered serializer needs its type to round-trip behind +`Arc` or preserve `UnknownCase` payloads, implement the +send-sync Any reader and return the concrete value as a boxed `Any` value: + +```rust +impl Serializer for CustomType { + fn fory_read_data_as_send_sync_any( + context: &mut ReadContext, + ) -> Result, Error> { + Ok(Box::new(Self::fory_read_data(context)?)) + } + + // Implement the ordinary Serializer methods as shown above. + // ... +} +``` + +Do not override this method for values that contain fields whose types are not +`Send + Sync`, such as `Rc`, `RcWeak`, `RefCell`, or `Cell`. + +## Registering Custom Serializers + +```rust +let mut fory = Fory::builder().xlang(false).build(); +fory.register_serializer::(100)?; + +let custom = CustomType { + value: 42, + name: "test".to_string(), +}; +let bytes = fory.serialize(&custom)?; +let decoded: CustomType = fory.deserialize(&bytes)?; +assert_eq!(custom, decoded); +``` + +## WriteContext and ReadContext + +The `WriteContext` and `ReadContext` provide access to: + +- **writer/reader**: Binary buffer operations +- **type_resolver**: Type registration information +- **ref_resolver**: Reference tracking (for shared/circular references) + +### Common Writer Methods + +```rust +// Primitive types +context.writer.write_i8(value); +context.writer.write_i16(value); +context.writer.write_i32(value); +context.writer.write_i64(value); +context.writer.write_f32(value); +context.writer.write_f64(value); +context.writer.write_bool(value); + +// Variable-length integers +context.writer.write_var_i32(value); +context.writer.write_var_u32(value); + +// Strings +context.writer.write_utf8_string(&string); +``` + +### Common Reader Methods + +```rust +// Primitive types +let value = context.reader.read_i8(); +let value = context.reader.read_i16(); +let value = context.reader.read_i32(); +let value = context.reader.read_i64(); +let value = context.reader.read_f32(); +let value = context.reader.read_f64(); +let value = context.reader.read_bool(); + +// Variable-length integers +let value = context.reader.read_var_i32(); +let value = context.reader.read_var_u32(); + +// Strings +let string = context.reader.read_utf8_string(len); +``` + +## Best Practices + +1. **Use variable-length encoding** for integers that may be small +2. **Write length first** for variable-length data +3. **Handle errors properly** in read methods +4. **Implement ForyDefault** for schema evolution support + +## Related Topics + +- [Type Registration](type-registration.md) - Registering serializers +- [Basic Serialization](basic-serialization.md) - Using ForyStruct derive +- [Schema Evolution](schema-evolution.md) - Compatible mode diff --git a/versioned_docs/version-1.3.0/guide/rust/grpc-support.md b/versioned_docs/version-1.3.0/guide/rust/grpc-support.md new file mode 100644 index 00000000000..f2b3ea81497 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/rust/grpc-support.md @@ -0,0 +1,338 @@ +--- +title: gRPC Support +sidebar_position: 12 +id: grpc_support +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. +--- + +Fory can generate Rust gRPC service companions for schemas that define +services. The generated code uses `tonic` for transport and Fory for request and +response payload serialization. + +Use this mode when every RPC peer is generated from the same Fory IDL, protobuf +IDL, or FlatBuffers IDL and you want gRPC transport semantics with Fory payload +encoding. Use standard protobuf gRPC code generation when clients or tools must +consume protobuf message bytes directly. + +## Add Dependencies + +Add `tonic` and `bytes` to the crate that compiles the generated service files. +Fory Rust crates do not add gRPC as a hard dependency. Add `tokio` for async +servers and clients, and `tokio-stream` when your service implementation needs +to build streaming responses or request streams. + +```toml +[dependencies] +fory = "1.3.0" +bytes = "1" +tonic = { version = "0.14", features = ["transport"] } +tokio = { version = "1", features = ["macros", "rt-multi-thread"] } +tokio-stream = "0.1" +``` + +Use dependency versions that are compatible with the rest of your service +stack. + +## Define a Service + +Service definitions can come from Fory IDL, protobuf IDL, or FlatBuffers +`rpc_service` definitions. A Fory IDL service looks like this: + +```protobuf +package demo.greeter; + +message HelloRequest { + string name = 1; +} + +message HelloReply { + string reply = 1; +} + +service Greeter { + rpc SayHello (HelloRequest) returns (HelloReply); +} +``` + +Generate Rust model and gRPC companion code with `--grpc`: + +```bash +foryc service.fdl --rust_out=./generated/rust --grpc +``` + +For this schema, the Rust generator emits: + +| File | Purpose | +| ------------------------------ | -------------------------------------------- | +| `demo_greeter.rs` | Fory model types and registration helpers | +| `demo_greeter_service.rs` | Async service trait and gRPC path constants | +| `demo_greeter_service_grpc.rs` | tonic client, server wrapper, and Fory codec | + +Add the generated files to your crate root: + +```rust +pub mod demo_greeter; +pub mod demo_greeter_service; +pub mod demo_greeter_service_grpc; +``` + +## Implement a Server + +Implement the generated async trait and add the generated server wrapper to a +normal `tonic` server. + +```rust +use demo_greeter::{HelloReply, HelloRequest}; +use demo_greeter_service::Greeter; +use demo_greeter_service_grpc::greeter_server::GreeterServer; +use tonic::{Request, Response, Status}; + +#[derive(Default)] +struct MyGreeter; + +#[tonic::async_trait] +impl Greeter for MyGreeter { + async fn say_hello( + &self, + request: Request, + ) -> Result, Status> { + let request = request.into_inner(); + Ok(Response::new(HelloReply { + reply: format!("Hello, {}", request.name), + })) + } +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + let addr = "[::1]:50051".parse()?; + tonic::transport::Server::builder() + .add_service(GreeterServer::new(MyGreeter::default())) + .serve(addr) + .await?; + Ok(()) +} +``` + +Generated request and response types are serialized by the generated service +code, so service implementations do not perform manual Fory registration. + +## Create a Client + +Use the generated tonic client: + +```rust +use demo_greeter::HelloRequest; +use demo_greeter_service_grpc::greeter_client::GreeterClient; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let mut client = GreeterClient::connect("http://[::1]:50051").await?; + let response = client + .say_hello(HelloRequest { + name: "Fory".to_string(), + }) + .await?; + println!("{}", response.into_inner().reply); + Ok(()) +} +``` + +`tonic` still owns channel configuration, TLS, deadlines, metadata, +interceptors, and transport lifecycle. + +## Streaming RPCs + +Fory service definitions can use unary, server-streaming, client-streaming, and +bidirectional streaming RPC shapes: + +```protobuf +service Greeter { + rpc SayHello (HelloRequest) returns (HelloReply); + rpc LotsOfReplies (HelloRequest) returns (stream HelloReply); + rpc LotsOfGreetings (stream HelloRequest) returns (HelloReply); + rpc Chat (stream HelloRequest) returns (stream HelloReply); +} +``` + +Generated Rust code follows tonic conventions: + +- Unary methods use `tonic::Request` and return `tonic::Response`. +- Server-streaming methods return a response whose inner value is a stream of + `Result`. +- Client-streaming and bidirectional methods receive `tonic::Streaming`. +- The generated client module exposes matching async methods for each service + method. +- The generated codec is used for every message frame, including streaming + frames. + +Use the generated trait signatures as the source of truth for concrete +associated stream types in your service implementation: + +```rust +use demo_greeter::{HelloReply, HelloRequest}; +use demo_greeter_service::Greeter; +use std::pin::Pin; +use tokio_stream::{self as stream, Stream, StreamExt}; +use tonic::{Request, Response, Status}; + +#[derive(Default)] +struct MyGreeter; + +type ReplyStream = + Pin> + Send + 'static>>; + +#[tonic::async_trait] +impl Greeter for MyGreeter { + type LotsOfRepliesStream = ReplyStream; + type ChatStream = ReplyStream; + + async fn lots_of_replies( + &self, + request: Request, + ) -> Result, Status> { + let name = request.into_inner().name; + let replies = vec![ + Ok(HelloReply { + reply: format!("Hello, {name}"), + }), + Ok(HelloReply { + reply: format!("Welcome, {name}"), + }), + ]; + Ok(Response::new(Box::pin(stream::iter(replies)))) + } + + async fn lots_of_greetings( + &self, + request: Request>, + ) -> Result, Status> { + let mut requests = request.into_inner(); + let mut names = Vec::new(); + while let Some(request) = requests.next().await { + names.push(request?.name); + } + Ok(Response::new(HelloReply { + reply: names.join(", "), + })) + } + + async fn chat( + &self, + request: Request>, + ) -> Result, Status> { + let replies = request.into_inner().map(|request| { + request.map(|request| HelloReply { + reply: format!("Hello, {}", request.name), + }) + }); + Ok(Response::new(Box::pin(replies))) + } +} +``` + +Generated clients return tonic streaming responses: + +```rust +use demo_greeter::HelloRequest; +use demo_greeter_service_grpc::greeter_client::GreeterClient; +use tokio_stream as stream; + +let mut client = GreeterClient::connect("http://[::1]:50051").await?; + +let mut replies = client + .lots_of_replies(HelloRequest { + name: "Fory".to_string(), + }) + .await? + .into_inner(); +while let Some(reply) = replies.message().await? { + println!("{}", reply.reply); +} + +let greetings = stream::iter(vec![ + HelloRequest { + name: "Ada".to_string(), + }, + HelloRequest { + name: "Grace".to_string(), + }, +]); +let summary = client.lots_of_greetings(greetings).await?.into_inner(); +println!("{}", summary.reply); + +let chat_requests = stream::iter(vec![ + HelloRequest { + name: "Fory".to_string(), + }, + HelloRequest { + name: "RPC".to_string(), + }, +]); +let mut chat = client.chat(chat_requests).await?.into_inner(); +while let Some(reply) = chat.message().await? { + println!("{}", reply.reply); +} +``` + +The generated descriptors preserve the exact IDL service and method names for +the gRPC path. + +## Thread Safety and Payload Types + +Generated Rust gRPC payloads must be `Send + 'static` so tonic can move request +and response values across async tasks. If a schema uses non-thread-safe +reference metadata for a request or response type, Rust gRPC generation rejects +that service. Use thread-safe reference shapes for gRPC payloads, or keep the +non-thread-safe type out of the RPC boundary. + +## gRPC Runtime Behavior + +The generated service companion only supplies Fory serialization and tonic +bindings. Operational behavior remains standard tonic behavior: + +- Deadlines and cancellations +- TLS and authentication +- Tower middleware and interceptors +- Status codes and metadata +- Channel and server lifecycle +- Backpressure through async streams + +## Troubleshooting + +### Missing `tonic` or `bytes` Crates + +Add the dependencies shown above to the crate that compiles the generated +service files. + +### `UNIMPLEMENTED` + +Confirm that the generated server wrapper was added with +`Server::builder().add_service(...)`, and that the client and server were +generated from the same package, service, and method names. + +### Non-Thread-Safe Reference Errors During Code Generation + +Rust gRPC payloads must be `Send + 'static`. Change the request or response +schema to use thread-safe reference shapes, or wrap the non-thread-safe data in a +type that is not part of the gRPC payload. + +### Protobuf Clients Cannot Decode the Service + +Fory gRPC companions do not use protobuf wire encoding for messages. Use a +Fory-generated client for Fory-generated services, or provide a separate +protobuf service endpoint for generic protobuf clients. diff --git a/versioned_docs/version-1.3.0/guide/rust/index.md b/versioned_docs/version-1.3.0/guide/rust/index.md new file mode 100644 index 00000000000..7129c6a67bf --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/rust/index.md @@ -0,0 +1,201 @@ +--- +title: Rust Serialization Guide +sidebar_position: 0 +id: serialization_index +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. +--- + +**Apache Fory™** is a blazing fast multi-language serialization framework powered by **JIT compilation** and **zero-copy** techniques, providing up to **ultra-fast performance** while maintaining ease of use and safety. + +The Rust implementation provides versatile and high-performance serialization with automatic memory management and compile-time type safety. It supports both xlang mode for cross-language payloads and native mode for Rust-only payloads. + +## Why Apache Fory™ Rust? + +- **Fast binary encoding**: Zero-copy deserialization and optimized binary protocols +- **Xlang**: Seamlessly serialize/deserialize data across Java, Python, C++, + Go, Rust, JavaScript/TypeScript, C#, Swift, Dart, Scala, and Kotlin +- **Type-safe**: Compile-time type checking with derive macros +- **Circular references**: Automatic tracking of shared and circular references with `Rc`/`Arc` and weak pointers +- **Polymorphic**: Serialize trait objects with `Box`, `Rc`, and `Arc` +- **Schema evolution**: Compatible mode for independent schema changes +- **Two formats**: Object graph serialization and zero-copy row-based format + +## Crates + +| Crate | Description | Version | +| --------------------------------------------------------------------------- | --------------------------------------------------------- | --------------------------------------------- | +| [`fory`](https://github.com/apache/fory/blob/main/rust/fory) | User-facing API, runtime types, and derive macros | [1.3.0](https://crates.io/crates/fory) | +| [`fory-core`](https://github.com/apache/fory/blob/main/rust/fory-core/) | Lower-level runtime crate for advanced integrations | [1.3.0](https://crates.io/crates/fory-core) | +| [`fory-derive`](https://github.com/apache/fory/blob/main/rust/fory-derive/) | Lower-level procedural macro crate for direct runtime use | [1.3.0](https://crates.io/crates/fory-derive) | + +Most applications should depend on `fory` only. It re-exports the derive +macros and the public runtime types needed by generated code. Use `fory-core` +or `fory-derive` directly only when intentionally building on the lower-level +runtime crates. + +## Quick Start + +Add Apache Fory™ to your `Cargo.toml`: + +```toml +[dependencies] +fory = "1.3.0" +``` + +### Basic Example + +```rust +use fory::{Fory, Error, Reader}; +use fory::ForyStruct; + +#[derive(ForyStruct, Debug, PartialEq)] +struct User { + name: String, + age: i32, + email: String, +} + +fn main() -> Result<(), Error> { + let mut fory = Fory::builder().xlang(true).build(); + fory.register::(1)?; + + let user = User { + name: "Alice".to_string(), + age: 30, + email: "alice@example.com".to_string(), + }; + + // Serialize + let bytes = fory.serialize(&user)?; + // Deserialize + let decoded: User = fory.deserialize(&bytes)?; + assert_eq!(user, decoded); + + // Serialize to specified buffer + let mut buf: Vec = vec![]; + fory.serialize_to(&mut buf, &user)?; + // Deserialize from specified buffer + let mut reader = Reader::new(&buf); + let decoded: User = fory.deserialize_from(&mut reader)?; + assert_eq!(user, decoded); + Ok(()) +} +``` + +## Xlang Mode And Native Mode + +Use xlang mode for cross-language payloads and schemas shared with other Fory implementations. Xlang mode is the default Rust wire mode, and Rust examples that use it set `.xlang(true)` explicitly so the mode choice is visible. + +Use native mode for Rust-only traffic. Native mode is selected with `.xlang(false)` and keeps Rust object serialization in Rust-native form. It is optimized for Rust's type system and covers Rust-specific object features such as trait objects and shared-reference patterns that are not portable xlang payloads. Compatible mode is enabled by default. Set `.compatible(false)` only when every reader and writer uses the same Rust schema and you want faster serialization and smaller size. + +See [Xlang Serialization](xlang-serialization.md) for Rust xlang registration and interoperability rules, and [Native Serialization](native-serialization.md) for Rust-only payloads. + +## Thread Safety + +Apache Fory™ Rust is fully thread-safe: `Fory` implements both `Send` and `Sync`, so one configured instance can be shared across threads for concurrent work. The internal read/write context pools are lazily initialized with thread-safe primitives, letting worker threads reuse buffers without coordination. + +```rust +use fory::{Fory, Error}; +use fory::ForyStruct; +use std::sync::Arc; +use std::thread; + +#[derive(ForyStruct, Clone, Copy, Debug, PartialEq)] +struct Item { + value: i32, +} + +fn main() -> Result<(), Error> { + let mut fory = Fory::builder().xlang(true).build(); + fory.register::(1000)?; + + let fory = Arc::new(fory); + let handles: Vec<_> = (0..8) + .map(|i| { + let shared = Arc::clone(&fory); + thread::spawn(move || { + let item = Item { value: i }; + shared.serialize(&item) + }) + }) + .collect(); + + for handle in handles { + let bytes = handle.join().unwrap()?; + let item: Item = fory.deserialize(&bytes)?; + assert!(item.value >= 0); + } + + Ok(()) +} +``` + +**Tip:** Perform registrations (such as `fory.register::(id)`) before spawning threads so every worker sees the same metadata. Once configured, wrapping the instance in `Arc` is enough to fan out serialization and deserialization tasks safely. + +## Architecture + +The Rust implementation consists of three main crates: + +``` +fory/ # High-level API +├── src/lib.rs # Public API exports + +fory-core/ # Core serialization engine +├── src/ +│ ├── fory.rs # Main serialization entry point +│ ├── buffer.rs # Binary buffer management +│ ├── serializer/ # Type-specific serializers +│ ├── resolver/ # Type resolution and metadata +│ ├── meta/ # Meta string compression +│ ├── row/ # Row format implementation +│ └── types.rs # Type definitions + +fory-derive/ # Procedural macros +├── src/ +│ ├── object/ # ForyStruct macro +│ └── fory_row.rs # ForyRow macro +``` + +## Use Cases + +### Object Serialization + +- Complex data structures with nested objects and references +- Cross-language communication in microservices +- General-purpose serialization with full type safety +- Schema evolution with compatible mode +- Graph-like data structures with circular references + +### Row-Based Serialization + +- High-throughput data processing +- Analytics workloads requiring fast field access +- Memory-constrained environments +- Real-time data streaming applications +- Zero-copy scenarios + +## Next Steps + +- [Configuration](configuration.md) - Fory builder options and modes +- [Basic Serialization](basic-serialization.md) - Object graph serialization +- [Xlang Serialization](xlang-serialization.md) - xlang mode +- [Native Serialization](native-serialization.md) - Rust-only serialization +- [References](references.md) - Shared and circular references +- [Polymorphism](polymorphism.md) - Trait object serialization +- [Custom Serializers](custom-serializers.md) - Extend serialization behavior +- [Row Format](row-format.md) - Zero-copy row-based format +- [gRPC Support](grpc-support.md) - Fory payloads over tonic diff --git a/versioned_docs/version-1.3.0/guide/rust/native-serialization.md b/versioned_docs/version-1.3.0/guide/rust/native-serialization.md new file mode 100644 index 00000000000..d00c1408e24 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/rust/native-serialization.md @@ -0,0 +1,232 @@ +--- +title: Native Serialization +sidebar_position: 3 +id: native_serialization +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. +--- + +Rust native serialization is the Rust-only wire mode selected with `.xlang(false)`. Use it when +every writer and reader is Rust and the payload should preserve Rust object-graph behavior instead +of the portable xlang type system. + +Use [Xlang Serialization](xlang-serialization.md), the default Rust mode, when bytes must be read +by Java, Python, C++, Go, JavaScript/TypeScript, C#, Swift, Dart, Scala, +Kotlin, or another non-Rust Fory implementation. + +## When To Use Native Serialization + +Use native serialization when: + +- A payload is produced and consumed only by Rust applications. +- The data model uses Rust-specific object graph features such as `Rc`, `Arc`, weak + pointers, `RefCell`, `Mutex`, trait objects, or `dyn Any`. +- You want faster serialization and smaller size, and every reader uses the same schema as the + writer. +- You need compatible schema evolution for Rust-only rolling deployments. +- You want compile-time serializers from `#[derive(ForyStruct)]` without portable xlang mapping + constraints. + +## Create a Native-Mode Fory Instance + +```rust +use fory::{Error, Fory, ForyStruct}; + +#[derive(ForyStruct, Debug, PartialEq)] +struct Order { + id: i64, + amount: f64, +} + +fn main() -> Result<(), Error> { + let mut fory = Fory::builder().xlang(false).build(); + fory.register::(100)?; + + let order = Order { id: 1, amount: 42.5 }; + let bytes = fory.serialize(&order)?; + let decoded: Order = fory.deserialize(&bytes)?; + assert_eq!(order, decoded); + Ok(()) +} +``` + +Perform registrations before sharing a `Fory` instance across threads. Once configured, `Fory` can +be shared through `Arc`. + +## Schema Evolution + +Native serialization defaults to compatible mode. Keep that default when Rust-only writer and reader +versions can differ: + +```rust +let mut writer = Fory::builder().xlang(false).build(); +let mut reader = Fory::builder().xlang(false).build(); +``` + +Compatible mode uses metadata to tolerate added, removed, or reordered fields when field +identity remains compatible. See [Schema Evolution](schema-evolution.md). + +For faster serialization and smaller size, set `.compatible(false)` only when +every reader and writer always uses the same Rust schema. + +## Registration + +Register application structs and enum-like types before serialization: + +```rust +fory.register::(100)?; +fory.register_by_name::("example.Order")?; +``` + +Use explicit numeric IDs for compact payloads and stable deployments. Use named registration +when independent teams coordinate type identity by names; add a namespace prefix with `.` when needed. + +## Rust Object Surface + +Native serialization owns the Rust-specific object surface: + +- Structs and tuple structs with `#[derive(ForyStruct)]`. +- Enums and union-like models supported by Fory derive macros. +- `Vec`, maps, sets, tuples, arrays, and optional values. +- `Box`, `Rc`, `Arc`, `RcWeak`, and `ArcWeak`. +- `RefCell` and `Mutex`. +- Trait objects such as `Box`, `Rc`, and `Arc`. +- Runtime type dispatch with `Box`, `Rc`, and + `Arc` for registered non-container payloads. Wrap + containers in registered structs, enums, or unions before using them behind + erased `Any` carriers. +- Date and time carriers, including optional `chrono` support. + +Use [Basic Serialization](basic-serialization.md), [References](references.md), and +[Trait Object Serialization](polymorphism.md) for focused examples. + +## Shared And Circular References + +Native mode can preserve shared references with `Rc` and `Arc`: + +```rust +use fory::{Error, Fory}; +use std::rc::Rc; + +fn main() -> Result<(), Error> { + let fory = Fory::builder().xlang(false).build(); + let shared = Rc::new(String::from("shared")); + let values = vec![shared.clone(), shared.clone()]; + + let bytes = fory.serialize(&values)?; + let decoded: Vec> = fory.deserialize(&bytes)?; + assert!(Rc::ptr_eq(&decoded[0], &decoded[1])); + Ok(()) +} +``` + +Use `.track_ref(true)` when weak pointers or explicit cyclic graphs need reference tracking: + +```rust +let mut fory = Fory::builder().xlang(false).track_ref(true).build(); +``` + +Weak pointers serialize as references to their target when the target is still alive, and as null +when the target has been dropped. + +## Trait Objects + +Trait objects are Rust language features and belong in native serialization: + +```rust +use fory::{register_trait_type, Error, Fory, ForyStruct, Serializer}; + +trait Animal: Serializer { + fn name(&self) -> &str; +} + +#[derive(ForyStruct)] +struct Dog { + name: String, +} + +impl Animal for Dog { + fn name(&self) -> &str { + &self.name + } +} + +register_trait_type!(Animal, Dog); + +fn main() -> Result<(), Error> { + let mut fory = Fory::builder().xlang(false).build(); + fory.register::(100)?; + + let value: Box = Box::new(Dog { name: "Milo".into() }); + let bytes = fory.serialize(&value)?; + let decoded: Box = fory.deserialize(&bytes)?; + assert_eq!(decoded.name(), "Milo"); + Ok(()) +} +``` + +Register every concrete implementation that can appear behind the trait object. + +## Performance Guidelines + +- Reuse a configured `Fory` instance and register types before concurrent use. +- Use `.compatible(false)` only when every reader and writer always uses the same Rust schema and + the application wants faster serialization and smaller size. +- Use derive-generated serializers for application structs. +- Use `.track_ref(true)` only for weak-pointer or cyclic graph scenarios that require it. +- Prefer concrete typed fields over `dyn Any` or trait objects on hot paths. + +## Native And Xlang Comparison + +| Requirement | Use native serialization | Use xlang serialization | +| -------------------------------------- | ------------------------ | ----------------------- | +| Rust-only payloads | Yes | Optional | +| Non-Rust readers or writers | No | Yes | +| `Rc`, `Arc`, weak pointers | Yes | No | +| Trait objects and `dyn Any` | Yes | No | +| Same-schema compact payloads | Yes | No | +| Compatible schema evolution by default | Yes | Yes | +| Portable type mapping across languages | No | Yes | + +## Troubleshooting + +### A non-Rust implementation cannot read the payload + +The writer is using native serialization. Rebuild it with `.xlang(true)` and align type +registration with every peer. + +### A weak pointer fails to resolve + +Use `.track_ref(true)` and make sure the target object is still alive when serialized. Dropped weak +targets deserialize as null. + +### A trait object cannot deserialize + +Register the trait mapping and every concrete implementation that can appear behind the trait +object. + +### A rolling deployment fails after a field change + +Native serialization defaults to compatible mode. Keep that default when schemas can differ. + +## Related Topics + +- [Xlang Serialization](xlang-serialization.md) - Cross-language Rust payloads +- [Configuration](configuration.md) - Builder options +- [Basic Serialization](basic-serialization.md) - Object graph serialization +- [Shared & Circular References](references.md) - `Rc`, `Arc`, and weak pointers +- [Trait Object Serialization](polymorphism.md) - Trait objects and dynamic dispatch +- [Schema Evolution](schema-evolution.md) - Compatible mode diff --git a/versioned_docs/version-1.3.0/guide/rust/polymorphism.md b/versioned_docs/version-1.3.0/guide/rust/polymorphism.md new file mode 100644 index 00000000000..c0dbaf45392 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/rust/polymorphism.md @@ -0,0 +1,267 @@ +--- +title: Trait Object Serialization +sidebar_position: 8 +id: polymorphism +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. +--- + +Apache Fory™ supports polymorphic serialization through trait objects, enabling dynamic dispatch and type flexibility. + +## Supported Trait Object Types + +- `Box` - Owned trait objects +- `Rc` - Reference-counted trait objects +- `Arc` - Thread-safe reference-counted trait objects +- `Vec>`, `HashMap>` - Collections of trait objects + +## Basic Trait Object Serialization + +```rust +use fory::{Fory, register_trait_type}; +use fory::Serializer; +use fory::ForyStruct; + +trait Animal: Serializer { + fn speak(&self) -> String; + fn name(&self) -> &str; +} + +#[derive(ForyStruct)] +struct Dog { name: String, breed: String } + +impl Animal for Dog { + fn speak(&self) -> String { "Woof!".to_string() } + fn name(&self) -> &str { &self.name } +} + +#[derive(ForyStruct)] +struct Cat { name: String, color: String } + +impl Animal for Cat { + fn speak(&self) -> String { "Meow!".to_string() } + fn name(&self) -> &str { &self.name } +} + +// Register trait implementations +register_trait_type!(Animal, Dog, Cat); + +#[derive(ForyStruct)] +struct Zoo { + star_animal: Box, +} + +let mut fory = Fory::builder().xlang(false).build(); +fory.register::(100)?; +fory.register::(101)?; +fory.register::(102)?; + +let zoo = Zoo { + star_animal: Box::new(Dog { + name: "Buddy".to_string(), + breed: "Labrador".to_string(), + }), +}; + +let bytes = fory.serialize(&zoo)?; +let decoded: Zoo = fory.deserialize(&bytes)?; + +assert_eq!(decoded.star_animal.name(), "Buddy"); +assert_eq!(decoded.star_animal.speak(), "Woof!"); +``` + +## Serializing dyn Any Trait Objects + +Apache Fory™ supports serializing `Box`, `Rc`, and +`Arc` for dynamic type dispatch: + +**Key points:** + +- Works with registered concrete non-container types that implement `Serializer` +- Requires downcasting after deserialization to access the concrete type +- Type information is preserved during serialization +- Useful for plugin systems and dynamic type handling + +```rust +use std::rc::Rc; +use std::any::Any; + +let dog_any: Rc = Rc::new(Dog { + name: "Rex".to_string(), + breed: "Golden".to_string() +}); + +// Serialize the Any wrapper +let bytes = fory.serialize(&dog_any)?; +let decoded: Rc = fory.deserialize(&bytes)?; + +// Downcast back to the concrete type +let unwrapped = decoded.downcast_ref::().unwrap(); +assert_eq!(unwrapped.name, "Rex"); +``` + +For thread-safe scenarios, use `Arc`: + +```rust +use std::sync::Arc; +use std::any::Any; + +let dog_any: Arc = Arc::new(Dog { + name: "Buddy".to_string(), + breed: "Labrador".to_string() +}); + +let bytes = fory.serialize(&dog_any)?; +let decoded: Arc = fory.deserialize(&bytes)?; + +// Downcast to concrete type +let unwrapped = decoded.downcast_ref::().unwrap(); +assert_eq!(unwrapped.name, "Buddy"); +``` + +`Box`, `Rc`, and `Arc` are supported +erased `Any` carriers for registered concrete non-container payloads. +Use `Arc` when the erased payload must be shareable +across threads; the concrete payload type must also satisfy `Send + Sync`. +Registered structs, enums, and unions that satisfy those bounds can be used as +the erased payload. + +The unsupported case is a generic container used directly as the top-level +erased payload. This applies to all erased `Any` carriers: `Box`, +`Rc`, and `Arc`. Unsupported direct payloads +include list-, map-, and set-like containers such as `Vec`, `Vec`, +`HashMap`, `HashSet`, and `LinkedList`. + +If you need to put a container in an erased `Any` payload, wrap it in a +registered struct, enum, or union and use that wrapper as the erased payload: + +```rust +use fory::{Fory, ForyStruct}; +use std::any::Any; +use std::sync::Arc; + +#[derive(ForyStruct)] +struct IntList { + values: Vec, +} + +let mut fory = Fory::builder().xlang(false).build(); +fory.register::(100)?; + +let value: Arc = Arc::new(IntList { + values: vec![1, 2, 3], +}); +let bytes = fory.serialize(&value)?; +let decoded: Arc = fory.deserialize(&bytes)?; +``` + +The wrapper makes the erased payload a concrete registered type while the +container remains a normal typed field. The same wrapper model is the supported +path for `Box` and `Rc`. + +## Rc/Arc-Based Trait Objects in Structs + +For fields with `Rc` or `Arc`, Fory automatically handles the conversion: + +```rust +use std::sync::Arc; +use std::rc::Rc; +use std::collections::HashMap; + +#[derive(ForyStruct)] +struct AnimalShelter { + animals_rc: Vec>, + animals_arc: Vec>, + registry: HashMap>, +} + +let mut fory = Fory::builder().xlang(false).build(); +fory.register::(100)?; +fory.register::(101)?; +fory.register::(102)?; + +let shelter = AnimalShelter { + animals_rc: vec![ + Rc::new(Dog { name: "Rex".to_string(), breed: "Golden".to_string() }), + Rc::new(Cat { name: "Mittens".to_string(), color: "Gray".to_string() }), + ], + animals_arc: vec![ + Arc::new(Dog { name: "Buddy".to_string(), breed: "Labrador".to_string() }), + ], + registry: HashMap::from([ + ("pet1".to_string(), Arc::new(Dog { + name: "Max".to_string(), + breed: "Shepherd".to_string() + }) as Arc), + ]), +}; + +let bytes = fory.serialize(&shelter)?; +let decoded: AnimalShelter = fory.deserialize(&bytes)?; + +assert_eq!(decoded.animals_rc[0].name(), "Rex"); +assert_eq!(decoded.animals_arc[0].speak(), "Woof!"); +``` + +## Standalone Trait Object Serialization + +Due to Rust's orphan rule, `Rc` and `Arc` cannot implement `Serializer` directly. For standalone serialization (not inside struct fields), the `register_trait_type!` macro generates wrapper types. + +**Note:** If you don't want to use wrapper types for concrete non-container payloads, you can serialize as `Box`, `Rc`, or `Arc` instead (see the dyn Any section above). + +The `register_trait_type!` macro generates `AnimalRc` and `AnimalArc` wrapper types: + +```rust +// For Rc +let dog_rc: Rc = Rc::new(Dog { + name: "Rex".to_string(), + breed: "Golden".to_string() +}); +let wrapper = AnimalRc::from(dog_rc); + +let bytes = fory.serialize(&wrapper)?; +let decoded: AnimalRc = fory.deserialize(&bytes)?; + +// Unwrap back to Rc +let unwrapped: Rc = decoded.unwrap(); +assert_eq!(unwrapped.name(), "Rex"); + +// For Arc +let dog_arc: Arc = Arc::new(Dog { + name: "Buddy".to_string(), + breed: "Labrador".to_string() +}); +let wrapper = AnimalArc::from(dog_arc); + +let bytes = fory.serialize(&wrapper)?; +let decoded: AnimalArc = fory.deserialize(&bytes)?; + +let unwrapped: Arc = decoded.unwrap(); +assert_eq!(unwrapped.name(), "Buddy"); +``` + +## Best Practices + +1. **Use `register_trait_type!`** to register all trait implementations +2. **Keep compatible mode enabled** for trait objects +3. **Register all concrete types** before serialization +4. **Prefer dyn Any** for simpler standalone serialization + +## Related Topics + +- [References](references.md) - Rc/Arc shared references +- [Schema Evolution](schema-evolution.md) - Compatible mode +- [Type Registration](type-registration.md) - Registering types diff --git a/versioned_docs/version-1.3.0/guide/rust/references.md b/versioned_docs/version-1.3.0/guide/rust/references.md new file mode 100644 index 00000000000..a0e1a20acca --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/rust/references.md @@ -0,0 +1,213 @@ +--- +title: Shared & Circular References +sidebar_position: 7 +id: references +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. +--- + +Apache Fory™ automatically tracks and preserves reference identity for shared objects using `Rc` and `Arc`. + +## Shared References + +When the same object is referenced multiple times, Fory serializes it only once and uses reference IDs for subsequent occurrences. This ensures: + +- **Space efficiency**: No data duplication in serialized output +- **Reference identity preservation**: Deserialized objects maintain the same sharing relationships +- **Circular reference support**: Use `RcWeak` and `ArcWeak` to break cycles + +### Shared References with Rc + +```rust +use fory::Fory; +use std::rc::Rc; + +let fory = Fory::builder().xlang(false).build(); + +// Create a shared value +let shared = Rc::new(String::from("shared_value")); + +// Reference it multiple times +let data = vec![shared.clone(), shared.clone(), shared.clone()]; + +// The shared value is serialized only once +let bytes = fory.serialize(&data)?; +let decoded: Vec> = fory.deserialize(&bytes)?; + +// Verify reference identity is preserved +assert_eq!(decoded.len(), 3); +assert_eq!(*decoded[0], "shared_value"); + +// All three Rc pointers point to the same object +assert!(Rc::ptr_eq(&decoded[0], &decoded[1])); +assert!(Rc::ptr_eq(&decoded[1], &decoded[2])); +``` + +### Shared References with Arc + +For thread-safe shared references, use `Arc`: + +```rust +use fory::Fory; +use std::sync::Arc; + +let fory = Fory::builder().xlang(false).build(); + +let shared = Arc::new(String::from("shared_value")); +let data = vec![shared.clone(), shared.clone()]; + +let bytes = fory.serialize(&data)?; +let decoded: Vec> = fory.deserialize(&bytes)?; + +assert!(Arc::ptr_eq(&decoded[0], &decoded[1])); +``` + +## Circular References with Weak Pointers + +To serialize circular references like parent-child relationships or doubly-linked structures, use `RcWeak` or `ArcWeak` to break the cycle. + +**How it works:** + +- Weak pointers serialize as references to their target objects +- If the strong pointer has been dropped, weak serializes as `Null` +- Forward references (weak appearing before target) are resolved via callbacks +- All clones of a weak pointer share the same internal cell for automatic updates + +### Circular References with RcWeak + +```rust +use fory::{Fory, Error}; +use fory::ForyStruct; +use fory::RcWeak; +use std::rc::Rc; +use std::cell::RefCell; + +#[derive(ForyStruct, Debug)] +struct Node { + value: i32, + parent: RcWeak>, + children: Vec>>, +} + +let mut fory = Fory::builder().xlang(false).build(); +fory.register::(2000)?; + +// Build a parent-child tree +let parent = Rc::new(RefCell::new(Node { + value: 1, + parent: RcWeak::new(), + children: vec![], +})); + +let child1 = Rc::new(RefCell::new(Node { + value: 2, + parent: RcWeak::from(&parent), + children: vec![], +})); + +let child2 = Rc::new(RefCell::new(Node { + value: 3, + parent: RcWeak::from(&parent), + children: vec![], +})); + +parent.borrow_mut().children.push(child1.clone()); +parent.borrow_mut().children.push(child2.clone()); + +// Serialize and deserialize the circular structure +let bytes = fory.serialize(&parent)?; +let decoded: Rc> = fory.deserialize(&bytes)?; + +// Verify the circular relationship +assert_eq!(decoded.borrow().children.len(), 2); +for child in &decoded.borrow().children { + let upgraded_parent = child.borrow().parent.upgrade().unwrap(); + assert!(Rc::ptr_eq(&decoded, &upgraded_parent)); +} +``` + +### Thread-Safe Circular Graphs with Arc + +```rust +use fory::{Fory, Error}; +use fory::ForyStruct; +use fory::ArcWeak; +use std::sync::{Arc, Mutex}; + +#[derive(ForyStruct)] +struct Node { + val: i32, + parent: ArcWeak>, + children: Vec>>, +} + +let mut fory = Fory::builder().xlang(false).build(); +fory.register::(6000)?; + +let parent = Arc::new(Mutex::new(Node { + val: 10, + parent: ArcWeak::new(), + children: vec![], +})); + +let child1 = Arc::new(Mutex::new(Node { + val: 20, + parent: ArcWeak::from(&parent), + children: vec![], +})); + +let child2 = Arc::new(Mutex::new(Node { + val: 30, + parent: ArcWeak::from(&parent), + children: vec![], +})); + +parent.lock().unwrap().children.push(child1.clone()); +parent.lock().unwrap().children.push(child2.clone()); + +let bytes = fory.serialize(&parent)?; +let decoded: Arc> = fory.deserialize(&bytes)?; + +assert_eq!(decoded.lock().unwrap().children.len(), 2); +for child in &decoded.lock().unwrap().children { + let upgraded_parent = child.lock().unwrap().parent.upgrade().unwrap(); + assert!(Arc::ptr_eq(&decoded, &upgraded_parent)); +} +``` + +## Supported Smart Pointer Types + +| Type | Description | +| ------------ | --------------------------------------------------- | +| `Rc` | Reference counting, shared refs tracked | +| `Arc` | Thread-safe reference counting, shared refs tracked | +| `RcWeak` | Weak reference to `Rc`, breaks circular refs | +| `ArcWeak` | Weak reference to `Arc`, breaks circular refs | +| `RefCell` | Interior mutability with runtime borrow checking | +| `Mutex` | Thread-safe interior mutability | + +## Best Practices + +1. **Use Rc/Arc for shared data**: Let Fory handle deduplication +2. **Use weak pointers for cycles**: Prevent infinite recursion +3. **Prefer Arc for thread-safe scenarios**: When data crosses thread boundaries +4. **Combine with RefCell/Mutex**: For interior mutability + +## Related Topics + +- [Basic Serialization](basic-serialization.md) - Supported types +- [Polymorphism](polymorphism.md) - Trait objects with Rc/Arc +- [Configuration](configuration.md) - Reference tracking options diff --git a/versioned_docs/version-1.3.0/guide/rust/row-format.md b/versioned_docs/version-1.3.0/guide/rust/row-format.md new file mode 100644 index 00000000000..9c5c5455256 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/rust/row-format.md @@ -0,0 +1,126 @@ +--- +title: Row Format +sidebar_position: 11 +id: row_format +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. +--- + +Apache Fory™ provides a high-performance **row format** for zero-copy deserialization. + +## Overview + +Unlike traditional object serialization that reconstructs entire objects in memory, row format enables **random access** to fields directly from binary data without full deserialization. + +**Key benefits:** + +- **Zero-copy access**: Read fields without allocating or copying data +- **Partial deserialization**: Access only the fields you need +- **Memory-mapped files**: Work with data larger than RAM +- **Cache-friendly**: Sequential memory layout for better CPU cache utilization +- **Lazy evaluation**: Defer expensive operations until field access + +## When to Use Row Format + +- Analytics workloads with selective field access +- Large datasets where only a subset of fields is needed +- Memory-constrained environments +- High-throughput data pipelines +- Reading from memory-mapped files or shared memory + +## Basic Usage + +```rust +use fory::{to_row, from_row}; +use fory::ForyRow; +use std::collections::BTreeMap; + +#[derive(ForyRow)] +struct UserProfile { + id: i64, + username: String, + email: String, + scores: Vec, + preferences: BTreeMap, + is_active: bool, +} + +let profile = UserProfile { + id: 12345, + username: "alice".to_string(), + email: "alice@example.com".to_string(), + scores: vec![95, 87, 92, 88], + preferences: BTreeMap::from([ + ("theme".to_string(), "dark".to_string()), + ("language".to_string(), "en".to_string()), + ]), + is_active: true, +}; + +// Serialize to row format +let row_data = to_row(&profile).unwrap(); + +// Zero-copy deserialization - no object allocation! +let row = from_row::(&row_data); + +// Access fields directly from binary data +assert_eq!(row.id(), 12345); +assert_eq!(row.username(), "alice"); +assert_eq!(row.email(), "alice@example.com"); +assert_eq!(row.is_active(), true); + +// Access collections efficiently +let scores = row.scores(); +assert_eq!(scores.size(), 4); +assert_eq!(scores.get(0).unwrap(), 95); +assert_eq!(scores.get(1).unwrap(), 87); + +let prefs = row.preferences(); +assert_eq!(prefs.keys().size(), 2); +assert_eq!(prefs.keys().get(0).unwrap(), "language"); +assert_eq!(prefs.values().get(0).unwrap(), "en"); +``` + +## How It Works + +- Fields are encoded in a binary row with fixed offsets for primitives +- Variable-length data (strings, collections) stored with offset pointers +- Null bitmap tracks which fields are present +- Nested structures supported through recursive row encoding + +## Performance Comparison + +| Operation | Object Format | Row Format | +| -------------------- | ----------------------------- | ------------------------------- | +| Full deserialization | Allocates all objects | Zero allocation | +| Single field access | Full deserialization required | Direct offset read | +| Memory usage | Full object graph in memory | Only accessed fields in memory | +| Suitable for | Small objects, full access | Large objects, selective access | + +## ForyRow vs ForyStruct + +| Feature | `#[derive(ForyRow)]` | `#[derive(ForyStruct)]` | +| --------------- | --------------------- | -------------------------- | +| Deserialization | Zero-copy, lazy | Full object reconstruction | +| Field access | Direct from binary | Normal struct access | +| Memory usage | Minimal | Full object | +| Best for | Analytics, large data | General serialization | + +## Related Topics + +- [Basic Serialization](basic-serialization.md) - Object graph serialization +- [Xlang Serialization](xlang-serialization.md) - Row format across languages +- [Row Format Specification](https://fory.apache.org/docs/specification/row_format_spec) - Protocol details diff --git a/versioned_docs/version-1.3.0/guide/rust/schema-evolution.md b/versioned_docs/version-1.3.0/guide/rust/schema-evolution.md new file mode 100644 index 00000000000..199e684ade9 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/rust/schema-evolution.md @@ -0,0 +1,250 @@ +--- +title: Schema Evolution +sidebar_position: 9 +id: schema_evolution +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. +--- + +Apache Fory™ supports schema evolution in **Compatible mode**, allowing serialization and deserialization peers to have different type definitions. + +## Compatible Mode + +Compatible mode is enabled by default: + +```rust +use fory::Fory; +use fory::ForyStruct; +use std::collections::HashMap; + +#[derive(ForyStruct, Debug)] +struct PersonV1 { + name: String, + age: i32, + address: String, +} + +#[derive(ForyStruct, Debug)] +struct PersonV2 { + name: String, + age: i32, + // address removed + // phone added + phone: Option, + metadata: HashMap, +} + +let mut fory1 = Fory::builder().xlang(false).build(); +fory1.register::(1)?; + +let mut fory2 = Fory::builder().xlang(false).build(); +fory2.register::(1)?; + +let person_v1 = PersonV1 { + name: "Alice".to_string(), + age: 30, + address: "123 Main St".to_string(), +}; + +// Serialize with V1 +let bytes = fory1.serialize(&person_v1)?; + +// Deserialize with V2 - missing fields get default values +let person_v2: PersonV2 = fory2.deserialize(&bytes)?; +assert_eq!(person_v2.name, "Alice"); +assert_eq!(person_v2.age, 30); +assert_eq!(person_v2.phone, None); +``` + +## Schema Evolution Features + +- Add new fields with default values +- Remove obsolete fields (skipped during deserialization) +- Change field nullability (`T` ↔ `Option`) +- Reorder fields (matched by name, not position) +- Change selected scalar field types when the value converts without precision or range loss +- Type-safe fallback to default values for missing fields + +Compatible readers can handle selected scalar field type changes when the value is lossless. A +matched field can read between `bool`, `String`, numeric scalars, and decimal fields when the +converted value has the same logical value. For example, `"true"` and `"false"` can be read as +booleans, `"123"` can be read as a numeric field that can hold `123`, numbers and decimals can be +read as canonical strings, and numeric widening or narrowing succeeds only when no precision or range +is lost. Numeric strings use finite ASCII decimal syntax. Optional fields still compose with these +conversions, but reference-tracked scalar type changes are incompatible. Invalid strings and lossy +conversions fail during deserialization. + +## Compatibility Rules + +- Field names must match (case-sensitive) +- Type changes are supported only for nullable/non-nullable changes and selected lossless scalar + conversions +- Nested struct types must be registered on both sides + +## Same-Schema Optimization + +Use `.compatible(false)` only when the schema used to deserialize every payload is always the same as the schema used to serialize it, and you want faster serialization and smaller size. For xlang payloads, use `.compatible(false)` only after verifying that every language uses the same schema, or when native types are generated from Fory schema IDL. + +```rust +let mut fory = Fory::builder() + .xlang(false) + .compatible(false) + .build(); +``` + +For one struct, you can opt out of evolution metadata with `#[fory(evolving = false)]`: + +```rust +use fory::ForyStruct; + +#[derive(ForyStruct)] +#[fory(evolving = false)] +struct SameSchemaMessage { + id: i32, +} +``` + +## Enum Support + +Apache Fory™ supports three types of enum variants with full schema evolution in Compatible mode: + +**Variant Types:** + +- **Unit**: C-style enums (`Status::Active`) +- **Unnamed**: Tuple-like variants (`Message::Pair(String, i32)`) +- **Named**: Struct-like variants (`Event::Click { x: i32, y: i32 }`) + +```rust +use fory::{Fory, ForyUnion}; + +#[derive(ForyUnion, Debug, PartialEq)] +enum Value { + #[fory(default)] + Null, + Bool(bool), + Number(f64), + Text(String), + Object { name: String, value: i32 }, +} + +let mut fory = Fory::builder().xlang(false).build(); +fory.register::(1)?; + +let value = Value::Object { name: "score".to_string(), value: 100 }; +let bytes = fory.serialize(&value)?; +let decoded: Value = fory.deserialize(&bytes)?; +assert_eq!(value, decoded); +``` + +For typed ADT unions whose cases are unit or single-payload variants, add an +`#[fory(unknown)] Unknown(::fory::UnknownCase)` variant when you need to +preserve future payload variants. Do not make the unknown variant the default; +keep a real schema case marked `#[fory(default)]`. Register future payload types +locally before deserializing unknown cases you need to preserve. + +`UnknownCase` stores its payload as `Arc`, so preserved +payload types must satisfy `Send + Sync`. Direct generic containers are not +supported as erased `Any` payloads; wrap the container in a registered derived +type if an unknown case needs to preserve it. + +### Enum Schema Evolution + +Compatible mode enables robust schema evolution with variant type encoding (2 bits): + +- `0b0` = Unit, `0b1` = Unnamed, `0b10` = Named + +```rust +use fory::{Fory, ForyUnion}; + +// Old version +#[derive(ForyUnion)] +enum OldEvent { + #[fory(default)] + Click { x: i32, y: i32 }, + Scroll { delta: f64 }, +} + +// New version - added field and variant +#[derive(ForyUnion)] +enum NewEvent { + #[fory(default)] + Unknown, + Click { x: i32, y: i32, timestamp: u64 }, // Added field + Scroll { delta: f64 }, + KeyPress(String), // New variant +} + +let mut fory = Fory::builder().xlang(false).build(); + +// Serialize with old schema +let old_bytes = fory.serialize(&OldEvent::Click { x: 100, y: 200 })?; + +// Deserialize with new schema - timestamp gets default value (0) +let new_event: NewEvent = fory.deserialize(&old_bytes)?; +assert!(matches!(new_event, NewEvent::Click { x: 100, y: 200, timestamp: 0 })); +``` + +**Evolution capabilities:** + +- **Unknown variants** → Falls back to default variant +- **Named variant fields** → Add/remove fields (missing fields use defaults) +- **Unnamed variant elements** → Add/remove elements (extras skipped, missing use defaults) +- **Variant type mismatches** → Automatically uses default value for current variant + +**Best practices:** + +- Always mark exactly one union variant with `#[fory(default)]` +- Named variants provide better evolution than unnamed +- Use compatible mode for cross-version communication + +## Tuple Support + +Apache Fory™ supports tuples up to 22 elements out of the box with efficient serialization in both compatible mode and the same-schema optimization. + +**Features:** + +- Automatic serialization for tuples from 1 to 22 elements +- Heterogeneous type support (each element can be a different type) +- Schema evolution in Compatible mode (handles missing/extra elements) + +**Schema modes:** + +1. **Same-schema optimization**: Serializes elements sequentially without collection headers for minimal overhead +2. **Compatible mode**: Uses collection protocol with type metadata for schema evolution + +```rust +use fory::{Fory, Error}; + +let mut fory = Fory::builder().xlang(false).build(); + +// Tuple with heterogeneous types +let data: (i32, String, bool, Vec) = ( + 42, + "hello".to_string(), + true, + vec![1, 2, 3], +); + +let bytes = fory.serialize(&data)?; +let decoded: (i32, String, bool, Vec) = fory.deserialize(&bytes)?; +assert_eq!(data, decoded); +``` + +## Related Topics + +- [Configuration](configuration.md) - Compatible mode settings +- [Polymorphism](polymorphism.md) - Trait objects with schema evolution +- [Xlang Serialization](xlang-serialization.md) - Schema evolution across languages diff --git a/versioned_docs/version-1.3.0/guide/rust/schema-metadata.md b/versioned_docs/version-1.3.0/guide/rust/schema-metadata.md new file mode 100644 index 00000000000..4aa3a892cfc --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/rust/schema-metadata.md @@ -0,0 +1,459 @@ +--- +title: Schema Metadata +sidebar_position: 6 +id: schema_metadata +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 page explains how to configure field-level metadata for serialization in Rust. + +## Overview + +Apache Fory™ provides the `#[fory(...)]` attribute macro to specify optional field-level metadata at compile time. This enables: + +- **Tag IDs**: Assign compact numeric IDs to minimize struct field meta size overhead +- **Nullability**: Control whether fields can be null +- **Reference Tracking**: Enable reference tracking for shared ownership types +- **Field Skipping**: Exclude fields from serialization +- **Encoding Control**: Specify how integers are encoded (varint, fixed, tagged) + +## Basic Syntax + +The `#[fory(...)]` attribute is placed on individual struct fields: + +```rust +use fory::ForyStruct; + +#[derive(ForyStruct)] +struct Person { + #[fory(id = 0)] + name: String, + + #[fory(id = 1)] + age: i32, + + #[fory(id = 2, nullable)] + nickname: Option, +} +``` + +Multiple options are separated by commas. + +## Available Options + +### Field ID (`id = N`) + +Assigns a numeric ID to a field to minimize struct field meta size overhead: + +```rust +#[derive(ForyStruct)] +struct User { + #[fory(id = 0)] + id: i64, + + #[fory(id = 1)] + name: String, + + #[fory(id = 2)] + age: i32, +} +``` + +**Benefits**: + +- Smaller serialized size (numeric IDs vs field names in metadata) +- Allows renaming fields without breaking binary compatibility + +**Recommendation**: It is recommended to configure field IDs for compatible mode since it reduces serialization cost. + +**Notes**: + +- IDs must be unique within a struct +- IDs must be non-negative +- If not specified, field name is used in metadata (larger overhead) + +### Skipping Fields (`skip`) + +Excludes a field from serialization: + +```rust +#[derive(ForyStruct)] +struct User { + #[fory(id = 0)] + id: i64, + + #[fory(id = 1)] + name: String, + + #[fory(skip)] + password: String, // Not serialized +} +``` + +The `password` field will not be included in serialized output and will remain at its default value after deserialization. + +### Nullable (`nullable`) + +Controls whether null flags are written for fields: + +```rust +use fory::{Fory, RcWeak}; + +#[derive(ForyStruct)] +struct Record { + // RcWeak is nullable by default, override to non-nullable + #[fory(id = 0, nullable = false)] + required_ref: RcWeak, +} +``` + +**Default Behavior**: + +| Type | Default Nullable | +| ------------------------- | ---------------- | +| `Option` | `true` | +| `RcWeak`, `ArcWeak` | `true` | +| All other types | `false` | + +**Notes**: + +- For `Option`, `RcWeak`, `ArcWeak`, nullable defaults to true +- For all other types, nullable defaults to false +- Use `nullable = false` to override defaults for types that are nullable by default + +### Reference Tracking (`ref`) + +Controls per-field reference tracking for shared ownership types: + +```rust +use std::rc::Rc; +use std::sync::Arc; + +#[derive(ForyStruct)] +struct Container { + // Enable reference tracking (default for Rc/Arc) + #[fory(id = 0, ref = true)] + shared_data: Rc, + + // Disable reference tracking + #[fory(id = 1, ref = false)] + unique_data: Rc, +} +``` + +**Default Behavior**: + +| Type | Default Ref Tracking | +| --------------------------------- | -------------------- | +| `Rc`, `Arc` | `true` | +| `RcWeak`, `ArcWeak` | `true` | +| `Option>`, `Option>` | `true` (inherited) | +| All other types | `false` | + +**Use Cases**: + +- Enable for fields that may be circular or shared +- Disable for fields that are always unique (optimization) + +### Encoding (`encoding`) + +Controls how integer fields are encoded: + +```rust +#[derive(ForyStruct)] +struct Metrics { + // Variable-length encoding (smaller for small values) + #[fory(id = 0, encoding = varint)] + count: i64, + + // Fixed-length encoding (consistent size) + #[fory(id = 1, encoding = fixed)] + timestamp: i64, + + // Tagged encoding (includes type tag, u64 only) + #[fory(id = 2, encoding = tagged)] + value: u64, +} +``` + +**Supported Encodings**: + +| Type | Options | Default | +| ------------ | --------------------------- | -------- | +| `i32`, `u32` | `varint`, `fixed` | `varint` | +| `i64`, `u64` | `varint`, `fixed`, `tagged` | `varint` | + +**When to Use**: + +- `varint`: Best for values that are often small (default) +- `fixed`: Best for values that use full range (e.g., timestamps, hashes) +- `tagged`: When type information needs to be preserved (u64 only) + +### Nested Collection Configuration + +Use `list(element(...))` and `map(key(...), value(...))` when an override belongs +to a nested element instead of the outer field: + +```rust +use std::collections::HashMap; + +#[derive(ForyStruct)] +struct Data { + #[fory(list(element(encoding = fixed)))] + fixed_values: Vec, + + #[fory(map(key(encoding = fixed), value(nullable = true, encoding = tagged)))] + values_by_id: HashMap, Option>, +} +``` + +`compress` has been removed. Use `encoding = varint` or `encoding = fixed` +directly. + +## Type Classification + +Fory classifies field types to determine default behavior: + +| Type Class | Examples | Default Nullable | Default Ref | +| ---------- | ------------------------------ | ---------------- | ----------- | +| Primitive | `i8`, `i32`, `f64`, `bool` | `false` | `false` | +| Option | `Option` | `true` | `false` | +| Rc | `Rc` | `false` | `true` | +| Arc | `Arc` | `false` | `true` | +| RcWeak | `RcWeak` (fory type) | `true` | `true` | +| ArcWeak | `ArcWeak` (fory type) | `true` | `true` | +| Other | `String`, `Vec`, user types | `false` | `false` | + +**Special Case**: `Option>` and `Option>` inherit the inner type's ref tracking behavior. + +## Complete Example + +```rust +use fory::ForyStruct; +use std::rc::Rc; + +#[derive(ForyStruct, Default)] +struct Document { + // Required fields with tag IDs + #[fory(id = 0)] + title: String, + + #[fory(id = 1)] + version: i32, + + // Optional field (nullable by default for Option) + #[fory(id = 2)] + description: Option, + + // Reference-tracked shared pointer + #[fory(id = 3)] + parent: Rc, + + // Nullable + reference-tracked + #[fory(id = 4, nullable)] + related: Option>, + + // Counter with varint encoding (small values) + #[fory(id = 5, encoding = varint)] + view_count: u64, + + // Timestamp with fixed encoding (full range values) + #[fory(id = 6, encoding = fixed)] + created_at: i64, + + // Skip sensitive field + #[fory(skip)] + internal_state: String, +} + +fn main() { + let fory = fory::Fory::builder().xlang(false).build(); + + let doc = Document { + title: "My Document".to_string(), + version: 1, + description: Some("A sample document".to_string()), + parent: Rc::new(Document::default()), + related: None, // Allowed because nullable + view_count: 42, + created_at: 1704067200, + internal_state: "secret".to_string(), // Will be skipped + }; + + let bytes = fory.serialize(&doc).unwrap(); + let decoded: Document = fory.deserialize(&bytes).unwrap(); +} +``` + +## Compile-Time Validation + +Invalid configurations are caught at compile time: + +```rust +// Error: duplicate field IDs +#[derive(ForyStruct)] +struct Bad { + #[fory(id = 0)] + field1: String, + + #[fory(id = 0)] // Compile error: duplicate id + field2: String, +} + +// Error: invalid id value +#[derive(ForyStruct)] +struct Bad2 { + #[fory(id = -1)] // Compile error: id must be non-negative + field: String, +} + +// Error: invalid encoding for i32 +#[derive(ForyStruct)] +struct Bad3 { + #[fory(encoding = tagged)] // Compile error: tagged is only valid for i64/u64 + field: i32, +} +``` + +## Cross-Language Compatibility + +When serializing data to be read by other languages (Java, C++, Go, Python), use schema metadata to match encoding expectations: + +```rust +#[derive(ForyStruct)] +struct CrossLangData { + // Matches Java Integer with varint + #[fory(id = 0, encoding = varint)] + int_var: i32, + + // Matches Java Integer with fixed + #[fory(id = 1, encoding = fixed)] + int_fixed: i32, + + // Matches Java Long with tagged encoding + #[fory(id = 2, encoding = tagged)] + long_tagged: u64, + + // Nullable pointer matches Java nullable reference + #[fory(id = 3, nullable)] + optional: Option, +} +``` + +## Schema Evolution + +Compatible mode supports schema evolution. It is recommended to configure field IDs to reduce serialization cost: + +```rust +// Version 1 +#[derive(ForyStruct)] +struct DataV1 { + #[fory(id = 0)] + id: i64, + + #[fory(id = 1)] + name: String, +} + +// Version 2: Added new field +#[derive(ForyStruct)] +struct DataV2 { + #[fory(id = 0)] + id: i64, + + #[fory(id = 1)] + name: String, + + #[fory(id = 2)] + email: Option, // New nullable field +} +``` + +Data serialized with V1 can be deserialized with V2 (new field will be `None`). + +Alternatively, field IDs can be omitted (field names will be used in metadata with larger overhead): + +```rust +#[derive(ForyStruct)] +struct Data { + id: i64, + name: String, +} +``` + +## Default Values + +- **Nullable**: `Option`, `RcWeak`, and `ArcWeak` are nullable by default; all other types are non-nullable +- **Ref tracking**: `Rc`, `Arc`, `RcWeak`, and `ArcWeak` enable ref tracking by default; all other types are disabled + +You **need to configure fields** when: + +- A field can be None (use `Option`) +- A field needs reference tracking for shared/circular objects (use `ref = true`) +- Integer types need specific encoding for cross-language compatibility +- You want to reduce metadata size (use field IDs) + +```rust +// Xlang mode: explicit configuration required +#[derive(ForyStruct)] +struct User { + #[fory(id = 0)] + name: String, // Non-nullable by default + + #[fory(id = 1)] + email: Option, // Nullable (Option) + + #[fory(id = 2, ref = true)] + friend: Rc, // Ref tracking (default for Rc) +} +``` + +### Default Values Summary + +| Type | Default Nullable | Default Ref Tracking | +| ------------------------- | ---------------- | -------------------- | +| Primitives, `String` | `false` | `false` | +| `Option` | `true` | `false` | +| `Rc`, `Arc` | `false` | `true` | +| `RcWeak`, `ArcWeak` | `true` | `true` | + +## Best Practices + +1. **Configure field IDs**: Recommended for compatible mode to reduce serialization cost +2. **Use `skip` for sensitive data**: Passwords, tokens, internal state +3. **Enable ref tracking for shared objects**: When the same pointer appears multiple times +4. **Disable ref tracking for unique fields**: Optimization when you know the field is unique +5. **Choose appropriate encoding**: `varint` for small values, `fixed` for full-range values +6. **Keep IDs stable**: Once assigned, don't change field IDs + +## Options Reference + +| Option | Syntax | Description | Valid For | +| ---------- | -------------------------------- | ------------------------------------ | -------------------------- | +| `id` | `id = N` | Field tag ID to reduce metadata size | All fields | +| `skip` | `skip` | Exclude field from serialization | All fields | +| `nullable` | `nullable` or `nullable = bool` | Control null flag writing | All fields | +| `ref` | `ref` or `ref = bool` | Control reference tracking | `Rc`, `Arc`, weak types | +| `encoding` | `encoding = varint/fixed/tagged` | Integer encoding method | `i32`, `u32`, `i64`, `u64` | +| `list` | `list(element(...))` | Element schema metadata | `Vec` | +| `map` | `map(key(...), value(...))` | Key/value schema metadata | `HashMap` | + +## Related Topics + +- [Basic Serialization](basic-serialization.md) - Getting started with Fory serialization +- [Schema Evolution](schema-evolution.md) - Compatible mode and schema evolution +- [Xlang Serialization](xlang-serialization.md) - Interoperability with Java, C++, Go, Python diff --git a/versioned_docs/version-1.3.0/guide/rust/troubleshooting.md b/versioned_docs/version-1.3.0/guide/rust/troubleshooting.md new file mode 100644 index 00000000000..6f25c1f9274 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/rust/troubleshooting.md @@ -0,0 +1,167 @@ +--- +title: Troubleshooting +sidebar_position: 13 +id: troubleshooting +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 page covers common issues and debugging techniques for Apache Fory™ Rust. + +## Common Issues + +### Type Registry Errors + +**Error**: `TypeId ... not found in type_info registry` + +**Cause**: The type was never registered with the current `Fory` instance. + +**Solution**: Register the type before serialization: + +```rust +let mut fory = Fory::default(); +fory.register::(100)?; // Register before use +``` + +Confirm that: + +- Every serializable struct or trait implementation calls `fory.register::(type_id)` +- The same IDs are reused on the deserialize side + +### Type Mismatch Errors + +**Cause**: Field types are incompatible or schema has changed. + +**Solution**: + +- Keep compatible mode enabled for schema evolution +- Ensure field types match across versions + +```rust +// Remove any compatible(false) override from the peers. +let fory = Fory::builder() + // existing options + .build(); +``` + +## Debugging Techniques + +### Enable Panic on Error for Backtraces + +Toggle `FORY_PANIC_ON_ERROR=1` alongside `RUST_BACKTRACE=1` to panic at the exact site an error is constructed: + +```bash +RUST_BACKTRACE=1 FORY_PANIC_ON_ERROR=1 cargo test --features tests +``` + +Reset the variable afterwards to avoid aborting user-facing code paths. + +### Struct Field Tracing + +Add the `#[fory(debug)]` attribute alongside `#[derive(ForyStruct)]` to emit hook invocations: + +```rust +#[derive(ForyStruct)] +#[fory(debug)] +struct MyStruct { + field1: i32, + field2: String, +} +``` + +Once compiled with debug hooks, call these functions to plug in custom callbacks: + +- `set_before_write_field_func` +- `set_after_write_field_func` +- `set_before_read_field_func` +- `set_after_read_field_func` + +Use `reset_struct_debug_hooks()` when you want the defaults back. + +### Lightweight Logging + +Without custom hooks, enable `ENABLE_FORY_DEBUG_OUTPUT=1` to print field-level read/write events: + +```bash +ENABLE_FORY_DEBUG_OUTPUT=1 cargo test --features tests +``` + +This is especially useful when investigating alignment or cursor mismatches. + +### Inspect Generated Code + +Use `cargo expand` to inspect code generated by Fory derive macros: + +```bash +cargo expand --test mod $mod$::$file$ > expanded.rs +``` + +## Running Tests + +### Run All Tests + +```bash +cargo test --features tests +``` + +### Run Specific Test + +```bash +cargo test -p tests --test $test_file $test_method +``` + +### Run Test with Debugging + +```bash +RUST_BACKTRACE=1 FORY_PANIC_ON_ERROR=1 ENABLE_FORY_DEBUG_OUTPUT=1 \ + cargo test --test mod $dir$::$test_file::$test_method -- --nocapture +``` + +## Test-Time Hygiene + +Some integration tests expect `FORY_PANIC_ON_ERROR` to remain unset. Export it only for focused debugging sessions: + +```bash +# For specific debugging only +FORY_PANIC_ON_ERROR=1 cargo test -p tests --test specific_test -- --nocapture + +# Normal test run (without panic on error) +cargo test --features tests +``` + +## Error Handling Best Practices + +Prefer the static constructors on the facade `Error` type: + +- `Error::type_mismatch` +- `Error::invalid_data` +- `Error::unknown` + +This keeps diagnostics consistent and makes opt-in panics work correctly. + +## Quick Reference + +| Environment Variable | Purpose | +| ---------------------------- | ----------------------------------- | +| `RUST_BACKTRACE=1` | Enable stack traces | +| `FORY_PANIC_ON_ERROR=1` | Panic at error site for debugging | +| `ENABLE_FORY_DEBUG_OUTPUT=1` | Print field-level read/write events | + +## Related Topics + +- [Configuration](configuration.md) - Fory options +- [Type Registration](type-registration.md) - Registration best practices +- [Schema Evolution](schema-evolution.md) - Compatible mode diff --git a/versioned_docs/version-1.3.0/guide/rust/type-registration.md b/versioned_docs/version-1.3.0/guide/rust/type-registration.md new file mode 100644 index 00000000000..d2830b99f03 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/rust/type-registration.md @@ -0,0 +1,125 @@ +--- +title: Type Registration +sidebar_position: 5 +id: type_registration +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 page covers type registration methods in Apache Fory™ Rust. + +## Register by ID + +Register types with a numeric ID for fast, compact serialization: + +```rust +use fory::Fory; +use fory::ForyStruct; + +#[derive(ForyStruct)] +struct User { + name: String, + age: i32, +} + +let mut fory = Fory::builder().xlang(false).build(); +fory.register::(1)?; + +let user = User { + name: "Alice".to_string(), + age: 30, +}; + +let bytes = fory.serialize(&user)?; +let decoded: User = fory.deserialize(&bytes)?; +``` + +## Register by Name + +For cross-language compatibility, register with a stable name. Use `.` to separate a +namespace prefix from the type name: + +```rust +let mut fory = Fory::builder().xlang(true).build(); + +// Register with symbolic type identity +fory.register_by_name::("com.example.MyStruct")?; +``` + +## Register Custom Serializer + +For types that need custom serialization logic: + +```rust +let mut fory = Fory::builder().xlang(false).build(); +fory.register_serializer::(100)?; +``` + +## Registration Consistency + +Rust registration APIs use explicit IDs or explicit names. Keep the same registration mapping on serializer and deserializer peers: + +```rust +// Serializer side +let mut fory = Fory::builder().xlang(false).build(); +fory.register::(1)?; +fory.register::(2)?; +fory.register::(3)?; + +// Deserializer side - MUST use the same ID mapping +let mut fory = Fory::builder().xlang(false).build(); +fory.register::(1)?; +fory.register::(2)?; +fory.register::(3)?; +``` + +## Thread-Safe Registration + +Perform all registrations before spawning threads: + +```rust +use std::sync::Arc; +use std::thread; + +let mut fory = Fory::builder().xlang(false).build(); +fory.register::(1)?; +fory.register::(2)?; + +// Now share across threads +let fory = Arc::new(fory); + +let handles: Vec<_> = (0..4) + .map(|_| { + let shared = Arc::clone(&fory); + thread::spawn(move || { + // Use fory for serialization + }) + }) + .collect(); +``` + +## Best Practices + +1. **Use consistent IDs**: Same type ID across all languages for cross-language compatibility +2. **Register before threading**: Complete all registrations before spawning threads +3. **Use namespace for xlang**: Makes type names consistent across languages +4. **Explicit IDs for stability**: Avoid auto-generated IDs in production + +## Related Topics + +- [Configuration](configuration.md) - Fory builder options +- [Xlang Serialization](xlang-serialization.md) - xlang mode registration +- [Custom Serializers](custom-serializers.md) - Custom serialization diff --git a/versioned_docs/version-1.3.0/guide/rust/xlang-serialization.md b/versioned_docs/version-1.3.0/guide/rust/xlang-serialization.md new file mode 100644 index 00000000000..0f83e18224b --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/rust/xlang-serialization.md @@ -0,0 +1,190 @@ +--- +title: Xlang Serialization +sidebar_position: 2 +id: xlang_serialization +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. +--- + +Apache Fory™ supports seamless data exchange across Java, Python, C++, Go, +Rust, JavaScript/TypeScript, C#, Swift, Dart, Scala, and Kotlin. + +## Create an Xlang Fory Instance + +Rust defaults to xlang mode with compatible schema evolution. Set the mode explicitly in xlang examples: + +```rust +use fory::Fory; + +// Use xlang mode +let mut fory = Fory::builder().xlang(true).build(); + +// Register types with consistent IDs across languages +fory.register::(100)?; + +// Or use name-based registration +fory.register_by_name::("com.example.MyStruct")?; +``` + +## Type Registration for Xlang + +### Register by ID + +For fast, compact serialization with consistent IDs across languages: + +```rust +let mut fory = Fory::builder().xlang(true).build(); + +fory.register::(100)?; // Same ID in Java, Python, etc. +``` + +### Register by Name + +For more flexible type naming: + +```rust +fory.register_by_name::("com.example.User")?; +``` + +## Xlang Example + +### Rust (Serializer) + +```rust +use fory::Fory; +use fory::ForyStruct; + +#[derive(ForyStruct)] +struct Person { + name: String, + age: i32, +} + +let mut fory = Fory::builder().xlang(true).build(); + +fory.register::(100)?; + +let person = Person { + name: "Alice".to_string(), + age: 30, +}; + +let bytes = fory.serialize(&person)?; +// bytes can be deserialized by Java, Python, etc. +``` + +### Java (Deserializer) + +```java +import org.apache.fory.*; +import org.apache.fory.config.*; + +public class Person { + public String name; + public int age; +} + +Fory fory = Fory.builder() + .withXlang(true) + .withRefTracking(true) + .build(); + +fory.register(Person.class, 100); // Same ID as Rust + +Person person = (Person) fory.deserialize(bytesFromRust); +``` + +### Python (Deserializer) + +```python +import pyfory +from dataclasses import dataclass + +@dataclass +class Person: + name: str + age: pyfory.Int32 + +fory = pyfory.Fory(xlang=True, ref=True) +fory.register_type(Person, type_id=100) # Same ID as Rust + +person = fory.deserialize(bytes_from_rust) +``` + +## Type Mapping + +See [xlang_type_mapping.md](../../specification/xlang_type_mapping.md) for complete type mapping across languages. + +### Common Type Mappings + +| Rust | Java | Python | +| --------------- | -------------- | --------------- | +| `i32` | `int` | `int32` | +| `i64` | `long` | `int64` | +| `f32` | `float` | `float32` | +| `f64` | `double` | `float64` | +| `Float16` | `Float16` | `float16` | +| `BFloat16` | `BFloat16` | `bfloat16` | +| `String` | `String` | `str` | +| `Vec` | `List` | `List[T]` | +| `Vec` | `Float16List` | `Float16Array` | +| `Vec` | `BFloat16List` | `BFloat16Array` | +| `[Float16; N]` | `Float16List` | `Float16Array` | +| `[BFloat16; N]` | `BFloat16List` | `BFloat16Array` | +| `HashMap` | `Map` | `Dict[K,V]` | +| `Option` | nullable `T` | `Optional[T]` | + +### Lists and Dense Arrays + +Rust `Vec` maps to Fory `list` by default for manual structs. Use an +explicit array field attribute when the schema is dense `array`. + +| Fory schema | Rust carrier and metadata | +| ----------------- | ------------------------------ | +| `list` | `Vec` | +| `array` | `#[fory(array)] Vec` | +| `array` | `#[fory(array)] Vec` | +| `array` | `#[fory(array)] Vec` | +| `array` | `#[fory(array)] Vec` | +| `array` | `#[fory(array)] Vec` | +| `array` | `#[fory(array)] Vec` | +| `array` | `#[fory(array)] Vec` | +| `array` | `#[fory(array)] Vec` | +| `array` | `#[fory(array)] Vec` | +| `array` | `#[fory(array)] Vec` | +| `array` | `#[fory(array)] Vec` | +| `array` | `#[fory(array)] Vec` | +| `array` | `#[fory(array)] Vec` | + +## Best Practices + +1. **Use consistent type IDs** across all languages +2. **Keep compatible mode** for schema evolution +3. **Register all types** before serialization +4. **Test cross-language** compatibility during development + +## See Also + +- [Xlang Serialization Specification](../../specification/xlang_serialization_spec.md) +- [Type Mapping Reference](../../specification/xlang_type_mapping.md) +- [Java Xlang Serialization Guide](../java/xlang-serialization.md) +- [Python Xlang Serialization Guide](../python/xlang-serialization.md) + +## Related Topics + +- [Configuration](configuration.md) - xlang mode configuration +- [Schema Evolution](schema-evolution.md) - Compatible mode +- [Type Registration](type-registration.md) - Registration methods diff --git a/versioned_docs/version-1.3.0/guide/scala/_category_.json b/versioned_docs/version-1.3.0/guide/scala/_category_.json new file mode 100644 index 00000000000..a0191e28738 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/scala/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "Scala", + "position": 10, + "collapsible": true, + "collapsed": true +} diff --git a/versioned_docs/version-1.3.0/guide/scala/configuration.md b/versioned_docs/version-1.3.0/guide/scala/configuration.md new file mode 100644 index 00000000000..397657d873d --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/scala/configuration.md @@ -0,0 +1,195 @@ +--- +title: Configuration +sidebar_position: 1 +id: configuration +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 page covers Scala-specific Fory instance configuration and creation. + +## Xlang Setup + +Fory Scala follows the Java builder default: xlang mode with compatible schema +evolution. Use this path for cross-language Scala payloads, schema IDL generated +Scala models, and macro-derived xlang serializers. + +```scala +import org.apache.fory.scala.ForyScala + +val fory = ForyScala.builder() + .withXlang(true) + .build() +``` + +Register application classes before serialization: + +```scala +fory.register(classOf[Person]) +fory.register(classOf[Point]) +``` + +## Native Mode Setup + +For same-language Scala/JVM payloads that need native JVM object behavior, you +must: + +1. Create the Fory instance with `ForyScala.builder().withXlang(false)`, or install + `ForyScala` with `Fory.builder().withXlang(false).withModule(ForyScala)`. +2. Register application classes before serialization. + +```scala +import org.apache.fory.scala.ForyScala + +val fory = ForyScala.builder().withXlang(false) + .build() +``` + +### Registering Scala Internal Types + +Depending on the object types you serialize, you may need to register some Scala internal types: + +```scala +fory.register(Class.forName("scala.Enumeration.Val")) +``` + +To avoid such registration, you can disable class registration: + +```scala +val fory = ForyScala.builder().withXlang(false) + .requireClassRegistration(false) + .build() +``` + +> **Note**: Disabling class registration allows deserialization of unknown types. This is more flexible but may be insecure if the classes contain malicious code. + +### Reference Tracking + +Circular references are common in Scala. Reference tracking should be enabled with `withRefTracking(true)`: + +```scala +val fory = ForyScala.builder() + .withRefTracking(true) + .build() +``` + +> **Note**: If you don't enable reference tracking, [StackOverflowError](https://github.com/apache/fory/issues/1032) may occur for some Scala versions when serializing Scala Enumeration. + +## Thread Safety + +Fory instance creation is not cheap. Instances should be shared between multiple serializations. + +### Single-Thread Usage + +```scala +import org.apache.fory.Fory +import org.apache.fory.scala.ForyScala + +object ForyHolder { + val fory: Fory = ForyScala.builder() + .withXlang(true) + .build() +} +``` + +### Multi-Thread Usage + +For multi-threaded applications, use `ThreadSafeFory`: + +```scala +import org.apache.fory.ThreadSafeFory +import org.apache.fory.scala.ForyScala + +object ForyHolder { + val fory: ThreadSafeFory = ForyScala.builder() + .withXlang(true) + .buildThreadSafeFory() +} +``` + +## Configuration + +All configuration options from Fory Java are available. See [Java Configuration](../java/configuration.md) for the complete list. + +Common options for Scala native-mode payloads: + +```scala +import org.apache.fory.scala.ForyScala + +val fory = ForyScala.builder().withXlang(false) + // Enable reference tracking for circular references + .withRefTracking(true) + // Same-schema optimization. Use only when every reader and writer + // always uses the same Scala/JVM schema. + .withCompatible(false) + // Enable async compilation for better startup performance + .withAsyncCompilation(true) + .build() +``` + +## Xlang Mode + +For Scala xlang or schema IDL generated code, use the default xlang mode and +register the generated schema module: + +```scala +import org.apache.fory.scala.ForyScala +import example.ExampleForyModule + +val fory = ForyScala.builder() + .withXlang(true) + .withRefTracking(true) + .withModule(ExampleForyModule) + .build() +``` + +In xlang mode, Scala collections use canonical `list`, `set`, and `map` +payloads instead of Scala factory payloads. Generated optional fields use +`Option[T]`. + +## Compatible Mode + +Compatible mode is enabled by default through the Java builder in both xlang and native mode. Keep +this default when models may evolve independently, when services deploy separately, or when xlang +schemas are written by hand in different languages. + +Use `withCompatible(false)` only when the class schema used to deserialize every payload is always +the same as the class schema used to serialize it and you want faster serialization and smaller size. +For xlang payloads, call `withCompatible(false)` only after verifying that every language uses the same schema, or when native types are generated from Fory schema IDL. + +## Security + +Scala uses the Java configuration surface. Keep class registration enabled for production +and any untrusted payload source: + +```scala +val fory = ForyScala.builder() + .requireClassRegistration(true) + .withMaxDepth(50) + .withMaxTypeFields(512) + .withMaxTypeMetaBytes(4096) + .build() +``` + +Security-related configuration: + +- Keep `requireClassRegistration(true)` and register application classes or generated modules. +- Use `withMaxDepth(...)` to reject unexpectedly deep object graphs. +- Keep `withMaxTypeFields(...)`, `withMaxTypeMetaBytes(...)`, and the remote schema-version limits + at their defaults unless the data is not malicious and a trusted peer sends larger metadata or + many schema versions. +- Follow [Java Configuration](../java/configuration.md#security) for allow-listing and unknown-class + controls. diff --git a/versioned_docs/version-1.3.0/guide/scala/default-values.md b/versioned_docs/version-1.3.0/guide/scala/default-values.md new file mode 100644 index 00000000000..8624405709a --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/scala/default-values.md @@ -0,0 +1,169 @@ +--- +title: Default Values +sidebar_position: 4 +id: default_values +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. +--- + +Fory supports Scala class default values during native-mode deserialization when compatible mode is enabled. This feature enables forward/backward compatibility when case classes or regular Scala classes have default parameters. + +## Overview + +When a Scala class has default parameters, the Scala compiler generates methods in the companion object (for case classes) or in the class itself (for regular Scala classes) like `apply$default$1`, `apply$default$2`, etc. that return the default values. Fory can detect these methods and use them when deserializing objects where certain fields are missing from the serialized data. + +## Supported Class Types + +Fory supports default values for: + +- **Case classes** with default parameters +- **Regular Scala classes** with default parameters in their primary constructor +- **Nested case classes** with default parameters + +## How It Works + +1. **Detection**: Fory detects if a class is a Scala class by checking for the presence of default value methods (`apply$default$N` or `$default$N`). + +2. **Default Value Discovery**: + - For case classes: Fory scans the companion object for methods named `apply$default$1`, `apply$default$2`, etc. + - For regular Scala classes: Fory scans the class itself for methods named `$default$1`, `$default$2`, etc. + +3. **Field Mapping**: During deserialization, Fory identifies fields that exist in the target class but are missing from the serialized data. + +4. **Value Application**: After reading all available fields from the serialized data, Fory applies default values to any missing fields. + +## Usage + +This feature is available when: + +- Compatible mode is enabled for the native-mode Fory instance. This is the default with + `withXlang(false)`. +- The target class is detected as a Scala class with default values +- A field is missing from the serialized data but exists in the target class + +No additional configuration is required. + +## Examples + +### Case Class with Default Values + +```scala +import org.apache.fory.scala.ForyScala + +// Class WITHOUT default values (for serialization) +case class PersonV1(name: String) + +// Class WITH default values (for deserialization) +case class PersonV2(name: String, age: Int = 25, city: String = "Unknown") + +val fory = ForyScala.builder().withXlang(false) + .build() + +// Serialize using class without default values +val original = PersonV1("John") +val serialized = fory.serialize(original) + +// Deserialize into class with default values +// Missing fields will use defaults +val deserialized = fory.deserialize(serialized).asInstanceOf[PersonV2] +// deserialized.name == "John" +// deserialized.age == 25 (default) +// deserialized.city == "Unknown" (default) +``` + +### Regular Scala Class with Default Values + +```scala +// Class WITHOUT default values (for serialization) +class EmployeeV1(val name: String) + +// Class WITH default values (for deserialization) +class EmployeeV2( + val name: String, + val age: Int = 30, + val department: String = "Engineering" +) + +val fory = ForyScala.builder().withXlang(false) + .build() + +// Serialize using class without default values +val original = new EmployeeV1("Jane") +val serialized = fory.serialize(original) + +// Deserialize into class with default values +val deserialized = fory.deserialize(serialized).asInstanceOf[EmployeeV2] +// deserialized.name == "Jane" +// deserialized.age == 30 (default) +// deserialized.department == "Engineering" (default) +``` + +### Complex Default Values + +Default values can be complex expressions: + +```scala +// Class WITHOUT default values (for serialization) +case class ConfigV1(name: String) + +// Class WITH default values (for deserialization) +case class ConfigV2( + name: String, + settings: Map[String, String] = Map("default" -> "value"), + tags: List[String] = List("default"), + enabled: Boolean = true +) + +val fory = ForyScala.builder().withXlang(false) + .build() + +val original = ConfigV1("myConfig") +val serialized = fory.serialize(original) + +val deserialized = fory.deserialize(serialized).asInstanceOf[ConfigV2] +// deserialized.name == "myConfig" +// deserialized.settings == Map("default" -> "value") +// deserialized.tags == List("default") +// deserialized.enabled == true +``` + +### Nested Case Classes + +```scala +object Models { + // Class WITHOUT default values (for serialization) + case class PersonV1(name: String) + + // Classes WITH default values (for deserialization) + case class Address(street: String, city: String = "DefaultCity") + case class PersonV2(name: String, address: Address = Address("DefaultStreet")) +} + +val fory = ForyScala.builder().withXlang(false) + .build() + +val original = Models.PersonV1("Alice") +val serialized = fory.serialize(original) + +val deserialized = fory.deserialize(serialized).asInstanceOf[Models.PersonV2] +// deserialized.name == "Alice" +// deserialized.address == Address("DefaultStreet", "DefaultCity") +``` + +## Related Topics + +- [Schema Evolution](../java/schema-evolution.md) - Forward/backward compatibility in Java +- [Configuration](configuration.md) - Setting up Fory with compatible mode diff --git a/versioned_docs/version-1.3.0/guide/scala/grpc-support.md b/versioned_docs/version-1.3.0/guide/scala/grpc-support.md new file mode 100644 index 00000000000..f89f47d689b --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/scala/grpc-support.md @@ -0,0 +1,397 @@ +--- +title: gRPC Support +sidebar_position: 6 +id: grpc_support +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. +--- + +Fory can generate Scala 3 gRPC service companions for schemas that define +services. The generated service code uses normal grpc-java channels, servers, +deadlines, status codes, interceptors, and transport security, while request +and response objects are serialized with Fory instead of protobuf. + +Use this mode when both sides of the RPC are generated from the same Fory IDL, +protobuf IDL, or FlatBuffers IDL and you want gRPC transport semantics with +Fory payload encoding. Use standard protobuf gRPC code generation when your API +must be consumed by generic protobuf clients, reflection tools, or components +that expect protobuf message bytes. + +## Add Dependencies + +Generated Scala service files compile against grpc-java. The `fory-scala` +artifact does not add gRPC as a hard dependency, so add grpc-java dependencies +in your application build and align the version with the rest of your service +stack. + +```sbt +libraryDependencies ++= Seq( + "org.apache.fory" %% "fory-scala" % "", + "io.grpc" % "grpc-api" % "", + "io.grpc" % "grpc-stub" % "", + "io.grpc" % "grpc-netty-shaded" % "" +) +``` + +Generated Scala models and gRPC service companions are Scala 3 source. The +`fory-scala` artifact remains cross-built for Scala 2.13 and Scala 3, and the +dependency-free `org.apache.fory.scala.rpc` handle traits are available from +the shared artifact. + +## Define a Service + +Service definitions can come from Fory IDL, protobuf IDL, or FlatBuffers +`rpc_service` definitions. A Fory IDL service looks like this: + +```protobuf +package demo.greeter; + +message HelloRequest { + string name = 1; +} + +message HelloReply { + string reply = 1; +} + +service Greeter { + rpc SayHello (HelloRequest) returns (HelloReply); +} +``` + +Generate Scala model and gRPC companion code with `--grpc`: + +```bash +foryc service.fdl --scala_out=./generated/scala --grpc +``` + +For this schema, the Scala generator emits: + +| File | Purpose | +| ------------------------- | -------------------------------------------- | +| `HelloRequest.scala` | Fory model type for the request | +| `HelloReply.scala` | Fory model type for the response | +| `GreeterForyModule.scala` | Fory registration module for generated types | +| `GreeterGrpc.scala` | grpc-java service base, client, and codecs | + +## Implement a Server + +Extend the generated `GreeterGrpc.GreeterImplBase` class and register it with a +standard grpc-java `Server`. Unary RPCs can be implemented with a direct +request-to-response method: + +```scala +package demo.greeter + +import io.grpc.ServerBuilder + +final class GreeterService extends GreeterGrpc.GreeterImplBase { + override def sayHello(request: HelloRequest): HelloReply = + HelloReply(s"Hello, ${request.name}") +} + +@main def runServer(): Unit = { + val server = ServerBuilder + .forPort(50051) + .addService(new GreeterService) + .build() + .start() + server.awaitTermination() +} +``` + +Generated request and response types are registered by the generated code, so +service implementations do not perform manual serializer registration. + +## Create a Client + +Use the generated client with an ordinary grpc-java channel: + +```scala +package demo.greeter + +import io.grpc.ManagedChannelBuilder +import scala.concurrent.Await +import scala.concurrent.duration.DurationInt + +@main def runClient(): Unit = { + val channel = ManagedChannelBuilder + .forAddress("localhost", 50051) + .usePlaintext() + .build() + try { + val client = GreeterGrpc.newClient(channel) + val call = client.sayHello(HelloRequest("Fory")) + val reply = Await.result(call.asFuture, 30.seconds) + println(reply.reply) + } finally { + channel.shutdownNow() + } +} +``` + +Unary Scala-friendly methods return `RpcFuture[A]`. Use `asFuture` for Scala +composition, and call `cancel()` when the RPC should be cancelled before it +completes. The same generated client also exposes grpc-java-style per-method +variants such as observer-based async calls, blocking calls, and +`ListenableFuture` unary calls. + +## Streaming RPCs + +Fory service definitions can use the same gRPC streaming shapes as grpc-java: + +```protobuf +service Greeter { + rpc SayHello (HelloRequest) returns (HelloReply); + rpc LotsOfReplies (HelloRequest) returns (stream HelloReply); + rpc LotsOfGreetings (stream HelloRequest) returns (HelloReply); + rpc Chat (stream HelloRequest) returns (stream HelloReply); +} +``` + +Generated Scala methods use these shapes: + +| IDL shape | Scala client convenience | grpc-java-style methods | +| ----------------------- | ------------------------ | ------------------------------------------------ | +| Unary | `RpcFuture[Resp]` | Async observer, blocking, and `ListenableFuture` | +| Server streaming | `RpcIterator[Resp]` | Async observer and blocking iterator | +| Client streaming | None | `StreamObserver` request stream | +| Bidirectional streaming | None | `StreamObserver` request and response streams | + +The Scala-friendly convenience layer covers the cases where a direct Scala +handle can keep the important lifecycle controls. Unary calls use +`RpcFuture[A]` so callers can compose with Scala `Future` without losing +cancellation. Server-streaming calls use `RpcIterator[A]` so callers can consume +responses with the normal Scala `Iterator` contract while still being able to +close the underlying RPC. Client-streaming and bidirectional streaming stay on +grpc-java `StreamObserver` APIs because the request stream lifecycle, +completion, cancellation, and flow-control rules are the grpc-java rules. + +### Server-Streaming Clients + +Use the Scala-friendly method when the client wants pull-style consumption: + +```scala +val stream = client.lotsOfReplies(HelloRequest("Fory")) +try { + while (stream.hasNext) { + val reply = stream.next() + println(reply.reply) + } +} finally { + stream.close() +} +``` + +`RpcIterator[A]` extends Scala `Iterator[A]` and `AutoCloseable`. A fully +consumed stream closes when the server completes it. If the caller stops early, +call `close()` or `cancel()` to release the gRPC call and notify the server that +the response stream is no longer needed. + +Use the observer overload when the client wants grpc-java async callbacks: + +```scala +import io.grpc.stub.StreamObserver + +client.lotsOfReplies( + HelloRequest("Fory"), + new StreamObserver[HelloReply] { + override def onNext(value: HelloReply): Unit = + println(value.reply) + + override def onError(t: Throwable): Unit = + t.printStackTrace() + + override def onCompleted(): Unit = + println("done") + } +) +``` + +The generated client also exposes a blocking grpc-java-style iterator through +`lotsOfRepliesBlocking(request)`. Prefer the Scala-friendly `RpcIterator` when +you need early cancellation; use the blocking iterator only when matching an +existing grpc-java workflow. + +### Client-Streaming Clients + +For client-streaming RPCs, the generated method accepts a response observer and +returns the request observer. Send every request with `onNext`, then call +`onCompleted` exactly once when the client has finished sending: + +```scala +import io.grpc.stub.StreamObserver + +val requests = client.lotsOfGreetings( + new StreamObserver[HelloReply] { + override def onNext(value: HelloReply): Unit = + println(value.reply) + + override def onError(t: Throwable): Unit = + t.printStackTrace() + + override def onCompleted(): Unit = + println("server completed") + } +) + +requests.onNext(HelloRequest("Ada")) +requests.onNext(HelloRequest("Grace")) +requests.onCompleted() +``` + +If the client cannot finish sending requests, signal the failure with +`requests.onError(error)`. Deadlines, cancellation, and call options are the +standard grpc-java stub features, so they are configured on the generated client +stub before starting the call. + +### Bidirectional Clients + +Bidirectional streaming uses the same grpc-java request observer pattern, but +responses can arrive while the client is still sending requests: + +```scala +import io.grpc.stub.StreamObserver + +val requests = client.chat( + new StreamObserver[HelloReply] { + override def onNext(value: HelloReply): Unit = + println(value.reply) + + override def onError(t: Throwable): Unit = + t.printStackTrace() + + override def onCompleted(): Unit = + println("chat closed") + } +) + +requests.onNext(HelloRequest("first")) +requests.onNext(HelloRequest("second")) +requests.onCompleted() +``` + +Use grpc-java observer subtypes such as `ClientResponseObserver`, +`ClientCallStreamObserver`, or `ServerCallStreamObserver` when an application +needs manual inbound flow control, readiness callbacks, cancellation handlers, +or direct transport-level cancellation. The generated Scala methods accept the +standard grpc-java observer types, so those advanced grpc-java patterns remain +available without a separate Fory API. + +### Streaming Servers + +Unary server methods can use the direct Scala-friendly override shown earlier. +Streaming server methods use grpc-java observers. A server-streaming +implementation receives one request and writes zero or more responses: + +```scala +import io.grpc.stub.StreamObserver +import scala.util.control.NonFatal + +final class GreeterService extends GreeterGrpc.GreeterImplBase { + override def lotsOfReplies( + request: HelloRequest, + responseObserver: StreamObserver[HelloReply] + ): Unit = + try { + responseObserver.onNext(HelloReply(s"Hello, ${request.name}")) + responseObserver.onNext(HelloReply(s"Welcome, ${request.name}")) + responseObserver.onCompleted() + } catch { + case NonFatal(e) => + responseObserver.onError(e) + } +} +``` + +Client-streaming servers return an observer for incoming requests and write the +single response when the request stream completes: + +```scala +import io.grpc.stub.StreamObserver +import scala.collection.mutable.ArrayBuffer + +final class GreeterService extends GreeterGrpc.GreeterImplBase { + override def lotsOfGreetings( + responseObserver: StreamObserver[HelloReply] + ): StreamObserver[HelloRequest] = + new StreamObserver[HelloRequest] { + private val names = ArrayBuffer.empty[String] + + override def onNext(value: HelloRequest): Unit = + names += value.name + + override def onError(t: Throwable): Unit = + names.clear() + + override def onCompleted(): Unit = { + responseObserver.onNext(HelloReply(names.mkString("Hello ", ", ", ""))) + responseObserver.onCompleted() + } + } +} +``` + +Bidirectional servers also return an observer for incoming requests, but may +emit responses from each `onNext` call: + +```scala +import io.grpc.stub.StreamObserver + +final class GreeterService extends GreeterGrpc.GreeterImplBase { + override def chat( + responseObserver: StreamObserver[HelloReply] + ): StreamObserver[HelloRequest] = + new StreamObserver[HelloRequest] { + override def onNext(value: HelloRequest): Unit = + responseObserver.onNext(HelloReply(s"Echo: ${value.name}")) + + override def onError(t: Throwable): Unit = () + + override def onCompleted(): Unit = + responseObserver.onCompleted() + } +} +``` + +Server-streaming, client-streaming, and bidirectional server methods use +grpc-java `StreamObserver` APIs because streaming completion, request flow +control, cancellation, and backpressure follow grpc-java behavior. + +## gRPC Runtime Behavior + +The generated service code only replaces request and response serialization. +All normal gRPC operational features still belong to grpc-java: + +- Deadlines and cancellations +- TLS and authentication +- Name resolution and load balancing +- Client and server interceptors +- Status codes and metadata +- Channel pooling and lifecycle management + +## Troubleshooting + +### Missing `io.grpc` or Guava Classes + +Add the grpc-java dependencies shown above. Generated Fory service files import +grpc-java APIs, but `fory-scala` intentionally does not depend on gRPC. + +### Generic Protobuf Clients Cannot Read Payloads + +Fory-generated gRPC services use Fory bytes inside gRPC message frames, not +protobuf message bytes. Use a Fory-generated client for Fory-generated services, +or provide a separate protobuf service endpoint for generic protobuf clients. diff --git a/versioned_docs/version-1.3.0/guide/scala/index.md b/versioned_docs/version-1.3.0/guide/scala/index.md new file mode 100644 index 00000000000..94b000da6b3 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/scala/index.md @@ -0,0 +1,122 @@ +--- +title: Scala Serialization Guide +sidebar_position: 0 +id: serialization_index +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. +--- + +Apache Fory™ Scala provides optimized serializers for Scala types, built on top of Fory Java. It supports xlang mode for cross-language payloads and native mode for Scala/JVM-only object serialization. It supports all Scala object serialization: + +- `case` class serialization +- `pojo/bean` class serialization +- `object` singleton serialization +- `collection` serialization (Seq, List, Map, etc.) +- `tuple` and `either` types +- `Option` types +- Scala 2 and 3 enumerations + +The library artifact supports Scala 2.13 and Scala 3. Schema IDL generated +Scala source and macro-derived xlang serializers require Scala 3. + +## Features + +Fory Scala inherits all features from Fory Java, plus Scala-specific optimizations: + +- **High Performance**: JIT code generation, zero-copy, 20-170x faster than traditional serialization +- **Scala Type Support**: Optimized serializers for case classes, singletons, collections, tuples, Option, Either +- **Default Value Support**: Automatic handling of Scala class default parameters during schema evolution +- **Singleton Preservation**: `object` singletons maintain referential equality after deserialization +- **Schema Evolution**: Forward/backward compatibility for class schema changes + +See [Java Features](../java/index.md#features) for complete feature list. + +## Installation + +Add the dependency with sbt: + +```sbt +libraryDependencies += "org.apache.fory" %% "fory-scala" % "1.3.0" +``` + +### JDK25+ + +Scala uses the Fory Java core when running. On JDK25+, open `java.lang.invoke` to +Fory. Use `ALL-UNNAMED` when Fory is on the classpath: + +```bash +--add-opens=java.base/java.lang.invoke=ALL-UNNAMED +``` + +Use the Fory core module name when Fory is on the module path: + +```bash +--add-opens=java.base/java.lang.invoke=org.apache.fory.core +``` + +## Quick Start + +```scala +import org.apache.fory.Fory +import org.apache.fory.scala.ForyScala + +case class Person(name: String, id: Long, github: String) +case class Point(x: Int, y: Int, z: Int) + +object ScalaExample { + val fory: Fory = ForyScala.builder() + .withXlang(true) + .build() + + fory.register(classOf[Person]) + fory.register(classOf[Point]) + + def main(args: Array[String]): Unit = { + val p = Person("Shawn Yang", 1, "https://github.com/chaokunyang") + println(fory.deserialize(fory.serialize(p))) + println(fory.deserialize(fory.serialize(Point(1, 2, 3)))) + } +} +``` + +## Xlang Mode And Native Mode + +Use xlang mode for cross-language payloads and schemas shared with other Fory implementations. Xlang mode is the default Scala wire mode through the JVM builder, and Scala examples that use it set `.withXlang(true)` explicitly so the mode choice is visible. + +Use native mode for Scala/JVM-only traffic. Native mode is selected with `.withXlang(false)` and inherits the JVM native-mode object serialization path from Fory Java while adding Scala-specific serializers for case classes, collections, tuples, options, and enumerations. It is optimized for JVM and Scala type systems and is the right path for same-language Scala/JVM framework replacement payloads. Compatible mode is enabled by default. Set `.withCompatible(false)` only when every reader and writer uses the same Scala/JVM schema and you want faster serialization and smaller size. + +See [Configuration](configuration.md) for Scala builder setup and [Java Native Serialization](../java/native-serialization.md) for the full JVM native-mode behavior. + +## Built on Fory Java + +Fory Scala is built on top of Fory Java. Most configuration options, features, and concepts from Fory Java apply directly to Scala. Refer to the Java documentation for: + +- [Configuration](../java/configuration.md) - All ForyBuilder options +- [Basic Serialization](../java/basic-serialization.md) - Serialization patterns and APIs +- [Type Registration](../java/type-registration.md) - Class registration and security +- [Schema Evolution](../java/schema-evolution.md) - Forward/backward compatibility +- [Custom Serializers](../java/custom-serializers.md) - Implement custom serializers +- [Compression](../java/compression.md) - Int, long, and string compression +- [Troubleshooting](../java/troubleshooting.md) - Common issues and solutions + +## Scala-Specific Documentation + +- [Configuration](configuration.md) - Scala-specific Fory setup requirements +- [Type Serialization](type-serialization.md) - Serializing Scala types +- [Schema Metadata](schema-metadata.md) - Scala annotations, references, enum IDs, and union metadata +- [Default Values](default-values.md) - Scala class default values support +- [Schema IDL And Xlang](schema-idl.md) - Scala 3 generated models and macro-derived xlang serializers +- [gRPC Support](grpc-support.md) - Scala 3 generated gRPC service companions diff --git a/versioned_docs/version-1.3.0/guide/scala/schema-idl.md b/versioned_docs/version-1.3.0/guide/scala/schema-idl.md new file mode 100644 index 00000000000..d61d04294ed --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/scala/schema-idl.md @@ -0,0 +1,193 @@ +--- +title: Schema IDL And Xlang +sidebar_position: 5 +id: schema_idl +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. +--- + +The Fory schema IDL Scala target generates Scala 3 source for xlang payloads. +The Fory Scala artifact remains cross-built for Scala 2.13 and Scala 3; only the +schema IDL output and quoted macro derivation require Scala 3. + +## Setup + +Generated Scala code uses the public macro API in `org.apache.fory.scala` and +the shared JVM annotations in `org.apache.fory.annotation`. Macro internals live +under `org.apache.fory.scala.internal`. + +```scala +import org.apache.fory.scala.{ForyScala, ForySerializer} +import example.ExampleForyModule + +val fory = ForyScala.builder() + .withXlang(true) + .withRefTracking(true) + .withModule(ExampleForyModule) + .build() +``` + +Generated schema modules are also Fory modules. Use `.withModule(...)` when +creating a custom Fory instance, or use the generated no-argument `toBytes` and +`fromBytes` helpers when the generated default Fory instance is sufficient. + +Schemas with service definitions can also generate Scala 3 gRPC service +companions with `foryc --scala_out=... --grpc`. See +[gRPC Support](grpc-support.md) for dependencies and client/server examples. + +Generated helpers register message type identities before installing message +serializers. This two-phase order lets mutually recursive message graphs build +descriptor metadata through the normal `TypeResolver` path without temporary +serializers or Scala-specific registration state in Java core. Enums and unions +are registered with their serializers directly because their derived serializers +own case dispatch. + +## Generated Messages + +Acyclic messages generate case classes: + +```scala +import org.apache.fory.annotation.{ForyField, ForyStruct} +import org.apache.fory.scala.ForySerializer + +@ForyStruct +final case class Person( + @ForyField(id = 1) name: String, + @ForyField(id = 2) email: Option[String] +) derives ForySerializer +``` + +Schema `optional T` fields are stored as `Option[T]`. + +Messages in compiler-detected construction cycles generate normal classes with +mutable serialized fields so the deserializer can allocate and register the +object before reading fields that can point back to it. A top-level `ref Foo`, +nested `list`, or `any` field does not by itself force this shape. +The compiler analyzes message and union dependencies together, so +message-to-union-to-message cycles also make the participating messages normal +classes. Acyclic owner messages that only contain a cyclic nested type remain +case classes. + +Reference tracking is expressed with the shared `@Ref` annotation, including +type-use positions: + +```scala +@ForyStruct +final class Node() derives ForySerializer { + @ForyField(id = 1) + var children: List[Node @Ref] = List.empty + + @Ref + @ForyField(id = 2) + var parent: Option[Node] = None +} +``` + +`@Ref` is the JVM reference-tracking annotation for Scala macro and IDL APIs. +Use field or constructor-parameter `@Ref` for a top-level `ref T` field. Use +type-use `T @Ref` only for nested element/value/payload refs, such as +`list`. + +Generated xlang collection fields use immutable Scala collection types: +`List[T]`, `Set[T]`, and `Map[K, V]`. Fory's xlang serializers can also rebuild +supported mutable collection interfaces such as `scala.collection.Seq` +and `scala.collection.Map`, but concrete mutable collection classes are outside +the schema IDL surface unless explicitly generated. + +## Generated Enums + +IDL enums generate Scala 3 enums only. The compiler does not emit Java enum +files. + +```scala +import org.apache.fory.annotation.ForyEnumId + +enum Status { + @ForyEnumId(0) + case Unknown + + @ForyEnumId(1) + case Ok +} +``` + +Generated registration uses `ScalaSerializers.registerEnum(...)` so the stable +Fory enum IDs from case-level `@ForyEnumId` metadata are used in xlang mode. + +## Generated Unions + +IDL unions generate Scala 3 ADT enums with macro-derived serializers: + +```scala +package example + +import org.apache.fory.annotation.{ForyCase, ForyUnion, ForyUnknownCase, UInt32Type} +import org.apache.fory.config.Int32Encoding +import org.apache.fory.scala.ForySerializer +import org.apache.fory.`type`.union.UnknownCase + +@ForyUnion +enum SearchTarget derives ForySerializer { + @ForyUnknownCase + case Unknown(value: UnknownCase) + + @ForyCase(id = 0) + case User(value: _root_.example.User) + + @ForyCase(id = 1) + case FixedId(value: Long @UInt32Type(encoding = Int32Encoding.FIXED)) +} +``` + +When a generated Scala union case name matches the payload type simple name, +packaged output keeps the case name and qualifies the payload type. If a target +output mode cannot express a legal qualifier for a conflict, the IDL compiler +appends `Case` to the generated case name. + +Schema-defined union cases use non-negative IDs, and a typed union must declare +at least one non-`Unknown` case. The Scala unknown-case carrier is selected by +`@ForyUnknownCase`, not by a schema case ID. Its payload stores the original case +ID and the deserialized value. When a reader sees a newer case ID, it returns +`Unknown(UnknownCase)` instead of failing solely because the case ID is not known +locally. + +The macro writes the existing xlang union envelope directly. It does not +allocate temporary Java `Union` carriers. + +## Manual Scala 3 Derivation + +Manual Scala 3 models can derive the same serializer typeclass: + +```scala +@ForyStruct +final class Record(@ForyField(id = 1) val id: Int) derives ForySerializer { + @ForyField(id = 2) + var name: String = "" +} +``` + +The macro generates direct constructor calls for constructor-owned fields and +direct assignments for mutable post-construction fields. It builds descriptor +metadata from Scala compile-time types, including nested generics, `Option`, +arrays, scalar encoding annotations, nullability, and `@Ref` metadata. Java +reflection is not the source of truth for generated Scala metadata. + +During copy, cyclic graphs are supported when the copied root can be allocated +and registered before cyclic fields are copied, which is the normal-class shape +used by schema IDL for construction cycles. If a copy starts at an immutable +constructor-owned value that participates in the cycle, such as a Scala enum +case or case class, the serializer fails with a clear error because no copied +identity can be published until construction has completed. diff --git a/versioned_docs/version-1.3.0/guide/scala/schema-metadata.md b/versioned_docs/version-1.3.0/guide/scala/schema-metadata.md new file mode 100644 index 00000000000..18cc897afc8 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/scala/schema-metadata.md @@ -0,0 +1,124 @@ +--- +title: Schema Metadata +sidebar_position: 3 +id: schema_metadata +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. +--- + +Scala schema metadata is used by schema IDL generated code and Scala 3 macro-derived xlang +serializers. Metadata is declared with the shared JVM Fory annotations and Scala compile-time type +information. + +## Struct Fields + +Schema messages can use `@ForyStruct` and `@ForyField(id = N)`: + +```scala +import org.apache.fory.annotation.{ForyField, ForyStruct} +import org.apache.fory.scala.ForySerializer + +@ForyStruct +final case class Person( + @ForyField(id = 1) name: String, + @ForyField(id = 2) email: Option[String] +) derives ForySerializer +``` + +Schema `optional T` fields are represented as `Option[T]`. + +## Reference Tracking + +Reference tracking uses the shared JVM `@Ref` annotation. Use field or constructor-parameter +`@Ref` for a top-level `ref T` field, and type-use `T @Ref` for nested collection or map payloads: + +```scala +import org.apache.fory.annotation.{ForyField, ForyStruct, Ref} + +@ForyStruct +final class Node() derives ForySerializer { + @ForyField(id = 1) + var children: List[Node @Ref] = List.empty + + @Ref + @ForyField(id = 2) + var parent: Option[Node] = None +} +``` + +## Enum IDs + +IDL enums generate Scala 3 enums. Stable Fory enum IDs come from case-level `@ForyEnumId` metadata: + +```scala +import org.apache.fory.annotation.ForyEnumId + +enum Status { + @ForyEnumId(0) + case Unknown + + @ForyEnumId(1) + case Ok +} +``` + +Generated registration uses `ScalaSerializers.registerEnum(...)` so these stable IDs are used in +xlang mode. + +## Unions + +IDL unions generate Scala 3 ADT enums with `@ForyUnion` and `@ForyCase` metadata: + +```scala +package example + +import org.apache.fory.annotation.{ForyCase, ForyUnion, ForyUnknownCase, UInt32Type} +import org.apache.fory.config.Int32Encoding +import org.apache.fory.scala.ForySerializer +import org.apache.fory.`type`.union.UnknownCase + +@ForyUnion +enum SearchTarget derives ForySerializer { + @ForyUnknownCase + case Unknown(value: UnknownCase) + + @ForyCase(id = 0) + case User(value: _root_.example.User) + + @ForyCase(id = 1) + case FixedId(value: Long @UInt32Type(encoding = Int32Encoding.FIXED)) +} +``` + +Schema-defined union cases use non-negative IDs, and a typed union must declare +at least one non-`Unknown` case. The unknown-case carrier is selected by +`@ForyUnknownCase`, not by a schema case ID. +When a generated Scala union case name matches the payload type simple name, +packaged output keeps the case name and qualifies the payload type. If a target +output mode cannot express a legal qualifier for a conflict, the IDL compiler +appends `Case` to the generated case name. + +## Generated Metadata Source + +The Scala macro builds descriptor metadata from Scala compile-time types, including nested +generics, `Option`, arrays, scalar encoding annotations, nullability, and `@Ref` metadata. Java +reflection is not the source of truth for generated Scala metadata. + +## Related Topics + +- [Schema IDL And Xlang](schema-idl.md) +- [Configuration](configuration.md) +- [Default Values](default-values.md) diff --git a/versioned_docs/version-1.3.0/guide/scala/type-serialization.md b/versioned_docs/version-1.3.0/guide/scala/type-serialization.md new file mode 100644 index 00000000000..44856c584a9 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/scala/type-serialization.md @@ -0,0 +1,179 @@ +--- +title: Type Serialization +sidebar_position: 2 +id: type_serialization +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 page covers serialization of Scala-specific JVM types in native mode. For +cross-language Scala models, use the xlang path described in +[Schema IDL And Xlang](schema-idl.md). + +When compatible mode is enabled, Scala readers use the JVM compatible-read rules for selected +scalar field type changes. A matched field can read between `Boolean`, `String`, numeric scalars, +and `java.math.BigDecimal` when the converted value has the same logical value. For example, +`"true"` and `"false"` can be read as booleans, `"123"` can be read as a numeric field that can hold +`123`, numbers and decimals can be read as canonical strings, and numeric widening or narrowing +succeeds only when no precision or range is lost. Numeric strings use finite ASCII decimal syntax. +Invalid strings and lossy conversions fail during deserialization. Optional and boxed fields still +compose with these conversions, but reference-tracked scalar type changes are incompatible. + +## Setup + +All examples assume the following setup: + +```scala +import org.apache.fory.Fory +import org.apache.fory.scala.ForyScala + +val fory = ForyScala.builder().withXlang(false) + .build() +``` + +## Case Class + +```scala +case class Person(github: String, age: Int, id: Long) + +fory.register(classOf[Person]) + +val p = Person("https://github.com/chaokunyang", 18, 1) +println(fory.deserialize(fory.serialize(p))) +``` + +## POJO Class + +```scala +class Foo(f1: Int, f2: String) { + override def toString: String = s"Foo($f1, $f2)" +} + +fory.register(classOf[Foo]) + +println(fory.deserialize(fory.serialize(new Foo(1, "chaokunyang")))) +``` + +## Object Singleton + +Scala `object` singletons are serialized and deserialized to the same instance: + +```scala +object MySingleton { + val value = 42 +} + +fory.register(MySingleton.getClass) + +val o1 = fory.deserialize(fory.serialize(MySingleton)) +val o2 = fory.deserialize(fory.serialize(MySingleton)) +println(o1 == o2) // true +``` + +## Collection + +Scala collections are fully supported: + +```scala +val seq = Seq(1, 2) +val list = List("a", "b") +val map = Map("a" -> 1, "b" -> 2) + +println(fory.deserialize(fory.serialize(seq))) +println(fory.deserialize(fory.serialize(list))) +println(fory.deserialize(fory.serialize(map))) +``` + +## Tuple + +All Scala tuple types (Tuple1 through Tuple22) are supported: + +```scala +val tuple2 = (100, 10000L) +println(fory.deserialize(fory.serialize(tuple2))) + +val tuple4 = (100, 10000L, 10000L, "str") +println(fory.deserialize(fory.serialize(tuple4))) +``` + +## Enum + +### Scala 3 Enum + +```scala +enum Color { case Red, Green, Blue } + +fory.register(classOf[Color]) + +println(fory.deserialize(fory.serialize(Color.Green))) +``` + +### Scala 2 Enumeration + +```scala +object ColorEnum extends Enumeration { + type ColorEnum = Value + val Red, Green, Blue = Value +} + +fory.register(Class.forName("scala.Enumeration.Val")) + +println(fory.deserialize(fory.serialize(ColorEnum.Green))) +``` + +> **Note**: For Scala 2 Enumeration, you may need to register `scala.Enumeration.Val` or enable reference tracking to avoid `StackOverflowError`. + +## Option + +```scala +val some: Option[Long] = Some(100) +println(fory.deserialize(fory.serialize(some))) + +val none: Option[Long] = None +println(fory.deserialize(fory.serialize(none))) +``` + +## Either + +```scala +val right: Either[String, Int] = Right(42) +println(fory.deserialize(fory.serialize(right))) + +val left: Either[String, Int] = Left("error") +println(fory.deserialize(fory.serialize(left))) +``` + +## Nested Types + +Complex nested structures are fully supported: + +```scala +case class Address(street: String, city: String) +case class Company(name: String, address: Address) +case class Employee(name: String, company: Company, tags: List[String]) + +fory.register(classOf[Address]) +fory.register(classOf[Company]) +fory.register(classOf[Employee]) + +val employee = Employee( + "John", + Company("Acme", Address("123 Main St", "Springfield")), + List("developer", "scala") +) + +println(fory.deserialize(fory.serialize(employee))) +``` diff --git a/versioned_docs/version-1.3.0/guide/swift/_category_.json b/versioned_docs/version-1.3.0/guide/swift/_category_.json new file mode 100644 index 00000000000..dfdaf599681 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/swift/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "Swift", + "position": 8, + "collapsible": true, + "collapsed": true +} diff --git a/versioned_docs/version-1.3.0/guide/swift/basic-serialization.md b/versioned_docs/version-1.3.0/guide/swift/basic-serialization.md new file mode 100644 index 00000000000..7781f3c3c19 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/swift/basic-serialization.md @@ -0,0 +1,118 @@ +--- +title: Basic Serialization +sidebar_position: 1 +id: basic_serialization +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 page covers object graph serialization and core API usage in Swift. + +## Object Graph Serialization + +Use `@ForyStruct`, `@ForyEnum`, or `@ForyUnion`, register types, then serialize and deserialize. + +```swift +import Foundation +import Fory + +@ForyStruct +struct Address: Equatable { + var street: String = "" + var zip: Int32 = 0 +} + +@ForyStruct +struct Person: Equatable { + var id: Int64 = 0 + var name: String = "" + var nickname: String? = nil + var tags: Set = [] + var scores: [Int32] = [] + var addresses: [Address] = [] + var metadata: [Int8: Int32?] = [:] +} + +let fory = Fory() +fory.register(Address.self, id: 100) +fory.register(Person.self, id: 101) + +let person = Person( + id: 42, + name: "Alice", + nickname: nil, + tags: ["swift", "xlang"], + scores: [10, 20, 30], + addresses: [Address(street: "Main", zip: 94107)], + metadata: [1: 100, 2: nil] +) + +let data = try fory.serialize(person) +let decoded: Person = try fory.deserialize(data) +assert(decoded == person) +``` + +## Working with Existing Buffers + +Append serialized bytes to an existing `Data` and deserialize from `ByteBuffer`. + +```swift +var output = Data() +try fory.serialize(person, to: &output) + +let inputBuffer = ByteBuffer(data: output) +let fromBuffer: Person = try fory.deserialize(from: inputBuffer) +assert(fromBuffer == person) +``` + +## Built-in Supported Types + +### Primitive and scalar + +- `Bool` +- `Int8`, `Int16`, `Int32`, `Int64`, `Int` +- `UInt8`, `UInt16`, `UInt32`, `UInt64`, `UInt` +- `Float`, `Double` +- `String` +- `Data` + +### Date and time + +- `Date` +- `LocalDate` +- `Duration` + +Use `Date` for timestamp values and `LocalDate` for day-only dates. `LocalDate` +supports epoch-day and `Date` conversions through `fromEpochDay(_:)`, +`toEpochDay()`, `init(utcDate:)`, and `toUTCDate()`. + +### Collections + +- `[T]` where `T: Serializer` +- `Set` where `T: Serializer & Hashable` +- `[K: V]` where `K: Serializer & Hashable`, `V: Serializer` +- Optional variants (`T?`) + +### Dynamic + +- `Any` +- `AnyObject` +- `any Serializer` +- `AnyHashable` +- `[Any]` +- `[String: Any]` +- `[Int32: Any]` +- `[AnyHashable: Any]` diff --git a/versioned_docs/version-1.3.0/guide/swift/configuration.md b/versioned_docs/version-1.3.0/guide/swift/configuration.md new file mode 100644 index 00000000000..e3a0478817d --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/swift/configuration.md @@ -0,0 +1,144 @@ +--- +title: Configuration +sidebar_position: 2 +id: configuration +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 page covers `Config` and recommended Fory presets. + +## Config + +`Fory` is configured with: + +```swift +public struct Config { + public let trackRef: Bool + public let compatible: Bool + public let checkClassVersion: Bool + public let maxDepth: Int + public let maxTypeFields: Int + public let maxTypeMetaBytes: Int + public let maxSchemaVersionsPerType: Int + public let maxAverageSchemaVersionsPerType: Int +} +``` + +Default configuration: + +```swift +let fory = Fory() // ref=false, compatible=true +``` + +Swift supports the xlang wire format only, so there is no `xlang` option in +`Config` or the `Fory` initializer. + +## Threading + +`Fory` is single-threaded and optimized to reuse one read/write context pair on the calling thread. +Reuse one instance per thread and do not use the same instance concurrently. + +## Options + +### `trackRef` + +Enables shared/circular reference tracking for reference-trackable types. + +- `false`: No reference table (smaller/faster for acyclic or value-only graphs) +- `true`: Preserve object identity for class/reference graphs + +```swift +let fory = Fory(ref: true) +``` + +### `compatible` + +Enables compatible schema mode for evolution across versions. + +- `false`: Faster serialization and smaller size +- `true`: Compatible mode (supports add/remove/reorder fields) + +Use `compatible: false` only when every reader and writer always uses the same schema and you want faster serialization and smaller size. For cross-language payloads, set `compatible: false` only after verifying that every language uses the same schema, or when native types are generated from Fory schema IDL. + +```swift +let fory = Fory(compatible: false) +``` + +### `checkClassVersion` + +Controls class-version validation when compatible mode is disabled. When +omitted, it defaults to `true` when `compatible: false` and `false` when +`compatible: true`. + +```swift +let fory = Fory(compatible: false, checkClassVersion: true) +``` + +### Size and Depth Limits + +`maxDepth` bounds decoded payload nesting depth. Compatible-mode remote metadata +is also limited: + +- `maxTypeFields` defaults to `512` and limits fields in one received struct metadata body. +- `maxTypeMetaBytes` defaults to `4096` and limits encoded body bytes in one received TypeMeta body, + excluding the 8-byte header and any extended-size varint. +- `maxSchemaVersionsPerType` defaults to `10` and limits accepted remote metadata versions for one + logical type. +- `maxAverageSchemaVersionsPerType` defaults to `3` and limits the average across accepted remote + types. The effective global floor is `8192` schemas. + +```swift +let fory = Fory( + maxDepth: 5, + maxTypeFields: 512, + maxTypeMetaBytes: 4096, + maxSchemaVersionsPerType: 10, + maxAverageSchemaVersionsPerType: 3 +) +``` + +## Recommended Presets + +### Default service payloads + +```swift +let fory = Fory() +``` + +### Graph/object identity workloads + +```swift +let fory = Fory(ref: true) +``` + +### Same-schema optimization + +Use this only when every reader and writer always uses the same schema. + +```swift +let fory = Fory(compatible: false) +``` + +## Security + +Security-related configuration: + +- Register only the expected generated models before deserializing untrusted payloads. +- Use `checkClassVersion` with `compatible: false` for intentional same-schema payloads. +- Set `maxDepth` for the largest nesting depth your service accepts. +- Keep the remote schema metadata limits at their defaults unless the data is not malicious and a + trusted peer sends larger metadata or many schema versions. diff --git a/versioned_docs/version-1.3.0/guide/swift/custom-serializers.md b/versioned_docs/version-1.3.0/guide/swift/custom-serializers.md new file mode 100644 index 00000000000..2ced1667801 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/swift/custom-serializers.md @@ -0,0 +1,84 @@ +--- +title: Custom Serializers +sidebar_position: 9 +id: custom_serializers +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. +--- + +For types that cannot or should not use Fory model macros, implement `Serializer` manually. + +## When to Use Custom Serializers + +- External types with strict wire compatibility requirements +- Specialized compact encodings +- Existing payload adaptation paths +- Highly tuned hot-path serialization + +## Implementing `Serializer` + +```swift +import Foundation +import Fory + +struct UUIDBox: Serializer, Equatable { + var value: UUID = UUID(uuidString: "00000000-0000-0000-0000-000000000000")! + + static func foryDefault() -> UUIDBox { + UUIDBox() + } + + static var staticTypeId: ForyTypeId { + .ext + } + + func foryWriteData(_ context: WriteContext, hasGenerics: Bool) throws { + _ = hasGenerics + try value.uuidString.foryWriteData(context, hasGenerics: false) + } + + static func foryReadData(_ context: ReadContext) throws -> UUIDBox { + let raw = try String.foryReadData(context) + guard let uuid = UUID(uuidString: raw) else { + throw ForyError.invalidData("invalid UUID string: \(raw)") + } + return UUIDBox(value: uuid) + } +} +``` + +## Register and Use + +```swift +let fory = Fory() +fory.register(UUIDBox.self, id: 300) + +let input = UUIDBox(value: UUID()) +let data = try fory.serialize(input) +let output: UUIDBox = try fory.deserialize(data) + +assert(input == output) +``` + +## Choosing `staticTypeId` + +For manually implemented custom types, use `staticTypeId` that matches the wire kind you are implementing. + +Typical choices: + +- `.structType`: regular structured object +- `.enumType` / `.typedUnion`: enum-like values +- `.ext`: extension/custom kind diff --git a/versioned_docs/version-1.3.0/guide/swift/index.md b/versioned_docs/version-1.3.0/guide/swift/index.md new file mode 100644 index 00000000000..92dcb709e48 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/swift/index.md @@ -0,0 +1,83 @@ +--- +title: Swift Serialization Guide +sidebar_position: 0 +id: serialization_index +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. +--- + +Apache Fory Swift provides high-performance object graph serialization with strong type safety, macro-based code generation, schema evolution, and cross-language compatibility. + +## Why Fory Swift? + +- Fast binary serialization for Swift value and reference types +- `@ForyStruct`, `@ForyEnum`, and `@ForyUnion` macros for zero-boilerplate model serialization +- Xlang protocol compatibility with Java, Rust, Go, Python, and more +- Compatible mode for schema evolution across versions +- Built-in support for dynamic values (`Any`, `AnyObject`, `any Serializer`, `AnyHashable`) +- Reference tracking for shared/circular graphs, including weak references on classes + +## Install + +Add Fory Swift from the Apache Fory GitHub repository: + +```swift +dependencies: [ + .package(url: "https://github.com/apache/fory.git", exact: "$version") +], +targets: [ + .target( + name: "MyApp", + dependencies: [ + .product(name: "Fory", package: "fory") + ] + ) +] +``` + +## Guide Contents + +- [Configuration](configuration.md) +- [Basic Serialization](basic-serialization.md) +- [Xlang Serialization](xlang-serialization.md) +- [Schema Metadata](schema-metadata.md) +- [Type Registration](type-registration.md) +- [Custom Serializers](custom-serializers.md) +- [Shared and Circular References](references.md) +- [Polymorphism and Dynamic Types](polymorphism.md) +- [Schema Evolution](schema-evolution.md) +- [Troubleshooting](troubleshooting.md) + +## Quick Example + +```swift +import Fory + +@ForyStruct +struct User: Equatable { + var name: String = "" + var age: Int32 = 0 +} + +let fory = Fory() +fory.register(User.self, id: 1) + +let input = User(name: "alice", age: 30) +let data = try fory.serialize(input) +let output: User = try fory.deserialize(data) + +assert(input == output) +``` diff --git a/versioned_docs/version-1.3.0/guide/swift/polymorphism.md b/versioned_docs/version-1.3.0/guide/swift/polymorphism.md new file mode 100644 index 00000000000..fa52427d308 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/swift/polymorphism.md @@ -0,0 +1,80 @@ +--- +title: Polymorphism and Dynamic Types +sidebar_position: 7 +id: polymorphism +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. +--- + +Fory Swift supports dynamic serialization for `Any`, `AnyObject`, and `any Serializer`. + +## Top-level Dynamic APIs + +```swift +let fory = Fory() + +let dynamic: Any = Int32(7) +let data = try fory.serialize(dynamic) +let decoded: Any = try fory.deserialize(data) +``` + +Equivalent overloads exist for: + +- `AnyObject` +- `any Serializer` +- `AnyHashable` +- `[Any]` +- `[String: Any]` +- `[Int32: Any]` +- `[AnyHashable: Any]` + +## Dynamic Fields in Fory Model Types + +```swift +@ForyStruct +struct DynamicHolder { + var value: Any = ForyAnyNullValue() + var list: [Any] = [] + var byName: [String: Any] = [:] + var byId: [Int32: Any] = [:] + var byDynamicKey: [AnyHashable: Any] = [:] +} +``` + +## Concrete Type Registration Still Applies + +If dynamic values contain user-defined types, register those concrete types. + +```swift +@ForyStruct +struct Address { + var street: String = "" + var zip: Int32 = 0 +} + +let fory = Fory() +fory.register(Address.self, id: 100) +``` + +## Null Semantics + +- `Any` null representation: `ForyAnyNullValue` +- `AnyObject` null representation: `NSNull` +- Optional dynamic values map to the corresponding null representation on decode + +## Current Limitations + +- `AnyHashable` keys must wrap values that are both `Hashable` and supported by Fory dynamic serialization diff --git a/versioned_docs/version-1.3.0/guide/swift/references.md b/versioned_docs/version-1.3.0/guide/swift/references.md new file mode 100644 index 00000000000..aef7f724357 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/swift/references.md @@ -0,0 +1,111 @@ +--- +title: Shared and Circular References +sidebar_position: 6 +id: references +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. +--- + +Swift reference tracking is controlled by `Config.trackRef`. + +## Enable Reference Tracking + +```swift +let fory = Fory(ref: true) +``` + +When enabled, reference-trackable types preserve identity and cycles. + +## Shared Reference Example + +```swift +import Fory + +@ForyStruct +final class Animal { + var name: String = "" + + required init() {} + + init(name: String) { + self.name = name + } +} + +@ForyStruct +final class AnimalPair { + var first: Animal? = nil + var second: Animal? = nil + + required init() {} + + init(first: Animal? = nil, second: Animal? = nil) { + self.first = first + self.second = second + } +} + +let fory = Fory(ref: true) +fory.register(Animal.self, id: 200) +fory.register(AnimalPair.self, id: 201) + +let shared = Animal(name: "cat") +let input = AnimalPair(first: shared, second: shared) + +let data = try fory.serialize(input) +let decoded: AnimalPair = try fory.deserialize(data) + +assert(decoded.first === decoded.second) +``` + +## Circular Reference Example (Use `weak`) + +`trackRef` preserves the reference graph, but it does not change ARC ownership. +Use `weak` on at least one edge in a cycle to avoid leaks. + +```swift +import Fory + +@ForyStruct +final class Node { + var value: Int32 = 0 + weak var next: Node? = nil + + required init() {} + + init(value: Int32, next: Node? = nil) { + self.value = value + self.next = next + } +} + +let fory = Fory(ref: true) +fory.register(Node.self, id: 300) + +let node = Node(value: 7) +node.next = node + +let data = try fory.serialize(node) +let decoded: Node = try fory.deserialize(data) + +assert(decoded.next === decoded) +``` + +## Notes + +- Value types (`struct`, primitive values) do not carry identity semantics +- `trackRef` controls serialization graph identity, not ARC memory ownership +- Use `trackRef=false` for purely value-based payloads to reduce overhead diff --git a/versioned_docs/version-1.3.0/guide/swift/schema-evolution.md b/versioned_docs/version-1.3.0/guide/swift/schema-evolution.md new file mode 100644 index 00000000000..ed22293d742 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/swift/schema-evolution.md @@ -0,0 +1,93 @@ +--- +title: Schema Evolution +sidebar_position: 8 +id: schema_evolution +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. +--- + +Fory supports schema evolution through compatible mode, which is enabled by default in Swift. + +Compatible readers also tolerate selected scalar field type changes when the value is lossless. A +matched field can read between `Bool`, `String`, numeric scalars, and `Decimal` when the converted +value has the same logical value. For example, `"true"` and `"false"` can be read as booleans, +`"123"` can be read as a numeric field that can hold `123`, numbers and decimals can be read as +canonical strings, and numeric widening or narrowing succeeds only when no precision or range is +lost. Numeric strings use finite ASCII decimal syntax. Invalid strings and lossy conversions fail +during deserialization. + +Scalar conversion also composes with optional fields. A present optional value is converted by the +same rules, while a missing optional value keeps Swift's normal compatible-mode default for the +local field. Reference-tracked scalar type changes are incompatible. + +## Default Compatible Mode + +```swift +let fory = Fory() +``` + +## Example: Evolving a Struct + +```swift +import Fory + +@ForyStruct +struct PersonV1 { + var name: String = "" + var age: Int32 = 0 + var address: String = "" +} + +@ForyStruct +struct PersonV2 { + var name: String = "" + var age: Int32 = 0 + var phone: String? = nil // added field +} + +let writer = Fory() +writer.register(PersonV1.self, id: 1) + +let reader = Fory() +reader.register(PersonV2.self, id: 1) + +let v1 = PersonV1(name: "alice", age: 30, address: "main st") +let bytes = try writer.serialize(v1) +let v2: PersonV2 = try reader.deserialize(bytes) + +assert(v2.name == "alice") +assert(v2.age == 30) +assert(v2.phone == nil) +``` + +## What Is Safe in Compatible Mode + +- Add new fields +- Remove old fields +- Reorder fields +- Change a matched scalar field between `Bool`, `String`, numeric scalars, or `Decimal` when every + value you write is lossless for the reader + +## What Is Not Safe + +- Arbitrary type changes for an existing field, including scalar values that are out of range, + rounded, non-finite, or not accepted by the compatible numeric string grammar +- Inconsistent registration mapping across peers + +## Same-Schema Optimization + +Set `compatible: false` only when every reader and writer always uses the same schema and you want +faster serialization and smaller size. For xlang payloads, set `compatible: false` only after verifying that every language uses the same schema, or when native types are generated from Fory schema IDL. diff --git a/versioned_docs/version-1.3.0/guide/swift/schema-metadata.md b/versioned_docs/version-1.3.0/guide/swift/schema-metadata.md new file mode 100644 index 00000000000..32b8801593d --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/swift/schema-metadata.md @@ -0,0 +1,163 @@ +--- +title: Schema Metadata +sidebar_position: 4 +id: schema_metadata +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 page covers macro-level schema metadata in Swift. + +## Available Macro Attributes + +- `@ForyStruct` on struct/class models +- `@ForyEnum` on C-style enum models +- `@ForyUnion` and `@ForyCase` on associated-value enum models +- `@ForyField(encoding: ...)` on numeric fields +- `@ListField`, `@ArrayField`, `@SetField`, and `@MapField` for collection field metadata + +## `@ForyField(encoding:)` + +Use `@ForyField` to override integer encoding strategy. + +```swift +@ForyStruct +struct Metrics: Equatable { + @ForyField(encoding: .fixed) + var u32Fixed: UInt32 = 0 + + @ForyField(encoding: .tagged) + var u64Tagged: UInt64 = 0 +} +``` + +### Supported combinations + +| Swift type | Supported encoding values | Default encoding | +| ---------- | ------------------------------ | ---------------- | +| `Int32` | `.varint`, `.fixed` | `.varint` | +| `UInt32` | `.varint`, `.fixed` | `.varint` | +| `Int64` | `.varint`, `.fixed`, `.tagged` | `.varint` | +| `UInt64` | `.varint`, `.fixed`, `.tagged` | `.varint` | +| `Int` | `.varint`, `.fixed`, `.tagged` | `.varint` | +| `UInt` | `.varint`, `.fixed`, `.tagged` | `.varint` | + +Compile-time validation rejects unsupported combinations (for example, `Int32` with `.tagged`). + +## Nested Collection Field Metadata + +Use `@ListField`, `@ArrayField`, `@SetField`, and `@MapField` when a collection field +needs type-specific wire metadata, such as fixed or tagged integer encoding inside a +container. Use `@ArrayField` for dense non-null bool, integer, and floating-point arrays. + +```swift +@ForyStruct +struct NestedMetrics: Equatable { + @ListField(element: .encoding(.fixed)) + var values: [Int32?] = [] + + @ArrayField(element: .int32()) + var denseValues: [Int32] = [] + + @SetField(element: .encoding(.fixed)) + var ids: Set = [] + + @MapField(key: .encoding(.fixed), value: .encoding(.tagged)) + var byId: [Int32: UInt64] = [:] + + @MapField(value: .list(element: .encoding(.fixed))) + var groups: [String: [Int32?]] = [:] +} +``` + +Non-null `List` elements with fixed-width signed or unsigned integer metadata are +classified and encoded as the matching Fory primitive packed-array type. `Set` +fields stay classified as Fory sets, including fixed-width integer sets. + +When the Swift property type is an alias or otherwise needs a full hint, use +`@ForyField(type:)`: + +```swift +typealias MetricsMap = [String: [Int32?]] + +@ForyStruct +struct AliasMetrics: Equatable { + @ForyField(type: .map( + key: .string, + value: .list(.int32(nullable: true, encoding: .fixed)) + )) + var metrics: MetricsMap = [:] +} +``` + +Union payloads use the same DSL through `@ForyCase(payload:)`: + +```swift +@ForyUnion +enum Event { + @ForyUnknownCase + case unknown(UnknownCase) + + @ForyCase(id: 0) + case created(String) + + @ForyCase(id: 1, payload: .uint64(encoding: .fixed)) + case deleted(UInt64) +} +``` + +Every `@ForyUnion` must declare `@ForyUnknownCase case unknown(UnknownCase)` and +at least one non-`unknown` case. The unknown case is only the Fory-owned +forward-compatibility carrier and cannot be the default value source. It is +omitted from the schema case table because the marker only selects the carrier +and does not add a schema entry. Schema cases use non-negative IDs. + +## Model Macro Requirements + +### Struct and class fields + +- Stored properties must declare explicit types +- Computed properties are ignored +- Static/class properties are ignored + +### Class requirement + +Classes annotated with `@ForyStruct` must provide a `required init()` for default construction. + +```swift +@ForyStruct +final class Node { + var value: Int32 = 0 + var next: Node? = nil + + required init() {} +} +``` + +## Dynamic Any Fields in Macro Types + +Fory model macros support dynamic fields and nested containers: + +- `Any`, `AnyObject`, `any Serializer` +- `AnyHashable` +- `[Any]` +- `[String: Any]` +- `[Int32: Any]` +- `[AnyHashable: Any]` + +Current limitations: + +- `Dictionary` is only supported when `K` is `String`, `Int32`, or `AnyHashable` diff --git a/versioned_docs/version-1.3.0/guide/swift/troubleshooting.md b/versioned_docs/version-1.3.0/guide/swift/troubleshooting.md new file mode 100644 index 00000000000..b08818c698f --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/swift/troubleshooting.md @@ -0,0 +1,94 @@ +--- +title: Troubleshooting +sidebar_position: 11 +id: troubleshooting +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 page covers common Swift issues and how to debug them. + +## Common Runtime Errors + +### `Type not registered: ...` + +Cause: user type was not registered on the current `Fory` instance. + +Fix: + +```swift +fory.register(MyType.self, id: 100) +``` + +### `Type mismatch: expected ..., got ...` + +Cause: registration mapping or field type info differs across peers. + +Fix: + +- Ensure both sides register the same type ID/name mapping +- Verify field type compatibility + +### `Invalid data: xlang bitmap mismatch` + +Cause: the input was produced by a peer that did not write the xlang +wire format Swift expects. + +Fix: configure the peer serializer to write xlang format. Swift already uses +xlang format and has no native-mode switch. + +### `Invalid data: class version hash mismatch` + +Cause: schema changed while `compatible: false`. + +Fix: + +- Keep compatible mode enabled for evolving schemas. +- Or use `compatible: false` only when every reader and writer uses the same schema. + +## Common Macro-time Errors + +### `@ForyStruct requires explicit types for stored properties` + +Add explicit type annotations to stored properties. + +### `Fory enum associated values cannot have default values` + +Remove default values from enum case associated values. + +### `Set<...> with Any elements is not supported by @ForyStruct yet` + +Use `[Any]` or a typed set instead. + +### `Dictionary<..., ...> with Any values is only supported for String, Int32, or AnyHashable keys` + +Switch key type to `String`, `Int32`, or `AnyHashable`, or avoid dynamic `Any` map values. + +## Debugging Commands + +Run Swift tests: + +```bash +cd swift +ENABLE_FORY_DEBUG_OUTPUT=1 swift test +``` + +Run Java-driven Swift xlang tests: + +```bash +cd java/fory-core +ENABLE_FORY_DEBUG_OUTPUT=1 FORY_SWIFT_JAVA_CI=1 mvn -T16 test -Dtest=org.apache.fory.xlang.SwiftXlangTest +``` diff --git a/versioned_docs/version-1.3.0/guide/swift/type-registration.md b/versioned_docs/version-1.3.0/guide/swift/type-registration.md new file mode 100644 index 00000000000..696d87f62a2 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/swift/type-registration.md @@ -0,0 +1,72 @@ +--- +title: Type Registration +sidebar_position: 5 +id: type_registration +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 page covers registration APIs for user-defined types. + +## Why Registration Is Required + +User types (`struct`, `class`, enum/union, ext types) must be registered before serialization/deserialization. + +If a type is missing, deserialization fails with: + +- `Type not registered: ...` + +## Register by Numeric ID + +Use a stable ID shared by serializer and deserializer peers. + +```swift +@ForyStruct +struct User { + var name: String = "" + var age: Int32 = 0 +} + +let fory = Fory() +fory.register(User.self, id: 1) +``` + +## Register by Name + +### Fully-qualified name + +```swift +try fory.register(User.self, name: "com.example.User") +``` + +`name` is split by the last `.`: + +- namespace: `com.example` +- type name: `User` + +Simple names such as `User` use an empty namespace. Empty names and names ending in `.` are invalid. + +## Consistency Rules + +Keep registration mapping consistent across peers: + +- ID mode: same type uses same numeric ID on all peers +- Name mode: same type uses same namespace and type name on all peers +- Do not mix ID and name mapping for the same logical type across services + +## Dynamic Types and Registration + +When serializing dynamic values (`Any`, `AnyObject`, `any Serializer`) that contain user-defined types, the concrete types must still be registered. diff --git a/versioned_docs/version-1.3.0/guide/swift/xlang-serialization.md b/versioned_docs/version-1.3.0/guide/swift/xlang-serialization.md new file mode 100644 index 00000000000..f60c8423d6c --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/swift/xlang-serialization.md @@ -0,0 +1,120 @@ +--- +title: Xlang Serialization +sidebar_position: 3 +id: xlang_serialization +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. +--- + +Fory Swift can exchange payloads with other Fory implementations using the xlang protocol. + +## Recommended Xlang Configuration + +```swift +let fory = Fory() +``` + +## Register Types with Shared Identity + +### ID-based registration + +```swift +@ForyStruct +struct Order { + var id: Int64 = 0 + var amount: Double = 0 +} + +let fory = Fory() +fory.register(Order.self, id: 100) +``` + +### Name-based registration + +```swift +try fory.register(Order.self, name: "com.example.Order") +``` + +## Xlang Rules + +- Keep type registration mapping consistent across languages +- Keep compatible mode enabled when independently evolving schemas. Swift enables it by default. +- Register all user-defined concrete types used by dynamic fields (`Any`, `any Serializer`) + +## Lists and Dense Arrays + +Swift `Array` fields map to Fory `list` unless field metadata explicitly +requests dense `array`. Use `array` only for one-dimensional bool or +numeric data. + +| Fory schema | Swift field metadata sketch | +| ----------------- | -------------------------------------------------------- | +| `list` | `@ListField(element: .int32()) var ids: [Int32]` | +| `array` | `@ArrayField(element: .bool) var flags: [Bool]` | +| `array` | `@ArrayField(element: .int8) var values: [Int8]` | +| `array` | `@ArrayField(element: .int16) var values: [Int16]` | +| `array` | `@ArrayField(element: .int32()) var values: [Int32]` | +| `array` | `@ArrayField(element: .int64()) var values: [Int64]` | +| `array` | `@ArrayField(element: .uint8) var values: [UInt8]` | +| `array` | `@ArrayField(element: .uint16) var values: [UInt16]` | +| `array` | `@ArrayField(element: .uint32()) var values: [UInt32]` | +| `array` | `@ArrayField(element: .uint64()) var values: [UInt64]` | +| `array` | `@ArrayField(element: .float16) var values: [Float16]` | +| `array` | `@ArrayField(element: .bfloat16) var values: [BFloat16]` | +| `array` | `@ArrayField(element: .float32) var values: [Float]` | +| `array` | `@ArrayField(element: .float64) var values: [Double]` | + +## Swift IDL Workflow + +Generate Swift models directly from Fory IDL/Proto/FBS inputs: + +```bash +foryc schema.fdl --swift_out ./Sources/Generated +``` + +Generated Swift code includes: + +- `@ForyStruct`, `@ForyEnum`, `@ForyUnion`, and field/case metadata +- Tagged union enums (associated-value enum cases) +- `ForyModule.install(_:)` helpers with transitive import installation +- `toBytes` / `fromBytes` helpers on generated types + +Install the generated module before xlang serialization: + +```swift +let fory = Fory(ref: true) +try Addressbook.ForyModule.install(fory) + +let payload = try fory.serialize(book) +let decoded: Addressbook.AddressBook = try fory.deserialize(payload) +``` + +### Run Swift IDL Integration Tests + +```bash +cd integration_tests/idl_tests +./run_swift_tests.sh +``` + +This runs Swift roundtrip matrix tests and Java peer roundtrip checks (`IDL_PEER_LANG=swift`). + +## Debugging Xlang Tests + +Enable debug output when running xlang tests: + +```bash +ENABLE_FORY_DEBUG_OUTPUT=1 FORY_SWIFT_JAVA_CI=1 mvn -T16 test -Dtest=org.apache.fory.xlang.SwiftXlangTest +``` diff --git a/versioned_docs/version-1.3.0/guide/xlang/_category_.json b/versioned_docs/version-1.3.0/guide/xlang/_category_.json new file mode 100644 index 00000000000..ab2d183de06 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/xlang/_category_.json @@ -0,0 +1,6 @@ +{ + "label": "Cross Language", + "position": 12, + "collapsible": true, + "collapsed": true +} diff --git a/versioned_docs/version-1.3.0/guide/xlang/field-nullability.md b/versioned_docs/version-1.3.0/guide/xlang/field-nullability.md new file mode 100644 index 00000000000..991d4fbf1f7 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/xlang/field-nullability.md @@ -0,0 +1,267 @@ +--- +title: Field Nullability +sidebar_position: 40 +id: field_nullability +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 page explains how Fory handles field nullability in cross-language (xlang) serialization mode. + +## Default Behavior + +In xlang mode, **fields are non-nullable by default**. This means: + +- Values must always be present (non-null) +- No null flag byte is written for the field +- Serialization is more compact + +The following types are nullable by default: + +- `Optional` (Java, C++) +- Java boxed types (`Integer`, `Long`, `Double`, etc.) +- Go pointer types (`*int32`, `*string`, etc.) +- Rust `Option` +- Python `Optional[T]` +- Scala `Option[T]` + +| Field Type | Default Nullable | Null Flag Written | +| ------------------------------------------ | ---------------- | ----------------- | +| Primitives (`int`, `bool`, `float`, etc.) | No | No | +| `String` | No | No | +| `List`, `Map`, `Set` | No | No | +| Custom structs | No | No | +| Enums | No | No | +| Java boxed types (`Integer`, `Long`, etc.) | Yes | Yes | +| Go pointer types (`*int32`, `*string`) | Yes | Yes | +| `Optional` / `Option` | Yes | Yes | + +## Wire Format + +The nullable flag controls whether a **null flag byte** is written before the field value: + +``` +Non-nullable field: [value data] +Nullable field: [null_flag] [value data if not null] +``` + +Where `null_flag` is: + +- `-1` (NULL_FLAG): Value is null +- `-2` (NOT_NULL_VALUE_FLAG): Value is present + +## Nullable vs Reference Tracking + +These are related but distinct concepts: + +| Concept | Purpose | Flag Values | +| ---------------------- | ------------------------------------ | ------------------------------------------- | +| **Nullable** | Allow null values for a field | `-1` (null), `-2` (not null) | +| **Reference Tracking** | Deduplicate shared object references | `-1` (null), `-2` (not null), `≥0` (ref ID) | + +Key differences: + +- **Nullable only**: Writes `-1` or `-2` flag, no reference deduplication +- **Reference tracking**: Extends nullable semantics with reference IDs (`≥0`) for previously seen objects +- Both use the same flag byte position—ref tracking is a superset of nullable + +When `refTracking=true`, the null flag byte doubles as a ref flag: + +``` +ref_flag = -1 → null value +ref_flag = -2 → new object (first occurrence) +ref_flag >= 0 → reference to object at index ref_flag +``` + +For detailed reference tracking behavior, see [Reference Tracking](field-reference-tracking.md). + +## Language-Specific Examples + +### Java + +```java +public class Person { + // Non-nullable by default in xlang mode + String name; // Must not be null + int age; // Primitive, always non-nullable + List tags; // Must not be null + + // Explicitly nullable + @Nullable + String nickname; // Can be null + + // Optional wrapper - nullable by default + Optional bio; // Can be empty/null +} + +Fory fory = Fory.builder() + .withXlang(true) + .build(); +fory.register(Person.class, "example.Person"); +``` + +### Python + +```python +from dataclasses import dataclass +from typing import Optional, List +import pyfory + +@dataclass +class Person: + # Non-nullable by default + name: str # Must have a value + age: pyfory.Int32 # Primitive + tags: List[str] # Must not be None + + # Optional makes it nullable + nickname: Optional[str] = None # Can be None + bio: Optional[str] = None # Can be None + +fory = pyfory.Fory(xlang=True) +fory.register_type(Person, name="example.Person") +``` + +### Rust + +```rust +use fory::{Fory, ForyStruct}; + +#[derive(ForyStruct)] +struct Person { + // Non-nullable by default + name: String, + age: i32, + tags: Vec, + + // Option is nullable + nickname: Option, // Can be None + bio: Option, // Can be None +} +``` + +### Go + +```go +type Person struct { + // Non-nullable by default + Name string + Age int32 + Tags []string + + // Pointer types for nullable fields + Nickname *string // Can be nil + Bio *string // Can be nil +} + +fory := forygo.NewFory(forygo.WithXlang(true)) +fory.RegisterStructByName(Person{}, "example.Person") +``` + +### C++ + +```cpp +struct Person { + // Non-nullable by default + std::string name; + int32_t age; + std::vector tags; + + // std::optional for nullable + std::optional nickname; + std::optional bio; +}; +FORY_STRUCT(Person, name, age, tags, nickname, bio); +``` + +## Customizing Nullability + +### Java: @Nullable Annotation + +```java +public class Config { + @Nullable + String optionalSetting; // Explicitly nullable + + String requiredSetting; // Explicitly non-nullable (default) +} +``` + +### C++: FORY_STRUCT Field Config + +```cpp +struct Config { + std::optional optional_setting; + std::string required_setting; +}; + +FORY_STRUCT(Config, + (optional_setting, fory::F(1)), + (required_setting, fory::F(2)) +); +``` + +For nullable pointer carriers, opt in with `.nullable()`: + +```cpp +struct ConfigRef { + std::shared_ptr optional_setting; + std::shared_ptr required_setting; +}; + +FORY_STRUCT(ConfigRef, + (optional_setting, fory::F(1).nullable()), + (required_setting, fory::F(2)) +); +``` + +## Null Value Handling + +When a non-nullable field receives a null value: + +| Language | Behavior | +| -------- | ---------------------------------------------------- | +| Java | Throws `NullPointerException` or serialization error | +| Python | Raises `TypeError` or serialization error | +| Rust | Compile-time error (non-Option types can't be None) | +| Go | Zero value is used (empty string, 0, etc.) | +| C++ | Default-constructed value or undefined behavior | + +## Schema Compatibility + +The nullable flag is part of the struct schema fingerprint. When compatible mode is disabled, changing a field's nullability is a **breaking change** that will cause schema version mismatch errors. + +``` +Schema A: { name: String (non-nullable) } +Schema B: { name: String (nullable) } +// These have different fingerprints when compatible mode is disabled +``` + +In compatible mode, top-level scalar fields can still be matched when their scalar type is otherwise compatible and the nullability or optional wrapper differs. Present values are read through compatible scalar conversion and must satisfy the normal lossless conversion checks. Remote null values follow the compatible-read null/default behavior for the local field. + +## Best Practices + +1. **Use non-nullable by default**: Only make fields nullable when null is a valid semantic value +2. **Use Optional/Option wrappers**: Instead of raw types with nullable annotation +3. **Be consistent across languages**: Use the same nullability for corresponding fields +4. **Document nullable fields**: Make it clear which fields can be null in your API + +## See Also + +- [Reference Tracking](field-reference-tracking.md) - Shared and circular reference handling +- [Serialization](serialization.md) - Basic cross-language serialization +- [Type Mapping](../../specification/xlang_type_mapping.md) - Cross-language type mapping reference +- [Xlang Specification](../../specification/xlang_serialization_spec.md) - Binary protocol details diff --git a/versioned_docs/version-1.3.0/guide/xlang/field-reference-tracking.md b/versioned_docs/version-1.3.0/guide/xlang/field-reference-tracking.md new file mode 100644 index 00000000000..c3755b81a82 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/xlang/field-reference-tracking.md @@ -0,0 +1,303 @@ +--- +title: Reference Tracking +sidebar_position: 45 +id: reference_tracking +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 page explains how Fory handles reference tracking for shared and circular references in cross-language serialization. + +## Overview + +Reference tracking enables: + +- **Shared references**: Same object referenced multiple times is serialized once +- **Circular references**: Objects that reference themselves or form cycles +- **Memory efficiency**: No duplicate data for repeated objects + +## Enabling Reference Tracking + +### Java + +```java +Fory fory = Fory.builder() + .withXlang(true) + .withRefTracking(true) + .build(); +``` + +### Python + +```python +fory = pyfory.Fory(xlang=True, ref=True) +``` + +### Go + +```go +fory := forygo.NewFory( + forygo.WithXlang(true), + forygo.WithTrackRef(true), +) +``` + +### C++ + +```cpp +auto fory = fory::serialization::Fory::builder().xlang(true).track_ref(true).build(); +``` + +### Rust + +```rust +let fory = Fory::builder() + .xlang(true) + .track_ref(true).build(); +``` + +### Scala + +```scala +import org.apache.fory.scala.ForyScala + +val fory = ForyScala.builder() + .withXlang(true) + .withRefTracking(true) + .build() +``` + +## Wire Format + +When reference tracking is enabled, nullable fields write a **ref flag byte** before the value: + +``` +[ref_flag] [value data if not null/ref] +``` + +Where `ref_flag` is: + +| Value | Meaning | +| -------------------------- | ----------------------------------------------------- | +| `-1` (NULL_FLAG) | Value is null | +| `-2` (NOT_NULL_VALUE_FLAG) | Value is present, first occurrence | +| `≥0` | Reference ID pointing to previously serialized object | + +## Reference Tracking vs Nullability + +These are **independent** concepts: + +| Concept | Purpose | Controlled By | +| ---------------------- | ------------------------------------------ | ---------------------------------------- | +| **Nullability** | Whether a field can hold null values | Field type (`Optional`) or annotation | +| **Reference Tracking** | Whether duplicate objects are deduplicated | Global `refTracking` option | + +Key behavior: + +- Ref flag bytes are **only written for nullable fields** +- Non-nullable fields skip ref flags entirely, even with `refTracking=true` +- Reference deduplication only applies to objects that appear multiple times + +```java +// Reference tracking enabled, but non-nullable fields still skip ref flags +Fory fory = Fory.builder() + .withXlang(true) + .withRefTracking(true) + .build(); +``` + +## Per-Field Reference Tracking + +By default, **most fields do not track references** even when global `refTracking=true`. Only specific pointer/smart pointer types track references by default. + +### Default Behavior by Language + +| Language | Default Ref Tracking | Types That Track Refs by Default | +| -------- | -------------------- | ---------------------------------------------------------- | +| Java | No | None (use annotation to enable) | +| Python | No | None (use annotation to enable) | +| Go | No | None (use `fory:"ref"` to enable) | +| C++ | Yes | `std::shared_ptr`, `fory::serialization::SharedWeak` | +| Rust | No | `Rc`, `Arc`, `Weak` | +| Scala | No | None (use `@Ref` to enable) | + +### Customizing Per-Field Ref Tracking + +#### Java: @Ref Annotation + +```java +public class Document { + // Default: no ref tracking + String title; + + // Enable ref tracking for this field + @Ref + Author author; + + // Shared across documents, track refs to avoid duplicates + List<@Ref Tag> tags; +} +``` + +#### C++: FORY_STRUCT Field Config + +```cpp +struct Document { + std::string title; + + // shared_ptr/SharedWeak track refs by default + std::shared_ptr author; + fory::serialization::SharedWeak data; + + std::shared_ptr tag_owner; +}; +FORY_STRUCT(Document, + title, + author, + data, + (tag_owner, fory::F().ref()) +); +``` + +To disable reference tracking for C++ entirely, set +`Fory::builder().xlang(true).track_ref(false).build()` on the serializer. + +#### Rust: Field Attributes + +```rust +use fory::ForyStruct; +use std::rc::Rc; + +#[derive(ForyStruct)] +struct Document { + title: String, + + // Rc/Arc track refs by default + author: Rc, + + // Explicitly enable ref tracking + #[fory(ref = true)] + tags: Vec, +} +``` + +#### Scala: @Ref Annotation + +Scala schema IDL and Scala 3 macro derivation use the same shared JVM `@Ref` +annotation: + +```scala +import org.apache.fory.annotation.{ForyField, ForyStruct, Ref} +import org.apache.fory.scala.ForySerializer + +@ForyStruct +final class Node() derives ForySerializer { + @ForyField(id = 1) + var children: List[Node @Ref] = List.empty + + @Ref + @ForyField(id = 2) + var parent: Option[Node] = None +} +``` + +For Scala, top-level field reference tracking is owned by `@Ref` on the field or +constructor parameter. Type-use `T @Ref` is for nested element/value/payload +references, such as `List[Node @Ref]`. + +#### Go: Struct Tags + +```go +type Document struct { + Title string + + // Enable ref tracking for pointer to struct + Author *Author `fory:"ref"` + + // Enable ref tracking for slice + Tags []Tag `fory:"ref"` +} +``` + +### When to Enable Per-Field Ref Tracking + +Enable ref tracking for fields that: + +- May contain the same object instance multiple times +- Are part of circular reference chains +- Hold large objects that might be shared + +Disable (or leave default) for fields that: + +- Always contain unique values +- Are primitives or simple value types +- Don't participate in object sharing + +## Example: Shared References + +```java +public class Container { + List data; + List sameData; // Points to same list +} + +Container obj = new Container(); +obj.data = Arrays.asList("a", "b", "c"); +obj.sameData = obj.data; // Shared reference + +// With refTracking=true: data serialized once, sameData stores reference ID +// With refTracking=false: data serialized twice (duplicate) +``` + +## Example: Circular References + +```java +public class Node { + String value; + Node next; +} + +Node a = new Node("A"); +Node b = new Node("B"); +a.next = b; +b.next = a; // Circular reference + +// With refTracking=true: works correctly +// With refTracking=false: infinite recursion error +``` + +## Language Support + +| Language | Shared Refs | Circular Refs | +| ---------- | ----------- | -------------------- | +| Java | Yes | Yes | +| Python | Yes | Yes | +| Go | Yes | Yes | +| C++ | Yes | Yes | +| JavaScript | Yes | Yes | +| Rust | Yes | No (ownership rules) | + +## Performance Considerations + +- **Overhead**: Reference tracking adds a hash map lookup per object +- **When to enable**: Use when data has shared/circular references +- **When to disable**: Use for simple data structures without sharing + +## See Also + +- [Field Nullability](field-nullability.md) - How nullability affects serialization +- [Serialization](serialization.md) - Basic cross-language serialization examples +- [Xlang Specification](../../specification/xlang_serialization_spec.md) - Binary protocol details diff --git a/versioned_docs/version-1.3.0/guide/xlang/field-type-meta.md b/versioned_docs/version-1.3.0/guide/xlang/field-type-meta.md new file mode 100644 index 00000000000..b0cf239bee3 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/xlang/field-type-meta.md @@ -0,0 +1,283 @@ +--- +title: Field Type Meta +sidebar_position: 46 +id: field_type_meta +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. +--- + +Field type meta configuration controls whether type information is written during serialization for struct fields. This is essential for supporting polymorphism where the actual concrete type may differ from the declared field type. + +## Overview + +When serializing a struct field, Fory needs to determine whether to write type metadata: + +- **Static typing**: Use the declared field type's serializer directly (no type info written) +- **Dynamic typing**: Write type information to support subtypes + +## When Type Meta Is Needed + +Type metadata is required when: + +1. **Interface/abstract fields**: The declared type is abstract, so concrete type must be recorded +2. **Polymorphic fields**: The concrete type may be a subclass of the declared type +3. **Cross-language compatibility**: When the receiver needs type information to deserialize correctly + +Type metadata is NOT needed when: + +1. **Final/concrete types**: The declared type is final/sealed and cannot be subclassed +2. **Primitive types**: Type is known at compile time +3. **Performance optimization**: When you know the concrete type always matches the declared type + +## Language-Specific Configuration + +### Java + +Java requires explicit configuration because concrete classes can be subclassed unless marked `final`. + +Use the `@ForyField` annotation with the `dynamic` parameter: + +```java +import org.apache.fory.annotation.ForyField; +import org.apache.fory.annotation.ForyField.Dynamic; + +public class Container { + // AUTO (default): Interface types write type info, concrete types don't + @ForyField(id = 0) + private Shape shape; // Interface - type info written + + // FALSE: Never write type info (use declared type's serializer) + @ForyField(id = 1, dynamic = Dynamic.FALSE) + private Circle circle; // Always treated as Circle + + // TRUE: Always write type info (support subtypes) + @ForyField(id = 2, dynamic = Dynamic.TRUE) + private Shape concreteShape; // Type info written even if concrete +} +``` + +**Dynamic Options**: + +| Value | Behavior | +| ------- | ------------------------------------------------------ | +| `AUTO` | Interface/abstract types are dynamic, concrete are not | +| `FALSE` | Never write type info, use declared type's serializer | +| `TRUE` | Always write type info to support subtypes | + +**Use Cases**: + +- `AUTO`: Default behavior, suitable for most cases +- `FALSE`: Performance optimization when you know the exact type +- `TRUE`: When a concrete field may hold subclass instances + +### C++ + +C++ uses the `.dynamic(bool)` builder method inside `FORY_STRUCT`: + +```cpp +#include "fory/serialization/fory.h" + +// Abstract base class with pure virtual methods +struct Animal { + virtual ~Animal() = default; + virtual std::string speak() const = 0; +}; + +struct Zoo { + // Auto: type info written because Animal is polymorphic (std::is_polymorphic) + std::shared_ptr animal; + + // Force non-dynamic: skip type info even though Animal is polymorphic + std::shared_ptr fixed_animal; + + // Force dynamic: write type info even for non-polymorphic types + std::shared_ptr polymorphic_data; +}; +FORY_STRUCT(Zoo, + (animal, fory::F(0).nullable()), // Auto-detect polymorphism + (fixed_animal, fory::F(1).nullable().dynamic(false)), // Skip type info + (polymorphic_data, fory::F(2).dynamic(true)) // Force type info +); +``` + +**Default Behavior**: Fory auto-detects polymorphism via `std::is_polymorphic`. Types with pure virtual methods are treated as dynamic by default. + +### Go and Rust + +Go and Rust do **not** require explicit dynamic configuration because: + +- **Go**: Interface types are inherently dynamic - Fory can determine from the type whether it's an interface +- **Rust**: Trait objects (`dyn Trait`) are explicitly marked in the type system + +The type system in these languages already indicates whether a field is polymorphic: + +```go +// Go: interface types are automatically dynamic +type Container struct { + Shape Shape // Interface - type info written automatically + Circle Circle // Concrete struct - no type info needed +} +``` + +```rust +// Rust: trait objects are explicitly marked +struct Container { + shape: Box, // Trait object - type info written automatically + circle: Circle, // Concrete type - no type info needed +} +``` + +### Python + +Use `pyfory.field()` with the `dynamic` parameter: + +```python +from dataclasses import dataclass +from abc import ABC, abstractmethod +import pyfory + +class Shape(ABC): + @abstractmethod + def area(self) -> float: + pass + +@dataclass +class Circle(Shape): + radius: float = 0.0 + + def area(self) -> float: + return 3.14159 * self.radius * self.radius + +@dataclass +class Container: + # Abstract class: dynamic is always True (type info written) + shape: Shape = pyfory.field(id=0) + + # Concrete type with explicit dynamic=True (force type info) + circle: Circle = pyfory.field(id=1, dynamic=True) + + # Concrete type with explicit dynamic=False (skip type info) + fixed_circle: Circle = pyfory.field(id=2, dynamic=False) +``` + +**Default Behavior**: + +| Mode | Abstract Class | Concrete Object Types | Numeric/str/time Types | +| ----------- | -------------- | --------------------- | ---------------------- | +| Native mode | `True` | `True` | `False` | +| Xlang mode | `True` | `False` | `False` | + +- **Abstract classes**: `dynamic` is always `True` (type info must be written) +- **Native mode**: `dynamic` defaults to `True` for object types, `False` for numeric/str/time types +- **Xlang mode**: `dynamic` defaults to `False` for concrete types + +## Default Behavior + +| Language | Interface/Abstract Types | Concrete Types | +| -------- | ------------------------ | ---------------- | +| Java | Dynamic (write type) | Static (no type) | +| C++ | Dynamic (virtual) | Static | +| Go | Dynamic (interface) | Static (struct) | +| Rust | Dynamic (dyn Trait) | Static | +| Python | Dynamic (all objects) | Dynamic | + +## Performance Considerations + +Writing type metadata has overhead: + +- **Space**: Type information adds bytes to serialized output +- **Time**: Type resolution during serialization/deserialization + +Use `dynamic = FALSE` (Java) or `dynamic(false)` (C++) when: + +- You're certain the concrete type matches the declared type +- Performance is critical and polymorphism is not needed +- The field type is effectively final + +## Cross-Language Compatibility + +When serializing data for cross-language consumption: + +1. **Use consistent type registration**: Register types with the same ID across languages +2. **Prefer explicit configuration**: Use `dynamic = TRUE` when unsure about receiver's expectations +3. **Document polymorphic fields**: Make it clear which fields may contain subtypes + +## Example: Polymorphic Container + +### Java + +```java +public interface Animal { + String speak(); +} + +public class Dog implements Animal { + private String name; + + @Override + public String speak() { return "Woof!"; } +} + +public class Cat implements Animal { + private String name; + + @Override + public String speak() { return "Meow!"; } +} + +public class Zoo { + // Type info written because Animal is an interface + @ForyField(id = 0) + private Animal animal; + + // Force type info for concrete type that may hold subtypes + @ForyField(id = 1, dynamic = Dynamic.TRUE) + private Dog maybeMixedBreed; +} +``` + +### C++ + +```cpp +// Abstract base class with pure virtual methods +class Animal { +public: + virtual std::string speak() const = 0; + virtual ~Animal() = default; +}; + +class Dog : public Animal { +public: + std::string name; + std::string speak() const override { return "Woof!"; } +}; + +struct Zoo { + std::shared_ptr animal; + std::shared_ptr maybe_mixed_breed; +}; + +FORY_STRUCT(Zoo, + (animal, fory::F(0).nullable()), // Auto-detect (Animal is polymorphic) + (maybe_mixed_breed, fory::F(1).dynamic(true)) // Force dynamic for concrete type +); +``` + +## Related Topics + +- [Field Nullability](field-nullability.md) - Controlling null handling for fields +- [Field Reference Tracking](field-reference-tracking.md) - Managing shared/circular references +- [Type Mapping](../../specification/xlang_type_mapping.md) - Cross-language type compatibility diff --git a/versioned_docs/version-1.3.0/guide/xlang/getting-started.md b/versioned_docs/version-1.3.0/guide/xlang/getting-started.md new file mode 100644 index 00000000000..ed7cd7da9f3 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/xlang/getting-started.md @@ -0,0 +1,465 @@ +--- +title: Getting Started +sidebar_position: 10 +id: getting_started +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 guide covers installation and basic setup for cross-language serialization in all supported languages. + +## Installation + +### Java + +**Maven:** + +```xml + + org.apache.fory + fory-core + 1.3.0 + +``` + +**Gradle:** + +```gradle +implementation 'org.apache.fory:fory-core:1.3.0' +``` + +### Python + +```bash +pip install pyfory +``` + +### Go + +```bash +go get github.com/apache/fory/go/fory +``` + +### Rust + +```toml +[dependencies] +fory = "1.3.0" +``` + +### JavaScript/TypeScript + +```bash +npm install @apache-fory/core +``` + +For the optional Node.js string fast path: + +```bash +npm install @apache-fory/core @apache-fory/hps +``` + +### C\# + +```bash +dotnet add package Apache.Fory --version 1.3.0 +``` + +### Dart + +```bash +dart pub add fory:^1.3.0 +dart pub add dev:build_runner +``` + +### Swift + +Add Fory to `Package.swift`: + +```swift +dependencies: [ + .package(url: "https://github.com/apache/fory.git", exact: "1.3.0") +] +``` + +### Scala + +```scala +libraryDependencies += "org.apache.fory" %% "fory-scala" % "1.3.0" +``` + +### Kotlin + +```kotlin +implementation("org.apache.fory:fory-kotlin:1.3.0") +``` + +### C++ + +Use Bazel or CMake to build from source. See [C++ Guide](../cpp/index.md) for details. + +## Create an Xlang Fory Instance + +Xlang mode is the default for implementations that expose a mode switch. Swift, C#, JavaScript/TypeScript, +and Dart only expose the xlang wire format. The examples below keep compatible schema evolution on +the default path and show only options that change another setting. + +### Java + +```java +import org.apache.fory.*; +import org.apache.fory.config.*; + +Fory fory = Fory.builder() + .withXlang(true) + .withRefTracking(true) // Optional: for circular references + .build(); +``` + +### Python + +```python +import pyfory + +fory = pyfory.Fory(xlang=True) + +# Enable reference tracking when needed +fory = pyfory.Fory(xlang=True, ref=True) +``` + +### Go + +```go +import forygo "github.com/apache/fory/go/fory" + +fory := forygo.NewFory(forygo.WithXlang(true)) +// Or with reference tracking +fory := forygo.NewFory(forygo.WithXlang(true), forygo.WithTrackRef(true)) +``` + +### Rust + +```rust +use fory::Fory; + +let fory = Fory::builder().xlang(true).build(); +``` + +### JavaScript/TypeScript + +```javascript +import Fory, { Type } from "@apache-fory/core"; + +const fory = new Fory(); +``` + +### C\# + +```csharp +using Apache.Fory; + +Fory fory = Fory.Builder().Build(); +``` + +### Dart + +```dart +import 'package:fory/fory.dart'; + +final fory = Fory(); +``` + +### Swift + +```swift +import Fory + +let fory = Fory() +``` + +### Scala + +```scala +import org.apache.fory.scala.ForyScala + +val fory = ForyScala.builder() + .withXlang(true) + .build() +``` + +### Kotlin + +```kotlin +import org.apache.fory.kotlin.ForyKotlin + +val fory = ForyKotlin.builder() + .withXlang(true) + .build() +``` + +### C++ + +```cpp +#include "fory/serialization/fory.h" + +using namespace fory::serialization; + +auto fory = Fory::builder().xlang(true).build(); +``` + +## Type Registration + +Custom types must be registered with consistent names or IDs across all languages. + +### Register by Name (Recommended) + +Using string names is more flexible and less prone to conflicts: + +**Java:** + +```java +fory.register(Person.class, "example.Person"); +``` + +**Python:** + +```python +fory.register_type(Person, name="example.Person") +``` + +**Go:** + +```go +fory.RegisterStructByName(Person{}, "example.Person") +``` + +**Rust:** + +```rust +use fory::{Fory, ForyStruct}; + +#[derive(ForyStruct)] +struct Person { + name: String, + age: i32, +} + +let mut fory = Fory::builder().xlang(true).build(); +fory + .register_by_name::("example.Person") + .expect("register Person"); +``` + +**JavaScript/TypeScript:** + +```javascript +const personType = Type.struct( + { typeName: "example.Person" }, + { + name: Type.string(), + age: Type.int32(), + }, +); +const { serialize, deserialize } = fory.register(personType); +``` + +**C++:** + +```cpp +fory.register_struct("example.Person"); +// For enums, use register_enum: +// fory.register_enum("example.Color"); +``` + +**C#:** + +```csharp +fory.Register("example.Person"); +``` + +**Dart:** + +```dart +PersonForyModule.register( + fory, + Person, + name: 'example.Person', +); +``` + +**Swift:** + +```swift +try fory.register(Person.self, name: "example.Person") +``` + +**Scala:** + +```scala +fory.register(classOf[Person], "example.Person") +``` + +**Kotlin:** + +```kotlin +fory.register(Person::class.java, "example.Person") +``` + +### Register by ID + +Using numeric IDs is faster and produces smaller binary output: + +**Java:** + +```java +fory.register(Person.class, 100); +``` + +**Python:** + +```python +fory.register_type(Person, type_id=100) +``` + +**Go:** + +```go +fory.RegisterStruct(Person{}, 100) +``` + +**Rust:** + +```rust +fory.register::(100)?; +``` + +**JavaScript/TypeScript:** + +```javascript +const personType = Type.struct( + { typeId: 100 }, + { + name: Type.string(), + age: Type.int32(), + }, +); +``` + +**C++:** + +```cpp +fory.register_struct(100); +// For enums, use register_enum: +// fory.register_enum(101); +``` + +**C#:** + +```csharp +fory.Register(100); +``` + +**Dart:** + +```dart +PersonForyModule.register(fory, Person, id: 100); +``` + +**Swift:** + +```swift +fory.register(Person.self, id: 100) +``` + +**Scala:** + +```scala +fory.register(classOf[Person], 100) +``` + +**Kotlin:** + +```kotlin +fory.register(Person::class.java, 100) +``` + +## Hello World Example + +A complete example showing serialization in Java and deserialization in Python: + +### Java (Serializer) + +```java +import org.apache.fory.*; +import org.apache.fory.config.*; +import java.nio.file.*; + +public class Person { + public String name; + public int age; +} + +public class HelloWorld { + public static void main(String[] args) throws Exception { + Fory fory = Fory.builder().withXlang(true).build(); + fory.register(Person.class, "example.Person"); + + Person person = new Person(); + person.name = "Alice"; + person.age = 30; + + byte[] bytes = fory.serialize(person); + Files.write(Path.of("person.bin"), bytes); + System.out.println("Serialized to person.bin"); + } +} +``` + +### Python (Deserializer) + +```python +import pyfory +from dataclasses import dataclass + +@dataclass +class Person: + name: str + age: pyfory.Int32 + +fory = pyfory.Fory(xlang=True) +fory.register_type(Person, name="example.Person") + +with open("person.bin", "rb") as f: + data = f.read() + +person = fory.deserialize(data) +print(f"Name: {person.name}, Age: {person.age}") +# Output: Name: Alice, Age: 30 +``` + +## Best Practices + +1. **Use consistent type names**: Ensure all languages use the same type name or ID +2. **Enable reference tracking**: If your data has circular or shared references +3. **Reuse Fory instances**: Creating Fory is expensive; reuse instances +4. **Use type annotations**: In Python, use markers such as `pyfory.Int32` for precise type mapping +5. **Test cross-language**: Verify serialization works across all target languages + +## Next Steps + +- [Type Mapping](../../specification/xlang_type_mapping.md) - Cross-language type mapping reference +- [Serialization](serialization.md) - Detailed serialization examples +- [Troubleshooting](troubleshooting.md) - Common issues and solutions diff --git a/versioned_docs/version-1.3.0/guide/xlang/index.md b/versioned_docs/version-1.3.0/guide/xlang/index.md new file mode 100644 index 00000000000..e17d241f019 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/xlang/index.md @@ -0,0 +1,179 @@ +--- +title: Xlang Serialization Guide +sidebar_position: 0 +id: serialization_index +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. +--- + +Apache Fory™ xlang serialization is the default wire format for cross-language payloads. Serialize +data in one language and deserialize it in another without manual conversion. You can use direct +language model types for small contracts, or use Fory IDL and code generation when a schema-first +workflow is a better fit. + +## Features + +- **No IDL required**: Serialize objects directly with language model types. +- **Multi-language support**: Java, Python, C++, Go, Rust, + JavaScript/TypeScript, C#, Swift, Dart, Scala, and Kotlin interoperate through + the same xlang format. +- **Reference support**: Shared and circular references work across language boundaries when reference tracking is enabled in each peer. +- **Schema evolution**: Compatible mode is the xlang default so readers can tolerate added, removed, or reordered fields. +- **Out-of-band buffers**: Language implementations can expose zero-copy buffer paths for large binary data. +- **High performance**: Fory implementations use generated serializers, JIT serializers, or optimized code paths where available. + +## Supported Languages + +| Language | Status | Package or target | +| --------------------- | --------- | -------------------------------- | +| Java | Supported | `org.apache.fory:fory-core` | +| Python | Supported | `pyfory` | +| C++ | Supported | Bazel/CMake build | +| Go | Supported | `github.com/apache/fory/go/fory` | +| Rust | Supported | `fory` crate | +| JavaScript/TypeScript | Supported | `@apache-fory/core` | +| C# | Supported | `Apache.Fory` | +| Swift | Supported | Swift Package Manager target | +| Dart | Supported | `fory` package | +| Scala | Supported | `org.apache.fory:fory-scala` | +| Kotlin | Supported | `org.apache.fory:fory-kotlin` | + +## When to Use Xlang Mode + +Use xlang mode when: + +- Building multi-language microservices +- Creating polyglot data pipelines +- Sharing data between frontend JavaScript/TypeScript and backend services such + as Java, Python, Go, C#, Scala, or Kotlin + +Use native mode for same-language traffic in Java, Scala, Kotlin, Python, C++, +Go, or Rust: + +- All serialization/deserialization happens in the same language +- You need language-specific features such as Python pickle-style objects or Java serialization hooks +- You want native-mode payloads for same-language services + +## Quick Example + +### Java (Producer) + +```java +import org.apache.fory.*; +import org.apache.fory.config.*; + +public class Person { + public String name; + public int age; +} + +Fory fory = Fory.builder().withXlang(true).build(); +fory.register(Person.class, "example.Person"); + +Person person = new Person(); +person.name = "Alice"; +person.age = 30; +byte[] bytes = fory.serialize(person); +// Send bytes to Python, Go, Rust, etc. +``` + +### Python (Consumer) + +```python +import pyfory +from dataclasses import dataclass + +@dataclass +class Person: + name: str + age: pyfory.Int32 + +fory = pyfory.Fory(xlang=True) +fory.register_type(Person, name="example.Person") + +# Receive bytes from Java +person = fory.deserialize(bytes_from_java) +print(f"{person.name}, {person.age}") # Alice, 30 +``` + +## Fory IDL + +For schema-first projects, Fory also provides **Fory IDL** and code generation. + +- Compiler docs: [Fory IDL Overview](../../compiler/index.md) +- Best for large multi-language message contracts and long-lived schemas + +### Minimal IDL Example + +Create `person.fdl`: + +```protobuf +package example; + +message Person { + string name = 1; + int32 age = 2; + optional string email = 3; +} +``` + +Generate code: + +```bash +foryc person.fdl --lang java,python,cpp,go,rust,javascript,csharp,swift,dart,scala,kotlin --output ./generated +``` + +This generates native language types with consistent field/type mappings across all targets. + +## When to Fory IDL + +| Option | Use When | Why | +| ---------------------------------- | ---------------------------------------------------------------------- | -------------------------------------------------------------------------------- | +| Native xlang types (no IDL) | You only have a few message types and want to move quickly | Avoids the integration/setup cost of introducing and operating the compiler | +| Fory IDL (schema-first + codegen) | You have many messages across multiple languages/teams/services | Provides a single contract, stronger consistency, and easier long-term evolution | +| Hybrid (start native, move to IDL) | Project starts small but message count and cross-team dependency grows | Lets you keep early velocity, then standardize once schema complexity increases | + +## Documentation + +| Topic | Description | +| --------------------------------------------------------- | ------------------------------------------------ | +| [Getting Started](getting-started.md) | Installation and basic setup for all languages | +| [Type Mapping](../../specification/xlang_type_mapping.md) | Xlang type mapping reference | +| [Serialization](serialization.md) | Built-in types, custom types, reference handling | +| [Zero-Copy](zero-copy.md) | Out-of-band serialization for large data | +| [Row Format](row_format.md) | Cache-friendly binary format with random access | +| [Troubleshooting](troubleshooting.md) | Common issues and solutions | + +## Language-Specific Guides + +For language-specific details and API reference: + +- [Java Xlang Serialization Guide](../java/xlang-serialization.md) +- [Python Xlang Serialization Guide](../python/xlang-serialization.md) +- [C++ Xlang Serialization Guide](../cpp/xlang-serialization.md) +- [Go Xlang Serialization Guide](../go/xlang-serialization.md) +- [Rust Xlang Serialization Guide](../rust/xlang-serialization.md) +- [JavaScript/TypeScript Xlang Serialization Guide](../javascript/xlang-serialization.md) +- [C# Xlang Serialization Guide](../csharp/xlang-serialization.md) +- [Swift Xlang Serialization Guide](../swift/xlang-serialization.md) +- [Dart Xlang Serialization Guide](../dart/xlang-serialization.md) +- [Scala Schema IDL And Xlang Guide](../scala/schema-idl.md) +- [Kotlin Static Generated Serializers Guide](../kotlin/static-generated-serializers.md) + +## Specifications + +- [Xlang Serialization Specification](../../specification/xlang_serialization_spec.md) - Binary protocol details +- [Type Mapping Specification](../../specification/xlang_type_mapping.md) - Complete type mapping reference diff --git a/versioned_docs/version-1.3.0/guide/xlang/row_format.md b/versioned_docs/version-1.3.0/guide/xlang/row_format.md new file mode 100644 index 00000000000..afc256b7b30 --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/xlang/row_format.md @@ -0,0 +1,190 @@ +--- +title: Row Format +sidebar_position: 60 +id: row_format +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. +--- + +Fory Row Format is a cache-friendly binary format designed for efficient random access and partial serialization. Unlike object graph serialization, row format allows you to read individual fields without deserializing the entire object. + +## Features + +- **Zero-Copy Random Access**: Read specific fields directly from binary data +- **Partial Serialization**: Skip unnecessary fields during serialization +- **Cross-Language Compatible**: Row format data can be shared between Java, Python, and C++ +- **Apache Arrow Integration**: Convert row format to/from Arrow RecordBatch for analytics (Java/Python) + +## Java + +```java +public class Bar { + String f1; + List f2; +} + +public class Foo { + int f1; + List f2; + Map f3; + List f4; +} + +RowEncoder encoder = Encoders.bean(Foo.class); +Foo foo = new Foo(); +foo.f1 = 10; +foo.f2 = IntStream.range(0, 1000000).boxed().collect(Collectors.toList()); +foo.f3 = IntStream.range(0, 1000000).boxed().collect(Collectors.toMap(i -> "k"+i, i->i)); +List bars = new ArrayList<>(1000000); +for (int i = 0; i < 1000000; i++) { + Bar bar = new Bar(); + bar.f1 = "s"+i; + bar.f2 = LongStream.range(0, 10).boxed().collect(Collectors.toList()); + bars.add(bar); +} +foo.f4 = bars; +// Can be zero-copy read by Python +BinaryRow binaryRow = encoder.toRow(foo); +// Can be data from Python +Foo newFoo = encoder.fromRow(binaryRow); +// Zero-copy read List f2 +BinaryArray binaryArray2 = binaryRow.getArray(1); +// Zero-copy read List f4 +BinaryArray binaryArray4 = binaryRow.getArray(3); +// Zero-copy read 11th element of List f4 +BinaryRow barStruct = binaryArray4.getStruct(10); + +// Zero-copy read 6th element of f2 of 11th element of List f4 +barStruct.getArray(1).getInt64(5); +RowEncoder barEncoder = Encoders.bean(Bar.class); +// Deserialize part of data +Bar newBar = barEncoder.fromRow(barStruct); +Bar newBar2 = barEncoder.fromRow(binaryArray4.getStruct(20)); +``` + +## Python + +```python +@dataclass +class Bar: + f1: str + f2: List[pa.int64] +@dataclass +class Foo: + f1: pa.int32 + f2: List[pa.int32] + f3: Dict[str, pa.int32] + f4: List[Bar] + +encoder = pyfory.encoder(Foo) +foo = Foo(f1=10, f2=list(range(1000_000)), + f3={f"k{i}": i for i in range(1000_000)}, + f4=[Bar(f1=f"s{i}", f2=list(range(10))) for i in range(1000_000)]) +binary: bytes = encoder.to_row(foo).to_bytes() +print(f"start: {datetime.datetime.now()}") +foo_row = pyfory.RowData(encoder.schema, binary) +print(foo_row.f2[100000], foo_row.f4[100000].f1, foo_row.f4[200000].f2[5]) +print(f"end: {datetime.datetime.now()}") + +binary = pickle.dumps(foo) +print(f"pickle start: {datetime.datetime.now()}") +new_foo = pickle.loads(binary) +print(new_foo.f2[100000], new_foo.f4[100000].f1, new_foo.f4[200000].f2[5]) +print(f"pickle end: {datetime.datetime.now()}") +``` + +## Apache Arrow Support + +Fory Row Format supports automatic conversion from/to Arrow Table/RecordBatch for analytics workloads. + +### Java + +```java +Schema schema = TypeInference.inferSchema(BeanA.class); +ArrowWriter arrowWriter = ArrowUtils.createArrowWriter(schema); +Encoder encoder = Encoders.rowEncoder(BeanA.class); +for (int i = 0; i < 10; i++) { + BeanA beanA = BeanA.createBeanA(2); + arrowWriter.write(encoder.toRow(beanA)); +} +return arrowWriter.finishAsRecordBatch(); +``` + +### Python + +```python +import pyfory +encoder = pyfory.encoder(Foo) +encoder.to_arrow_record_batch([foo] * 10000) +encoder.to_arrow_table([foo] * 10000) +``` + +## Support for Interface and Extension Types + +Fory supports row format mapping for Java `interface` types and subclassed (`extends`) types, enabling more dynamic and flexible data schemas. + +These enhancements were introduced in [#2243](https://github.com/apache/fory/pull/2243), [#2250](https://github.com/apache/fory/pull/2250), and [#2256](https://github.com/apache/fory/pull/2256). + +### Example: Interface Mapping with RowEncoder + +```java +public interface Animal { + String speak(); +} + +public class Dog implements Animal { + public String name; + + @Override + public String speak() { + return "Woof"; + } +} + +// Encode and decode using RowEncoder with interface type +RowEncoder encoder = Encoders.bean(Animal.class); +Dog dog = new Dog(); +dog.name = "Bingo"; +BinaryRow row = encoder.toRow(dog); +Animal decoded = encoder.fromRow(row); +System.out.println(decoded.speak()); // Woof +``` + +### Example: Extension Type with RowEncoder + +```java +public class Parent { + public String parentField; +} + +public class Child extends Parent { + public String childField; +} + +// Encode and decode using RowEncoder with parent class type +RowEncoder encoder = Encoders.bean(Parent.class); +Child child = new Child(); +child.parentField = "Hello"; +child.childField = "World"; +BinaryRow row = encoder.toRow(child); +Parent decoded = encoder.fromRow(row); +``` + +## See Also + +- [Row Format Specification](https://fory.apache.org/docs/specification/row_format_spec) - Binary format details +- [Java Row Format Guide](../java/row-format.md) - Java-specific row format documentation +- [Python Row Format Guide](../python/row-format.md) - Python-specific row format documentation diff --git a/versioned_docs/version-1.3.0/guide/xlang/serialization.md b/versioned_docs/version-1.3.0/guide/xlang/serialization.md new file mode 100644 index 00000000000..cdf136a353e --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/xlang/serialization.md @@ -0,0 +1,573 @@ +--- +title: Serialization +sidebar_position: 30 +id: serialization +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 page demonstrates common cross-language serialization patterns. Data serialized in one +supported language can be deserialized in any other supported language when peers use matching type +identity, field schema, and compatibility settings. + +## Remote Schema Metadata Limits + +Compatible mode may receive remote metadata (`TypeDef` or `TypeMeta`) for types that are not already +known by the reader. Fory limits how many distinct remote metadata versions can be accepted, and +also limits the size of each received metadata body: + +- `maxSchemaVersionsPerType`: maximum accepted remote metadata versions for one logical type. The + default is `10`. +- `maxAverageSchemaVersionsPerType`: average accepted remote metadata versions across all accepted + remote types. The default is `3`; the effective global floor is `8192` metadata entries. +- `maxTypeFields`: maximum fields declared by one received struct metadata body. The default is + `512`. +- `maxTypeMetaBytes`: maximum encoded metadata body bytes for one received TypeDef or TypeMeta body, + excluding the 8-byte header and any extended-size varint. The default is `4096`. + +These limits are resource protections. They do not change wire format, registration requirements, +dynamic type loading, unknown-type handling, or schema-evolution compatibility. + +Raise these values only when the data is not malicious and a trusted peer sends larger metadata or +many schema versions. + +| Language | Field-count option | Metadata-bytes option | Per-type option | Average option | +| --------------------- | ------------------- | ---------------------- | ------------------------------ | -------------------------------------- | +| Java | `withMaxTypeFields` | `withMaxTypeMetaBytes` | `withMaxSchemaVersionsPerType` | `withMaxAverageSchemaVersionsPerType` | +| Scala | `withMaxTypeFields` | `withMaxTypeMetaBytes` | `withMaxSchemaVersionsPerType` | `withMaxAverageSchemaVersionsPerType` | +| Kotlin | `withMaxTypeFields` | `withMaxTypeMetaBytes` | `withMaxSchemaVersionsPerType` | `withMaxAverageSchemaVersionsPerType` | +| Python | `max_type_fields` | `max_type_meta_bytes` | `max_schema_versions_per_type` | `max_average_schema_versions_per_type` | +| JavaScript/TypeScript | `maxTypeFields` | `maxTypeMetaBytes` | `maxSchemaVersionsPerType` | `maxAverageSchemaVersionsPerType` | +| C++ | `max_type_fields` | `max_type_meta_bytes` | `max_schema_versions_per_type` | `max_average_schema_versions_per_type` | +| Go | `WithMaxTypeFields` | `WithMaxTypeMetaBytes` | `WithMaxSchemaVersionsPerType` | `WithMaxAverageSchemaVersionsPerType` | +| Rust | `max_type_fields` | `max_type_meta_bytes` | `max_schema_versions_per_type` | `max_average_schema_versions_per_type` | +| C# | `MaxTypeFields` | `MaxTypeMetaBytes` | `MaxSchemaVersionsPerType` | `MaxAverageSchemaVersionsPerType` | +| Swift | `maxTypeFields` | `maxTypeMetaBytes` | `maxSchemaVersionsPerType` | `maxAverageSchemaVersionsPerType` | +| Dart | `maxTypeFields` | `maxTypeMetaBytes` | `maxSchemaVersionsPerType` | `maxAverageSchemaVersionsPerType` | + +## Serialize Built-in Types + +Common types can be serialized automatically without registration: primitive numeric types, string, binary, array, list, map, and more. + +Reduced-precision floating-point values are also part of the built-in xlang type system: + +- `float16` and `array` +- `bfloat16` and `array` + +Use the language-specific carrier types documented in the type mapping reference. Python uses `pyfory.Float16` and `pyfory.BFloat16` as annotation markers only; scalar values are native Python `float`, and dense reduced-precision arrays use `pyfory.Float16Array` and `pyfory.BFloat16Array`. Go uses the `float16` and `bfloat16` packages for scalar, slice, and array carriers; JavaScript uses `number` for scalar `float16` and `bfloat16`, and dense array carriers `BoolArray`, `Float16Array`, and `BFloat16Array` for the corresponding `array` schemas. Dart uses `double` plus `Float16Type` or `Bfloat16Type` metadata for scalar fields, and `Float16List` / `Bfloat16List` for dense arrays. Java uses `@ArrayType` on supported reduced-precision carriers for `array` / `array` schema, while general object arrays stay on the `list` path; C++, Rust, and C# provide their own dedicated scalar and array carriers. + +When `compatible=true`, a direct struct/class field can evolve between `list` and `array` for dense bool/numeric `T`. Integer list element encodings in the same signedness and width domain match the corresponding dense array element domain. This applies only to the immediate matched field schema. It does not apply to nested collection, map, array, union, or generic positions. A peer `list` schema can be read into a local `array` field when the actual payload has no null elements. If the payload carries a null element or ref-tracked element encoding, reading it into a local `array` field raises a compatible-read error. + +### Java + +```java +import org.apache.fory.*; +import org.apache.fory.config.*; + +import java.util.*; + +public class Example1 { + public static void main(String[] args) { + Fory fory = Fory.builder().withXlang(true).build(); + List list = ofArrayList(true, false, "str", -1.1, 1, new int[100], new double[20]); + byte[] bytes = fory.serialize(list); + // bytes can be deserialized by other languages + fory.deserialize(bytes); + Map map = new HashMap<>(); + map.put("k1", "v1"); + map.put("k2", list); + map.put("k3", -1); + bytes = fory.serialize(map); + // bytes can be deserialized by other languages + fory.deserialize(bytes); + } +} +``` + +### Python + +```python +import pyfory +import numpy as np + +fory = pyfory.Fory(xlang=True) +object_list = [True, False, "str", -1.1, 1, + np.full(100, 0, dtype=np.int32), np.full(20, 0.0, dtype=np.double)] +data = fory.serialize(object_list) +# bytes can be deserialized by other languages +new_list = fory.deserialize(data) +object_map = {"k1": "v1", "k2": object_list, "k3": -1} +data = fory.serialize(object_map) +# bytes can be deserialized by other languages +new_map = fory.deserialize(data) +print(new_map) +``` + +### Go + +```go +package main + +import forygo "github.com/apache/fory/go/fory" +import "fmt" + +func main() { + list := []any{true, false, "str", -1.1, 1, make([]int32, 10), make([]float64, 20)} + fory := forygo.NewFory(forygo.WithXlang(true)) + bytes, err := fory.Marshal(list) + if err != nil { + panic(err) + } + var newValue any + // bytes can be deserialized by other languages + if err := fory.Unmarshal(bytes, &newValue); err != nil { + panic(err) + } + fmt.Println(newValue) + dict := map[string]any{ + "k1": "v1", + "k2": list, + "k3": -1, + } + bytes, err = fory.Marshal(dict) + if err != nil { + panic(err) + } + // bytes can be deserialized by other languages + if err := fory.Unmarshal(bytes, &newValue); err != nil { + panic(err) + } + fmt.Println(newValue) +} +``` + +### JavaScript + +```javascript +import Fory from "@apache-fory/core"; + +const fory = new Fory(); +const input = fory.serialize("hello fory"); +const result = fory.deserialize(input); +console.log(result); +``` + +### Rust + +```rust +use fory::Fory; + +fn run() { + let fory = Fory::builder().xlang(true).build(); + let bin = fory.serialize(&"hello".to_string()).expect("serialize success"); + let obj: String = fory.deserialize(&bin).expect("deserialize success"); + assert_eq!("hello".to_string(), obj); +} +``` + +## Serialize Custom Types + +User-defined types must be registered using the register API to establish the mapping relationship between types in different languages. Use consistent type names across all languages. + +### Java + +```java +import org.apache.fory.*; +import org.apache.fory.config.*; +import java.util.*; + +public class Example2 { + public static class SomeClass1 { + Object f1; + Map f2; + } + + public static class SomeClass2 { + Object f1; + String f2; + List f3; + Map f4; + Byte f5; + Short f6; + Integer f7; + Long f8; + Float f9; + Double f10; + short[] f11; + List f12; + } + + public static Object createObject() { + SomeClass1 obj1 = new SomeClass1(); + obj1.f1 = true; + obj1.f2 = ofHashMap((byte) -1, 2); + SomeClass2 obj = new SomeClass2(); + obj.f1 = obj1; + obj.f2 = "abc"; + obj.f3 = ofArrayList("abc", "abc"); + obj.f4 = ofHashMap((byte) 1, 2); + obj.f5 = Byte.MAX_VALUE; + obj.f6 = Short.MAX_VALUE; + obj.f7 = Integer.MAX_VALUE; + obj.f8 = Long.MAX_VALUE; + obj.f9 = 1.0f / 2; + obj.f10 = 1 / 3.0; + obj.f11 = new short[]{(short) 1, (short) 2}; + obj.f12 = ofArrayList((short) -1, (short) 4); + return obj; + } + + // mvn exec:java -Dexec.mainClass="org.apache.fory.examples.Example2" + public static void main(String[] args) { + Fory fory = Fory.builder().withXlang(true).build(); + fory.register(SomeClass1.class, "example.SomeClass1"); + fory.register(SomeClass2.class, "example.SomeClass2"); + byte[] bytes = fory.serialize(createObject()); + // bytes can be deserialized by other languages + System.out.println(fory.deserialize(bytes)); + } +} +``` + +### Python + +```python +from dataclasses import dataclass +from typing import List, Dict, Any +import pyfory, array + + +@dataclass +class SomeClass1: + f1: Any + f2: Dict[pyfory.Int8, pyfory.Int32] + + +@dataclass +class SomeClass2: + f1: Any = None + f2: str = None + f3: List[str] = None + f4: Dict[pyfory.Int8, pyfory.Int32] = None + f5: pyfory.Int8 = None + f6: pyfory.Int16 = None + f7: pyfory.Int32 = None + # int type will be taken as `pyfory.Int64`. + # use `pyfory.Int32` for type hint if peer uses more narrow type. + f8: int = None + f9: pyfory.Float32 = None + # float type will be taken as `pyfory.Float64` + f10: float = None + f11: pyfory.Array[pyfory.Int16] = None + f12: List[pyfory.Int16] = None + + +if __name__ == "__main__": + f = pyfory.Fory(xlang=True) + f.register_type(SomeClass1, name="example.SomeClass1") + f.register_type(SomeClass2, name="example.SomeClass2") + obj1 = SomeClass1(f1=True, f2={-1: 2}) + obj = SomeClass2( + f1=obj1, + f2="abc", + f3=["abc", "abc"], + f4={1: 2}, + f5=2 ** 7 - 1, + f6=2 ** 15 - 1, + f7=2 ** 31 - 1, + f8=2 ** 63 - 1, + f9=1.0 / 2, + f10=1 / 3.0, + f11=array.array("h", [1, 2]), + f12=[-1, 4], + ) + data = f.serialize(obj) + # bytes can be deserialized by other languages + print(f.deserialize(data)) +``` + +### Go + +```go +package main + +import forygo "github.com/apache/fory/go/fory" +import "fmt" + +func main() { + type SomeClass1 struct { + F1 any + F2 map[int8]int32 + } + + type SomeClass2 struct { + F1 any + F2 string + F3 []any + F4 map[int8]int32 + F5 int8 + F6 int16 + F7 int32 + F8 int64 + F9 float32 + F10 float64 + F11 []int16 + F12 []int16 + } + serializer := forygo.NewFory(forygo.WithXlang(true)) + if err := serializer.RegisterStructByName(SomeClass1{}, "example.SomeClass1"); err != nil { + panic(err) + } + if err := serializer.RegisterStructByName(SomeClass2{}, "example.SomeClass2"); err != nil { + panic(err) + } + obj1 := &SomeClass1{F1: true, F2: map[int8]int32{-1: 2}} + obj := &SomeClass2{ + F1: obj1, + F2: "abc", + F3: []any{"abc", "abc"}, + F4: map[int8]int32{1: 2}, + F5: 127, + F6: 32767, + F7: 2147483647, + F8: 9223372036854775807, + F9: 1.0 / 2, + F10: 1.0 / 3.0, + F11: []int16{1, 2}, + F12: []int16{-1, 4}, + } + bytes, err := serializer.Marshal(obj) + if err != nil { + panic(err) + } + var newValue any + // bytes can be deserialized by other languages + if err := serializer.Unmarshal(bytes, &newValue); err != nil { + panic(err) + } + fmt.Println(newValue) +} +``` + +### JavaScript + +```javascript +import Fory, { Type } from "@apache-fory/core"; + +// Describe data structures using JSON schema +const description = Type.struct( + { typeName: "example.foo" }, + { + foo: Type.string(), + }, +); +const fory = new Fory(); +const { serialize, deserialize } = fory.register(description); +const input = serialize({ foo: "hello fory" }); +const result = deserialize(input); +console.log(result); +``` + +### Rust + +```rust +use chrono::{NaiveDate, NaiveDateTime}; +use fory::{Fory, ForyStruct}; +use std::collections::HashMap; + +#[test] +fn complex_struct() { + #[derive(ForyStruct, Debug, PartialEq)] + struct Animal { + category: String, + } + + #[derive(ForyStruct, Debug, PartialEq)] + struct Person { + c1: Vec, // binary + c2: Vec, // primitive array + animal: Vec, + c3: Vec>, + name: String, + c4: HashMap, + age: u16, + op: Option, + op2: Option, + date: NaiveDate, + time: NaiveDateTime, + c5: f32, + c6: f64, + } + let person: Person = Person { + c1: vec![1, 2, 3], + c2: vec![5, 6, 7], + c3: vec![vec![1, 2], vec![1, 3]], + animal: vec![Animal { + category: "Dog".to_string(), + }], + c4: HashMap::from([ + ("hello1".to_string(), "hello2".to_string()), + ("hello2".to_string(), "hello3".to_string()), + ]), + age: 12, + name: "helo".to_string(), + op: Some("option".to_string()), + op2: None, + date: NaiveDate::from_ymd_opt(2025, 12, 12).unwrap(), + time: NaiveDateTime::from_timestamp_opt(1689912359, 0).unwrap(), + c5: 2.0, + c6: 4.0, + }; + + let mut fory = Fory::builder().xlang(true).build(); + fory + .register_by_name::("example.foo2") + .expect("register Animal"); + fory + .register_by_name::("example.foo") + .expect("register Person"); + let bin = fory.serialize(&person).expect("serialize success"); + let obj: Person = fory.deserialize(&bin).expect("deserialize success"); + assert_eq!(person, obj); +} +``` + +## Serialize Shared and Circular References + +Shared references and circular references can be serialized automatically with no duplicate data or recursion errors. Enable reference tracking to use this feature. + +### Java + +```java +import org.apache.fory.*; +import org.apache.fory.config.*; +import java.util.*; + +public class ReferenceExample { + public static class SomeClass { + SomeClass f1; + Map f2; + Map f3; + } + + public static Object createObject() { + SomeClass obj = new SomeClass(); + obj.f1 = obj; + obj.f2 = ofHashMap("k1", "v1", "k2", "v2"); + obj.f3 = obj.f2; + return obj; + } + + // mvn exec:java -Dexec.mainClass="org.apache.fory.examples.ReferenceExample" + public static void main(String[] args) { + Fory fory = Fory.builder() + .withXlang(true) + .withRefTracking(true) + .build(); + fory.register(SomeClass.class, "example.SomeClass"); + byte[] bytes = fory.serialize(createObject()); + // bytes can be deserialized by other languages + System.out.println(fory.deserialize(bytes)); + } +} +``` + +### Python + +```python +from typing import Dict +import pyfory + +class SomeClass: + f1: "SomeClass" + f2: Dict[str, str] + f3: Dict[str, str] + +fory = pyfory.Fory(xlang=True, ref=True) +fory.register_type(SomeClass, name="example.SomeClass") +obj = SomeClass() +obj.f2 = {"k1": "v1", "k2": "v2"} +obj.f1, obj.f3 = obj, obj.f2 +data = fory.serialize(obj) +# bytes can be deserialized by other languages +print(fory.deserialize(data)) +``` + +### Go + +```go +package main + +import forygo "github.com/apache/fory/go/fory" +import "fmt" + +func main() { + type SomeClass struct { + F1 *SomeClass + F2 map[string]string + F3 map[string]string + } + fory := forygo.NewFory(forygo.WithXlang(true), forygo.WithTrackRef(true)) + if err := fory.RegisterStruct(SomeClass{}, 65); err != nil { + panic(err) + } + value := &SomeClass{F2: map[string]string{"k1": "v1", "k2": "v2"}} + value.F3 = value.F2 + value.F1 = value + bytes, err := fory.Marshal(value) + if err != nil { + panic(err) + } + var newValue any + // bytes can be deserialized by other languages + if err := fory.Unmarshal(bytes, &newValue); err != nil { + panic(err) + } + fmt.Println(newValue) +} +``` + +### JavaScript + +```javascript +import Fory, { Type } from "@apache-fory/core"; + +const description = Type.struct("example.foo", { + foo: Type.string(), + bar: Type.struct("example.foo").setTrackingRef(true), +}); + +const fory = new Fory({ ref: true }); +const { serialize, deserialize } = fory.register(description); +const data: any = { + foo: "hello fory", +}; +data.bar = data; +const input = serialize(data); +const result = deserialize(input); +console.log(result.bar.foo === result.foo); +``` + +### Rust + +Circular references cannot be implemented in Rust due to ownership restrictions. + +## See Also + +- [Zero-Copy Serialization](zero-copy.md) - Out-of-band serialization for large data +- [Type Mapping](../../specification/xlang_type_mapping.md) - Cross-language type mapping reference +- [Getting Started](getting-started.md) - Installation and setup +- [Xlang Serialization Specification](../../specification/xlang_serialization_spec.md) - Binary protocol details diff --git a/versioned_docs/version-1.3.0/guide/xlang/troubleshooting.md b/versioned_docs/version-1.3.0/guide/xlang/troubleshooting.md new file mode 100644 index 00000000000..503ac45fb7b --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/xlang/troubleshooting.md @@ -0,0 +1,321 @@ +--- +title: Troubleshooting +sidebar_position: 70 +id: troubleshooting +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 page covers common issues and solutions when using cross-language serialization. + +## Type Registration Errors + +### "Type not registered" Error + +**Symptom:** + +``` +Error: Type 'example.Person' is not registered +``` + +**Cause:** The type was not registered before deserialization, or the type name doesn't match. + +**Solution:** + +1. Ensure the type is registered with the same name on both sides: + + ```java + // Java + fory.register(Person.class, "example.Person"); + ``` + + ```python + # Python + fory.register_type(Person, name="example.Person") + ``` + +2. Check for typos or case differences in type names + +3. Register types before any serialization/deserialization calls + +### "Type ID mismatch" Error + +**Symptom:** + +``` +Error: Expected type ID 100, got 101 +``` + +**Cause:** Different type IDs used across languages. + +**Solution:** Use consistent type IDs: + +```java +// Java +fory.register(Person.class, 100); +fory.register(Address.class, 101); +``` + +```python +# Python +fory.register_type(Person, type_id=100) +fory.register_type(Address, type_id=101) +``` + +## Type Mapping Issues + +### Integer Overflow + +**Symptom:** Values are truncated or wrapped unexpectedly. + +**Cause:** Using different integer sizes across languages. + +**Solution:** + +1. In Python, use explicit type annotations: + + ```python + @dataclass + class Data: + value: pyfory.Int32 # Not just 'int' + ``` + +2. Ensure integer ranges are compatible: + - `int8`: -128 to 127 + - `int16`: -32,768 to 32,767 + - `int32`: -2,147,483,648 to 2,147,483,647 + +### Float Precision Loss + +**Symptom:** Float values have unexpected precision. + +**Cause:** Mixing `float32` and `float64` types. + +**Solution:** + +1. Use consistent float types: + + ```python + @dataclass + class Data: + value: pyfory.Float32 # Explicit 32-bit float + ``` + +2. Be aware that Python's `float` maps to `float64` by default + +### String Encoding Errors + +**Symptom:** + +``` +Error: Invalid UTF-8 sequence +``` + +**Cause:** Non-UTF-8 encoded strings. + +**Solution:** + +1. Ensure all strings are valid UTF-8 +2. In Python, decode bytes before serialization: + + ```python + text = raw_bytes.decode('utf-8') + ``` + +## Field Order Issues + +### "Field mismatch" Error + +**Symptom:** Deserialized objects have wrong field values. + +**Cause:** Field order differs between languages. + +**Solution:** Fory sorts fields by their snake_cased names. Ensure field names are consistent: + +```java +// Java - fields will be sorted: age, email, name +public class Person { + public String name; + public int age; + public String email; +} +``` + +```python +# Python - same field order +@dataclass +class Person: + name: str + age: pyfory.Int32 + email: str +``` + +## Reference Tracking Issues + +### Stack Overflow with Circular References + +**Symptom:** + +``` +StackOverflowError or RecursionError +``` + +**Cause:** Reference tracking is disabled but data has circular references. + +**Solution:** Enable reference tracking: + +```java +// Java +Fory fory = Fory.builder() + .withRefTracking(true) + .build(); +``` + +```python +# Python +fory = pyfory.Fory(ref=True) +``` + +### Duplicate Objects + +**Symptom:** Shared objects are duplicated after deserialization. + +**Cause:** Reference tracking is disabled. + +**Solution:** Enable reference tracking if objects are shared within the graph. + +## Xlang Type Issues + +### Incompatible Types in Xlang Mode + +**Symptom:** + +``` +Error: Type 'Optional' is not supported in xlang mode +``` + +**Cause:** Using Java-specific types that don't have cross-language equivalents. + +**Solution:** Use compatible types: + +```java +// Instead of Optional +public String email; // nullable + +// Instead of BigDecimal +public double amount; + +// Instead of EnumSet +public Set statuses; +``` + +## Version Compatibility + +### Schema Hash Mismatch + +**Symptom:** Deserialization fails with an error such as `class version hash mismatch`, +`schema version mismatch`, `struct version mismatch`, or `hash mismatch`. + +**Cause:** The writer and reader have disabled compatible mode while their struct/class schemas +differ. In xlang mode this can happen even when each language made a reasonable local change, +because field names, type annotations, field IDs, nullability, and generated schema metadata must +still align exactly. + +**Solution:** + +1. Align the schemas carefully on every service and language: field names or field IDs, field order, + type annotations, nullability, and type registration IDs/names. +2. Xlang mode defaults to compatible mode in current implementations. If a peer has explicitly selected + `compatible=false`, remove that override or enable compatible mode on every peer. + Compatible mode writes extra schema metadata, so payloads are larger, but it is recommended + for xlang services that may evolve independently. +3. Set `compatible=false` only when every reader and writer always uses the same schema. For xlang payloads, do this only after verifying that every language uses that schema, or when native types are generated from Fory schema IDL. + +### Serialization Format Changed + +**Symptom:** Deserialization fails after upgrading Fory. + +**Cause:** Breaking changes in serialization format. + +**Solution:** + +1. Ensure all services use compatible Fory versions +2. Check release notes for breaking changes +3. Consider using schema evolution (compatible mode) for gradual upgrades + +## Debugging Tips + +### Enable Debug Logging + +**Java:** + +```java +// Add to JVM options +-Dfory.debug=true +``` + +**Python:** + +```python +import logging +logging.getLogger('pyfory').setLevel(logging.DEBUG) +``` + +### Inspect Serialized Data + +Use hex dump to inspect the binary format: + +```python +data = fory.serialize(obj) +print(data.hex()) +``` + +### Test Round-Trip + +Always test round-trip serialization in each language: + +```java +byte[] bytes = fory.serialize(obj); +Object result = fory.deserialize(bytes); +assert obj.equals(result); +``` + +### Cross-Language Testing + +Test serialization across all target languages before deployment: + +```bash +# Serialize in Java +java -jar serializer.jar > data.bin + +# Deserialize in Python +python deserializer.py data.bin +``` + +## Common Mistakes + +1. **Not registering types**: Always register custom types before use +2. **Inconsistent type names/IDs**: Use the same names/IDs across all languages +3. **Mixing xlang and native payloads**: Keep every peer on the xlang wire format +4. **Wrong type annotations**: Use markers such as `pyfory.Int32` in Python +5. **Ignoring reference tracking**: Enable for circular/shared references + +## See Also + +- [Type Mapping](../../specification/xlang_type_mapping.md) - Cross-language type mapping reference +- [Getting Started](getting-started.md) - Setup guide +- [Java Troubleshooting](../java/troubleshooting.md) - Java-specific issues +- [Python Troubleshooting](../python/troubleshooting.md) - Python-specific issues diff --git a/versioned_docs/version-1.3.0/guide/xlang/zero-copy.md b/versioned_docs/version-1.3.0/guide/xlang/zero-copy.md new file mode 100644 index 00000000000..2d37215d30b --- /dev/null +++ b/versioned_docs/version-1.3.0/guide/xlang/zero-copy.md @@ -0,0 +1,203 @@ +--- +title: Zero-Copy Serialization +sidebar_position: 50 +id: zero_copy +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. +--- + +Zero-copy serialization allows large binary data (byte arrays, numeric arrays) to be serialized out-of-band, avoiding memory copies and reducing serialization overhead. + +## When to Use Zero-Copy + +Use zero-copy serialization when: + +- Serializing large byte arrays or binary blobs +- Working with numeric arrays (int[], double[], etc.) +- Transferring data over high-performance networks +- Memory efficiency is critical + +## How It Works + +1. **Serialization**: Large buffers are extracted and returned separately via a callback +2. **Transport**: The main serialized data and buffer objects are transmitted separately +3. **Deserialization**: Buffers are provided back to reconstruct the original object + +This avoids copying large data into the main serialization buffer. + +## Java + +```java +import org.apache.fory.*; +import org.apache.fory.config.*; +import org.apache.fory.serializer.BufferObject; +import org.apache.fory.memory.MemoryBuffer; + +import java.util.*; +import java.util.stream.Collectors; + +public class ZeroCopyExample { + public static void main(String[] args) { + Fory fory = Fory.builder().withXlang(true).build(); + + // Data with large arrays + List list = List.of( + "str", + new byte[1000], // Large byte array + new int[100], // Large int array + new double[100] // Large double array + ); + + // Collect buffer objects during serialization + Collection bufferObjects = new ArrayList<>(); + byte[] bytes = fory.serialize(list, e -> !bufferObjects.add(e)); + + // Convert to buffers for transport + List buffers = bufferObjects.stream() + .map(BufferObject::toBuffer) + .collect(Collectors.toList()); + + // Deserialize with buffers + Object result = fory.deserialize(bytes, buffers); + System.out.println(result); + } +} +``` + +## Python + +```python +import array +import pyfory +import numpy as np + +fory = pyfory.Fory(xlang=True) + +# Data with large arrays +data = [ + "str", + bytes(bytearray(1000)), # Large byte array + array.array("i", range(100)), # Large int array + np.full(100, 0.0, dtype=np.double) # Large numpy array +] + +# Collect buffer objects during serialization +serialized_objects = [] +serialized_data = fory.serialize(data, buffer_callback=serialized_objects.append) + +# Convert to buffers for transport +buffers = [obj.to_buffer() for obj in serialized_objects] + +# Deserialize with buffers +result = fory.deserialize(serialized_data, buffers=buffers) +print(result) +``` + +## Go + +```go +package main + +import forygo "github.com/apache/fory/go/fory" +import "fmt" + +func main() { + serializer := forygo.NewFory(forygo.WithXlang(true)) + + // Data with large arrays + list := []any{ + "str", + make([]byte, 1000), // Large byte array + } + + buf := forygo.NewByteBuffer(nil) + var bufferObjects []forygo.BufferObject + + // Collect buffer objects during serialization + if err := serializer.SerializeWithCallback(buf, list, func(o forygo.BufferObject) bool { + bufferObjects = append(bufferObjects, o) + return false + }); err != nil { + panic(err) + } + + // Convert to buffers for transport + var buffers []*forygo.ByteBuffer + for _, o := range bufferObjects { + buffers = append(buffers, o.ToBuffer()) + } + + // Deserialize with buffers + var newList []any + if err := serializer.DeserializeWithCallbackBuffers(buf, &newList, buffers); err != nil { + panic(err) + } + fmt.Println(newList) +} +``` + +## Use Cases + +### High-Performance Data Transfer + +When sending large datasets over the network: + +```java +// Sender +Collection buffers = new ArrayList<>(); +byte[] metadata = fory.serialize(dataObject, e -> !buffers.add(e)); + +// Send metadata and buffers separately +network.sendMetadata(metadata); +for (BufferObject buf : buffers) { + network.sendBuffer(buf.toBuffer()); +} + +// Receiver +byte[] metadata = network.receiveMetadata(); +List buffers = network.receiveBuffers(); +Object data = fory.deserialize(metadata, buffers); +``` + +### Memory-Mapped Files + +Zero-copy works well with memory-mapped files: + +```java +// Write +Collection buffers = new ArrayList<>(); +byte[] data = fory.serialize(largeObject, e -> !buffers.add(e)); +writeToFile("data.bin", data); +for (int i = 0; i < buffers.size(); i++) { + writeToFile("buffer" + i + ".bin", buffers.get(i).toBuffer()); +} + +// Read +byte[] data = readFromFile("data.bin"); +List buffers = readBufferFiles(); +Object result = fory.deserialize(data, buffers); +``` + +## Performance Considerations + +1. **Threshold**: Small arrays may not benefit from zero-copy due to callback overhead +2. **Network**: Zero-copy is most beneficial when buffers can be sent without copying +3. **Memory**: Reduces peak memory usage by avoiding buffer copies + +## See Also + +- [Serialization](serialization.md) - Standard serialization examples +- [Python Out-of-Band Guide](../python/out-of-band.md) - Python-specific zero-copy details diff --git a/versioned_docs/version-1.3.0/introduction/_category_.json b/versioned_docs/version-1.3.0/introduction/_category_.json new file mode 100644 index 00000000000..7317fd66509 --- /dev/null +++ b/versioned_docs/version-1.3.0/introduction/_category_.json @@ -0,0 +1,4 @@ +{ + "position": 1, + "label": "Introduction" +} diff --git a/versioned_docs/version-1.3.0/introduction/benchmark.md b/versioned_docs/version-1.3.0/introduction/benchmark.md new file mode 100644 index 00000000000..5fa3ef583a2 --- /dev/null +++ b/versioned_docs/version-1.3.0/introduction/benchmark.md @@ -0,0 +1,100 @@ +--- +id: benchmark +title: Benchmark +sidebar_position: 3 +--- + +> **Note**: Different serialization frameworks excel in different scenarios. Benchmark results are for reference only. +> For your specific use case, conduct benchmarks with appropriate configurations and workloads. + +## Java Benchmark + +The Java benchmark section compares Fory against popular Java serialization frameworks using the current benchmark suite from `docs/benchmarks/java`. + +**Serialization Throughput**: + +![Java Serialization Throughput](../benchmarks/java/java_repo_serialization_throughput.png) + +**Deserialization Throughput**: + +![Java Deserialization Throughput](../benchmarks/java/java_repo_deserialization_throughput.png) + +**Cross-Language Throughput**: + +![Java Cross-Language Throughput](../benchmarks/java/throughput.png) + +**Important**: Fory's runtime code generation requires proper warm-up for performance measurement: + +For additional benchmark notes, raw data, and the complete Java benchmark README, see [Java Benchmarks](https://github.com/apache/fory/tree/main/docs/benchmarks/java). + +## Python Benchmark + +Fory Python demonstrates strong performance compared to `pickle` and Protobuf across object and list workloads. + +![Python Throughput](../benchmarks/python/throughput.png) + +For benchmark setup, raw results, and reproduction steps, see [Python Benchmarks](../benchmarks/python/README.md). + +## Rust Benchmark + +Fory Rust demonstrates competitive performance compared to other Rust serialization frameworks. + +![Rust Throughput](../benchmarks/rust/throughput.png) + +Note: Results depend on hardware, dataset, and implementation versions. See the Rust guide for how to run benchmarks yourself: https://github.com/apache/fory/blob/main/benchmarks/rust_benchmark/README.md + +## C++ Benchmark + +Fory C++ demonstrates competitive performance compared to Protobuf C++ serialization framework. + +![C++ Throughput](../benchmarks/cpp/throughput.png) + +## Go Benchmark + +Fory Go demonstrates strong performance compared to Protobuf and Msgpack across +single-object and list workloads. + +![Go Throughput](../benchmarks/go/throughput.png) + +Note: Results depend on hardware, dataset, and implementation versions. See the +Go benchmark report for details: https://fory.apache.org/docs/benchmarks/go/ + +## C# Benchmark + +Fory C# demonstrates strong performance compared to Protobuf and Msgpack across +typed object serialization and deserialization workloads. + +![C# Throughput](../benchmarks/csharp/throughput.png) + +Note: Results depend on hardware and runtime versions. See the C# benchmark +report for details: https://fory.apache.org/docs/benchmarks/csharp/ + +## Swift Benchmark + +Fory Swift demonstrates strong performance compared to Protobuf and Msgpack +across both scalar-object and list workloads. + +![Swift Throughput](../benchmarks/swift/throughput.png) + +Note: Results depend on hardware and runtime versions. See the Swift benchmark +report for details: https://fory.apache.org/docs/benchmarks/swift/ + +## JavaScript Benchmark + +Fory JavaScript demonstrates strong performance compared to Protocol Buffers and +JSON across representative Node.js workloads. + +![JavaScript Throughput](../benchmarks/javascript/throughput.png) + +Note: Results depend on hardware, dataset, and runtime versions. See the +[JavaScript benchmark report](../benchmarks/javascript/README.md) for details. + +## Dart Benchmark + +Fory Dart demonstrates strong performance compared to Protocol Buffers across +representative object and list workloads. + +![Dart Throughput](../benchmarks/dart/throughput.png) + +Note: Results depend on hardware, dataset, and runtime versions. See the +[Dart benchmark report](../benchmarks/dart/README.md) for details. diff --git a/versioned_docs/version-1.3.0/introduction/features.md b/versioned_docs/version-1.3.0/introduction/features.md new file mode 100644 index 00000000000..1651d470360 --- /dev/null +++ b/versioned_docs/version-1.3.0/introduction/features.md @@ -0,0 +1,210 @@ +--- +id: features +title: Features +sidebar_position: 2 +--- + +## Core Capabilities + +### High-Performance Serialization + +Apache Fory™ delivers exceptional performance through advanced optimization techniques: + +- **JIT Compilation**: Runtime code generation for Java eliminates virtual method calls and inlines hot paths +- **Static Code Generation**: Compile-time code generation for Rust, C++, and Go delivers peak performance without runtime overhead +- **Zero-Copy Operations**: Direct memory access without intermediate buffer copies; row format enables random access and partial serialization +- **Intelligent Encoding**: Variable-length compression for integers and strings; SIMD acceleration for arrays (Java 16+) +- **Meta Sharing**: Class metadata packing reduces redundant type information across serializations + +### Cross-Language Serialization + +The **[xlang serialization format](../specification/xlang_serialization_spec.md)** enables seamless data exchange across programming languages: + +- **Automatic Type Mapping**: Intelligent conversion between language-specific types ([type mapping](../specification/xlang_type_mapping.md)) +- **Reference Preservation**: Shared and circular references work correctly across languages +- **Polymorphism**: Objects serialize/deserialize with their actual runtime types +- **Schema Evolution**: Optional forward/backward compatibility for evolving schemas +- **Automatic Serialization**: No IDL or schema definitions required; serialize any object directly without code generation + +### Row Format + +A cache-friendly **[row format](../specification/row_format_spec.md)** optimized for analytics workloads: + +- **Zero-Copy Random Access**: Read individual fields without deserializing entire objects +- **Partial Operations**: Selective field serialization and deserialization for efficiency +- **Apache Arrow Integration**: Seamless conversion to columnar format for analytics pipelines +- **Multi-Language**: Available in Java, Python, Rust and C++ + +### Security & Production-Readiness + +Enterprise-grade security and compatibility: + +- **Class Registration**: Whitelist-based deserialization control (enabled by default) +- **Depth Limiting**: Protection against recursive object graph attacks +- **Configurable Policies**: Custom class checkers and deserialization policies +- **Platform Support**: Java 8-24, GraalVM native image, multiple OS platforms + +## Java Features + +### High Performance + +- **JIT Code Generation**: Highly-extensible JIT framework generates serializer code at runtime using async multi-threaded compilation, delivering 20-170x speedup through: + - Inlining variables to reduce memory access + - Inlining method calls to eliminate virtual dispatch overhead + - Minimizing conditional branching + - Eliminating hash lookups +- **Zero-Copy**: Direct memory access without intermediate buffer copies; row format supports random access and partial serialization +- **Variable-Length Encoding**: Optimized compression for integers, longs +- **Meta Sharing**: Cached class metadata reduces redundant type information +- **SIMD Acceleration**: Java Vector API support for array operations (Java 16+) + +### Drop-in Replacement + +- **100% JDK Serialization Compatible**: Supports `writeObject`/`readObject`/`writeReplace`/`readResolve`/`readObjectNoData`/`Externalizable` +- **Java 8-24 Support**: Works across all modern Java versions including Java 17+ records +- **GraalVM Native Image**: AOT compilation support without reflection configuration + +### Advanced Features + +- **Reference Tracking**: Automatic handling of shared and circular references +- **Schema Evolution**: Forward/backward compatibility for class schema changes +- **Polymorphism**: Full support for inheritance hierarchies and interfaces +- **Deep Copy**: Efficient deep cloning of complex object graphs with reference preservation +- **Security**: Class registration and configurable deserialization policies + +## Python Features + +### **Flexible Serialization Modes** + +- **Python native Mode**: Full Python compatibility, drop-in replacement for pickle/cloudpickle +- **Cross-Language Mode**: Optimized for multi-language data exchange +- **Row Format**: Zero-copy row format for analytics workloads + +### Versatile Serialization Features + +- **Shared/circular reference support** for complex object graphs in both Python-native and cross-language modes +- **Polymorphism support** for customized types with automatic type dispatching +- **Schema evolution** support for backward/forward compatibility when using dataclasses in cross-language mode +- **Out-of-band buffer support** for zero-copy serialization of large data structures like NumPy arrays and Pandas DataFrames, compatible with pickle protocol 5 + +### **Blazing Fast Performance** + +- **Extremely fast performance** compared to other serialization frameworks +- **Runtime code generation** and **Cython-accelerated** core implementation for optimal performance + +### Compact Data Size + +- **Compact object graph protocol** with minimal space overhead—up to 3× size reduction compared to pickle/cloudpickle +- **Meta packing and sharing** to minimize type forward/backward compatibility space overhead + +### **Security & Safety** + +- **Strict mode** prevents deserialization of untrusted types by type registration and checks. +- **Reference tracking** for handling circular references safely + +## Rust Features + +### Why Apache Fory™ Rust? + +- **Blazingly Fast**: Zero-copy deserialization and optimized binary protocols +- **Cross-Language**: Seamlessly serialize/deserialize data across Java, Python, C++, Go, JavaScript, and Rust +- **Type-Safe**: Compile-time type checking with derive macros +- **Circular References**: Automatic tracking of shared and circular references with `Rc`/`Arc` and weak pointers +- **Polymorphic**: Serialize trait objects with `Box`, `Rc`, and `Arc` +- **Schema Evolution**: Compatible mode for independent schema changes +- **Two Modes**: Object graph serialization and zero-copy row-based format + +### Object Graph Serialization + +Automatic serialization of complex object graphs, preserving the structure and relationships between objects. The `#[derive(ForyObject)]` macro generates efficient serialization code at compile time, eliminating runtime overhead: + +- Nested struct serialization with arbitrary depth +- Collection types (Vec, HashMap, HashSet, BTreeMap) +- Enum type support +- Optional fields with `Option` +- Automatic handling of primitive types and strings +- Efficient binary encoding with variable-length integers + +### Shared and Circular References + +Automatically tracks and preserves reference identity for shared objects using `Rc` and `Arc`. When the same object is referenced multiple times, Fory serializes it only once and uses reference IDs for subsequent occurrences. This ensures: + +- **Space efficiency**: No data duplication in serialized output +- **Reference identity preservation**: Deserialized objects maintain the same sharing relationships +- **Circular reference support**: Use `RcWeak` and `ArcWeak` to break cycles + +### Trait Object Serialization + +Polymorphic serialization through trait objects, enabling dynamic dispatch and type flexibility. This is essential for plugin systems, heterogeneous collections, and extensible architectures. Supported trait object types: + +- `Box` - Owned trait objects +- `Rc` - Reference-counted trait objects +- `Arc` - Thread-safe reference-counted trait objects +- `Vec>`, `HashMap>` - Collections of trait objects + +### Schema Evolution + +Schema evolution in **Compatible mode**, allowing serialization and deserialization peers to have different type definitions. This enables independent evolution of services in distributed systems without breaking compatibility: + +- Add new fields with default values +- Remove obsolete fields (skipped during deserialization) +- Change field nullability (`T` ↔ `Option`) +- Reorder fields (matched by name, not position) +- Type-safe fallback to default values for missing fields + +### Custom Serializers + +For types that don't support `#[derive(ForyObject)]`, implement the `Serializer` trait manually. This is useful for: + +- External types from other crates +- Types with special serialization requirements +- Legacy data format compatibility +- Performance-critical custom encoding + +### Row-Based Serialization + +High-performance **row format** for zero-copy deserialization. Unlike traditional object serialization that reconstructs entire objects in memory, row format enables **random access** to fields directly from binary data without full deserialization. + +- **Zero-copy access**: Read fields without allocating or copying data +- **Partial deserialization**: Access only the fields you need +- **Memory-mapped files**: Work with data larger than RAM +- **Cache-friendly**: Sequential memory layout for better CPU cache utilization +- **Lazy evaluation**: Defer expensive operations until field access + +## Scala Features + +### Supported Types + +Apache Fory™ supports all scala object serialization: + +- `case` class serialization supported +- `pojo/bean` class serialization supported +- `object` singleton serialization supported +- `collection` serialization supported +- other types such as `tuple/either` and basic types are all supported too. + +Scala 2 and 3 are both supported. + +### Scala Class Default Values Support + +Fory supports Scala class default values during deserialization when using compatible mode. This feature enables forward/backward compatibility when case classes or regular Scala classes have default parameters. + +When a Scala class has default parameters, the Scala compiler generates methods in the companion object (for case classes) or in the class itself (for regular Scala classes) like `apply$default$1`, `apply$default$2`, etc. that return the default values. Fory can detect these methods and use them when deserializing objects where certain fields are missing from the serialized data. + +## Kotlin Features + +Kotlin support builds on the default Fory Java implementation and adds handling for Kotlin-specific types and schema evolution. + +### Supported Types + +- Primitive types: `Byte`, `Boolean`, `Int`, `Short`, `Long`, `Char`, `Float`, `Double` +- Unsigned types: `UByte`, `UShort`, `UInt`, `ULong` +- Strings, standard arrays, and standard collections through the default Java implementation +- Kotlin collections and arrays such as `ArrayDeque`, `Array`, `BooleanArray`, `ByteArray`, `IntArray`, `LongArray`, `UByteArray`, `UIntArray`, and `ULongArray` +- Common stdlib types including `Pair`, `Triple`, `Result`, `Regex`, `Random`, `Duration`, and `Uuid` +- Kotlin ranges and progressions such as `CharRange`, `IntRange`, `LongRange`, `UIntRange`, `ULongRange`, and related progression types +- Empty collections such as `emptyList`, `emptyMap`, and `emptySet` + +### Data Class Default Value Support + +In compatible mode, Fory can detect Kotlin data class default values and apply them during deserialization when fields are missing. This allows schemas to evolve more safely, for example when adding new fields with defaults. diff --git a/versioned_docs/version-1.3.0/introduction/overview.md b/versioned_docs/version-1.3.0/introduction/overview.md new file mode 100644 index 00000000000..d72dbc04ecd --- /dev/null +++ b/versioned_docs/version-1.3.0/introduction/overview.md @@ -0,0 +1,110 @@ +--- +id: overview +title: Overview +sidebar_position: 1 +--- + +**Apache Fory™** is a blazingly-fast multi-language serialization framework for +idiomatic domain objects, schema IDL, and cross-language data exchange. + +Fory is built for compact, high-throughput serialization across languages and +runtimes. It works directly with application objects, supports shared schemas +when you need a stable contract, and preserves object graph features such as +shared references, circular references, and polymorphic runtime types. + +## Quick Example + +Cross-language serialization — serialize in Rust, deserialize in Python: + +**Rust** + +```rust +use fory::{Fory, ForyObject}; + +#[derive(ForyObject, Debug, PartialEq)] +struct User { + name: String, + age: i32, +} + +fn main() { + let mut fory = Fory::default().xlang(true); + fory.register::(1); + + let user = User { name: "Alice".to_string(), age: 30 }; + let bytes = fory.serialize(&user).unwrap(); + let decoded: User = fory.deserialize(&bytes).unwrap(); + println!("{:?}", decoded); // User { name: "Alice", age: 30 } +} +``` + +**Python** + +```python +import pyfory +from dataclasses import dataclass + +@dataclass +class User: + name: str + age: pyfory.int32 + +fory = pyfory.Fory(xlang=True) +fory.register(User, type_id=1) + +user = User(name="Alice", age=30) +data = fory.serialize(user) +decoded = fory.deserialize(data) +print(decoded) # User(name='Alice', age=30) +``` + +## Key Features + +### Efficient Cross-Language Encoding + +The **[xlang serialization format](../specification/xlang_serialization_spec.md)** +exchanges compact binary payloads across supported languages: + +- **Compact metadata**: Type metadata and field information are packed to keep payloads small. +- **Schema evolution**: Compatible mode supports forward and backward evolution for application schemas. +- **Object graph semantics**: Shared references, circular references, and polymorphic runtime types are preserved across runtimes. +- **Type mapping**: Language-specific values are mapped through the shared [type mapping](../specification/xlang_type_mapping.md). + +### Domain Objects First + +Fory serializes native domain objects directly instead of forcing applications +through wrapper types: + +- Java classes, Scala/Kotlin types, and GraalVM native image workloads. +- Python dataclasses and Python-native object graphs. +- Go structs, Rust structs, C++ structs, C# models, Swift types, Dart models, and JavaScript/TypeScript values. +- Generated or annotated types when a shared contract is preferred. + +### Reference-Aware Schema IDL + +**[Fory IDL and the compiler](../compiler/index.md)** let teams define schemas +once and generate native domain objects for each target language: + +- Model numbers, strings, lists, maps, arrays, enums, structs, and unions. +- Express shared and circular references directly in the schema. +- Generate idiomatic host-language code without introducing transport-specific wrapper types into user code. +- Use schema IDL when services need a stable contract across independently maintained runtimes. + +### Row-Format Random Access + +A cache-friendly **[row format](../specification/row_format_spec.md)** is +optimized for analytics and partial-read workloads: + +- **Zero-copy random access**: Read fields, arrays, and nested values without rebuilding whole objects. +- **Partial operations**: Read only the values needed for a query or pipeline stage. +- **Apache Arrow integration**: Convert to columnar data for analytics pipelines. +- **Multi-language support**: Use row format from Java, Python, Rust, and C++. + +### Optimized Runtimes + +Fory keeps hot paths fast without making every runtime use the same implementation strategy: + +- **Java JIT serializers**: Runtime code generation eliminates reflection overhead and inlines hot paths. +- **Generated and static serializers**: Other runtimes use generated or static serializers where appropriate. +- **Zero-copy paths**: Row format and out-of-band buffers avoid unnecessary copies for large values. +- **Metadata sharing**: Repeated type information is shared or packed to reduce serialization overhead. diff --git a/versioned_docs/version-1.3.0/specification/java_serialization_spec.md b/versioned_docs/version-1.3.0/specification/java_serialization_spec.md new file mode 100644 index 00000000000..607fffe772e --- /dev/null +++ b/versioned_docs/version-1.3.0/specification/java_serialization_spec.md @@ -0,0 +1,767 @@ +--- +title: Java Serialization Format +sidebar_position: 1 +id: java_serialization_spec +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. +--- + +## Scope + +This document specifies the Apache Fory Java native binary format: the format +used by Java when `withXlang(false)` is configured. The format is optimized for +Java object graphs, Java collection implementations, Java primitive arrays, +Java class registration, Java serialization hooks, and optional schema +evolution. + +Java native mode and xlang mode share low-level building blocks such as +little-endian numeric payloads, variable-length integer encodings, reference +flags, meta string encodings, and TypeDef/ClassDef concepts. They are different +wire formats. In Java native mode, only the scalar type IDs from `BOOL` through +`STRING` are shared with xlang. Collection, map, struct, array, enum, and +native Java implementation type IDs are Java native IDs unless this document +explicitly says otherwise. + +See [Xlang Serialization Format](xlang_serialization_spec.md) for the +cross-language format. + +## Stream Layout + +A Java native stream contains one header byte followed by one or more root +objects. Each root object is encoded as a normal object slot: + +```text +| header | root_0 | root_1 | ... | + +root: +| reference flag | [type metadata] | [value payload] | +``` + +All multi-byte fixed-width values are little endian. A big-endian Java implementation +must still write and read little-endian payloads. + +The stream is stateful. Type metadata, class definitions, and object references +are assigned indexes as they are first encountered and may be referenced later +in the same stream. + +## Header + +The header is a single byte: + +```text +| bits 7..2 reserved | bit 1 out-of-band | bit 0 xlang | +``` + +- `xlang` must be `0` for Java native mode. +- `out-of-band` is `1` when a `BufferCallback` is configured. +- Reserved bits must be `0`. + +Java native mode does not write a language ID after the header. + +## Reference Slots + +Objects, nullable fields, and reference-tracked fields use the standard Fory +reference slot. The first byte is signed: + +| Flag | Byte | Payload that follows | +| --------------------- | ---- | ---------------------------------------------------------------- | +| `NULL_FLAG` | `-3` | No payload. The slot value is `null`. | +| `REF_FLAG` | `-2` | `varuint32` reference ID of an earlier object. | +| `NOT_NULL_VALUE_FLAG` | `-1` | Value payload. No reference ID is assigned for this occurrence. | +| `REF_VALUE_FLAG` | `0` | Value payload. Assign the next reference ID before reading data. | + +When reference tracking is disabled for a slot, writers use only `NULL_FLAG` +and `NOT_NULL_VALUE_FLAG`. + +Primitive field fast paths do not wrap non-null primitive values in a reference +slot. Boxed primitives and other nullable values use the slot selected by field +metadata. + +## Type Metadata + +Dynamic object slots write type metadata before the value payload. Type metadata +identifies the serializer and, when needed, carries class names or ClassDef +metadata. + +```text +| varuint32 type_id | [type-specific metadata] | +``` + +Registered Java classes, Java native built-ins, and Fory internal serializers +use numeric type IDs. Unregistered classes or classes registered by name carry +name metadata. Schema-evolution classes may carry a ClassDef. + +### Native Type ID Ranges + +| Range | Meaning | +| -------- | ------------------------------------------------------------------ | +| `0` | `UNKNOWN`, used in metadata for dynamic or object-typed positions. | +| `1..21` | Shared scalar IDs from `BOOL` through `STRING`. | +| `22..63` | Reserved in Java native mode for the xlang internal ID range. | +| `64..68` | Reserved for future Java native internal IDs. | +| `69..98` | Java native built-ins listed below. | +| `99+` | User and Fory class IDs assigned by the Java `ClassResolver`. | + +The shared scalar IDs are: + +| ID | Name | Java value domain | +| --- | --------------- | --------------------------------------- | +| 1 | `BOOL` | boolean values in xlang metadata | +| 2 | `INT8` | signed 8-bit integer metadata | +| 3 | `INT16` | signed 16-bit integer metadata | +| 4 | `INT32` | fixed-width signed 32-bit metadata | +| 5 | `VARINT32` | variable-width signed 32-bit metadata | +| 6 | `INT64` | fixed-width signed 64-bit metadata | +| 7 | `VARINT64` | variable-width signed 64-bit metadata | +| 8 | `TAGGED_INT64` | tagged signed 64-bit metadata | +| 9 | `UINT8` | unsigned 8-bit metadata | +| 10 | `UINT16` | unsigned 16-bit metadata | +| 11 | `UINT32` | fixed-width unsigned 32-bit metadata | +| 12 | `VAR_UINT32` | variable-width unsigned 32-bit metadata | +| 13 | `UINT64` | fixed-width unsigned 64-bit metadata | +| 14 | `VAR_UINT64` | variable-width unsigned 64-bit metadata | +| 15 | `TAGGED_UINT64` | tagged unsigned 64-bit metadata | +| 16 | `FLOAT8` | reserved 8-bit float metadata | +| 17 | `FLOAT16` | half precision float metadata | +| 18 | `BFLOAT16` | bfloat16 metadata | +| 19 | `FLOAT32` | 32-bit floating point metadata | +| 20 | `FLOAT64` | 64-bit floating point metadata | +| 21 | `STRING` | Java `String` | + +Java native built-ins start at ID `69`: + +| ID | Name | Java type or serializer owner | +| --- | ---------------------------- | --------------------------------------- | +| 69 | `VOID_ID` | `java.lang.Void` | +| 70 | `CHAR_ID` | `java.lang.Character` | +| 71 | `PRIMITIVE_VOID_ID` | `void` | +| 72 | `PRIMITIVE_BOOL_ID` | `boolean` | +| 73 | `PRIMITIVE_INT8_ID` | `byte` | +| 74 | `PRIMITIVE_CHAR_ID` | `char` | +| 75 | `PRIMITIVE_INT16_ID` | `short` | +| 76 | `PRIMITIVE_INT32_ID` | `int` | +| 77 | `PRIMITIVE_FLOAT32_ID` | `float` | +| 78 | `PRIMITIVE_INT64_ID` | `long` | +| 79 | `PRIMITIVE_FLOAT64_ID` | `double` | +| 80 | `PRIMITIVE_BOOLEAN_ARRAY_ID` | `boolean[]` | +| 81 | `PRIMITIVE_BYTE_ARRAY_ID` | `byte[]` | +| 82 | `PRIMITIVE_CHAR_ARRAY_ID` | `char[]` | +| 83 | `PRIMITIVE_SHORT_ARRAY_ID` | `short[]` | +| 84 | `PRIMITIVE_INT_ARRAY_ID` | `int[]` | +| 85 | `PRIMITIVE_FLOAT_ARRAY_ID` | `float[]` | +| 86 | `PRIMITIVE_LONG_ARRAY_ID` | `long[]` | +| 87 | `PRIMITIVE_DOUBLE_ARRAY_ID` | `double[]` | +| 88 | `STRING_ARRAY_ID` | `String[]` | +| 89 | `OBJECT_ARRAY_ID` | `Object[]` and object array serializers | +| 90 | `ARRAYLIST_ID` | `java.util.ArrayList` | +| 91 | `HASHMAP_ID` | `java.util.HashMap` | +| 92 | `HASHSET_ID` | `java.util.HashSet` | +| 93 | `CLASS_ID` | `java.lang.Class` | +| 94 | `EMPTY_OBJECT_ID` | Empty-object serializer | +| 95 | `LAMBDA_STUB_ID` | Lambda replacement type ID | +| 96 | `JDK_PROXY_STUB_ID` | JDK proxy replacement type ID | +| 97 | `REPLACE_STUB_ID` | `writeReplace`/`readResolve` type ID | +| 98 | `NONEXISTENT_META_SHARED_ID` | Unknown-class marker type ID | + +### Registered, Named, and Unregistered Classes + +Java native mode supports three class identity forms: + +- ID registration: the type ID is the registered numeric class ID. +- Name registration: the type metadata carries namespace and type name strings. +- Unregistered class: the type metadata carries the package name as namespace + and the simple Java class name as type name. + +Class registration is the fastest and most compact form. Name-based forms are +used when stable names are required or class registration is disabled. + +### Meta Sharing + +When meta sharing is enabled, class metadata is written once and referenced by a +stream-local index: + +```text +| varuint32 marker | [class definition bytes if new] | + +marker = (index << 1) | flag +flag = 0: new definition, class definition bytes follow +flag = 1: reference to an earlier definition +``` + +Indexes are assigned in first-use order. + +## Schema Modes + +Java native mode has two object schema modes. + +### Same-Schema Mode + +Same-schema mode is used when compatible mode is disabled. The writer and +reader must have matching fields and field order. No per-object ClassDef is +required for ordinary registered classes. Field values are written directly in +protocol order. + +### Compatible Mode + +Compatible mode writes ClassDef metadata for struct-like classes. Readers match +local fields against remote ClassDef fields by identifier, read matching fields, +and skip unknown fields using the remote field type metadata. Compatible mode is +the Java native schema-evolution path. + +In compatible mode, a matched field may read between direct top-level scalar +ClassDef schemas when the remote value can be represented by the local scalar +schema without changing the logical value. This is a read adaptation only: +writers keep emitting their local canonical field schema and payload, and +ClassDef metadata, same-schema mode, dynamic value serialization, and +unknown-field skipping continue to treat the original field schemas as distinct. + +The rule applies only to the immediate schema of a matched field. It does not +apply to dynamic root values, map keys, map values, collection elements, array +elements, enum values, temporal values, binary values, structs, or nested +generic/container positions. + +The scalar domains are Java boolean/boxed boolean, `String`, Java primitive and +boxed numeric scalar fields, Fory scalar annotations whose ClassDef metadata +identifies a narrower numeric wire domain, and `BigDecimal` as the exact decimal +numeric scalar. Java native-only metadata outside the type IDs shared with xlang +must still use the Java ClassDef metadata to identify the scalar domain. +Compatible scalar conversion applies only when both the remote and local +top-level ClassDef field metadata have `trackingRef = false`; if either matched +field has `trackingRef = true`, scalar type changes are schema/type incompatible +during compatible layout construction. Same scalar ClassDef field types with +matching top-level `trackingRef` and null/optional framing are exact same-schema +direct reads, not compatible scalar conversion. Same scalar ClassDef field types +with different top-level `trackingRef` framing are schema/type incompatible +because the wire framing differs. Same scalar ClassDef field types with +different top-level null/optional framing may still use the nullable/optional +composition rule below when both fields have `trackingRef = false`. + +Compatible scalar conversion follows the xlang scalar conversion contract: + +- `String` to boolean accepts exactly `"0"`, `"1"`, `"false"`, and `"true"`. + Boolean to `String` produces `"false"` or `"true"`. +- numeric to boolean accepts only exact zero and one. Boolean to numeric + produces exact zero or one in the local numeric domain. +- numeric to numeric succeeds only when the local numeric domain represents the + same mathematical value, including range checks, signedness checks, exact + integer/floating round-trip checks, floating signed-zero preservation, and + rejection of `NaN` across different floating type IDs. +- `BigDecimal` participates as an exact numeric scalar. Converted decimal values + use canonical scale: zero and non-zero integers use scale `0`; finite + fractional values use the smallest non-negative scale that preserves the + mathematical value and leaves an unscaled value not divisible by `10`. + Compatible conversion rejects numeric strings longer than `320` bytes before + arbitrary precision parsing. It also rejects converted decimal values whose + canonical exponent or scale work exceeds the `256` digit bound before + constructing large powers of ten or formatting plain decimal text. Same-type + `BigDecimal` reads preserve the ordinary decimal payload. +- `String` to numeric accepts only the finite compatible numeric literal grammar + from the xlang serialization spec and then applies the same lossless + target-domain checks. `NaN`, infinities, whitespace, leading plus signs, + Unicode decimal digits, underscores, grouping separators, non-decimal radices, + and type suffixes fail. +- numeric to `String` emits canonical finite numeric text: integers use plain + decimal text, floating values use exact plain decimal text with a decimal point + and signed-zero preservation, and `BigDecimal` values use exact plain decimal + text without exponent notation or insignificant trailing fractional zeros. + +Nullable fields, boxed carriers, and primitive defaults compose with scalar +conversion when the matched top-level field schemas have `trackingRef = false`. +Readers first consume the remote null/optional framing described by the remote +ClassDef field metadata. Present values are converted and then assigned or +wrapped into the local carrier. Null or absent remote values use the same +compatible-mode missing/null behavior already defined for the local field. +Reference-tracked scalar conversion is not supported. + +Schema pairs outside the scalar conversion matrix remain schema/type +compatibility errors while building the compatible layout. Once a matched field +is accepted as a scalar conversion action, invalid payload values are +deserialization data errors and must be reported as +`org.apache.fory.exception.DeserializationException`, not as schema misses or +registration errors. + +## Field Order + +Java native object serializers use the same deterministic field-order +categories as the current xlang protocol: + +1. Primitive non-nullable numeric and boolean scalar fields. +2. Primitive nullable numeric and boolean scalar fields, including boxed Java + primitive wrappers. +3. Non-primitive fields. + +Primitive groups keep the primitive comparator: + +1. Fixed-width primitive encodings before compressed or variable-width + primitive encodings. +2. Larger primitive width before smaller primitive width. +3. Internal primitive type ID ascending. +4. Field identifier. + +Non-primitive fields sort directly by field identifier. Non-primitive type ID, +serializer kind, collection kind, map kind, and Java implementation class do not +participate in field order. + +Field identifiers are selected as follows: + +- If a field has an explicit non-negative `@ForyField(id = ...)`, that numeric + ID is the field identifier. +- Otherwise, the Java field name converted to snake_case is the field + identifier. +- Negative annotation values are not valid field IDs. The annotation default + value `-1` means no explicit ID and is ignored for identifier selection. + +Identifier comparison is: + +1. If both fields have explicit IDs, compare IDs numerically. +2. If only one field has an explicit ID, the ID-based field sorts before the + name-based field. +3. If neither field has an explicit ID, compare snake_case names + lexicographically. +4. If identifiers are equal, use deterministic tie-breakers such as declaring + class and original field name. Untagged fields with the same snake_case + identifier in the same class are invalid. A child field that hides an + inherited field with the same Java field name keeps only the nearest field in + xlang TypeDef metadata because the inherited field has no distinct untagged + identifier. + +Generated serializers may keep separate internal descriptor groups for +primitive, collection, map, built-in, and user-defined serializers so they can +emit specialized fast paths. Those internal groups are an implementation detail +and must not change wire field order. + +## ClassDef Encoding + +Compatible mode and meta sharing encode Java class definitions as TypeDef +records. A TypeDef has an 8-byte header followed by class metadata bytes: + +```text +| 8-byte header | [varuint32 extra_size] | class metadata bytes | +``` + +Header bits: + +```text +| 52-bit hash | 3 reserved bits | 1 compress bit | 8 size bits | +``` + +- `size`: the lower 8 bits. If the value is `0xff`, read `extra_size` as + `varuint32` and add it to `0xff`. +- `compress`: set when class metadata bytes are compressed by the configured + meta compressor. +- `reserved`: must be zero. +- `hash`: 52 bits derived from MurmurHash3 x64_128 seed 47 over + `class_metadata_bytes || header_low12_le`. `header_low12_le` is the low 12 + header bits encoded as two little-endian bytes with the upper four bits of the + second byte clear. Take lane 0 of the MurmurHash3 result, left-shift it by 12 + with signed 64-bit wraparound, apply signed absolute value, and mask with + `0xfffffffffffff000`. + +### Class Metadata Body + +```text +| root_kind_and_layer_count | class_layer_0 | class_layer_1 | ... | + +class_layer: +| varuint32 class_header | [registered type IDs or names] | field_info... | +``` + +`root_kind_and_layer_count` stores the root TypeDef kind in the high four bits +and `(num_layers - 1)` in the low four bits. If the low four bits are `0b1111`, +read an extra `varuint32` and add it to `15`. + +Root kind codes: + +| Code | Kind | +| ----- | -------------------------------------------- | +| 0 | `STRUCT` | +| 1 | `COMPATIBLE_STRUCT` | +| 2 | `NAMED_STRUCT` | +| 3 | `NAMED_COMPATIBLE_STRUCT` | +| 4 | `ENUM` | +| 5 | `NAMED_ENUM` | +| 6 | `EXT` | +| 7 | `NAMED_EXT` | +| 8 | `TYPED_UNION` | +| 9 | `NAMED_UNION` | +| 10-14 | Reserved | +| 15 | Extended-kind escape, rejected until defined | + +`class_header = (num_fields << 1) | registered_flag`. + +- If `registered_flag == 1`, write the class type ID as one byte. For + user-registered `ENUM`, `STRUCT`, `COMPATIBLE_STRUCT`, `EXT`, and + `TYPED_UNION`, write the user type ID as `varuint32`. +- If `registered_flag == 0`, write namespace and type name as meta strings. + +Class layers are encoded from parent to leaf. Field lists inside each layer use +the field order defined above. + +Readers may reject a received TypeDef that exceeds runtime resource limits such +as maximum metadata body bytes or maximum fields in one TypeDef. These limits +are receive-side resource controls and do not change the TypeDef wire encoding, +type identity, dynamic class loading, unknown-class handling, registration +policy, or schema-evolution semantics. + +### Field Info + +Each field is encoded as: + +```text +| field_header | [extended_name_or_id_size] | [field name bytes] | field_type | +``` + +`field_header` bits: + +| Bits | Meaning | +| ---- | ------------------------------------------------ | +| 0 | `trackingRef` | +| 1 | `nullable` | +| 2..3 | field name encoding | +| 4..6 | encoded name length minus one, or compact tag ID | +| 7 | reserved, must be zero | + +Field name encodings: + +| Code | Encoding | +| ---- | ------------------------------------ | +| 0 | UTF-8 | +| 1 | all-to-lower special encoding | +| 2 | lower/upper/digit special encoding | +| 3 | tag ID; field name bytes are omitted | + +For name encodings, bits `4..6` store `encoded_length - 1` when it is less than +`7`. If the value is `7`, read an extra `varuint32` and add it to `7`. + +For tag ID encoding, bits `4..6` store the numeric field ID when it is less than +`7`. If the value is `7`, read an extra `varuint32` and add it to `7`. Field IDs +must be non-negative. Duplicate field IDs in one TypeDef are invalid. + +### Field Type + +Field types describe how compatible readers read or skip the field payload. +Top-level field types write only the type tag. Nested field types store +`nullable` and `trackingRef` in the low bits: + +```text +nested_field_type_header = (type_tag << 2) | (nullable << 1) | trackingRef +``` + +Type tags: + +| Tag | Field type | Payload | +| --- | --------------------------- | -------------------------------- | +| 0 | Object/dynamic | none | +| 1 | Map | key field type, value field type | +| 2 | Collection/List/Set | element field type | +| 3 | Java array | dimensions, component field type | +| 4 | Enum | none | +| 5+ | Registered or built-in type | `tag - 5` is the type ID | + +## Meta Strings + +Namespaces, type names, and field names use the meta string encodings defined +by the xlang specification. A meta string header stores the byte length and +encoding kind; extended lengths are written as `varuint32`. + +Package and namespace names use UTF-8, all-to-lower special encoding, or +lower/upper/digit special encoding. Type names use UTF-8, +lower/upper/digit special encoding, first-to-lower special encoding, or +all-to-lower special encoding. Field names use the field-info encoding table +above. + +## Primitive Values + +Primitive values are written without type metadata when the field serializer is +known statically: + +| Java type | Payload | +| --------- | --------------------------------------------------------------------------- | +| `boolean` | one byte: `0` or `1` | +| `byte` | one signed byte | +| `char` | two-byte UTF-16 code unit, little endian | +| `short` | two-byte signed integer, little endian | +| `int` | fixed int32 little endian, or ZigZag varint32 when configured | +| `long` | fixed int64 little endian, ZigZag varint64, or tagged int64 when configured | +| `float` | IEEE 754 binary32, little endian | +| `double` | IEEE 754 binary64, little endian | + +Boxed primitives use the same value payload after the selected null/reference +slot. + +## String Values + +Java strings are encoded as: + +```text +| varuint36_small7 header | bytes | + +header = (num_bytes << 2) | coder +``` + +`coder` values: + +| Value | Encoding | +| ----- | -------------------- | +| 0 | Latin-1 | +| 1 | UTF-16 little endian | +| 2 | UTF-8 | + +`num_bytes` is the byte length of the encoded payload. + +## Enum Values + +Enum value payload depends on configuration: + +- Ordinal mode writes the enum ordinal as `varuint32`. +- `@ForyEnumId` mode writes the configured non-negative enum tag as + `varuint32`. +- Name mode writes the enum constant name as a meta string. + +`@ForyEnumId` may be declared on enum constants, on one integer field, or on one +zero-argument integer getter, according to the Java API contract. Duplicate or +negative enum tags are invalid. + +## Arrays + +### Primitive Arrays + +Primitive arrays write a length prefix and contiguous little-endian element +payload: + +```text +| varuint32 byte_length | raw element bytes | +``` + +Compressed `int[]` and `long[]` arrays use element count followed by compressed +elements: + +```text +int[] compressed: +| varuint32 length | varint32... | + +long[] compressed: +| varuint32 length | varint64 or tagged_int64... | +``` + +`byte[]` is the binary serializer and writes `varuint32 length` followed by raw +bytes. + +### Object Arrays + +Object arrays write the array length and an element type mode: + +```text +| varuint32_small7 (length << 1 | monomorphic_flag) | +| [shared element class metadata] | +| element slots... | +``` + +- If `monomorphic_flag == 1`, all non-null elements use the same element + serializer. The shared element class metadata is written once. +- If `monomorphic_flag == 0`, each non-null element writes its own type + metadata. + +Each nullable or reference-tracked element is still represented by a reference +slot before its element payload. + +## Collections + +Java collection serializers write collection size, element flags, optional +shared element type metadata, and element payloads: + +```text +| varuint32_small7 size | elements_header | [element type metadata] | elements... | +``` + +`elements_header` bits: + +| Bit | Meaning | +| --- | ------------------------------------- | +| 0 | Element reference tracking is enabled | +| 1 | At least one element may be null | +| 2 | Declared element type is used | +| 3 | All non-null elements share one type | + +When all non-null elements share a type and the declared element type is not +used, the shared element type metadata is written once before element payloads. +Otherwise each non-null element writes its own type metadata. Null and reference +flags follow the reference-slot rules. + +### Collection Subclasses + +Specialized serializers for supported JDK collection subclasses write +subclass-owned field layers before the element payload: + +```text +| varuint32_small7 size | +| [comparator reference for sorted/priority collections] | +| varuint32_small7 num_class_layers | +| class_layer_fields... | +| elements_header | [element type metadata] | elements... | +``` + +`num_class_layers` is the exact number of subclass field layers encoded in the +payload. Readers must reject a payload whose layer count does not match the +local serializer because the value payload does not carry enough layer identity +to skip a mismatched subclass layout. + +## Maps + +Maps write entry count followed by one or more chunks. Each chunk groups entries +with compatible key and value metadata: + +```text +| varuint32_small7 size | chunk... | +``` + +Non-null chunks: + +```text +| header | uint8 chunk_size | [key type metadata] | [value type metadata] | entries... | +``` + +`chunk_size` is in `1..255`. + +`header` bits: + +| Bit | Meaning | +| --- | ----------------------------------- | +| 0 | Key reference tracking is enabled | +| 1 | Chunk may contain null keys | +| 2 | Declared key type is used | +| 3 | Value reference tracking is enabled | +| 4 | Chunk may contain null values | +| 5 | Declared value type is used | + +Null key or null value entries are encoded as single-entry special chunks +without a `chunk_size` byte: + +- null key and non-null value: special null-key header, then value payload. +- non-null key and null value: special null-value header, then key payload. +- null key and null value: `KV_NULL` header only. + +`EnumMap` writes its entry count, key enum class metadata, and then its normal +map entry payload: + +```text +| varuint32_small7 size | key enum class metadata | chunk... | +``` + +The key enum class metadata is present even when `size` is zero so an empty +`EnumMap` can be reconstructed with the original key type. + +### Map Subclasses + +Specialized serializers for supported JDK map subclasses write subclass-owned +field layers before entry chunks: + +```text +| varuint32_small7 size | +| [comparator reference for sorted maps] | +| varuint32_small7 num_class_layers | +| class_layer_fields... | +| chunk... | +``` + +Readers must reject mismatched `num_class_layers` for the same reason as +collection subclasses. + +## JDK Wrappers and Views + +Java native mode has serializers for selected JDK wrappers and views: + +- Unmodifiable and synchronized collection/map wrappers keep the wrapper type + metadata and write the wrapped source collection or map as a normal object + payload. +- Recognized sublist views keep the sublist type metadata and write one + serializer-owned mode byte. Mode `0` writes visible elements as a collection + payload. Mode `1` writes view offset, size, and source list reference. +- `Collections.newSetFromMap` writes the backing map payload. +- Immutable JDK collection serializers keep list, set, or map payload + semantics and materialize an equivalent immutable or unmodifiable container + on read. + +Android and JVM implementations may choose different concrete public backing +types for wrapper payloads, but the serializer-owned payload modes above define +the wire shape. + +## Struct and Object Payloads + +Struct-like object payloads contain field values in protocol field order. The +selected serializer owns the exact field fast path: + +```text +| field_0 payload | field_1 payload | ... | +``` + +For each field, field metadata decides whether the field writes a primitive +payload directly, a nullable slot, a reference-tracked slot, type metadata, or a +specialized collection/map/array payload. + +Compatible-mode readers use the remote ClassDef field list to map fields by +identifier. Unknown fields are skipped using their remote field type metadata. + +Generated serializers may split large generated methods and hoist serializers, +field offsets, collection metadata, or map metadata. Those generated-code +decisions must preserve the same object payload order. + +## Throwable Payloads + +`Throwable` serializers preserve standard Java throwable state and +subclass-owned fields: + +```text +| stack_trace_ref | cause_ref | message_ref | +| varuint32 suppressed_count | suppressed_ref... | +| varuint32 extra_field_count | extra_field_name/value... | +| varuint32_small7 num_class_layers | +| class_layer_fields... | +``` + +`extra_field_count` is reserved for serializer-owned extension fields and is +currently written as zero. `num_class_layers` must match the local throwable +serializer layout on read. + +## Replacement and Java Serialization Hooks + +Java native mode supports serializer-owned handling for Java object replacement +and Java serialization hooks: + +- `writeReplace`/`readResolve` values use replacement metadata and payloads + owned by the replacement serializer. +- JDK proxy and lambda replacements use their registered native type IDs. +- Types that require Java Object Serialization compatibility may be delegated to + serializers that reproduce the required Java semantics inside a Fory object + slot. + +These serializers still obey the stream header, reference slot, and type +metadata rules in this document. + +## Unknown Classes + +When meta sharing is enabled and a reader does not have a local class for a +remote ClassDef, Java may materialize an unknown-class value using +`NONEXISTENT_META_SHARED_ID`. The value stores enough field data to preserve and +copy the unknown value according to the unknown-class serializer. It does not +make the unknown Java class available to user code. + +## Out-of-Band Buffers + +When the header out-of-band bit is set, serializers may write references to +external buffers instead of writing all bytes inline. The callback defines the +external buffer transport. The main stream remains a valid Fory stream +containing references to those buffers at serializer-owned payload positions. diff --git a/versioned_docs/version-1.3.0/specification/row_format_spec.md b/versioned_docs/version-1.3.0/specification/row_format_spec.md new file mode 100644 index 00000000000..de1e8b50dae --- /dev/null +++ b/versioned_docs/version-1.3.0/specification/row_format_spec.md @@ -0,0 +1,540 @@ +--- +title: Row Format +sidebar_position: 2 +id: row_format_spec +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. +--- + +## Overview + +Apache Fory Row Format is a cache-friendly, random-access binary format designed for high-performance data processing. Unlike traditional serialization formats that require full deserialization, the row format enables: + +- **Random Field Access**: Read individual fields without deserializing the entire row +- **Zero-Copy Operations**: Direct memory access without data transformation +- **Cache-Friendly Layout**: Optimized memory layout for CPU cache efficiency +- **Cross-Language Support**: Consistent binary format across Java, C++, and Python + +Fory provides two row format variants: + +| Format | Languages | Use Case | +| --------------- | ----------------- | ------------------------------ | +| Standard Format | Java, C++, Python | Cross-language compatibility | +| Compact Format | Java only | Space efficiency, smaller rows | + +## Format Comparison + +| Feature | Standard Format | Compact Format | +| -------------------- | ----------------------------- | ----------------------------------- | +| Field Slot Size | Fixed 8 bytes | Natural width (1, 2, 4, or 8 bytes) | +| Null Bitmap Size | 8-byte aligned | Byte-aligned, can borrow padding | +| Null Bitmap Position | Before field slots | After field slots (at end) | +| Fixed-Size Structs | Variable region (offset+size) | Inline in fixed region | +| Field Ordering | Schema-defined order | Sorted by alignment | +| All Non-Nullable | Bitmap still present | Bitmap skipped entirely | +| Alignment | Strict 8-byte | Relaxed (2, 4, or 8-byte) | + +--- + +## Standard Row Format + +The standard format prioritizes cross-language compatibility and simplicity with uniform 8-byte field slots. + +### Design Principles + +1. **8-Byte Alignment**: All major structures are aligned to 8-byte boundaries for optimal memory access +2. **Fixed-Width Field Slots**: Every field uses an 8-byte slot for uniform offset calculation +3. **Null Bitmap**: Compact null tracking using bit vectors +4. **Relative Offsets**: Variable-length data uses relative offsets for sub-buffer navigation + +### Row Binary Layout + +A row stores structured data with the following layout: + +``` ++----------------+------------------+------------------+-----+------------------+------------------+ +| Null Bitmap | Field 0 Slot | Field 1 Slot | ... | Field N-1 Slot | Variable Data | ++----------------+------------------+------------------+-----+------------------+------------------+ +| B bytes | 8 bytes | 8 bytes | | 8 bytes | Variable size | +``` + +#### Null Bitmap + +The null bitmap tracks which fields contain null values: + +- **Size**: `((num_fields + 63) / 64) * 8` bytes (rounded up to nearest 8-byte word) +- **Encoding**: Each bit corresponds to a field index + - Bit value `1` = field is null + - Bit value `0` = field is not null +- **Bit Order**: Bit 0 of the first byte corresponds to field 0 + +**Example**: For 10 fields, bitmap size = `((10 + 63) / 64) * 8 = 8` bytes + +#### Field Slots + +Each field occupies a fixed 8-byte slot regardless of its actual data type: + +- **Slot Offset**: `bitmap_size + field_index * 8` +- **Total Fixed Region**: `bitmap_size + num_fields * 8` bytes + +**Field Slot Contents by Type**: + +| Type Category | Slot Contents | +| -------------- | ----------------------------------- | +| Fixed-width | Value stored directly (zero-padded) | +| Variable-width | Offset + Size encoding (see below) | + +#### Variable-Width Data Encoding + +Variable-length fields (strings, arrays, maps, nested structs) store an offset-size pair in their slot: + +``` ++---------------------------+---------------------------+ +| Relative Offset | Size | +| (32 bits) | (32 bits) | ++---------------------------+---------------------------+ +|<-------------- 64-bit field slot value -------------->| +``` + +- **Relative Offset** (upper 32 bits): Offset from the row's base address +- **Size** (lower 32 bits): Size of the variable-width data in bytes + +**Encoding**: + +``` +offset_and_size = (relative_offset << 32) | size +``` + +**Decoding**: + +``` +relative_offset = (offset_and_size >> 32) & 0xFFFFFFFF +size = offset_and_size & 0xFFFFFFFF +``` + +#### Variable Data Region + +Variable-length data is stored after the fixed region: + +- Data is written sequentially as fields are set +- Each variable-length value is padded to 8-byte alignment +- Padding bytes are zeroed for deterministic output + +### Array Binary Layout + +Arrays store homogeneous sequences of elements: + +``` ++------------------+------------------+------------------+ +| Element Count | Null Bitmap | Element Data | ++------------------+------------------+------------------+ +| 8 bytes | B bytes | Variable size | +``` + +#### Array Header + +| Field | Size | Description | +| ------------- | ------------------------------- | --------------------------- | +| Element Count | 8 bytes | Number of elements (uint64) | +| Null Bitmap | `((count + 63) / 64) * 8` bytes | Per-element null flags | + +**Header Size**: `8 + ((num_elements + 63) / 64) * 8` bytes + +#### Array Element Data + +Elements are stored contiguously after the header: + +- **Fixed-width elements**: Stored with their natural width (1, 2, 4, or 8 bytes) +- **Variable-width elements**: Stored as 8-byte offset+size pairs + +**Element Offset**: `header_size + element_index * element_size` + +**Data Region Size**: Rounded up to nearest 8-byte boundary + +#### Array Element Sizes + +| Element Type | Element Size | +| ---------------- | --------------------- | +| bool | 1 byte | +| int8 | 1 byte | +| int16 | 2 bytes | +| int32 | 4 bytes | +| int64 | 8 bytes | +| float32 | 4 bytes | +| float64 | 8 bytes | +| string/binary | 8 bytes (offset+size) | +| array/map/struct | 8 bytes (offset+size) | + +### Map Binary Layout + +Maps store key-value pairs as two separate arrays: + +``` ++------------------+------------------+------------------+ +| Keys Array Size | Keys Array | Values Array | ++------------------+------------------+------------------+ +| 8 bytes | Variable size | Variable size | +``` + +#### Map Structure + +| Field | Size | Description | +| --------------- | -------- | --------------------------------- | +| Keys Array Size | 8 bytes | Total size of keys array in bytes | +| Keys Array | Variable | Full array structure for keys | +| Values Array | Variable | Full array structure for values | + +**Keys Array Offset**: `base_offset + 8` +**Values Array Offset**: `base_offset + 8 + keys_array_size` + +Both keys and values arrays follow the standard array binary layout. + +### Nested Struct Layout + +Nested structs are stored as complete row structures within the variable data region: + +1. Parent field slot contains offset+size pointing to nested row +2. Nested row has its own null bitmap and field slots +3. Supports arbitrary nesting depth + +``` +Parent Row: ++----------------+------------------+------------------+ +| Null Bitmap | ... Slots ... | Nested Row Data | ++----------------+------------------+------------------+ + | ^ + | offset+size | + +------------------->+ + +Nested Row: ++----------------+------------------+------------------+ +| Null Bitmap | Field Slots | Variable Data | ++----------------+------------------+------------------+ +``` + +--- + +## Compact Row Format (Java Only) + +The compact format provides space-efficient encoding with additional optimizations. It is currently implemented in Java only. + +> **Note**: The compact format is still under development and may not be stable yet. + +### Design Principles + +1. **Natural Width Storage**: Fixed-size fields use their natural byte width instead of 8 bytes +2. **Alignment-Based Field Sorting**: Fields are sorted by alignment requirements to minimize padding +3. **Conditional Null Bitmap**: Null bitmap is omitted when all fields are non-nullable +4. **Inline Fixed-Size Structs**: Nested structs with all fixed-size fields are stored inline + +### Compact Row Binary Layout + +``` ++------------------+------------------+-----+------------------+----------------+------------------+ +| Field 0 Value | Field 1 Value | ... | Field N-1 Value | Null Bitmap | Variable Data | ++------------------+------------------+-----+------------------+----------------+------------------+ +| W0 bytes | W1 bytes | | WN-1 bytes | B bytes (opt) | Variable size | +``` + +#### Key Differences from Standard Format + +1. **Field Slot Sizes**: Each field uses its natural width (Wi = type width or 8 for variable) +2. **Null Bitmap Position**: Placed after field slots, can borrow alignment padding +3. **Field Order**: Fields are sorted by alignment (8-byte → 4-byte → 2-byte → 1-byte → variable) +4. **Conditional Bitmap**: Skipped entirely if all fields are non-nullable + +#### Null Bitmap (Compact) + +- **Size**: `(num_nullable_fields + 7) / 8` bytes (byte-aligned, not 8-byte aligned) +- **Skipped**: When all fields are primitive/non-nullable +- **Position**: After all fixed-size field slots, can use alignment padding space + +#### Field Sorting Algorithm + +Fields are sorted to minimize padding and optimize alignment: + +``` +Priority order (highest to lowest): +1. Fields with 8-byte alignment (int64, float64, variable-width) +2. Fields with 4-byte alignment (int32, float32) +3. Fields with 2-byte alignment (int16) +4. Fields with 1-byte alignment (int8, bool) +``` + +Within each alignment group, larger fields come first. + +#### Fixed-Size Struct Inlining + +Nested structs with all fixed-size fields are stored inline in the parent row: + +**Standard Format** (nested struct with 2 int32 fields): + +``` +Parent slot: [offset (4 bytes) | size (4 bytes)] → Points to nested row (8+ bytes elsewhere) +``` + +**Compact Format** (same nested struct): + +``` +Parent slot: [int32 field 0 | int32 field 1] → 8 bytes total, inline +``` + +This eliminates the offset+size indirection for fixed-size nested structures. + +#### Fixed-Width Calculation + +A field's fixed width is determined recursively: + +- **Primitive types**: Natural byte width (1, 2, 4, or 8) +- **Struct types**: Sum of all child fixed widths (if all children are fixed-width) +- **Variable types** (string, array, map): Returns -1 (uses 8-byte offset+size slot) + +``` +fixed_width(field) = + if primitive: type_width + if struct and all_children_fixed: header_bytes + sum(fixed_width(child) for each child) + else: -1 (variable, uses 8-byte slot) +``` + +### Compact Array Binary Layout + +``` ++------------------+------------------+------------------+ +| Element Count | Null Bitmap | Element Data | ++------------------+------------------+------------------+ +| 4 bytes | B bytes (opt) | Variable size | +``` + +#### Compact Array Header + +| Field | Size | Description | +| ------------- | ----------------------------- | -------------------------- | +| Element Count | 4 bytes | Number of elements (int32) | +| Null Bitmap | `(count + 7) / 8` bytes (opt) | Per-element null flags | + +**Header Size Calculation**: + +``` +header_size = 4 + (element_nullable ? (num_elements + 7) / 8 : 0) + +// Round to 8-byte boundary only if element width is 8-byte aligned +if (fixed_width % 8 == 0): + header_size = round_to_8_bytes(header_size) +``` + +#### Key Differences from Standard Array + +1. **Element Count**: 4 bytes instead of 8 bytes +2. **Null Bitmap**: Byte-aligned, skipped if elements are non-nullable +3. **Fixed-Size Structs**: Inline storage for fixed-width struct elements + +--- + +## Common Specifications + +The following specifications apply to both standard and compact formats. + +### Type Encoding + +#### Primitive Types + +| Type | Width | Encoding | +| ------- | ------- | ------------------------------- | +| bool | 1 byte | `0x00` (false) or `0x01` (true) | +| int8 | 1 byte | Two's complement | +| int16 | 2 bytes | Two's complement, little-endian | +| int32 | 4 bytes | Two's complement, little-endian | +| int64 | 8 bytes | Two's complement, little-endian | +| float32 | 4 bytes | IEEE 754 single precision | +| float64 | 8 bytes | IEEE 754 double precision | + +#### Temporal Types + +| Type | Width | Encoding | +| --------- | ------- | ------------------------------------- | +| timestamp | 8 bytes | Microseconds since Unix epoch (int64) | +| date32 | 4 bytes | Days since Unix epoch (int32) | +| duration | 8 bytes | Duration in microseconds (int64) | + +#### String and Binary + +- **Encoding**: UTF-8 for strings, raw bytes for binary +- **Storage**: Offset+size pair in field slot, data in variable region +- **Padding**: Data padded to 8-byte alignment (standard) or natural alignment (compact) + +### Null Handling + +#### Row Null Handling + +- Null fields have their corresponding bit set to 1 in the null bitmap +- Field slot contents are undefined for null fields (standard) or zeroed (compact) +- Reading a null field returns a null/empty value indicator + +#### Array Null Handling + +- Null elements have their corresponding bit set to 1 in the array's null bitmap +- Element data is undefined for null elements +- Compact format: Bitmap skipped if elements are non-nullable + +#### Variable-Width Null Semantics + +When reading variable-width data from a null field: + +- Returns size of -1 or equivalent null indicator +- No data access is performed + +### Alignment and Padding + +#### Standard Format Alignment + +1. **Null Bitmap**: Size rounded to 8-byte boundary +2. **Field Slots**: Always 8 bytes each +3. **Variable Data**: Each value padded to 8-byte boundary +4. **Array Data**: Total data region padded to 8-byte boundary + +#### Compact Format Alignment + +1. **Field Slots**: Natural width (1, 2, 4, or 8 bytes) +2. **Null Bitmap**: Byte-aligned, placed after fields +3. **Variable Data**: Padded to 8-byte boundary only when needed +4. **Header**: May use relaxed alignment for smaller overhead + +#### Padding Bytes + +- All padding bytes must be set to zero +- Ensures deterministic serialization output +- Prevents information leakage from uninitialized memory + +## Size Calculations + +### Standard Row Size + +``` +row_size = bitmap_size + num_fields * 8 + variable_data_size + +where: + bitmap_size = ((num_fields + 63) / 64) * 8 + variable_data_size = sum of (padded_size for each variable field) + padded_size = ((size + 7) / 8) * 8 +``` + +### Compact Row Size + +``` +row_size = fixed_region_size + bitmap_size + variable_data_size + +where: + fixed_region_size = sum of (fixed_width(field) or 8 for each field) + bitmap_size = all_non_nullable ? 0 : (num_nullable_fields + 7) / 8 + // May be rounded to 8-byte boundary if has variable fields +``` + +### Standard Array Size + +``` +array_size = header_size + data_size + +where: + header_size = 8 + ((num_elements + 63) / 64) * 8 + data_size = ((num_elements * element_size + 7) / 8) * 8 +``` + +### Compact Array Size + +``` +array_size = header_size + data_size + +where: + header_size = 4 + (element_nullable ? (num_elements + 7) / 8 : 0) + // header_size rounded to 8 if element_width % 8 == 0 + data_size = num_elements * element_width +``` + +### Map Size + +``` +map_size = 8 + keys_array_size + values_array_size +``` + +## Summary Tables + +### Layout Summary + +| Component | Standard Format | Compact Format | +| ---------------- | ------------------------------- | ------------------------------------- | +| Row Header | `((N + 63) / 64) * 8` bytes | 0 or `(N + 7) / 8` bytes (at end) | +| Row Field Slots | `N * 8` bytes | `sum(field_widths)` bytes | +| Array Header | `8 + ((E + 63) / 64) * 8` bytes | `4 + (E + 7) / 8` bytes (if nullable) | +| Array Elements | `E * element_size` (8-aligned) | `E * element_width` | +| Map Header | 8 bytes | 8 bytes | +| Offset+Size Pair | 8 bytes (32-bit offset + size) | 8 bytes (same) | + +Where N = number of fields, E = number of elements + +### Type Width Summary + +| Category | Storage Width | Standard Slot | Compact Slot | +| ------------- | ------------- | ------------- | ------------ | +| bool | 1 byte | 8 bytes | 1 byte | +| int8 | 1 byte | 8 bytes | 1 byte | +| int16 | 2 bytes | 8 bytes | 2 bytes | +| int32 | 4 bytes | 8 bytes | 4 bytes | +| int64 | 8 bytes | 8 bytes | 8 bytes | +| float32 | 4 bytes | 8 bytes | 4 bytes | +| float64 | 8 bytes | 8 bytes | 8 bytes | +| string/binary | Variable | 8 bytes | 8 bytes | +| array | Variable | 8 bytes | 8 bytes | +| map | Variable | 8 bytes | 8 bytes | +| struct | Variable | 8 bytes | inline or 8 | + +## Implementation Notes + +### Endianness + +- All multi-byte integers are stored in **little-endian** format +- Floating-point values use native IEEE 754 representation + +### Memory Safety + +- Writers must zero padding bytes to prevent information leakage +- Readers must validate offsets and sizes before accessing data +- Buffer bounds checking is required for untrusted input + +### Performance Considerations + +**Standard Format**: + +- Fixed 8-byte slots enable O(1) field access with simple arithmetic +- 8-byte alignment optimizes CPU cache line usage +- Best for cross-language interoperability + +**Compact Format**: + +- Smaller row sizes reduce memory bandwidth +- Field sorting minimizes padding waste +- Inline structs eliminate pointer chasing +- Relaxed alignment may have slight CPU overhead on some architectures + +### When to Use Each Format + +| Scenario | Recommended Format | +| -------------------------------- | ------------------ | +| Cross-language data exchange | Standard | +| Java-only, memory-constrained | Compact | +| Many small primitive fields | Compact | +| Many nested fixed-size structs | Compact | +| Maximum read performance | Standard | +| Interoperability with C++/Python | Standard | diff --git a/versioned_docs/version-1.3.0/specification/xlang_implementation_guide.md b/versioned_docs/version-1.3.0/specification/xlang_implementation_guide.md new file mode 100644 index 00000000000..d8fb2050123 --- /dev/null +++ b/versioned_docs/version-1.3.0/specification/xlang_implementation_guide.md @@ -0,0 +1,674 @@ +--- +title: Xlang Implementation Guide +sidebar_position: 10 +id: xlang_implementation_guide +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. +--- + +## Overview + +This guide describes the current xlang implementation ownership model used by +the xlang runtimes. + +The wire format is defined by +[Xlang Serialization Spec](xlang_serialization_spec.md). This document is about +service boundaries, operation flow, and internal ownership. New language implementations do not +need the same class names, but they should preserve the same control flow: + +- root operations stay on the `Fory` facade +- nested payload work stays on explicit read and write contexts +- type metadata stays in the type resolver layer +- serializers stay payload-focused + +When this guide conflicts with the wire-format specification, follow +`docs/specification/xlang_serialization_spec.md`. When it conflicts with a +language-specific implementation detail, follow the current implementation code for +that language. + +## Source Of Truth + +Use these sources in this order: + +1. `docs/specification/xlang_serialization_spec.md` +2. the current implementation for the language +3. cross-language tests under `integration_tests/` + +For Dart, the implementation shape is centered on: + +- `Fory` +- `WriteContext` +- `ReadContext` +- `RefWriter` +- `RefReader` +- `TypeResolver` +- `StructSerializer` + +## Implementation Ownership Model + +### `Fory` is the root-operation facade + +`Fory` owns the reusable services for one Fory instance. + +In Dart, `Fory` owns exactly four reusable members: + +- `Buffer` +- `WriteContext` +- `ReadContext` +- `TypeResolver` + +In Java, `Fory` also owns instance-local services such as `JITContext` and +`CopyContext`, but the ownership rule is the same: `Fory` is the root facade, +not the place where nested serializers do their work. + +`Fory` is responsible for: + +- preparing the shared buffer for root operations +- writing and reading the root xlang header bitmap +- delegating nested value encoding to `WriteContext` +- delegating nested value decoding to `ReadContext` +- owning registration through `TypeResolver` +- resetting operation-local context state in a top-level `finally` + +Nested serializers must not call back into root `serialize(...)` or +`deserialize(...)` entry points. + +### `WriteContext` and `ReadContext` hold operation-local state + +`WriteContext` and `ReadContext` are prepared by `Fory` for one root operation +and reset by `Fory` in a `finally` block before reuse. + +`prepare(...)` should only bind the active buffer and root-operation inputs. +`reset()` should clear operation-local mutable state. + +That operation-local state includes: + +- the current buffer +- the active `RefWriter` or `RefReader` +- meta-string state +- shared type-definition state +- operation-local scratch state keyed by identity +- logical object-graph depth + +Generated and hand-written serializers should treat these contexts as the only +source of operation-local services. Serializers must not keep ambient instance +state in thread locals, globals, or serializer instance fields. + +### `WriteContext` + +`WriteContext` owns all write-side per-operation state: + +- current `Buffer` +- `RefWriter` +- `MetaStringWriter` +- shared TypeDef write state +- root `trackRef` mode +- recursion depth and limits + +It exposes one-shot primitive helpers such as: + +- `writeBool` +- `writeInt32` +- `writeVarUInt32` + +These helpers are convenience methods. Serializers that perform repeated +primitive IO should cache `final buffer = context.buffer;` and call buffer +methods directly. + +### `ReadContext` + +`ReadContext` owns all read-side per-operation state: + +- current `Buffer` +- `RefReader` +- `MetaStringReader` +- shared TypeDef read state +- recursion depth and limits + +It exposes matching one-shot primitive helpers such as: + +- `readBool` +- `readInt32` +- `readVarUInt32` + +Generated struct serializers call `context.reference(value)` immediately after +constructing the target instance so back-references can resolve to that object. + +## Reference Tracking + +Reference handling is split behind two explicit services: + +- `RefWriter` writes null, ref, and new-value markers and remembers previously + written objects by identity. +- `RefReader` decodes those markers, reserves read reference IDs, and resolves + previously materialized objects. + +The xlang ref markers are: + +- `NULL_FLAG (-3)` +- `REF_FLAG (-2)` +- `NOT_NULL_VALUE_FLAG (-1)` +- `REF_VALUE_FLAG (0)` + +Key behavior: + +- basic values never use ref tracking +- field metadata controls ref behavior inside generated structs +- root `trackRef` is only for top-level graphs and container roots with no + field metadata +- serializers that allocate an object before all nested reads complete must bind + that object early with `context.reference(...)` + +## Type Resolution + +`TypeResolver` owns: + +- built-in type resolution +- registration by numeric id or by `namespace + typeName` +- serializer lookup +- struct metadata lookup +- type metadata encoding and decoding +- canonical encoded meta strings for package names, type names, and field names +- encoded-name lookup for named type resolution +- wire type decisions for struct, compatible struct, enum, ext, and union forms + +In Java xlang mode the concrete implementation is `XtypeResolver`. In Dart the +same ownership stays behind the internal `TypeResolver`. + +Serializers do not resolve class metadata themselves. They ask the current +context to read or write nested values, and the context delegates type work to +`TypeResolver`. + +## Root Frame Responsibilities + +Every root payload starts with a one-byte bitmap written and read by `Fory` +itself, not by serializers. + +Current xlang root bits: + +| Bit | Meaning | +| --- | -------------------------- | +| `0` | null root payload | +| `1` | xlang payload | +| `2` | out-of-band buffers in use | + +Keep the root bitmap separate from per-object ref markers: + +- the root bitmap describes the whole payload +- ref flags describe one nested value at a time + +## Serialization Flow + +### Root write path + +The current root write flow is: + +1. `Fory.serialize(...)` or `serializeTo(...)` prepares the target buffer. +2. `Fory` calls `writeContext.prepare(...)`. +3. `Fory` writes the root bitmap. +4. `Fory` delegates the root object to `WriteContext`. +5. `writeContext.reset()` runs in `finally`. + +For a non-null root value, `WriteContext.writeRootValue(...)` performs: + +1. ref/null framing +2. type metadata write +3. payload write + +Payload serializers are responsible only for the payload of their type. They do +not write the root bitmap and they do not own registration or type-header +encoding. + +### Nested writes use `WriteContext` + +Important rules: + +- nested serializers must use `WriteContext` helpers such as `writeRef(...)`, + `writeNonRef(...)`, and container helpers when they need ref handling or type + metadata +- repeated primitive writes should go directly through the buffer +- nested serializer flow should stay straight-line; do not add internal + `try/finally` blocks just to clean per-operation state +- top-level `Fory.serialize(...)` owns the operation reset `finally` + +## Deserialization Flow + +### Root read path + +The current root read flow mirrors the write flow: + +1. `Fory.deserialize(...)` or `deserializeFrom(...)` reads the root bitmap. +2. null roots return immediately. +3. `Fory` validates xlang mode and other root framing requirements. +4. `Fory` calls `readContext.prepare(...)`. +5. `Fory` delegates to `ReadContext`. +6. `readContext.reset()` runs in `finally`. + +### `ReadContext` owns ref reservation and payload materialization + +`ReadContext.readRef()` performs the normal xlang read sequence: + +1. consume the next ref marker +2. return `null` or a back-reference immediately when appropriate +3. reserve a fresh read ref id for new reference-tracked values +4. read type metadata +5. read the payload +6. bind the reserved read ref id to the completed object + +Primitive and string-like hot paths should read directly from the buffer; +complex payloads delegate to the resolved serializer. + +### Stream And Buffer Byte Reads + +Implementations must keep byte availability in the byte owner layer while +keeping string, binary, primitive-array, compression, and collection semantics in +serializers. + +The required byte-owner primitive for allocation-before-read checks is a +readability check such as `checkReadableBytes(byteCount)`. Implementations do +not need additional generic read-context methods for this design. After the +readability check succeeds, serializers use their existing local buffer read, +copy, or decode paths. + +The readability check is a byte operation only. It must not decode strings, +primitive-array element counts, compression modes, or collection capacity +policy. + +For large byte-counted values, every implementation should call the byte-owner +readability check before allocating a variable-length result. This applies to +binary values, strings, decimal or metadata bodies, and primitive wire arrays +whose encoded body is measured in bytes. For multi-byte primitive wire arrays, +compare the encoded byte count, not only the logical element count, with the +readable bytes. + +1. Validate the encoded byte count in the serializer. For fixed-width primitive + arrays, check overflow and element alignment before allocation, such as + `wireByteCount % elementByteWidth == 0`, then derive the logical element + count from the encoded byte count. +2. Call `checkReadableBytes(wireByteCount)` unconditionally before allocating + the variable-length result. Buffer-backed inputs normally return from this + check with only a bounds comparison. Stream-backed inputs use the same call; + the byte owner handles the fast path when enough bytes are already buffered + and otherwise fills the read buffer until the requested encoded body is + readable or an input error is recorded. +3. After readability is proven, allocate the final value once and copy or decode + from the current readable buffer into the final result. + +`checkReadableBytes` is not an `ensureCapacity(wireByteCount)` operation. In +stream mode it may end with the byte owner holding the full encoded body in its +read buffer, but it must grow that buffer as bytes are successfully read from +the stream. It should grow from current proven buffer capacity, such as by +doubling current capacity, and cap only when that bounded growth step reaches +the immediate target. A byte owner may use an owner-local availability signal as +a one-shot growth hint when the stream implementation itself is caller-owned +trusted code; if that hint is absent or insufficient, it must fall back to +bounded growth from already buffered bytes. It must not reserve the +attacker-declared length before input bytes or an owner-local growth hint +justify that intermediate buffer capacity. The stream slow path may pay one +extra intermediate buffer copy; this is preferable to serializer-local chunk +accumulation and repeated final-container growth. + +For byte-counted values, the serializer should not duplicate the byte owner's +fast-path branch by testing `availableBytes()` before calling +`checkReadableBytes`. Keeping that branch in the byte owner gives every language +the same correctness rule and keeps serializer hot paths focused on their own +wire semantics. + +For primitive wire arrays: + +- Compare and prove the encoded wire byte count, not only the logical element + count. +- Keep compression, bit-packing, byte-order conversion, and other primitive + array encoding semantics in the serializer. `checkReadableBytes` only proves + that the encoded bytes are present. +- For compressed or transformed bodies, the serializer must still validate the + decoded length and encoding-specific metadata before allocating or returning + the final value. + +The common serializer shape is: + +```text +wireByteCount = readVarUint32() +elementWidth = primitiveWireElementWidth(kind) +validate wireByteCount and element alignment +elementCount = wireByteCount / elementWidth + +ctx.checkReadableBytes(wireByteCount) +result = allocatePrimitiveResult(elementCount) +copy or decode wireByteCount bytes from the current readable buffer into result +advance the reader index by wireByteCount +return result +``` + +Byte values are the `elementWidth == 1` specialization of the same policy. In +that case the serializer shape is: + +```text +byteCount = readVarUint32() + +ctx.checkReadableBytes(byteCount) +result = allocateBytes(byteCount) +copy byteCount bytes from the current readable buffer into result +advance the reader index by byteCount +return result +``` + +This policy avoids three inefficient implementation shapes: + +- allocating the complete final contiguous value before the encoded body is + readable +- growing or repeatedly copying the final result container on stream slow paths +- adding serializer-local chunk buffers when the byte owner can prove + readability once and expose a normal buffered read + +Scratch buffers remain appropriate when the target representation is not a +direct byte target, such as string transcoding, compression, byte-order +conversion that is not performed in place, bit-packed values, or runtimes whose +stream API cannot read into a caller-provided target. + +For fixed-width primitive arrays, the final result must not become visible to +callers until the exact encoded byte count has been read successfully. + +For list, set, map, and other container readers, the declared logical element +count is not an encoded byte count, so serializers must still own all element, +chunk, nullability, reference, and type-dispatch semantics. It is still the +right allocation proof for count-based preallocation: after validating a +non-empty count and reading any serializer-owned header or type metadata that +precedes allocation, call `checkReadableBytes(logicalCount)` before allocating, +reserving, or size-hinting from that count. The byte owner handles buffer versus +stream readiness; the container serializer then allocates with the declared +count and reads elements through its normal owner path. + +This check is not a full container-body validation. It only prevents a small or +truncated input from causing a large count-based preallocation. Chunk sizes, +duplicate keys, element value semantics, and protocol strictness remain owned by +the container/map serializer and should be validated only when they protect a +real owner invariant. + +For TypeDef or TypeMeta bodies, first prove that the encoded metadata body bytes +are readable through the byte owner. Field-list allocation should happen after +that body readability check and should not use a separate small initial-capacity +cap as a security rule. + +Implementations should also bound received metadata bodies and struct field +lists on the cold metadata parse path. `maxTypeMetaBytes` limits one encoded +TypeDef or TypeMeta body, excluding the 8-byte header and any extended-size +varint, and is checked before copying or decompressing that body. +`maxTypeFields` limits the number of fields declared by one received struct +metadata body and is checked before reserving or allocating the field list. +These limits are runtime resource controls; they do not change wire encoding, +type identity, dynamic loading, unknown-type behavior, deserialization policy, +or schema-evolution semantics. Metadata cache hits and generated field readers +remain hot paths and must not add work for these limits. + +Remote schema-version limits belong to the same cold metadata owner path. +Header cache hits must skip the remaining metadata body and return cached +metadata without schema-limit checks, hash revalidation, allocation, or policy +work. On a header miss, keep the handling in one concrete owner path: prove and +read the metadata body bytes, validate the body against its header, validate +field counts, resolve the type through the existing registration and +deserialization-policy checks, compare exact local metadata by original encoded +bytes when applicable, check schema-version limits for non-local remote +metadata, build the required read state, publish to the persistent metadata +cache, and then record the schema count. Failed or incompatible metadata must +not publish to the persistent cache and must not consume schema-version counts. + +Remote metadata whose encoded bytes exactly match the local registered metadata +may use the local metadata without consuming the remote schema-version limit, +after the existing type and deserialization-policy checks for selecting that +local type have run. This exact-local bypass is not struct-only; it also applies +to named enum, ext, and union metadata when those metadata bodies are present and +match the local encoded bytes. Pure id-based enum, ext, and typed-union values +do not carry TypeDef or TypeMeta bodies and must stay on the normal type-id plus +user-type-id path. Compatible named enum, ext, and union metadata normally has +one version, but it still counts against accepted remote metadata totals when it +is sent as shared metadata and does not exactly match local metadata. +`maxTypeFields` applies only to struct field lists. + +The exact-local candidate must be derived inside the metadata owner path from +the decoded metadata identity: `userTypeId` for id-registered metadata, or +`(namespace, typeName)` for name-registered metadata. Do not thread extra +expected-type parameters through read callers solely for this check. This rule +applies to every runtime. Java and Python may lazy-build the local encoded +metadata only after this identity lookup selects a local class and the existing +class, registration, and deserialization-policy checks for that class have run. + +When a statically declared compatible named enum, ext, or union field reads +shared metadata, the decoded metadata must match the declared type id, +namespace, and type name before the metadata owner publishes it to the +persistent cache or records a schema count. Already accepted header or reference +cache hits still skip the body and must not rerun body-hash, schema-limit, or +registration checks, but the field reader must not treat metadata for a +different declared named type as the current field's metadata. + +Skip paths do not need to materialize skipped values. Existing byte-skip +operations should consume any available buffered prefix first, then skip or drop +remaining stream bytes in bounded steps. + +### Nested reads use `ReadContext` + +Important rules: + +- serializers that allocate the result object early must call + `context.reference(obj)` before reading nested children that may refer back to + it +- nested serializer flow should stay straight-line; do not add internal + `try/finally` blocks just to restore operation-local state +- top-level `Fory.deserialize(...)` owns the operation reset `finally` + +## Depth Tracking + +`WriteContext` and `ReadContext` track logical object depth explicitly. +`increaseDepth()` enforces `Config.maxDepth`. + +Depth should stay explicit on the contexts rather than relying on the native +call stack alone. At the same time, depth cleanup should not depend on nested +`try/finally` blocks throughout serializer code. Top-level context reset must be +able to recover operation-local state after failures. + +## Struct Compatibility + +Struct-specific schema/version framing and compatible-field layout belong in the +struct serializer layer, not on `Fory` and not on the public serializer API. + +In Dart that internal owner is `StructSerializer`. + +`StructSerializer` is responsible for: + +- schema-hash framing when compatibility mode is off and version checks are on +- compatible-struct field remapping when compatibility mode is on +- caching compatible read layouts +- skipping unknown compatible fields +- passing compatible read layouts explicitly to generated serializers +- classifying matched compatible fields as exact direct reads, compatible + conversions, or remote-only skips before generated dispatch + +When `Config.compatible` is enabled and the struct is marked evolving: + +- the wire type uses the compatible struct form +- the writer emits shared TypeDef metadata +- reads map incoming fields by identifier and skip unknown fields +- generated serializers apply matched fields directly while preserving their own + object construction and default-value rules +- exact matched field schemas use the same direct read shape as same-schema + reads and must not receive remote compatible metadata +- matched scalar fields may use compatible scalar conversion only when the + layout has classified a remote/local top-level scalar pair as lossless + convertible and both field schemas have `trackingRef = false` +- compatible scalar conversion applies only to the immediate matched field. + Nested collection, array, map key, and map value schemas must not be accepted + by recursively applying scalar conversion to child schemas. +- direct top-level `list` to dense `array` matched fields must be + classified as compatible when element domains match; the nullable element + schema bit alone is not a schema-pair rejection. Actual null element payloads + fail in the dense-array reader. Ref-tracked list-element framing is separate + and may remain rejected when the runtime cannot materialize it without + generic/reference paths. + +When `compatible` is disabled and `checkStructVersion` is enabled: + +- the writer emits the schema hash for struct payloads +- the read side checks that hash before reading fields + +Compatible scalar conversion is owned by the compatible struct field reader or +the generated compatible layout action. Root facades, read/write contexts, type +resolvers, class resolvers, xlang type resolvers, and raw buffer utilities must +not expose public conversion APIs or carry conversion state. Resolvers may +provide field schema metadata for layout classification, but the conversion +decision and value adaptation stay with the serializer-owned compatible field +layout. Layout classification must reject top-level scalar conversions when +either matched schema has `trackingRef = true` and must reject same scalar type +pairs whose top-level `trackingRef` framing differs; converters must not add a +reference-table path for scalar mismatches. Recursive schema comparison inside +containers must reject scalar mismatches instead of reusing the top-level scalar +conversion matrix. Generated serializers should consume the classified layout +decision directly: + +- source-generated serializers use the layout's matched-field dispatch key to + select exact direct field code, compatible conversion code, or skip code +- regenerated serializers may instead compile a remote-schema-specific + straight-line reader after classification, without a second outer matched-id + switch, when the generated source still has pure direct, pure conversion, and + explicit skip operations +- compatible scalar conversion cases must read the concrete remote wire scalar + selected by classification and compose only the required lossless conversion; + they must not call a generic runtime converter that redispatches by remote and + local scalar type IDs, field descriptors, field names, or schema eligibility + helpers + +Same-schema readers with matching reference and null/optional framing must keep +direct scalar read paths without conversion branches or per-field conversion +objects. Same raw scalar types with different null/optional framing may still +use the compatible nullable/optional composition path when both fields are not +reference-tracked. + +## Meta Strings And Shared Type Metadata + +Two explicit pieces of state back xlang type metadata: + +- `MetaStringWriter` and `MetaStringReader` deduplicate and decode namespace + and type-name strings +- shared TypeDef write/read state tracks announced TypeDef metadata + +Ownership rules: + +- canonical encoded names live in `TypeResolver` +- per-operation dynamic meta-string ids live on `MetaStringWriter` and + `MetaStringReader` +- shared type-definition tables are operation-local context state + +## Enums In Xlang Mode + +In xlang mode, enums are serialized by numeric tag, not by name. + +In Java: + +- the default tag is the declaration ordinal +- `@ForyEnumId` can override that with a stable explicit tag +- `serializeEnumByName(true)` affects native Java mode, not xlang mode + +Other language implementations should preserve the same wire rule even if the configuration or +annotation surface differs. + +## Out-Of-Band Buffer Objects + +Buffer-object handling follows the same split: + +- one root bit advertises whether out-of-band buffers are in play +- nested buffer-object payloads still decide in-band vs out-of-band one value at + a time +- serializers use read/write context helpers rather than bypassing the context layer + +## Code Generation + +The normal Dart integration path is: + +1. annotate structs with `@ForyStruct` +2. annotate field overrides with `@ForyField` +3. run `build_runner` +4. call the generated per-library helper, such as + `Fory.register(...)`, to bind private generated metadata and + register generated types + +Generated code should emit: + +- private serializer classes +- private metadata constants +- a public per-library registration helper that users call from application code +- private generated installation helpers that keep serializer factories private + +The public helper should be a thin generated wrapper around the Fory +registration API, not a public global registry or a second unrelated +registration API family. + +## Directory Layout + +Under each Dart package `lib/` tree, only one nested source layer is allowed. + +Allowed: + +- `lib/fory.dart` +- `lib/src/.dart` +- `lib/src//.dart` + +Not allowed: + +- `lib/src///.dart` + +## Serializer Design Rules For New Implementations + +Any new xlang implementation should follow these rules even if its surface API looks +different: + +1. Keep root operations on the `Fory` facade and nested payload work on + explicit read and write contexts. +2. Keep reference tracking behind dedicated read-side and write-side services + so the disabled path stays cheap. +3. Make serializers payload-only. Type metadata, registration, and root + framing belong to the `Fory` and type resolver layers. +4. Track per-operation state explicitly. Do not rely on ambient thread-local + instance state. +5. Reserve read reference IDs before materializing new objects, and bind + partially built objects as soon as a nested child may refer back to them. +6. Keep operation setup and operation cleanup separate. `prepare(...)` binds + the current operation inputs, and `reset()` clears operation-local state. +7. Preserve the separation between the root bitmap, per-object ref flags, type + headers, and payload bytes. +8. Keep internal naming in the serialization domain. Prefer words like + `serializer`, `binding`, and `layout`; avoid RPC-style terms such as + `session` or vague control-flow terms such as `plan`. +9. After any xlang protocol or ownership change, run the cross-language test + matrix and update both this guide and + [Xlang Serialization Spec](xlang_serialization_spec.md). + +## Validation + +For Dart implementation changes, run at minimum: + +```bash +cd dart +dart run build_runner build --delete-conflicting-outputs +dart analyze +dart test +``` + +For generated consumer coverage, also run: + +```bash +cd dart/packages/fory-test +dart run build_runner build --delete-conflicting-outputs +dart test +``` diff --git a/versioned_docs/version-1.3.0/specification/xlang_serialization_spec.md b/versioned_docs/version-1.3.0/specification/xlang_serialization_spec.md new file mode 100644 index 00000000000..71220b773c6 --- /dev/null +++ b/versioned_docs/version-1.3.0/specification/xlang_serialization_spec.md @@ -0,0 +1,1870 @@ +--- +title: Xlang Serialization Format +sidebar_position: 0 +id: xlang_serialization_spec +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. +--- + +## Cross-language Serialization Specification + +Apache Fory™ xlang serialization enables automatic cross-language object serialization with support for shared references, circular references, and polymorphism. Unlike traditional serialization frameworks that require IDL definitions and schema compilation, Fory serializes objects directly without any intermediate steps. + +Key characteristics: + +- **Automatic**: No IDL definition, no schema compilation, no manual object-to-protocol conversion +- **Cross-language**: Same binary format works across Java, Python, C++, Go, + Rust, JavaScript/TypeScript, C#, Swift, Dart, Scala, and Kotlin +- **Reference-aware**: Handles shared references and circular references without duplication or infinite recursion +- **Polymorphic**: Supports object polymorphism with concrete type resolution + +This specification defines the Fory xlang binary format. The format is dynamic rather than static, which enables flexibility and ease of use at the cost of additional complexity in the wire format. + +## Type Systems + +### Data Types + +- bool: a boolean value (true or false). +- int8: a 8-bit signed integer. +- int16: a 16-bit signed integer. +- int32: a 32-bit signed integer. Scalar type expressions may use fixed or varint encoding. +- int64: a 64-bit signed integer. Scalar type expressions may use fixed, varint/PVL, or tagged encoding. +- uint8: an 8-bit unsigned integer. +- uint16: a 16-bit unsigned integer. +- uint32: a 32-bit unsigned integer. Scalar type expressions may use fixed or varint encoding. +- uint64: a 64-bit unsigned integer. Scalar type expressions may use fixed, varint/PVL, or tagged encoding. +- float8: an 8-bit floating point number. +- float16: a 16-bit floating point number. +- bfloat16: a 16-bit brain floating point number. +- float32: a 32-bit floating point number. +- float64: a 64-bit floating point number including NaN and Infinity. +- string: a text string encoded using Latin1/UTF16/UTF-8 encoding. +- enum: a data type consisting of a set of named values. Rust enum with non-predefined field values are not supported as + an enum. +- named_enum: an enum whose value will be serialized as the registered name. +- struct: a dynamic(final) type serialized by Fory Struct serializer. i.e. it doesn't have subclasses. Suppose we're + deserializing `List`, we can save dynamic serializer dispatch since `SomeClass` is dynamic(final). +- compatible_struct: a dynamic(final) type serialized by Fory compatible Struct serializer. +- named_struct: a `struct` whose type mapping will be encoded as a name. +- named_compatible_struct: a `compatible_struct` whose type mapping will be encoded as a name. +- ext: a type which will be serialized by a customized serializer. +- named_ext: an `ext` type whose type mapping will be encoded as a name. +- list: a sequence of objects. +- set: an unordered set of unique elements. +- map: a map of key-value pairs. Map keys do not allow binary values, floating-point values, + decimal values, or collection-shaped values such as `list`, `map`, `set`, and `array`. +- duration: an absolute length of time, independent of any calendar/timezone, as a count of nanoseconds. +- timestamp: a point in time, independent of any calendar/timezone, encoded as seconds (int64) and nanoseconds + (uint32) since the epoch at UTC midnight on January 1, 1970. +- date: a naive date without timezone, encoded as a signed varint64 count of days since the Unix epoch. +- decimal: an exact decimal value encoded as a signed `scale` and an exact `unscaled` integer. +- binary: an variable-length array of bytes. +- array: `array` is dense one-dimensional bool or numeric data. Current xlang emits the canonical specialized + `*_ARRAY` type IDs for each supported element domain. `ARRAY (42)` is reserved for a future generic array encoding and + is not emitted by the current xlang format. `list` remains a separate schema kind. + - bool_array: canonical wire tag for `array`. + - int8_array: canonical wire tag for `array`. + - int16_array: canonical wire tag for `array`. + - int32_array: canonical wire tag for `array`. + - int64_array: canonical wire tag for `array`. + - uint8_array: canonical wire tag for `array`. + - uint16_array: canonical wire tag for `array`. + - uint32_array: canonical wire tag for `array`. + - uint64_array: canonical wire tag for `array`. + - float8_array: reserved canonical wire tag for `array`. + - float16_array: canonical wire tag for `array`. + - bfloat16_array: canonical wire tag for `array`. + - float32_array: canonical wire tag for `array`. + - float64_array: canonical wire tag for `array`. +- union: a tagged union type that can hold one of several alternative types. The active alternative is identified by a non-negative case ID. +- typed_union: a union value with registered numeric union type ID. +- named_union: a union value with embedded union type name or shared TypeDef. +- none: represents an empty/unit value with no data (e.g., for empty union alternatives). + +Note: + +- Unsigned integer types use the same byte sizes as their signed counterparts; the difference is in value interpretation. See [Type mapping](xlang_type_mapping.md) for language-specific type mappings. + +### Polymorphisms + +For polymorphism, if one non-final class is registered, and only one subclass is registered, then we can take all +elements in List/Map have same type, thus reduce per-element type checks. + +Collection/Array polymorphism are not fully supported, since some languages such as golang have only one collection +type. If users want to get exactly the type he passed, he must pass that type when deserializing or annotate that type +to the field of struct. + +### Type disambiguation + +Due to differences between type systems of languages, those types can't be mapped one-to-one between languages. When +deserializing, Fory use the target data structure type and the data type in the data jointly to determine how to +deserialize and populate the target data structure. For example: + +```java +class Foo { + int[] intArray; + Object[] objects; + List objectList; +} + +class Foo2 { + int[] intArray; + List objects; + List objectList; +} +``` + +`intArray` has `array` schema and uses the `int32_array` wire tag. Both `objects` and `objectList` have `list` +schema. These schema kinds are distinct; implementations must not treat general object arrays as dense numeric arrays. + +### List and Array Semantics + +`list` and `array` are different schema kinds. + +Use `list` for ordinary ordered collections whose elements may need collection +semantics, nullable element handling, reference handling, or object/string/bytes +payloads. A primitive `list` may still use an optimized homogeneous element +segment internally, but the payload is owned by the list protocol and carries +list metadata. + +Use `array` for dynamic-length dense one-dimensional bool or numeric data. +`array` elements are always non-null, non-reference-tracked, and fixed-width +by the array contract. `array` uses one byte per value. Integer arrays use +fixed-width little-endian element payloads even when the scalar `int32`, +`int64`, `uint32`, or `uint64` default encoding is varint/PVL in scalar or list +positions. + +Valid `array` element domains are: + +```text +bool +int8, int16, int32, int64 +uint8, uint16, uint32, uint64 +float16, bfloat16, float32, float64 +``` + +Invalid array schemas include `array`, +`array`, `array`, `array`, `array`, +`array>`, and arrays of structs, unions, enums, temporal values, +decimals, or dynamic `any` values. + +The current wire format keeps specialized primitive-array type IDs as the +canonical dynamic tags for `array`: + +| Schema | Dynamic wire tag | +| ----------------- | ---------------- | +| `array` | `BOOL_ARRAY` | +| `array` | `INT8_ARRAY` | +| `array` | `INT16_ARRAY` | +| `array` | `INT32_ARRAY` | +| `array` | `INT64_ARRAY` | +| `array` | `UINT8_ARRAY` | +| `array` | `UINT16_ARRAY` | +| `array` | `UINT32_ARRAY` | +| `array` | `UINT64_ARRAY` | +| `array` | `FLOAT16_ARRAY` | +| `array` | `BFLOAT16_ARRAY` | +| `array` | `FLOAT32_ARRAY` | +| `array` | `FLOAT64_ARRAY` | + +`ARRAY (42)` is reserved for a future generic or shaped-array descriptor and is +not emitted for dense primitive arrays. + +In schema-compatible mode only, a matched struct/class field may read between +direct top-level `list` and direct top-level `array` schemas when `T` +belongs to the valid dense array element domains above. Integer list element +encodings in the same signedness and width domain match the corresponding dense +array element domain. This is a read adaptation, not a schema-kind merge: +writers keep emitting their local canonical `list` or `array` payload, and +TypeDef/ClassDef encodings, fingerprints, dynamic root serialization, +same-schema mode, and unknown-field skipping continue to treat `list` +and `array` as distinct kinds. + +The adaptation is limited to the immediate schema of the matched compatible +field. It does not apply when `list` or `array` appears inside another +field type, including collection elements, map keys or values, array elements, +union alternatives, or other generic/container positions. A peer `list` +TypeDef element schema is not immediate schema incompatibility for a local +matched `array` field. Classification must accept the matched field when the +element domains match and the only element-schema difference is nullable +metadata. The reader must decide from the collection payload: if the payload +actually carries a null element, the local `array` field must raise a +compatible-read error. Null list elements must not be coerced to dense-array +default values. Reference-tracked list-element framing is separate from +nullable element schema. A runtime that cannot materialize ref-tracked list +elements into a dense array without generic/reference paths may reject that +field during compatible classification; if it accepts the field, reference +payloads that cannot be represented as dense array element values must fail +during read. + +The dense-array error rule applies to dense-array targets. A matched +`list` field read into a local `list` target must keep using list +semantics and preserve actual null elements; implementations must not route that +payload through a dense primitive-array materialization path that rejects nulls. + +In schema-compatible mode only, a matched struct/class field may read between +direct top-level `binary` and direct top-level `array` schemas. This is a +byte-sequence adaptation only: it does not merge TypeDef/ClassDef type IDs, +schema fingerprints, dynamic root serialization, same-schema mode, or nested +collection/map/array/union/generic positions. `array` is not part of this +adapter. + +In schema-compatible mode only, a matched struct/class field may also read +between direct top-level scalar schemas when the remote value can be represented +by the local scalar schema without changing the logical value. This is a +compatible read adaptation only: writers keep emitting their local canonical +schema and payload, and TypeDef/ClassDef encodings, fingerprints, dynamic root +serialization, same-schema mode, unknown-field skipping, and container +element schemas continue to treat the original scalar types as distinct. + +The scalar conversion rule applies only to the immediate schema of the matched +compatible field. It does not apply to dynamic root values, `any`, map keys, map +values, list elements, set elements, array elements, union alternatives, enum +values, time/date/duration values, binary values, structs, ext values, or nested +generic/container positions. It also applies only when both the remote and local +top-level field schemas have `trackingRef = false`; if either matched field +schema has `trackingRef = true`, scalar conversion is outside the compatible +layout matrix and scalar type changes remain schema/type incompatible. Same +scalar type IDs with matching top-level `trackingRef` and null/optional framing +are exact same-schema direct reads, not compatible scalar conversion. Same +scalar type IDs with different top-level `trackingRef` framing are schema/type +incompatible because the wire framing differs. Same scalar type IDs with +different top-level null/optional framing may still use the nullable/optional +composition rule below when both fields have `trackingRef = false`. + +The convertible scalar domains are `bool`, `string`, and numeric scalars. +Numeric scalars are signed integers (`int8`, `int16`, `int32`, `int64`), +unsigned integers (`uint8`, `uint16`, `uint32`, `uint64`), floating point +(`float16`, `bfloat16`, `float32`, `float64`), and `decimal`. Integer encoding +variants are the same semantic domain as their base width: fixed, variable, and +tagged integer encodings do not create additional conversion domains. + +Compatible scalar conversion MUST follow these rules: + +- `string` to `bool` accepts exactly `"0"`, `"1"`, `"false"`, and `"true"`. + The match is byte-for-byte ASCII; readers MUST NOT trim whitespace, accept a + leading sign, accept other letter case, or use locale-specific text. +- `bool` to `string` produces canonical lower-case `"false"` or `"true"`. +- numeric to `bool` accepts only exact numeric zero and exact numeric one. `NaN` + and infinities fail. Negative floating zero is zero. Decimal scale does not + affect the zero/one check. +- `bool` to numeric produces exact zero or one in the local numeric domain. +- numeric to numeric succeeds only when the local numeric domain represents the + same mathematical value. Integer conversions check target range and signedness; + integer-to-floating conversions check exact representability in the target + floating domain; floating-to-integer conversions require a finite integral + value within range; floating-to-floating conversions require exact + preservation after converting to the target and back to the source, including + the sign of zero. Floating infinities may convert only when the target floating + domain preserves the same infinity. `NaN` is not convertible across different + floating type IDs. +- decimal is an exact numeric scalar. Integer-to-decimal conversion uses scale + `0`; decimal-to-integer conversion requires an integral value in range; + floating-to-decimal conversion requires a finite value and converts the exact + binary floating value to canonical decimal form; decimal-to-floating + conversion requires exact representability in the target floating domain. + Same-type decimal reads preserve the ordinary decimal payload. Decimal values + produced by conversion use the canonical converted decimal form below. +- `string` to numeric accepts only the compatible numeric literal grammar below + and then applies the same lossless target-domain checks. `"NaN"`, + `"Infinity"`, `"-Infinity"`, and spelling variants fail because numeric + strings are finite-only. +- numeric to `string` emits canonical finite numeric text. Integer sources emit + decimal text with no leading zeros except `"0"`. Floating sources emit exact + plain decimal text that equals the source value and parses back to the same + source floating type; it includes a decimal point and at least one fractional + digit, preserves negative zero as `"-0.0"`, never uses exponent notation, and + fails for `NaN` and infinities. Decimal sources emit exact plain decimal text + with no exponent and no insignificant trailing fractional zeros; decimal zero + is `"0"`. + +The compatible numeric literal grammar is deliberately stricter than host +language parsers: + +- no leading or trailing whitespace; +- no leading plus sign; +- ASCII grammar only: signs, digits, decimal points, and exponent markers are + the ASCII bytes `-`, `0` through `9`, `.`, `e`, and `E`; +- no Unicode decimal digits, underscores, grouping separators, locale-specific + digits, hexadecimal, octal, binary, or type suffixes; +- integer literal: `-?(0|[1-9][0-9]*)`; +- decimal floating literal: + `-?(0|[1-9][0-9]*)\.[0-9]+([eE]-?(0|[1-9][0-9]*))?` or + `-?(0|[1-9][0-9]*)[eE]-?(0|[1-9][0-9]*)`. + +Readers MUST parse numeric strings with exact decimal, rational, or equivalent +checked algorithms. Parsing through a host floating type and then casting is not +valid unless the implementation also proves exactness against the original +literal. + +Canonical converted decimal form is: + +- zero: `unscaled = 0`, `scale = 0`; +- non-zero integers: `scale = 0` and the integer as `unscaled`; +- finite fractional values: the smallest non-negative scale whose + `unscaled * 10^-scale` equals the value and whose `unscaled` is not divisible + by `10`. + +Compatible scalar conversion MUST reject a numeric string before arbitrary +precision parsing when the raw string length is greater than `320`. It MUST also +reject a converted decimal before constructing large powers of ten or formatting +plain decimal text when its canonical converted form would require an exponent or +scale outside `[-256, 256]`, a positive scale greater than `256`, an unscaled +decimal magnitude with more than `256` significant digits, or a negative scale +whose formatted integer digit count would exceed `256`. These bounds apply only +to values produced by compatible scalar conversion, including string-to-decimal, +decimal-to-string, and floating-to-decimal conversion. Same-type decimal reads +preserve the ordinary decimal payload. A bounded public decimal carrier may +reject smaller values when it cannot represent the value exactly. + +Nullable, boxed, optional, and nullable-field composition is supported for +matched scalar pairs whose top-level field schemas have `trackingRef = false`. +Readers first consume the remote null/optional framing described by the remote +field metadata. If a value is present, the reader converts the unwrapped scalar +value and then assigns or wraps it into the local carrier. If the remote value +is null or absent, the reader uses the same missing/null compatible-field rule +it already applies for that local field; this feature does not introduce a +second null policy. Reference-tracked scalar conversion is not supported. + +Conversion failures are data errors, not schema misses. A schema pair outside +the conversion matrix remains a schema/type compatibility error when building +the compatible layout. Once a matched field is accepted as a scalar conversion +action, an invalid payload value MUST be reported through the implementation's +data-error path with enough context to identify the remote type, local type, and +field when that path has the information. + +Unknown-field skipping applies only when the remote field has no matching local +field identity. If a local field matches by tag ID or name but its schema is +outside the exact-read and compatible-adaptation rules, the reader MUST reject +the compatible layout instead of treating the field as missing, remote-only, or +skippable. + +Users can also provide meta hints for fields of a type, or the type whole. Here is an example in java which use +annotation to provide such information. + +```java +@ForyStruct +class Foo { + @ArrayType + @ForyField(id = 0) + int[] intArray; + + @ForyField(id = 1, dynamic = ForyField.Dynamic.TRUE) + Object object; + + @Nullable + @ForyField(id = 2) + List objectList; +} +``` + +Such information can be provided in other languages too: + +- cpp: use macro and template. +- golang: use struct tag. +- python: use typehint. +- rust: use macro. + +### Type ID + +All internal data types use an 8-bit internal ID (`0~255`, with `0~56` defined here). Users can +register types by numeric ID (`0~0xFFFFFFFE` in current implementations). User IDs are encoded +separately from the internal type ID; there is no bit shifting/packing. + +Named types (`NAMED_*`) do not embed a user ID; their names are carried in metadata instead. + +#### Internal Type ID Table + +| Type ID | Name | Description | +| ------- | ----------------------- | ------------------------------------------------------ | +| 0 | UNKNOWN | Unknown type, used for dynamic typing | +| 1 | BOOL | Boolean value | +| 2 | INT8 | 8-bit signed integer | +| 3 | INT16 | 16-bit signed integer | +| 4 | INT32 | 32-bit signed integer | +| 5 | VARINT32 | Variable-length encoded 32-bit signed integer | +| 6 | INT64 | 64-bit signed integer | +| 7 | VARINT64 | Variable-length encoded 64-bit signed integer | +| 8 | TAGGED_INT64 | Hybrid encoded 64-bit signed integer | +| 9 | UINT8 | 8-bit unsigned integer | +| 10 | UINT16 | 16-bit unsigned integer | +| 11 | UINT32 | 32-bit unsigned integer | +| 12 | VAR_UINT32 | Variable-length encoded 32-bit unsigned integer | +| 13 | UINT64 | 64-bit unsigned integer | +| 14 | VAR_UINT64 | Variable-length encoded 64-bit unsigned integer | +| 15 | TAGGED_UINT64 | Hybrid encoded 64-bit unsigned integer | +| 16 | FLOAT8 | 8-bit floating point (float8) | +| 17 | FLOAT16 | 16-bit floating point (half precision) | +| 18 | BFLOAT16 | 16-bit brain floating point | +| 19 | FLOAT32 | 32-bit floating point (single precision) | +| 20 | FLOAT64 | 64-bit floating point (double precision) | +| 21 | STRING | UTF-8/UTF-16/Latin1 encoded string | +| 22 | LIST | Ordered collection (List, Array, Vector) | +| 23 | SET | Unordered collection of unique elements | +| 24 | MAP | Key-value mapping | +| 25 | ENUM | Enum registered by numeric ID | +| 26 | NAMED_ENUM | Enum registered by namespace + type name | +| 27 | STRUCT | Struct registered by numeric ID (same-schema) | +| 28 | COMPATIBLE_STRUCT | Struct with schema evolution support (by ID) | +| 29 | NAMED_STRUCT | Struct registered by namespace + type name | +| 30 | NAMED_COMPATIBLE_STRUCT | Struct with schema evolution (by name) | +| 31 | EXT | Extension type registered by numeric ID | +| 32 | NAMED_EXT | Extension type registered by namespace + type name | +| 33 | UNION | Union value, schema identity not embedded | +| 34 | TYPED_UNION | Union value with registered numeric type ID | +| 35 | NAMED_UNION | Union value with embedded type name/TypeDef | +| 36 | NONE | Empty/unit type (no data) | +| 37 | DURATION | Time duration (seconds + nanoseconds) | +| 38 | TIMESTAMP | Point in time (seconds + nanoseconds since epoch) | +| 39 | DATE | Date without timezone (signed varint64 days) | +| 40 | DECIMAL | Arbitrary precision decimal (scale + unscaled) | +| 41 | BINARY | Raw binary data | +| 42 | ARRAY | Reserved for future dedicated multi-dimensional arrays | +| 43 | BOOL_ARRAY | 1D boolean array | +| 44 | INT8_ARRAY | 1D int8 array | +| 45 | INT16_ARRAY | 1D int16 array | +| 46 | INT32_ARRAY | 1D int32 array | +| 47 | INT64_ARRAY | 1D int64 array | +| 48 | UINT8_ARRAY | 1D uint8 array | +| 49 | UINT16_ARRAY | 1D uint16 array | +| 50 | UINT32_ARRAY | 1D uint32 array | +| 51 | UINT64_ARRAY | 1D uint64 array | +| 52 | FLOAT8_ARRAY | 1D float8 array | +| 53 | FLOAT16_ARRAY | 1D float16 array | +| 54 | BFLOAT16_ARRAY | 1D bfloat16 array | +| 55 | FLOAT32_ARRAY | 1D float32 array | +| 56 | FLOAT64_ARRAY | 1D float64 array | + +#### Type ID Encoding for User Types + +When registering user types (struct/ext/enum/union), the internal type ID is written as the 8-bit +kind. The user type ID is written separately as an unsigned varint32 (small7); there is no bit +shift or packing. + +**Examples:** + +| User ID | Type | Internal ID | Encoded User ID | Decimal | +| ------- | ----------------- | ----------- | --------------- | ------- | +| 0 | STRUCT | 27 | 0 | 0 | +| 0 | ENUM | 25 | 0 | 0 | +| 1 | STRUCT | 27 | 1 | 1 | +| 1 | COMPATIBLE_STRUCT | 28 | 1 | 1 | +| 2 | NAMED_STRUCT | 29 | 2 | 2 | + +When reading type IDs: + +- Read internal type ID from the type ID field. +- If the internal type is a user-registered kind, read `user_type_id` as varuint32. + +### Type mapping + +See [Type mapping](xlang_type_mapping.md) + +## Spec overview + +Here is the overall format: + +``` +| fory header | object ref meta | object type meta | object value data | +``` + +The data are serialized using little endian byte order for all types. + +## Fory header + +Fory header format for xlang serialization: + +``` +| 1 byte bitmap | ++--------------------------------+ +| flags | +``` + +Detailed byte layout: + +``` +Byte 0: Bitmap flags + - Bit 0: xlang flag (0x01) + - Bit 1: oob flag (0x02) + - Bits 2-7: reserved +``` + +- **xlang flag** (bit 0): 1 when serialization uses Fory xlang format, 0 when serialization uses a Fory native-mode format. +- **oob flag** (bit 1): 1 when out-of-band serialization is enabled (BufferCallback is not null), 0 otherwise. +- **reserved bits** (bits 2-7): must be zero. + +All data is encoded in little-endian format. + +## Reference Meta + +Reference tracking handles whether the object is null, and whether to track reference for the object by writing +corresponding flags and maintaining internal state. + +### Reference Flags + +| Flag | Byte Value (int8) | Hex | Description | +| ------------------- | ----------------- | ------ | -------------------------------------------------------------------------------------------------------- | +| NULL FLAG | `-3` | `0xFD` | Object is null. No further bytes are written for this object. | +| REF FLAG | `-2` | `0xFE` | Object was already serialized. Followed by unsigned varint32 reference ID. | +| NOT_NULL VALUE FLAG | `-1` | `0xFF` | Object is non-null but reference tracking is disabled for this type. Object data follows immediately. | +| REF VALUE FLAG | `0` | `0x00` | Object is referencable and this is its first occurrence. Object data follows. Assigns next reference ID. | + +### Reference Tracking Algorithm + +**Writing:** + +``` +function write_ref_or_null(buffer, obj): + if obj is null: + buffer.write_int8(NULL_FLAG) // -3 + return true // done, no more data to write + + if reference_tracking_enabled: + ref_id = lookup_written_objects(obj) + if ref_id exists: + buffer.write_int8(REF_FLAG) // -2 + buffer.write_varuint32(ref_id) + return true // done, reference written + else: + buffer.write_int8(REF_VALUE_FLAG) // 0 + add_to_written_objects(obj, next_ref_id++) + return false // continue to serialize object data + else: + buffer.write_int8(NOT_NULL_VALUE_FLAG) // -1 + return false // continue to serialize object data +``` + +**Reading:** + +``` +function read_ref_or_null(buffer): + flag = buffer.read_int8() + switch flag: + case NULL_FLAG (-3): + return (null, true) // null object, done + case REF_FLAG (-2): + ref_id = buffer.read_varuint32() + obj = get_from_read_objects(ref_id) + return (obj, true) // referenced object, done + case NOT_NULL_VALUE_FLAG (-1): + return (null, false) // non-null, continue reading + case REF_VALUE_FLAG (0): + reserve_ref_slot() // will be filled after reading + return (null, false) // non-null, continue reading +``` + +### Reference ID Assignment + +- Reference IDs are assigned sequentially starting from `0` +- The ID is assigned when `REF_VALUE_FLAG` is written (first occurrence) +- Objects are stored in a list/map indexed by their reference ID +- For reading, a reference slot is reserved before deserializing the object, then filled after + +### When Reference Tracking is Disabled + +When reference tracking is disabled globally or for specific types, only the `NULL` and `NOT_NULL VALUE` flags +will be used for reference meta. This reduces overhead for types that are known not to have references. + +### Language-Specific Considerations + +**Languages with nullable and reference types by default (Java, Python, JavaScript):** + +In xlang mode, for cross-language compatibility: + +- All fields are treated as **not-null** by default +- Reference tracking is **disabled** by default +- Users can explicitly mark fields as nullable or enable reference tracking via annotations +- `Optional` types (e.g., `java.util.Optional`, `typing.Optional`) are treated as nullable + +**Annotation examples:** + +```java +// Java: use @Ref for reference tracking +public class MyClass { + @Nullable + @Ref + private Object refField; + + private String requiredField; +} +``` + +```python +# Python: use typing with fory field descriptors +from pyfory import ForyField, Ref + +class MyClass: + ref_field: ForyField(Ref[SomeType], nullable=True) + required_field: ForyField(str, nullable=False) +``` + +**Languages with non-nullable types by default:** + +| Language | Null Representation | Reference Tracking Support | +| -------- | ------------------------- | --------------------------------------- | +| Rust | `Option::None` | Via `Rc`, `Arc`, `Weak` | +| C++ | `std::nullopt`, `nullptr` | Via `std::shared_ptr`, `weak_ptr` | +| Go | `nil` interface/pointer | Via pointer/interface types | + +**Important:** For languages like Rust that don't have implicit reference semantics, reference tracking must use +explicit smart pointers (`Rc`, `Arc`). + +## Type Meta + +Every non-primitive value begins with a type ID that identifies its concrete type. The type ID is +followed by optional type-specific metadata. + +### Type ID encoding + +- The type ID is written as an unsigned varint32 (small7). +- Internal types use their internal type ID directly (low 8 bits). +- User-registered types write the internal type ID, then write `user_type_id` as varuint32. + - `user_type_id` is a numeric ID (0~0xFFFFFFFE in current implementations). + - `internal_type_id` is one of `ENUM`, `STRUCT`, `COMPATIBLE_STRUCT`, `EXT`, or `TYPED_UNION`. +- Named types do not embed a user ID. They use `NAMED_*` internal type IDs and carry a namespace + and type name (or shared TypeDef) instead. + +### Type meta payload + +After the type ID: + +- **ENUM / STRUCT / EXT / TYPED_UNION**: no extra bytes beyond the `user_type_id` (registration by ID required on both sides). +- **COMPATIBLE_STRUCT**: + - If meta share is enabled, write a shared TypeDef entry (see below). + - If meta share is disabled, no extra bytes. +- **NAMED_ENUM / NAMED_STRUCT / NAMED_COMPATIBLE_STRUCT / NAMED_EXT / NAMED_UNION**: + - If meta share is disabled, write `namespace` and `type_name` as meta strings. + - If meta share is enabled, write a shared TypeDef entry (see below). +- **UNION**: no extra bytes at this layer. +- **LIST / SET / MAP / primitives**: no extra bytes at this layer. + +Pure id-based enum, ext, and typed-union values therefore do not carry a TypeDef +body. Receive-side TypeDef resource limits apply only when the stream actually +carries shared TypeDef metadata. + +`ARRAY (42)` is reserved for a future xlang extension for dedicated multi-dimensional arrays and +is not used in current xlang streams. + +Unregistered types are serialized as named types: + +- Enums -> `NAMED_ENUM` +- Struct-like classes -> `NAMED_STRUCT` (or `NAMED_COMPATIBLE_STRUCT` when meta share is enabled) +- Custom extension types -> `NAMED_EXT` +- Unions -> `NAMED_UNION` + +The namespace is the package/module name and the type name is the simple class name. + +### Shared Type Meta (streaming) + +When meta share is enabled, TypeDef metadata is written inline the first time a type is +encountered, and subsequent occurrences only reference it. + +Encoding: + +- `marker = (index << 1) | flag` +- `flag = 0`: new type definition follows +- `flag = 1`: reference to a previously written type definition +- `index` is the sequential index assigned to this type (starting from 0). + +Write algorithm: + +1. Look up the class in the per-stream meta context map. +2. If found, write `(index << 1) | 1`. +3. If not found: + - assign `index = next_id` + - write `(index << 1)` + - write the encoded TypeDef bytes immediately after + +Read algorithm: + +1. Read `marker` as varuint32. +2. `flag = marker & 1`, `index = marker >>> 1`. +3. If `flag == 1`, use the cached TypeDef at `index`. +4. If `flag == 0`, read a TypeDef, cache it at `index`, and use it. + +TypeDef bytes include the 8-byte global header and optional size extension. + +### TypeDef (schema evolution metadata) + +TypeDef describes a struct-like type (or a named enum/ext) for schema evolution and name +resolution. It is encoded as: + +``` +| 8-byte global header | [optional size varuint] | TypeDef body | +``` + +#### Global header + +The 8-byte header is a little-endian uint64: + +- Low 8 bits: meta size (number of bytes in the TypeDef body). + - If meta size >= 0xFF, the low 8 bits are set to 0xFF and an extra + `varuint32(meta_size - 0xFF)` follows immediately after the header. +- Bit 8: `COMPRESS_META` is reserved for a future xlang metadata-compression extension. + Current xlang writers MUST leave this bit unset and current xlang readers MUST treat a set bit + as unsupported. +- Bits 9-11: reserved for future extension (must be zero). +- High 52 bits: stored hash bits derived from MurmurHash3 x64_128 seed 47 over + `TypeDef body || header_low12_le`. `header_low12_le` is two little-endian bytes containing the low + 12 header bits (size, compress, and reserved bits); the upper four bits of the second byte are + zero. Take lane 0 of the 128-bit MurmurHash3 result as a signed int64, left-shift it by 12 with + two's-complement 64-bit wraparound, apply signed absolute value (leaving `INT64_MIN` unchanged), + then mask with `0xfffffffffffff000`. The final header is the masked hash bits OR-ed with the low + 12 header bits. + +#### TypeDef body + +TypeDef body has a single layer (fields are flattened in class hierarchy order): + +``` +| meta header (1 byte) | type spec | field info ... | +``` + +Meta header byte for struct TypeDefs: + +- Bit 7: `IS_STRUCT` (1). +- Bit 6: `COMPATIBLE`. +- Bit 5: `REGISTER_BY_NAME` (1 = namespace + type name, 0 = numeric user type ID). +- Bits 0-4: `num_fields` (0-30). + - If `num_fields == 31`, read an extra `varuint32` and add it. + +Meta header byte for non-struct TypeDefs: + +- Bit 7: `IS_STRUCT` (0). +- Bits 4-6: reserved (must be zero). +- Bits 0-3: kind code. + +Readers may reject a received TypeDef that exceeds runtime resource limits such +as maximum metadata body bytes or maximum fields in one struct TypeDef. These +limits are receive-side resource controls and do not change TypeDef wire +encoding, type identity, dynamic loading, unknown-type handling, registration +policy, or schema-evolution semantics. + +Non-struct kind codes: + +- `0`: `ENUM` +- `1`: `NAMED_ENUM` +- `2`: `EXT` +- `3`: `NAMED_EXT` +- `4`: `TYPED_UNION` +- `5`: `NAMED_UNION` +- `6-14`: reserved +- `15`: extended-kind escape, rejected until defined + +Type spec: + +- If `REGISTER_BY_NAME` is set: + - `namespace` meta string + - `type_name` meta string +- Otherwise: + - user type ID as `varuint32` + +Field info list: + +Each field is encoded as: + +``` +| field header (1 byte) | field type info | [field name bytes] | +``` + +Field header layout: + +- Bits 6-7: field name encoding (`UTF8`, `ALL_TO_LOWER_SPECIAL`, + `LOWER_UPPER_DIGIT_SPECIAL`, or `TAG_ID`) +- Bits 2-5: size + - For name encoding: `size = (name_bytes_length - 1)` + - For tag ID: `size = tag_id` + - If `size == 0b1111`, read `varuint32(size - 15)` and add it +- Bit 1: nullable flag +- Bit 0: reference tracking flag + +Field type info: + +- The top-level field type is written as `varuint32(type_id)` (small7) without flags. +- For `LIST` / `SET`, an element type follows, encoded as + `(nested_type_id << 2) | (nullable << 1) | tracking_ref`. +- For `MAP`, key type and value type follow, both encoded the same way. +- One-dimensional primitive arrays use `*_ARRAY` type IDs; other arrays are encoded as `LIST`. + +Field names: + +- If `TAG_ID` encoding is used, no name bytes are written. +- Otherwise, write the encoded field name bytes as a meta string. +- For xlang, field names are converted to `snake_case` before encoding for + cross-language compatibility. + +Field order: + +TypeDef field lists use the same ordering defined in [Field order](#field-order). Compatible +decoders must still match fields by name or tag ID rather than relying only on position. + +## Meta String + +Meta string is a compressed encoding for metadata strings such as field names, type names, and namespaces. +This compression significantly reduces the size of type metadata in serialized data. + +### Encoding Type IDs + +| ID | Name | Bits/Char | Character Set | +| --- | ------------------------- | --------- | ------------------------------------ | +| 0 | UTF8 | 8 | Any UTF-8 character | +| 1 | LOWER_SPECIAL | 5 | `a-z . _ $ \|` | +| 2 | LOWER_UPPER_DIGIT_SPECIAL | 6 | `a-z A-Z 0-9 . _` | +| 3 | FIRST_TO_LOWER_SPECIAL | 5 | First char uppercase, rest `a-z . _` | +| 4 | ALL_TO_LOWER_SPECIAL | 5 | `a-z A-Z . _` (uppercase escaped) | + +### Character Mapping Tables + +#### LOWER_SPECIAL (5 bits per character) + +| Character | Code (binary) | Code (decimal) | +| --------- | ------------- | -------------- | +| a-z | 00000-11001 | 0-25 | +| . | 11010 | 26 | +| \_ | 11011 | 27 | +| $ | 11100 | 28 | +| \| | 11101 | 29 | + +**Note:** The `|` character is used as an escape sequence in ALL_TO_LOWER_SPECIAL encoding. + +#### LOWER_UPPER_DIGIT_SPECIAL (6 bits per character) + +| Character | Code (binary) | Code (decimal) | +| --------- | ------------- | -------------- | +| a-z | 000000-011001 | 0-25 | +| A-Z | 011010-110011 | 26-51 | +| 0-9 | 110100-111101 | 52-61 | +| . | 111110 | 62 | +| \_ | 111111 | 63 | + +### Encoding Algorithms + +#### LOWER_SPECIAL Encoding + +For strings containing only `a-z`, `.`, `_`, `$`, `|`: + +``` +function encode_lower_special(str): + bits = [] + for char in str: + bits.append(lookup_lower_special[char]) // 5 bits each + + // Pad to byte boundary + total_bits = len(str) * 5 + padding_bits = (8 - (total_bits % 8)) % 8 + + // First bit indicates if last char should be stripped (due to padding) + strip_last = (padding_bits >= 5) + if strip_last: + prepend bit 1 + else: + prepend bit 0 + + return pack_bits_to_bytes(bits) +``` + +#### FIRST_TO_LOWER_SPECIAL Encoding + +For strings like `MyFieldName` where only the first character is uppercase: + +``` +function encode_first_to_lower_special(str): + // Convert first char to lowercase + modified = str[0].lower() + str[1:] + // Then use LOWER_SPECIAL encoding + return encode_lower_special(modified) +``` + +#### ALL_TO_LOWER_SPECIAL Encoding + +For strings with multiple uppercase characters like `MyTypeName`: + +``` +function encode_all_to_lower_special(str): + result = "" + for char in str: + if char.is_upper(): + result += "|" + char.lower() // Escape uppercase with | + else: + result += char + return encode_lower_special(result) +``` + +Example: `MyType` → `|my|type` → encoded with LOWER_SPECIAL + +### Encoding Selection Algorithm + +``` +function choose_encoding(str): + if all chars in str are in [a-z . _ $ |]: + return LOWER_SPECIAL + + if first char is uppercase AND rest are in [a-z . _]: + return FIRST_TO_LOWER_SPECIAL + + if all chars are in [a-z A-Z . _]: + lower_special_size = encode_all_to_lower_special(str).size + luds_size = encode_lower_upper_digit_special(str).size + if lower_special_size <= luds_size: + return ALL_TO_LOWER_SPECIAL + else: + return LOWER_UPPER_DIGIT_SPECIAL + + if all chars are in [a-z A-Z 0-9 . _]: + return LOWER_UPPER_DIGIT_SPECIAL + + return UTF8 +``` + +### Meta String Header Format + +Meta strings are written with a header that includes the encoding type: + +``` +| 3 bits encoding | 5+ bits length | encoded bytes | +``` + +Or for larger strings: + +``` +| varuint: (length << 3) | encoding | encoded bytes | +``` + +### Special Character Sets by Context + +Different contexts use different special characters: + +| Context | Special Chars | Notes | +| ---------- | ------------- | ---------------------------------- | +| Field Name | . \_ $ \| | $ for inner classes, \| for escape | +| Namespace | . \_ | Package/module separators | +| Type Name | $ \_ | $ for inner classes in Java | + +### Deduplication + +Meta strings are deduplicated within a serialization session: + +``` +First occurrence: | (length << 1) | [hash if large] | encoding | bytes | +Reference: | ((id + 1) << 1) | 1 | +``` + +- Bit 0 of the header indicates: 0 = new string, 1 = reference to previous +- Large strings (> 16 bytes) include 64-bit hash for content-based deduplication +- Small strings use exact byte comparison + +## Value Format + +### Basic types + +#### bool + +- size: 1 byte +- format: 0 for `false`, 1 for `true` + +#### int8 + +- size: 1 byte +- format: write as pure byte. + +#### int16 + +- size: 2 byte +- byte order: raw bytes of little endian order + +#### unsigned int32 + +- size: 4 byte +- byte order: raw bytes of little endian order + +#### unsigned varint32 + +- size: 1~5 bytes +- Format: The most significant bit (MSB) in every byte indicates whether to have the next byte. If the continuation + bit is set (i.e. `b & 0x80 == 0x80`), then the next byte should be read until a byte with unset continuation bit. + +**Encoding Algorithm:** + +``` +function write_varuint32(value): + while value >= 0x80: + buffer.write_byte((value & 0x7F) | 0x80) // 7 bits of data + continuation bit + value = value >> 7 + buffer.write_byte(value) // final byte without continuation bit +``` + +**Decoding Algorithm:** + +``` +function read_varuint32(): + result = 0 + shift = 0 + while true: + byte = buffer.read_byte() + result = result | ((byte & 0x7F) << shift) + if (byte & 0x80) == 0: + break + shift = shift + 7 + return result +``` + +**Byte sizes by value range:** + +| Value Range | Bytes | +| ---------------------- | ----- | +| 0 ~ 127 | 1 | +| 128 ~ 16383 | 2 | +| 16384 ~ 2097151 | 3 | +| 2097152 ~ 268435455 | 4 | +| 268435456 ~ 4294967295 | 5 | + +#### signed int32 + +- size: 4 bytes +- byte order: raw bytes of little endian order + +#### signed varint32 + +- size: 1~5 bytes +- Format: First convert the number into positive unsigned int using ZigZag encoding, then encode as unsigned varint. + +**ZigZag Encoding:** + +``` +// Encode: convert signed to unsigned +zigzag_value = (value << 1) ^ (value >> 31) + +// Decode: convert unsigned back to signed +original = (zigzag_value >> 1) ^ (-(zigzag_value & 1)) +// Or equivalently: +original = (zigzag_value >> 1) ^ (~(zigzag_value & 1) + 1) +``` + +ZigZag encoding maps signed integers to unsigned integers so that small absolute values (positive or negative) +have small encoded values: + +| Original | ZigZag Encoded | +| -------- | -------------- | +| 0 | 0 | +| -1 | 1 | +| 1 | 2 | +| -2 | 3 | +| 2 | 4 | +| ... | ... | + +#### unsigned int64 + +- size: 8 bytes +- byte order: raw bytes of little endian order + +#### unsigned varint64 + +- size: 1~9 bytes + +Uses PVL (Progressive Variable-Length) encoding: + +``` +function write_varuint64(value): + while value >= 0x80: + buffer.write_byte((value & 0x7F) | 0x80) + value = value >> 7 + buffer.write_byte(value) +``` + +| Value Range | Bytes | +| ------------- | ----- | +| 0 ~ 127 | 1 | +| 128 ~ 16383 | 2 | +| ... | ... | +| 2^56 ~ 2^63-1 | 9 | + +#### unsigned hybrid int64 (TAGGED_UINT64) + +- size: 4 or 9 bytes + +Optimized for unsigned values that fit in 31 bits (common case for IDs, sizes, counts, etc.): + +``` +if value in [0, 2147483647]: // fits in 31 bits (2^31 - 1), full unsigned range + write 4 bytes: ((int32) value) << 1 // bit 0 is 0, indicating 4-byte encoding +else: + write 1 byte: 0x01 // bit 0 is 1, indicating 9-byte encoding + write 8 bytes: value as little-endian uint64 +``` + +Reading: + +``` +first_int32 = read_int32_le() +if (first_int32 & 1) == 0: + return (uint64)(first_int32 >> 1) // 4-byte encoding, unsigned +else: + return read_uint64_le() // read remaining 8 bytes +``` + +Note: TAGGED_UINT64 uses the full 31 bits for positive values [0, 2^31-1], compared to TAGGED_INT64 which splits the range for signed values [-2^30, 2^30-1]. + +#### VarUint36Small + +A specialized encoding used for string headers that combines size (up to 36 bits) with encoding flags: + +``` +// Write: encodes (size << 2) | encoding_flags +function write_varuint36_small(value): + if value < 0x80: + buffer.write_byte(value) + else: + // Standard varint encoding for values >= 128 + write_varuint64(value) +``` + +This encoding is optimized for the common case where string length fits in 7 bits (strings < 32 characters). + +#### signed int64 + +- size: 8 bytes +- byte order: raw bytes of little endian order + +#### signed varint64 + +- size: 1~9 bytes + +Uses ZigZag encoding first, then PVL varint: + +``` +// Encode +zigzag_value = (value << 1) ^ (value >> 63) +write_varuint64(zigzag_value) + +// Decode +zigzag_value = read_varuint64() +value = (zigzag_value >> 1) ^ (-(zigzag_value & 1)) +``` + +#### signed hybrid int64 (TAGGED_INT64) + +- size: 4 or 9 bytes + +Optimized for small signed values: + +``` +if value in [-1073741824, 1073741823]: // fits in 30 bits + sign ([-2^30, 2^30-1]) + write 4 bytes: ((int32) value) << 1 // bit 0 is 0, indicating 4-byte encoding +else: + write 1 byte: 0x01 // bit 0 is 1, indicating 9-byte encoding + write 8 bytes: value as little-endian int64 +``` + +Reading: + +``` +first_int32 = read_int32_le() +if (first_int32 & 1) == 0: + return (int64)(first_int32 >> 1) // 4-byte encoding, sign-extended +else: + return read_int64_le() // read remaining 8 bytes +``` + +Note: TAGGED_INT64 uses 30 bits + sign for values [-2^30, 2^30-1], while TAGGED_UINT64 uses full 31 bits for unsigned values [0, 2^31-1]. + +#### float8 + +- size: 1 byte +- format: + - float8 has 4 kinds: float8 kind enum: float8_e4m3fn, float8_e4m3fnuz, float8_e5m2, float8_e5m2fnuz + - when serialize as field, write raw 8 bits as one byte directly + - when serialize as an object: write type kind as a byte, then write value byte + +#### float16 + +- size: 2 bytes +- format: encode the specified floating-point value according to the IEEE 754 standard binary16 format, preserving NaN values, then write as binary by little endian order. + +#### bfloat16 + +- size: 2 bytes +- format: encode the specified floating-point value according to the IEEE 754 standard bfloat16 format, preserving NaN values, then write as binary by little endian order. + +#### float32 + +- size: 4 byte +- format: encode the specified floating-point value according to the IEEE 754 floating-point "single format" bit layout, + preserving Not-a-Number (NaN) values, then write as binary by little endian order. + +#### float64 + +- size: 8 byte +- format: encode the specified floating-point value according to the IEEE 754 floating-point "double format" bit layout, + preserving Not-a-Number (NaN) values. then write as binary by little endian order. + +### string + +Format: + +``` +| varuint36_small: (size << 2) | encoding | binary data | +``` + +#### String Header + +The header is encoded using `varuint36_small` format, which combines the byte length and encoding type: + +``` +header = (byte_length << 2) | encoding_type +``` + +| Encoding Type | Value | Description | +| ------------- | ----- | --------------------------------------- | +| LATIN1 | 0 | ISO-8859-1 single-byte encoding | +| UTF16 | 1 | UTF-16 encoding (2 bytes per code unit) | +| UTF8 | 2 | UTF-8 variable-length encoding | +| Reserved | 3 | Reserved for future use | + +#### Encoding Algorithm + +**Writing:** + +``` +function write_string(str): + bytes = encode_to_bytes(str, chosen_encoding) + header = (bytes.length << 2) | encoding_type + buffer.write_varuint36_small(header) + buffer.write_bytes(bytes) +``` + +**Reading:** + +``` +function read_string(): + header = buffer.read_varuint36_small() + encoding = header & 0x03 + byte_length = header >> 2 + bytes = buffer.read_bytes(byte_length) + return decode_bytes(bytes, encoding) +``` + +#### Encoding Selection by Language + +**Writing:** + +| Language | Encoding Strategy | +| ------------ | -------------------------------------------------------- | +| Java (JDK8) | Detect at runtime: LATIN1 if all chars < 256, else UTF16 | +| Java (JDK9+) | Use String's internal coder: LATIN1 or UTF16 | +| Python | Can write LATIN1, UTF16, or UTF8 based on string content | +| C++ | UTF8 (`std::string`) or UTF16 (`std::u16string`) | +| Rust | UTF8 (`String`) | +| Go | UTF8 (`string`) | +| JavaScript | UTF8 | + +**Reading:** All languages support decoding all three encodings (LATIN1, UTF16, UTF8). + +**Recommendation:** Select encoding based on maximum performance - use the encoding that matches the language's native string representation to avoid conversion overhead. + +#### Empty String + +Empty strings are encoded with header `0` (length 0, any encoding) followed by no data bytes. + +### duration + +Duration is an absolute length of time, independent of any calendar/timezone, as a count of seconds and nanoseconds. + +Format: + +``` +| signed varint64: seconds | signed int32: nanoseconds | +``` + +- `seconds`: Number of seconds in the duration, encoded as a signed varint64. Can be positive or negative. +- `nanoseconds`: Nanosecond adjustment to the duration, encoded as a signed int32. + +Notes: + +- The duration is stored as two separate fields to maintain precision and avoid overflow issues. +- Seconds are encoded using varint64 for compact representation of common duration values. +- Nanoseconds are stored as a fixed int32 since the range is limited. + +#### Canonical Rules + +- Writers MUST normalize durations so `nanoseconds` is always in `[0, 1_000_000_000)`. +- Zero MUST be encoded as `seconds = 0` and `nanoseconds = 0`. +- Negative sub-second durations MUST borrow one second and use a positive nanosecond adjustment. + Example: `-0.5s` is encoded as `seconds = -1`, `nanoseconds = 500_000_000`. +- More generally, the encoded pair MUST satisfy: + - `duration = seconds + nanoseconds / 1_000_000_000` + - `0 <= nanoseconds < 1_000_000_000` + +#### Final Value + +After decoding `seconds` and `nanoseconds`, the duration value is reconstructed as the exact +duration represented by: + +`seconds + nanoseconds / 1_000_000_000` + +### collection/list + +Format: + +``` +| varuint32: length | 1 byte elements header | [optional type info] | elements data | +``` + +#### Elements Header + +The elements header is a single byte that encodes metadata about the collection elements to optimize serialization: + +``` +| bit 7-4 (reserved) | bit 3 | bit 2 | bit 1 | bit 0 | ++--------------------+-------------+------------------+----------+-----------+ +| reserved | is_same_type| is_decl_elem_type| has_null | track_ref | +``` + +| Bit | Name | Value | Meaning when SET (1) | Meaning when UNSET (0) | +| --- | ----------------- | ----- | ---------------------------------------- | --------------------------------------- | +| 0 | track_ref | 0x01 | Track references for elements | Don't track element references | +| 1 | has_null | 0x02 | Payload contains null element markers | No null elements (skip null checks) | +| 2 | is_decl_elem_type | 0x04 | Elements are the declared generic type | Element types differ from declared type | +| 3 | is_same_type | 0x08 | All elements have the same concrete type | Elements have different concrete types | + +**Common header values:** + +| Header | Hex | Meaning | +| ------ | --- | -------------------------------------------------------------- | +| 0x0C | 12 | Declared type + same type, non-null, no ref tracking (optimal) | +| 0x0D | 13 | Declared type + same type, non-null, with ref tracking | +| 0x0E | 14 | Declared type + same type, may have nulls, no ref tracking | +| 0x08 | 8 | Same type but not declared type (type info written once) | +| 0x00 | 0 | Different types, non-null, no ref tracking (type per element) | + +#### Type Info After Header + +When `is_decl_elem_type` (bit 2) is NOT set, the element type info is written once after the header if `is_same_type` (bit 3) is set: + +``` +| header (0x08) | type_id (varuint32) | elements... | +``` + +When both `is_decl_elem_type` and `is_same_type` are NOT set, type info is written per element. + +#### Element Serialization Based on Header + +The header determines how each element is serialized: + +#### elements data + +Based on the elements header, the serialization of elements data may skip `ref flag`/`null flag`/`element type info`. + +```python +fory = ... +buffer = ... +elems = ... +if element_type_is_same: + if not is_declared_type: + fory.write_type(buffer, elem_type) + elem_serializer = get_serializer(...) + if track_ref: + for elem in elems: + if not ref_resolver.write_ref_or_null(buffer, elem): + elem_serializer.write(buffer, elem) + elif has_null: + for elem in elems: + if elem is None: + buffer.write_byte(null_flag) + else: + buffer.write_byte(not_null_flag) + elem_serializer.write(buffer, elem) + else: + for elem in elems: + elem_serializer.write(buffer, elem) +else: + if track_ref: + for elem in elems: + fory.write_ref(buffer, elem) + elif has_null: + for elem in elems: + fory.write_nullable(buffer, elem) + else: + for elem in elems: + fory.write_value(buffer, elem) +``` + +[`CollectionSerializer#writeElements`](https://github.com/apache/fory/blob/20a1a78b17a75a123a6f5b7094c06ff77defc0fe/java/fory-core/src/main/java/org/apache/fory/serializer/collection/CollectionLikeSerializer.java#L302) +can be taken as an example. + +### array + +#### primitive array + +Primitive array are taken as a binary buffer, serialization will just write the length of array size as an unsigned int, +then copy the whole buffer into the stream. Multi-byte element arrays are always encoded in little-endian element order; +implementations whose native typed-array storage uses another byte order must swap or write elements explicitly instead of +copying native storage bytes unchanged. + +Such serialization won't compress the array. If users want to compress primitive array, users need to register custom +serializers for such types or mark it as list type. + +Float array specifics: + +- float16/bfloat16 array: write `varuint` length, then raw bytes in little endian order. +- float8 array: write element type kind as a byte, then `varuint` length, then raw bytes in little endian order. + +#### Multi-dimensional arrays + +Current xlang does not define a dedicated multi-dimensional array/tensor encoding. Multi-dimensional +arrays are serialized as nested lists, while one-dimensional primitive arrays use the `*_ARRAY` +type IDs. Internal type ID `ARRAY (42)` is reserved for a future dedicated multi-dimensional array +encoding and is not used in current xlang streams. + +#### object array + +Object array is serialized using the list format. Object component type will be taken as list element +generic type. + +### map + +Map uses a chunk-based format to handle heterogeneous key-value pairs efficiently: + +``` +| varuint32: total_size | chunk_1 | chunk_2 | ... | chunk_n | +``` + +#### Map Chunk Format + +Each chunk contains up to 255 key-value pairs with the same metadata characteristics: + +``` +| 1 byte | 1 byte | variable bytes | ++--------------+----------------+------------------------------+ +| KV header | chunk size N | N key-value pairs (N*2 obj) | +``` + +#### KV Header Bits + +The KV header is a single byte encoding metadata for both keys and values: + +``` +| bit 7-6 | bit 5 | bit 4 | bit 3 | bit 2 | bit 1 | bit 0 | ++------------+---------------+--------------+---------------+---------------+--------------+---------------+ +| reserved | val_decl_type | val_has_null | val_track_ref | key_decl_type | key_has_null | key_track_ref | +``` + +| Bit | Name | Value | Meaning when SET (1) | +| --- | ------------- | ----- | ---------------------------------------- | +| 0 | key_track_ref | 0x01 | Track references for keys | +| 1 | key_has_null | 0x02 | Keys may be null (rare, usually invalid) | +| 2 | key_decl_type | 0x04 | Key is the declared generic type | +| 3 | val_track_ref | 0x08 | Track references for values | +| 4 | val_has_null | 0x10 | Values may be null | +| 5 | val_decl_type | 0x20 | Value is the declared generic type | + +**Common KV header values:** + +| Header | Hex | Meaning | +| ------ | --- | ------------------------------------------------------------------- | +| 0x24 | 36 | Key + value are declared types, non-null, no ref tracking (optimal) | +| 0x2C | 44 | Key + value declared types, value tracks refs | +| 0x34 | 52 | Key + value declared types, value may be null | +| 0x00 | 0 | Key + value not declared types, non-null, no ref tracking | + +#### Chunk Size + +- Maximum chunk size: 255 pairs (fits in 1 byte) +- When key or value is null, that entry is serialized as a separate chunk with implicit size 1 (chunk size byte is skipped) +- Reader tracks accumulated count against total map size to know when to stop reading chunks + +#### Why Chunk-Based Format? + +Map iteration is expensive. Computing a single header for all pairs would require two passes. The chunk-based +approach allows: + +1. **Optimistic prediction**: Use first key-value pair to predict header +2. **Adaptive chunking**: Start new chunk if prediction fails for a pair +3. **Efficient reading**: Most maps fit in single chunk (< 255 pairs) +4. **Memory efficiency**: Minimal overhead for common homogeneous maps + +#### Why serialize chunk by chunk? + +When fory will use first key-value pair to predict header optimistically, it can't know how many pairs have same +meta(tracking kef ref, key has null and so on). If we don't write chunk by chunk with max chunk size, we must write at +least `X` bytes to take up a place for later to update the number which has same elements, `X` is the num_bytes for +encoding varint encoding of map size. + +And most map size are smaller than 255, if all pairs have same data, the chunk will be 1. This is common in golang/rust, +which object are not reference by default. + +Also, if only one or two keys have different meta, we can make it into a different chunk, so that most pairs can share +meta. + +The implementation can accumulate read count with map size to decide whether to read more chunks. + +### enum + +Enums are serialized as an unsigned varint enum ID. + +- If the enum definition provides an explicit enum ID / variant ID / stable numeric tag for a + value, that ID MUST be used. +- If no explicit enum ID is specified, the declaration ordinal is used as the enum ID by default. + +This means the wire contract is always an enum ID. When the enum ID comes from declaration order, +reordering enum values changes the wire IDs and can change the deserialized result. For +cross-language or long-lived schemas, users should prefer explicit stable enum IDs. + +### timestamp + +Timestamp represents a point in time independent of any calendar/timezone. It is encoded as: + +- `seconds` (int64): seconds since Unix epoch (1970-01-01T00:00:00Z) +- `nanos` (uint32): nanosecond adjustment within the second + +On write, implementations must normalize negative timestamps so that `nanos` is always in `[0, 1_000_000_000)`. +This is a fixed-size 12-byte payload (8 bytes seconds + 4 bytes nanos). + +### date + +Date represents a date without timezone. It is encoded as: + +- `days` (varint64): signed count of days since the Unix epoch (`1970-01-01`) + +The value is reconstructed as `LocalDate.ofEpochDay(days)` or the equivalent calendar-date constructor in +the target language implementation. + +This `varint64` encoding applies to xlang serialization only. Native, language-specific local-date +encodings are unchanged. + +### decimal + +A decimal value is encoded as: + +1. `scale`: signed varint32 +2. `unscaledHeader`: unsigned varint64 +3. optional `payload`: present only for large unscaled values + +The mathematical value is: + +`value = unscaled × 10^-scale` + +#### Scale + +- `scale` is encoded as signed varint32. +- `scale` carries no extra flags or mode bits. + +#### Unscaled Header + +`unscaledHeader` selects the encoding of `unscaled`: + +- If `(unscaledHeader & 1) == 0`, the value uses the small encoding. +- If `(unscaledHeader & 1) == 1`, the value uses the big encoding. + +#### Small Encoding + +For small values, `unscaled` must fit in signed 64-bit range and the zigzag-encoded value must fit in 63 bits. + +Encoding: + +- `unscaledHeader = zigzag(unscaled) << 1` +- no payload is written + +Decoding: + +- `unscaled = zigzagDecode(unscaledHeader >>> 1)` + +#### Big Encoding + +For big values, `unscaled` is encoded as sign plus magnitude bytes. + +Encoding: + +- `sign = 0` if `unscaled >= 0`, otherwise `1` +- `magnitude = abs(unscaled)` +- `len = byte length of magnitude in canonical minimal little-endian form` +- `meta = (len << 1) | sign` +- `unscaledHeader = (meta << 1) | 1` +- `payload = magnitude as canonical minimal little-endian bytes` + +Decoding: + +- `meta = unscaledHeader >>> 1` +- `sign = meta & 1` +- `len = meta >>> 1` +- read `len` bytes as little-endian unsigned magnitude +- `unscaled = magnitude` if `sign == 0`, otherwise `-magnitude` + +#### Canonical Rules + +- Zero must use the small encoding. +- Big encoding must not be used for zero. +- In big encoding, `payload` must be the minimal little-endian representation. +- Therefore, for big encoding, `len > 0` and `payload[len - 1] != 0`. + +#### Final Value + +After decoding `scale` and `unscaled`, the decimal value is reconstructed as: + +`value = unscaled × 10^-scale` + +### struct + +Struct means object of `class/pojo/struct/bean/record` type. Struct values are serialized by writing +fields in Fory order. The type meta before the value is written according to the rules in +[Type Meta](#type-meta). + +#### Field order + +Field order must be deterministic and identical across languages. This section defines the +language-neutral ordering algorithm; implementations must follow the rules here rather than any +language-specific helper classes. + +##### Step 1: Field identifier + +For every field, compute a stable identifier used for ordering: + +- If a non-negative tag ID is configured (e.g., `@ForyField(id=...)`), use the tag ID. +- Otherwise, use the field name converted to `snake_case`. + +Configured tag IDs must be non-negative. A negative configured tag ID is invalid; languages may +use a negative value only as a default or internal sentinel for "no tag ID configured", which falls +back to the `snake_case` field name and is not a tag ID. Tag IDs must be unique within a type; +duplicate tag IDs are invalid. + +Field identifiers compare as follows: + +1. If both fields have tag IDs, compare the IDs numerically. +2. If only one field has a tag ID, the tagged field sorts first. +3. If neither field has a tag ID, compare the `snake_case` names lexicographically. +4. If fields still compare equal, use deterministic language-local tie-breakers such as declaring + class name, original field name, or original field index. + +##### Step 2: Group assignment + +Assign each field to exactly one group in the following order: + +1. **Primitive (non-nullable)**: primitive or boxed numeric/boolean types without nullable metadata. +2. **Primitive (nullable)**: primitive or boxed numeric/boolean types with nullable metadata. +3. **Non-primitive**: every other field, including strings, time/date/duration/decimal/binary + values, unions, primitive arrays, collections, maps, enums, structs, ext/user-defined types, + UNKNOWN fields, object arrays, and all other non-primitive schemas. + +##### Step 3: Intra-group ordering + +Within each group, apply the following sort keys in order until a difference is found: + +**Primitive groups (1 and 2):** + +1. **Compression category**: fixed-size numeric and boolean types first, then compressed numeric + types (`VARINT32`, `VAR_UINT32`, `VARINT64`, `VAR_UINT64`, `TAGGED_INT64`, `TAGGED_UINT64`). +2. **Primitive size** (descending): 8-byte > 4-byte > 2-byte > 1-byte. +3. **Internal type ID** (ascending) as a tie-breaker for equal sizes. +4. **Field identifier** using the comparator from Step 1. + +**Non-primitive group (3):** + +1. **Field identifier** using the comparator from Step 1. + +If two fields still compare equal after the rules above, preserve a deterministic order by +comparing declaring class name and then the original field name. This tie-breaker should be +reachable only in invalid schemas (e.g., duplicate tag IDs). + +##### Notes + +- The ordering above is used for serialization order and TypeDef field lists. Schema hashes use + the field identifier ordering described in the schema hash section. +- Non-primitive type IDs and codec categories must not affect field order. Implementations may keep + internal categories to preserve optimized serializers and generated code paths, but the categories + are not ordering keys. +- The compressed numeric rule is critical for cross-language consistency: compressed integer + fields are always placed after all fixed-width integer fields. + +#### Same-schema mode (meta share disabled) + +Object value layout: + +``` +| [optional 4-byte schema hash] | field values | +``` + +The schema hash is written only when class-version checking is enabled. It is the low 32 bits of a +MurmurHash3 x64_128 of the struct fingerprint string: + +- For each field, build `,;`. +- Field identifier is the tag ID if present, otherwise the snake_case field name. +- Sort by the field identifier comparator from [Field order](#field-order) before concatenation. +- `field_type_fingerprint` is recursive: + - Leaf: `,,` + - `LIST` / `SET`: `,,[]` + - `MAP`: `,,[|]` +- Nested container element/key/value fingerprints include nested type ID, container shape, and effective integer encoding, but nested `nullable` and `ref` policy are always hashed as `0`. Only the root field `nullable` and `ref` bits participate in schema hash, because nested reads honor the wire null/ref flags directly. +- This schema-hash rule is only for same-schema mode without TypeDef metadata. It + does not permit compatible-mode matched-field classification to accept nested + nullability or reference-tracking mismatches. + +Field values are serialized in Fory order. Primitive fields are written as raw values (nullable +primitives include a null flag). Non-primitive fields write ref/null flags as needed and then the +value; polymorphic fields include type meta. + +#### Compatible mode (meta share enabled) + +The field value layout is the same as same-schema mode, but the type meta for +`COMPATIBLE_STRUCT` and `NAMED_COMPATIBLE_STRUCT` uses shared TypeDef entries. Deserializers use +TypeDef to map fields by name or tag ID and to honor nullable/ref flags from metadata; unknown fields +are skipped. + +### Union + +Union values are encoded using three union type IDs so the union schema identity lives in type meta (like +`STRUCT/ENUM/EXT`) and is easy to carry inside `Any`. + +#### IDL syntax + +```fdl +union Contact [id=0] { + string email = 0; + int32 phone = 1; +} +``` + +Rules: + +- A union schema MUST declare at least one schema-defined alternative. The + unknown-case carrier used by some language bindings is implementation-provided and is + omitted from the schema's alternative table. +- Each union alternative MUST have a stable non-negative tag number (`= 0`, `= 1`, ...). +- Tag numbers MUST be unique within the union and MUST NOT be reused. +- Unknown-case carriers exposed by language bindings have no local schema tag of + their own; they replay the original peer schema tag when reserialized. + +#### Type IDs and type meta + +| Type ID | Name | Meaning | +| ------: | ----------- | ---------------------------------------------------- | +| 33 | UNION | Union value, schema identity not embedded | +| 34 | TYPED_UNION | Union value with registered numeric type ID | +| 35 | NAMED_UNION | Union value with embedded type name / shared TypeDef | + +Type meta encoding: + +- `UNION (33)`: no additional type meta payload. +- `TYPED_UNION (34)`: write `user_type_id` as varuint32 after the type ID. +- `NAMED_UNION (35)`: followed by named type meta (namespace + type name, or shared TypeDef marker/body). + +Field TypeDef metadata MUST use `UNION` for statically typed union fields, +including generated typed ADT union fields. It MUST NOT use `TYPED_UNION` or +`NAMED_UNION` there, because the field owner already supplies the union schema. + +#### Union value payload + +A union payload is: + +``` +| case_id (varuint32) | case_value (Any-style value) | +``` + +`case_id` is the union alternative tag number. +Runtime APIs MAY expose zero-based ordinal indexes for generic union carriers; +those ordinals are valid wire `case_id` values when they are the schema's +alternative IDs. + +`case_value` MUST be encoded as a full xlang value: + +``` +| field_ref_meta | field_value_type_meta | field_value_bytes | +``` + +This is required even for primitives so unknown alternatives can be skipped safely. + +If a reader sees a `case_id` that is not present in its local union +schema, it SHOULD preserve the unknown case when the target language has a +language-neutral carrier for it. Such a carrier MUST expose the original case +ID and decoded value, and it MUST retain only implementation-internal wire type ID +state needed for reserialization. It MUST NOT store resolver-owned type +metadata or other context-owned state. Writers MUST use the stored original +case ID for the union envelope, not any generated carrier marker. Unknown-case +payload writers MUST emit the Any-style payload body in wire order: ref +metadata first, then full value type metadata, then value bytes. For internal +numeric type IDs, the type ID byte is the complete value type metadata and the +payload writer MAY use the stored wire type ID to preserve fixed, variable, or +tagged integer encodings when the decoded value has the expected concrete value type. +These scalar numeric payloads are not reference-tracked, so their ref metadata +is `NotNullValue`. Otherwise it MUST fall back to the language implementation's +ordinary polymorphic Any-value writer. Unknown carriers are implementation-provided +forward-compatibility containers, not entries in the local schema case table; +schema-defined union cases MAY use `0..N`. When an unknown carrier is written +back, the union envelope MUST use the carrier's original peer schema case ID +unchanged, including `0` if that was the original peer schema case ID. + +#### Wire layouts + +**UNION (schema known from context)** + +``` +| ... outer ref meta ... | type_id=UNION(33) | case_id | case_value | +``` + +**TYPED_UNION (schema identified by numeric id)** + +``` +| ... outer ref meta ... | type_id=TYPED_UNION(34) | user_type_id | case_id | case_value | +``` + +user_type_id: varuint32 numeric registration ID for the union schema. + +**NAMED_UNION (schema embedded by name/typedef)** + +``` +| ... outer ref meta ... | type_id=NAMED_UNION(35) | name_or_typedef | case_id | case_value | +``` + +#### Decoding rules + +1. Read outer ref meta and `type_id`. +2. If `TYPED_UNION`, read `user_type_id` and resolve the union schema by ID. +3. If `NAMED_UNION`, read named type meta and resolve the union schema. +4. Read `case_id`. +5. Read `case_value` as Any-style value (ref meta + type meta + value). + +If `case_id` is unknown, the decoder MUST still consume the case value using `field_value_type_meta` and +standard `skipValue(type_id)`. + +#### When to use each type ID + +- Use `UNION` when the union schema is known from context. This includes + statically typed generated union fields: the owning field metadata supplies + the union schema, so the field type ID remains `UNION` even if the root or + dynamic value form would identify the union as `TYPED_UNION` or `NAMED_UNION`. +- Use `TYPED_UNION` for dynamic containers when numeric registration is available. +- Use `NAMED_UNION` when name-based resolution is preferred or required. + +#### Compatibility notes + +- `case_id` is a stable identifier; added alternatives are forward compatible and unknown cases can be skipped. + +### Type + +Type will be serialized using type meta format. + +## Common Pitfalls + +1. **Byte Order**: Always use little-endian for multi-byte values +2. **Varint Sign Extension**: Ensure proper handling of signed vs unsigned varints +3. **Reference ID Ordering**: IDs must be assigned in serialization order +4. **Field Order Consistency**: Must match exactly across languages in same-schema mode; in compatible mode, match by TypeDef field names or tag IDs +5. **String Encoding**: Use best encoding for current language +6. **Null Handling**: Different languages represent null differently +7. **Empty Collections**: Still write length (0) and header byte +8. **Schema Hash Calculation**: Must use the same fingerprint and MurmurHash3 algorithm across languages when enabled + +## Language Implementation Guidelines + +See [Xlang Implementation Guide](xlang_implementation_guide.md) documentation. diff --git a/versioned_docs/version-1.3.0/specification/xlang_type_mapping.md b/versioned_docs/version-1.3.0/specification/xlang_type_mapping.md new file mode 100644 index 00000000000..1e7976fa06e --- /dev/null +++ b/versioned_docs/version-1.3.0/specification/xlang_type_mapping.md @@ -0,0 +1,232 @@ +--- +title: Xlang Type Mapping +sidebar_position: 7 +id: xlang_type_mapping +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. +--- + +Note: + +- For type definition, see [Type Systems in Spec](xlang_serialization_spec.md#type-systems) +- `int16_t[n]/vector` indicates `int16_t[n]/vector` +- Xlang serialization is the portable wire format shared by Java, Python, C++, + Go, Rust, JavaScript/TypeScript, C#, Swift, Dart, Scala, and Kotlin. Keep type + IDs, names, schemas, and compatibility settings aligned across every peer. + +## User Type IDs + +When registering user types (struct/ext/enum/union), the internal type ID is written as the 8-bit +kind, and the user type ID is written separately as an unsigned varint32. There is no bit +shift/packing, and `user_type_id` can be in the range `0~0xFFFFFFFE`. + +**Examples:** + +| User ID | Type | Internal ID | Encoded User ID | Decimal | +| ------- | ----------------- | ----------- | --------------- | ------- | +| 0 | STRUCT | 27 | 0 | 0 | +| 0 | ENUM | 25 | 0 | 0 | +| 1 | STRUCT | 27 | 1 | 1 | +| 1 | COMPATIBLE_STRUCT | 28 | 1 | 1 | +| 2 | NAMED_STRUCT | 29 | 2 | 2 | + +When reading type IDs: + +- Read internal type ID from the type ID field. +- If the internal type is a user-registered kind, read `user_type_id` as varuint32. + +## Type Mapping + +The first column names the Fory schema expression or canonical wire tag. Scalar +encoding rows such as `fixed int32` and `tagged int64` are not FDL type names; +FDL spells them as an encoding modifier plus a semantic integer type. + +| Fory schema / wire tag | Fory Type ID | Java | Python | JavaScript/TypeScript | C++ | Go | Rust | C# | Swift | Dart | Scala | Kotlin | +| ---------------------------------- | ------------ | ----------------------------------------- | ----------------------------------------- | ------------------------------------- | --------------------------------------------------- | ---------------------------------------------- | --------------------------------- | ---------------------------------- | ------------------------ | --------------------------- | ------------------------------- | ---------------------- | +| bool | 1 | bool/Boolean | bool | Boolean | bool | bool | bool | bool | Bool | bool | Boolean | Boolean | +| int8 | 2 | byte/Byte | int/pyfory.Int8 | Type.int8() | int8_t | int8 | i8 | sbyte | Int8 | int + `Int8Type` | Byte | Byte | +| int16 | 3 | short/Short | int/pyfory.Int16 | Type.int16() | int16_t | int16 | i16 | short | Int16 | int + `Int16Type` | Short | Short | +| fixed int32 | 4 | int/Integer | int/pyfory.FixedInt32 | `Type.int32({ encoding: "fixed" })` | int32_t | int32 | i32 | int + `S.Fixed` | Int32 + `.fixed` | int + fixed metadata | Int + fixed metadata | `@Fixed Int` | +| int32 | 5 | int/Integer | int/pyfory.Int32 | Type.int32() | int32_t | int32 | i32 | int | Int32 | int + `Int32Type` | Int | Int | +| fixed int64 | 6 | long/Long | int/pyfory.FixedInt64 | `Type.int64({ encoding: "fixed" })` | int64_t | int64 | i64 | long + `S.Fixed` | Int64 + `.fixed` | Int64 + fixed metadata | Long + fixed metadata | `@Fixed Long` | +| int64 | 7 | long/Long | int/pyfory.Int64 | Type.int64() | int64_t | int64 | i64 | long | Int64 | int / Int64 | Long | Long | +| tagged int64 | 8 | long/Long | int/pyfory.TaggedInt64 | `Type.int64({ encoding: "tagged" })` | int64_t | int64 | i64 | long + `S.Tagged` | Int64 + `.tagged` | Int64 + tagged metadata | Long + tagged metadata | `@Tagged Long` | +| uint8 | 9 | short/Short | int/pyfory.UInt8 | Type.uint8() | uint8_t | uint8 | u8 | byte | UInt8 | int + `Uint8Type` | Int + unsigned metadata | UByte | +| uint16 | 10 | int/Integer | int/pyfory.UInt16 | Type.uint16() | uint16_t | uint16 | u16 | ushort | UInt16 | int + `Uint16Type` | Int + unsigned metadata | UShort | +| fixed uint32 | 11 | long/Long | int/pyfory.FixedUInt32 | `Type.uint32({ encoding: "fixed" })` | uint32_t | uint32 | u32 | uint + `S.Fixed` | UInt32 + `.fixed` | int + fixed uint32 metadata | Long + fixed unsigned metadata | `@Fixed UInt` | +| uint32 | 12 | long/Long | int/pyfory.UInt32 | Type.uint32() | uint32_t | uint32 | u32 | uint | UInt32 | int + `Uint32Type` | Long + unsigned metadata | UInt | +| fixed uint64 | 13 | long/Long | int/pyfory.FixedUInt64 | `Type.uint64({ encoding: "fixed" })` | uint64_t | uint64 | u64 | ulong + `S.Fixed` | UInt64 + `.fixed` | Uint64 + fixed metadata | Long + fixed unsigned metadata | `@Fixed ULong` | +| uint64 | 14 | long/Long | int/pyfory.UInt64 | Type.uint64() | uint64_t | uint64 | u64 | ulong | UInt64 | Uint64 | Long + unsigned metadata | ULong | +| tagged uint64 | 15 | long/Long | int/pyfory.TaggedUInt64 | `Type.uint64({ encoding: "tagged" })` | uint64_t | uint64 | u64 | ulong + `S.Tagged` | UInt64 + `.tagged` | Uint64 + tagged metadata | Long + tagged unsigned metadata | `@Tagged ULong` | +| float8 | 16 | / | / | / | / | / | / | / | / | / | / | / | +| float16 | 17 | Float16 | native float / pyfory.Float16 annotation | `number` | `fory::float16_t` | `float16.Float16` | `Float16` | Half | Float16 | double + `Float16Type` | Float16 | Float16 | +| bfloat16 | 18 | BFloat16 | native float / pyfory.BFloat16 annotation | `number` | `fory::bfloat16_t` | `bfloat16.BFloat16` | `BFloat16` | BFloat16 | BFloat16 | double + `Bfloat16Type` | BFloat16 | BFloat16 | +| float32 | 19 | float/Float | float/pyfory.Float32 | Type.float32() | float | float32 | f32 | float | Float | Float32 | Float | Float | +| float64 | 20 | double/Double | float/pyfory.Float64 | Type.float64() | double | float64 | f64 | double | Double | double | Double | Double | +| string | 21 | String | str | String | string | string | String/str | string | String | String | String | String | +| list | 22 | List/Collection | list/tuple | array | vector | slice | Vec | `List` | `[T]` | `List` | `List[T]` | `List` | +| set | 23 | Set | set | / | set | fory.Set | Set | `HashSet` | `Set` | `Set` | `Set[T]` | `Set` | +| map | 24 | Map | dict | Map | unordered_map | map | HashMap | `Dictionary` | `[K: V]` | `Map` | `Map[K, V]` | `Map` | +| enum | 25 | Enum subclasses | enum subclasses | / | enum | / | enum | `[ForyEnum]` enum | enum | enum | Scala 3 enum | enum class | +| named_enum | 26 | Enum subclasses | enum subclasses | / | enum | / | enum | `[ForyEnum]` enum | enum | enum | Scala 3 enum | enum class | +| struct | 27 | pojo/record | data class | object | struct/class | struct | struct | `[ForyStruct]` class/struct | @ForyStruct struct/class | @ForyStruct class | case class/class | data class/class | +| compatible_struct | 28 | pojo/record | data class | object | struct/class | struct | struct | `[ForyStruct]` class/struct | @ForyStruct struct/class | @ForyStruct class | case class/class | data class/class | +| named_struct | 29 | pojo/record | data class | object | struct/class | struct | struct | `[ForyStruct]` class/struct | @ForyStruct struct/class | @ForyStruct class | case class/class | data class/class | +| named_compatible_struct | 30 | pojo/record | data class | object | struct/class | struct | struct | `[ForyStruct]` class/struct | @ForyStruct struct/class | @ForyStruct class | case class/class | data class/class | +| ext | 31 | pojo/record | data class | object | struct/class | struct | struct | `[ForyStruct]` class/struct | @ForyStruct struct/class | @ForyStruct class | case class/class | data class/class | +| named_ext | 32 | pojo/record | data class | object | struct/class | struct | struct | `[ForyStruct]` class/struct | @ForyStruct struct/class | @ForyStruct class | case class/class | data class/class | +| union | 33 | Union | typing.Union | / | `std::variant` | / | tagged union enum | `[ForyUnion]` ADT record | tagged enum | @ForyUnion class | ADT enum | sealed class | +| none | 36 | null | None | null | `std::monostate` | nil | `()` | null | nil | null | null | null | +| duration | 37 | Duration | timedelta | Number | duration | Duration | Duration | TimeSpan | Duration | Duration | java.time.Duration | kotlin.time.Duration | +| timestamp | 38 | Instant | datetime | Number | std::chrono::nanoseconds | Time | Timestamp | DateTime/DateTimeOffset | Date | Timestamp | java.time.Instant | java.time.Instant | +| date | 39 | LocalDate | datetime.date | Date | fory::serialization::Date | fory.Date | Date | DateOnly | LocalDate | LocalDate | java.time.LocalDate | java.time.LocalDate | +| decimal | 40 | BigDecimal | Decimal | Decimal | fory::serialization::Decimal | fory.Decimal | fory::Decimal | decimal | Decimal | Decimal | java.math.BigDecimal | java.math.BigDecimal | +| binary | 41 | byte[] | bytes | / | `uint8_t[n]/vector` | `[n]uint8/[]T` | `Vec` | byte[] | Data | Uint8List | Array[Byte] | ByteArray | +| `array` (bool_array) | 43 | bool[] | BoolArray / ndarray(np.bool\_) | BoolArray / Type.boolArray() | `bool[n]` | `[n]bool/[]T` | `Vec` | bool[] | [Bool] + @ArrayField | BoolList | Array[Boolean] | BooleanArray | +| `array` (int8_array) | 44 | `@Int8Type byte[]` | Int8Array / ndarray(int8) | Type.int8Array() | `int8_t[n]/vector` | `[n]int8/[]T` | `Vec` | sbyte[] | [Int8] + @ArrayField | Int8List | Array[Byte] + metadata | ByteArray + @ArrayType | +| `array` (int16_array) | 45 | short[] | Int16Array / ndarray(int16) | Type.int16Array() | `int16_t[n]/vector` | `[n]int16/[]T` | `Vec` | short[] | [Int16] + @ArrayField | Int16List | Array[Short] | ShortArray | +| `array` (int32_array) | 46 | int[] | Int32Array / ndarray(int32) | Type.int32Array() | `int32_t[n]/vector` | `[n]int32/[]T` | `Vec` | int[] | [Int32] + @ArrayField | Int32List | Array[Int] | IntArray | +| `array` (int64_array) | 47 | long[] | Int64Array / ndarray(int64) | Type.int64Array() | `int64_t[n]/vector` | `[n]int64/[]T` | `Vec` | long[] | [Int64] + @ArrayField | Int64List | Array[Long] | LongArray | +| `array` (uint8_array) | 48 | `@UInt8Type byte[]` | UInt8Array / ndarray(uint8) | Type.uint8Array() | `uint8_t[n]/vector` | `[n]uint8/[]T` | `Vec` | byte[] | [UInt8] + @ArrayField | Uint8List | Array[Byte] + metadata | UByteArray | +| `array` (uint16_array) | 49 | `@UInt16Type short[]` | UInt16Array / ndarray(uint16) | Type.uint16Array() | `uint16_t[n]/vector` | `[n]uint16/[]T` | `Vec` | ushort[] | [UInt16] + @ArrayField | Uint16List | Array[Short] + metadata | UShortArray | +| `array` (uint32_array) | 50 | `@UInt32Type int[]` | UInt32Array / ndarray(uint32) | Type.uint32Array() | `uint32_t[n]/vector` | `[n]uint32/[]T` | `Vec` | uint[] | [UInt32] + @ArrayField | Uint32List | Array[Int] + metadata | UIntArray | +| `array` (uint64_array) | 51 | `@UInt64Type long[]` | UInt64Array / ndarray(uint64) | Type.uint64Array() | `uint64_t[n]/vector` | `[n]uint64/[]T` | `Vec` | ulong[] | [UInt64] + @ArrayField | Uint64List | Array[Long] + metadata | ULongArray | +| `array` (float8_array) | 52 | / | / | / | / | / | / | / | / | / | / | / | +| `array` (float16_array) | 53 | `Float16Array` / `@Float16Type short[]` | Float16Array / ndarray(float16) | Float16Array / Type.float16Array() | `fory::float16_t[n]/std::vector` | `[N]float16.Float16` / `[]float16.Float16` | `Vec` / `[Float16; N]` | Half[] / `S.Array` | [Float16] + @ArrayField | Float16List | Array[Short] + metadata | Float16Array | +| `array` (bfloat16_array) | 54 | `BFloat16Array` / `@BFloat16Type short[]` | BFloat16Array / ndarray(bfloat16) | BFloat16Array / Type.bfloat16Array() | `fory::bfloat16_t[n]/std::vector` | `[N]bfloat16.BFloat16` / `[]bfloat16.BFloat16` | `Vec` / `[BFloat16; N]` | BFloat16[] / `S.Array` | [BFloat16] + @ArrayField | Bfloat16List | Array[Short] + metadata | BFloat16Array | +| `array` (float32_array) | 55 | float[] | Float32Array / ndarray(float32) | Type.float32Array() | `float[n]/vector` | `[n]float32/[]T` | `Vec` | float[] | [Float] + @ArrayField | Float32List | Array[Float] | FloatArray | +| `array` (float64_array) | 56 | double[] | Float64Array / ndarray(float64) | Type.float64Array() | `double[n]/vector` | `[n]float64/[]T` | `Vec` | double[] | [Double] + @ArrayField | Float64List | Array[Double] | DoubleArray | + +Notes: + +- Python `pyfory.Float16` and `pyfory.BFloat16` are reserved annotation markers; scalar values deserialize as native Python `float`. +- Python `BoolArray`, `Int8Array`, `Int16Array`, `Int32Array`, `Int64Array`, `UInt8Array`, `UInt16Array`, `UInt32Array`, `UInt64Array`, `Float16Array`, `BFloat16Array`, `Float32Array`, and `Float64Array` are public dense-array wrappers with list-like sequence behavior. +- JavaScript `BoolArray`, fallback `Float16Array`, and `BFloat16Array` are public dense-array wrappers backed by `Uint8Array` or `Uint16Array`. Scalar `float16` and `bfloat16` values use `number`. A JavaScript environment with native `Float16Array` may return that native carrier for `array`. +- Java plain `byte[]` maps to `binary`. Numeric byte arrays use type-use annotations: + `@Int8Type byte[]` for `array` and `@UInt8Type byte[]` for `array`. +- Dart uses `double` plus `Float16Type` or `Bfloat16Type` metadata for scalar + `float16` and `bfloat16`, `BoolList` for `array`, typed-data lists for integer/float32/float64 arrays, and + `Float16List` / `Bfloat16List` for `array` / `array`. Plain Dart `List` + maps to `list` unless a field uses `@ArrayField(element: BoolType())` or + `@ForyField(type: ArrayType(element: BoolType()))` with a `BoolList` carrier. +- `Float16[]` and `BFloat16[]` remain object arrays in xlang mode and serialize with the `list` wire type. +- `ARRAY (42)` is reserved for a future dedicated multi-dimensional array encoding and is not part + of the current xlang type-mapping surface. +- Current xlang uses `*_ARRAY` for one-dimensional primitive arrays and nested `list` for + multi-dimensional arrays. +- Kotlin KSP xlang maps `UByte`, `UShort`, `UInt`, and `ULong` to `uint8`, + `uint16`, `uint32`, and `uint64`. Kotlin primitive and unsigned array + carriers map to dense arrays. `ByteArray` maps to `binary` by default and to + `array` when its type use is marked with Fory `ArrayType`. + `array` and `array` use Java core `Float16Array` and + `BFloat16Array`. +- Kotlin xlang `duration` uses `kotlin.time.Duration`. Infinite values are not + representable by the xlang duration payload and must raise a serialization + error. +- `list` and `array` remain distinct schema kinds. In schema-compatible struct/class field + matching only, a direct top-level `list` field may be read as a direct top-level `array` + field, and a direct top-level `array` field may be read as a direct top-level `list` field, + when `T` is one of the dense bool/numeric array domains. Integer list element encodings in the + same signedness and width domain match the corresponding dense array element domain. The rule does + not apply inside nested collection, map, array, union, or generic positions. A peer `list` + element schema is still a compatible schema match for a local `array` field; if the actual + payload contains a null element, the dense-array reader raises a compatible-read error instead of + coercing the value. Reference-tracked list-element framing is separate from nullable element + schema and may be rejected during compatible field classification when the local matched field is + `array` and the runtime cannot materialize it without generic/reference paths. +- `binary` and `array` remain distinct schema kinds. In schema-compatible struct/class field + matching only, a direct top-level `binary` field may be read as a direct top-level `array` + field and the reverse may be read as the same byte sequence. This rule does not apply inside + nested collection, map, array, union, or generic positions, and it does not include `array`. +- The table above remains the canonical xlang schema mapping. Compatible readers may apply the + scalar field adaptation rules defined by `xlang_serialization_spec.md` during schema-compatible + struct/class field matching. Those rules do not change TypeDef metadata, dynamic root type + mapping, same-schema mode, or nested collection/map/array/union/generic positions. + +### Scala IDL Mapping + +The Scala schema IDL target emits Scala 3 source only. The `fory-scala` artifact remains cross-built +for Scala 2.13 and Scala 3. + +| Fory schema kind | Scala generated carrier | +| ------------------------------------- | ---------------------------------------------------------------------------------------- | +| `optional T` | `Option[T]` | +| `bool` | `Boolean` | +| `int8`, `int16`, `int32`, `int64` | `Byte`, `Short`, `Int`, `Long` | +| `uint8`, `uint16`, `uint32`, `uint64` | `Int`, `Int`, `Long`, `Long` plus unsigned Fory type metadata | +| `float16`, `bfloat16` | JVM `Float16` and `BFloat16` carriers | +| `float32`, `float64` | `Float`, `Double` | +| `string` | `String` | +| `binary` | `Array[Byte]` | +| `list`, `set`, `map` | `List[T]`, `Set[T]`, `Map[K, V]` | +| `array` | `Array[Boolean]` | +| `array`, `array` | `Array[Byte]` with signed/unsigned descriptor metadata | +| `array`, `array` | `Array[Short]` with signed/unsigned descriptor metadata | +| `array`, `array` | `Array[Int]` with signed/unsigned descriptor metadata | +| `array`, `array` | `Array[Long]` with signed/unsigned descriptor metadata | +| `array`, `array` | `Array[Short]` with reduced-precision descriptor metadata | +| `array`, `array` | `Array[Float]`, `Array[Double]` | +| `date`, `timestamp`, `duration` | `java.time.LocalDate`, `java.time.Instant`, `java.time.Duration` | +| `decimal` | `java.math.BigDecimal` | +| `message` | Scala 3 `case class` by default; normal class only for message/union construction cycles | +| `enum` | Scala 3 `enum` with stable Fory enum IDs on case-level `@ForyEnumId` annotations | +| `union` | Scala 3 ADT `enum derives ForySerializer` | +| `any` | `AnyRef` | + +Generated Scala descriptor metadata is produced by Scala 3 macro derivation +from Scala compile-time types, including nested generics, `Option`, arrays, +scalar encoding annotations, nullability, and `@Ref`. Java reflection is not the +source of truth for generated Scala TypeDef metadata. Scala `@Ref` metadata is +represented by the shared `org.apache.fory.annotation.Ref` annotation; `@Ref` +is the JVM owner for reference tracking metadata. + +## Type info + +Due to differences between type systems of languages, those types can't be mapped one-to-one between languages. + +If one host-language type corresponds to multiple Fory scalar encodings, for +example Java `long` can represent fixed, varint, or tagged `int64`, the user +must provide encoding metadata when the default is not the intended schema. + +## Type annotation + +If the type is a field of another class, users can provide meta hints for fields of a type, or for the whole type. +Such information can be provided in other languages too: + +- java: use annotation. +- cpp: use macro and template. +- golang: use struct tag. +- python: use typehint. +- rust: use macro. + +Here is en example: + +- Java: + + ```java + class Foo { + private @Int32Type int f1; + private List<@Int32Type Integer> f2; + } + ``` + +- Python: + + ```python + class Foo: + f1: pyfory.Int32 + f2: List[pyfory.Int32] + ``` diff --git a/versioned_docs/version-1.3.0/start/_category_.json b/versioned_docs/version-1.3.0/start/_category_.json new file mode 100644 index 00000000000..26f92c93a6a --- /dev/null +++ b/versioned_docs/version-1.3.0/start/_category_.json @@ -0,0 +1,4 @@ +{ + "position": 2, + "label": "Start" +} diff --git a/versioned_docs/version-1.3.0/start/install.md b/versioned_docs/version-1.3.0/start/install.md new file mode 100644 index 00000000000..76f33dad995 --- /dev/null +++ b/versioned_docs/version-1.3.0/start/install.md @@ -0,0 +1,177 @@ +--- +id: install +title: Install +sidebar_position: 0 +--- + +Apache Fory™ releases are available both as source artifacts and language-specific packages. + +For source downloads, see the Apache Fory™ [download](https://fory.apache.org/download) page. + +## Java + +Use Maven to add Apache Fory™: + +```xml + + org.apache.fory + fory-core + 1.3.0 + + + + + +``` + +## Scala + +Scala 2.13 with Maven: + +```xml + + org.apache.fory + fory-scala_2.13 + 1.3.0 + +``` + +Scala 3 with Maven: + +```xml + + org.apache.fory + fory-scala_3 + 1.3.0 + +``` + +Scala 2.13 with sbt: + +```sbt +libraryDependencies += "org.apache.fory" % "fory-scala_2.13" % "1.3.0" +``` + +Scala 3 with sbt: + +```sbt +libraryDependencies += "org.apache.fory" % "fory-scala_3" % "1.3.0" +``` + +## Kotlin + +Add Apache Fory™ Kotlin with Maven: + +```xml + + org.apache.fory + fory-kotlin + 1.3.0 + +``` + +## Python + +```bash +python -m pip install --upgrade pip +pip install pyfory==1.3.0 +``` + +## Go + +Use the full Go module path `github.com/apache/fory/go/fory`: + +```bash +go get github.com/apache/fory/go/fory@v1.3.0 +``` + +If your Go proxy has not picked up the new submodule tag yet, retry later or use `GOPROXY=direct` temporarily. + +## Rust + +```toml +[dependencies] +fory = "1.3.0" +``` + +Or use `cargo add`: + +```bash +cargo add fory@1.3.0 +``` + +## JavaScript / TypeScript + +Install the published JavaScript package from npm: + +```bash +npm install @apache-fory/core +``` + +Optional native acceleration requires Node.js 20+: + +```bash +npm install @apache-fory/hps +``` + +## Dart + +Add Apache Fory™ Dart to `pubspec.yaml`: + +```yaml +dependencies: + fory: ^1.3.0 + +dev_dependencies: + build_runner: ^2.4.13 +``` + +Generate serializers after defining annotated types: + +```bash +dart run build_runner build --delete-conflicting-outputs +``` + +## C\# + +Install the `Apache.Fory` NuGet package. It includes both the runtime and the source generator for `[ForyObject]` types. + +```bash +dotnet add package Apache.Fory --version 1.3.0 +``` + +```xml + + + +``` + +## Swift + +Add Apache Fory™ from the GitHub repository with Swift Package Manager: + +```swift +dependencies: [ + .package(url: "https://github.com/apache/fory.git", exact: "1.3.0") +], +targets: [ + .target( + name: "MyApp", + dependencies: [ + .product(name: "Fory", package: "fory") + ] + ) +] +``` diff --git a/versioned_docs/version-1.3.0/start/usage.md b/versioned_docs/version-1.3.0/start/usage.md new file mode 100644 index 00000000000..58481ca792e --- /dev/null +++ b/versioned_docs/version-1.3.0/start/usage.md @@ -0,0 +1,486 @@ +--- +id: usage +title: Usage +sidebar_position: 1 +--- + +This section provides quick examples for getting started with Apache Fory™. + +## Choose A Mode + +Apache Fory™ has two wire modes: + +- **Xlang mode** is the default and the portable format for payloads shared across languages. Use it for cross-language services and for runtimes that expose only xlang mode: Dart, JavaScript/TypeScript, C#, and Swift. +- **Native mode** is selected with `xlang=false` or the equivalent builder option in Java, Scala, Kotlin, Python, C++, Go, and Rust. Use it for same-language traffic because it follows the runtime's native type system, supports a broader language-specific object surface, and is optimized for that runtime. + +Xlang/default usage uses schema-compatible mode by default. Native mode uses schema-consistent payloads by default unless compatible mode is enabled explicitly. + +## Xlang Mode + +Use xlang mode when bytes need to cross runtime boundaries. Register custom types with the same numeric ID or namespace/type name on every peer. + +Dual-mode runtimes set the xlang option explicitly in the examples below. Dart, JavaScript/TypeScript, C#, and Swift are xlang-only, so their examples do not show an xlang switch. + +### Java + +```java +import org.apache.fory.Fory; + +public class XlangExample { + public record Person(String name, int age) {} + + public static void main(String[] args) { + Fory fory = Fory.builder() + .withXlang(true) + .build(); + fory.register(Person.class, "example", "Person"); + + Person person = new Person("chaokunyang", 28); + byte[] bytes = fory.serialize(person); + Person result = (Person) fory.deserialize(bytes); + System.out.println(result.name() + " " + result.age()); + } +} +``` + +### Python + +```python +from dataclasses import dataclass +import pyfory + +@dataclass +class Person: + name: str + age: pyfory.Int32 + +fory = pyfory.Fory(xlang=True) +fory.register(Person, typename="example.Person") + +person = Person(name="chaokunyang", age=28) +data = fory.serialize(person) +result = fory.deserialize(data) +print(result.name, result.age) +``` + +### Dart + +```dart +import 'package:fory/fory.dart'; + +part 'person.fory.dart'; + +@ForyStruct() +class Person { + Person(); + + String name = ''; + + @ForyField(type: Int32Type()) + int age = 0; +} + +void main() { + final fory = Fory(); + PersonFory.register( + fory, + Person, + namespace: 'example', + typeName: 'Person', + ); + + final person = Person() + ..name = 'chaokunyang' + ..age = 28; + + final bytes = fory.serialize(person); + final result = fory.deserialize(bytes); + print('${result.name} ${result.age}'); +} +``` + +### Go + +```go +package main + +import ( + "fmt" + + "github.com/apache/fory/go/fory" +) + +type Person struct { + Name string + Age int32 +} + +func main() { + f := fory.New(fory.WithXlang(true)) + if err := f.RegisterStruct(Person{}, 1); err != nil { + panic(err) + } + + person := &Person{Name: "chaokunyang", Age: 28} + data, err := f.Serialize(person) + if err != nil { + panic(err) + } + + var result Person + if err := f.Deserialize(data, &result); err != nil { + panic(err) + } + + fmt.Printf("%s %d\n", result.Name, result.Age) +} +``` + +### Rust + +```rust +use fory::{Error, Fory, ForyObject}; + +#[derive(ForyObject, Debug, PartialEq)] +struct Person { + name: String, + age: i32, +} + +fn main() -> Result<(), Error> { + let mut fory = Fory::builder().xlang(true).build(); + fory.register_by_name::("example", "Person")?; + + let person = Person { + name: "chaokunyang".to_string(), + age: 28, + }; + + let bytes = fory.serialize(&person)?; + let result: Person = fory.deserialize(&bytes)?; + assert_eq!(person, result); + Ok(()) +} +``` + +### C++ + +```cpp +#include +#include + +#include "fory/serialization/fory.h" + +using namespace fory::serialization; + +struct Person { + std::string name; + int32_t age; + + bool operator==(const Person &other) const { + return name == other.name && age == other.age; + } + + FORY_STRUCT(Person, name, age); +}; + +int main() { + auto fory = Fory::builder().xlang(true).build(); + fory.register_struct(1); + + Person person{"chaokunyang", 28}; + auto bytes = fory.serialize(person).value(); + auto result = fory.deserialize(bytes).value(); + assert(person == result); + return 0; +} +``` + +### Scala + +```scala +import org.apache.fory.Fory +import org.apache.fory.scala.ForyScala + +case class Person(name: String, age: Int) + +object Example { + def main(args: Array[String]): Unit = { + val fory: Fory = ForyScala.builder() + .withXlang(true) + .build() + fory.register(classOf[Person]) + + val bytes = fory.serialize(Person("chaokunyang", 28)) + val result = fory.deserialize(bytes).asInstanceOf[Person] + println(s"${result.name} ${result.age}") + } +} +``` + +### Kotlin + +```kotlin +import org.apache.fory.ThreadSafeFory +import org.apache.fory.kotlin.ForyKotlin + +data class Person(val name: String, val age: Int) + +fun main() { + val fory: ThreadSafeFory = ForyKotlin.builder() + .withXlang(true) + .requireClassRegistration(true) + .buildThreadSafeFory() + fory.register(Person::class.java) + + val bytes = fory.serialize(Person("chaokunyang", 28)) + val result = fory.deserialize(bytes) as Person + println("${result.name} ${result.age}") +} +``` + +### JavaScript / TypeScript + +```typescript +import Fory, { Type } from "@apache-fory/core"; + +const personType = Type.struct( + { typeName: "example.Person" }, + { + name: Type.string(), + age: Type.int32(), + }, +); + +const fory = new Fory(); +const { serialize, deserialize } = fory.register(personType); + +const payload = serialize({ name: "chaokunyang", age: 28 }); +const result = deserialize(payload); +console.log(result); +``` + +### C\# + +```csharp +using Apache.Fory; + +[ForyObject] +public sealed class Person +{ + public string Name { get; set; } = string.Empty; + public int Age { get; set; } +} + +Fory fory = Fory.Builder().Build(); +fory.Register(1); + +Person person = new() { Name = "chaokunyang", Age = 28 }; +byte[] data = fory.Serialize(person); +Person result = fory.Deserialize(data); + +Console.WriteLine($"{result.Name} {result.Age}"); +``` + +### Swift + +```swift +import Fory + +@ForyStruct +struct Person: Equatable { + var name: String = "" + var age: Int32 = 0 +} + +let fory = Fory() +fory.register(Person.self, id: 1) + +let person = Person(name: "chaokunyang", age: 28) +let data = try fory.serialize(person) +let result: Person = try fory.deserialize(data) + +print("\(result.name) \(result.age)") +``` + +For more cross-language rules and examples, see: + +- [Cross-Language Serialization Guide](../guide/xlang/index.md) +- [Java Guide](../guide/java/index.md) +- [Python Guide](../guide/python/index.md) +- [Dart Guide](../guide/dart/index.md) +- [Go Guide](../guide/go/index.md) +- [Rust Guide](../guide/rust/index.md) +- [C++ Guide](../guide/cpp/index.md) +- [C# Guide](../guide/csharp/index.md) +- [Swift Guide](../guide/swift/index.md) + +## Native Mode + +Use native mode only when every reader and writer is the same runtime family. Native mode supports broader language-specific object models than portable xlang mappings and is optimized for the owning runtime. + +Java and Python native modes are first-class same-language entry points. Use Java native mode when replacing JDK serialization, Kryo, FST, Hessian, or Java-only Protocol Buffers payloads. Use Python native mode when replacing `pickle` or `cloudpickle` for Python-only payloads. + +Dart, JavaScript/TypeScript, C#, and Swift do not expose native mode. + +### Java + +```java +Fory fory = Fory.builder() + .withXlang(false) + .requireClassRegistration(true) + .build(); +``` + +Register Java classes and use `serialize` / `deserialize` as usual. See the [Java Guide](../guide/java/index.md) for Java object hooks, `Externalizable`, dynamic object graphs, object copy, and Java native-mode zero-copy buffers. + +### Python + +```python +import pyfory + +fory = pyfory.Fory(xlang=False, ref=False, strict=True) +``` + +Register Python classes and use `serialize` / `deserialize` as usual. See the [Python Guide](../guide/python/index.md) for native-mode pickle replacement behavior and security settings. + +### Go + +```go +f := fory.New(fory.WithXlang(false)) +``` + +Use native mode for Go-only structs, pointers, interfaces, and Go-specific type behavior. See the [Go Guide](../guide/go/index.md) for struct tags and native-mode configuration. + +### Rust + +```rust +let mut fory = Fory::builder().xlang(false).build(); +``` + +Use native mode for Rust-only payloads that rely on Rust-specific object behavior. See the [Rust Guide](../guide/rust/index.md) for derive, references, and supported types. + +### C++ + +```cpp +auto fory = Fory::builder().xlang(false).build(); +``` + +Use native mode for C++-only traffic that does not need portable xlang type mappings. See the [C++ Guide](../guide/cpp/index.md) for `FORY_STRUCT`, configuration, and schema metadata. + +### Scala + +```scala +val fory = ForyScala.builder() + .withXlang(false) + .build() +``` + +Use native mode for Scala/JVM-only traffic that needs Scala case classes, collections, tuples, options, or enums on the JVM runtime path. See the [Scala Guide](../guide/scala/index.md). + +### Kotlin + +```kotlin +val fory = ForyKotlin.builder() + .withXlang(false) + .requireClassRegistration(true) + .buildThreadSafeFory() +``` + +Use native mode for Kotlin/JVM-only traffic that needs Kotlin data classes, nullable types, ranges, unsigned values, or Kotlin collections on the JVM runtime path. See the [Kotlin Guide](../guide/kotlin/index.md). + +## Row Format Encoding + +Row format provides zero-copy random access to serialized data, making it ideal for analytics workloads and data processing pipelines. + +### Java + +```java +import org.apache.fory.format.*; +import java.util.*; +import java.util.stream.*; + +public class Bar { + String f1; + List f2; +} + +public class Foo { + int f1; + List f2; + Map f3; + List f4; +} + +RowEncoder encoder = Encoders.bean(Foo.class); +Foo foo = new Foo(); +foo.f1 = 10; +foo.f2 = IntStream.range(0, 1000000).boxed().collect(Collectors.toList()); +foo.f3 = IntStream.range(0, 1000000).boxed().collect(Collectors.toMap(i -> "k"+i, i -> i)); + +List bars = new ArrayList<>(1000000); +for (int i = 0; i < 1000000; i++) { + Bar bar = new Bar(); + bar.f1 = "s" + i; + bar.f2 = LongStream.range(0, 10).boxed().collect(Collectors.toList()); + bars.add(bar); +} +foo.f4 = bars; + +// Serialize to row format (can be zero-copy read by Python) +BinaryRow binaryRow = encoder.toRow(foo); + +// Deserialize entire object +Foo newFoo = encoder.fromRow(binaryRow); + +// Zero-copy access to nested fields without full deserialization +BinaryArray binaryArray2 = binaryRow.getArray(1); // Access f2 field +BinaryArray binaryArray4 = binaryRow.getArray(3); // Access f4 field +BinaryRow barStruct = binaryArray4.getStruct(10); // Access 11th Bar element +long value = barStruct.getArray(1).getInt64(5); // Access nested value + +// Partial deserialization +RowEncoder barEncoder = Encoders.bean(Bar.class); +Bar newBar = barEncoder.fromRow(barStruct); +Bar newBar2 = barEncoder.fromRow(binaryArray4.getStruct(20)); +``` + +### Python + +```python +from dataclasses import dataclass +from typing import List, Dict +import pyarrow as pa +import pyfory + +@dataclass +class Bar: + f1: str + f2: List[pa.int64] + +@dataclass +class Foo: + f1: pa.int32 + f2: List[pa.int32] + f3: Dict[str, pa.int32] + f4: List[Bar] + +encoder = pyfory.encoder(Foo) +foo = Foo( + f1=10, + f2=list(range(1000_000)), + f3={f"k{i}": i for i in range(1000_000)}, + f4=[Bar(f1=f"s{i}", f2=list(range(10))) for i in range(1000_000)] +) + +# Serialize to row format +binary: bytes = encoder.to_row(foo).to_bytes() + +# Zero-copy random access without full deserialization +foo_row = pyfory.RowData(encoder.schema, binary) +print(foo_row.f2[100000]) # Access element directly +print(foo_row.f4[100000].f1) # Access nested field +print(foo_row.f4[200000].f2[5]) # Access deeply nested field +``` + +For more details on row format, see [Java Row Format Guide](../guide/java/row-format.md) or [Python Row Format Guide](../guide/python/row-format.md). diff --git a/versioned_sidebars/version-1.3.0-sidebars.json b/versioned_sidebars/version-1.3.0-sidebars.json new file mode 100644 index 00000000000..da2589bd7c1 --- /dev/null +++ b/versioned_sidebars/version-1.3.0-sidebars.json @@ -0,0 +1,60 @@ +{ + "docsSidebar": [ + { + "type": "category", + "label": "Introduction", + "items": [ + { + "type": "autogenerated", + "dirName": "introduction" + } + ], + "collapsed": false + }, + { + "type": "category", + "label": "Start", + "items": [ + { + "type": "autogenerated", + "dirName": "start" + } + ], + "collapsed": false + }, + { + "type": "category", + "label": "Schema IDL & Compiler", + "items": [ + { + "type": "autogenerated", + "dirName": "compiler" + } + ], + "collapsed": true + }, + { + "type": "category", + "label": "Guide", + "items": [ + { + "type": "autogenerated", + "dirName": "guide" + } + ], + "collapsed": false + } + ], + "specificationSidebar": [ + { + "type": "autogenerated", + "dirName": "specification" + } + ], + "communitySidebar": [ + { + "type": "autogenerated", + "dirName": "community" + } + ] +} diff --git a/versions.json b/versions.json index ce074f359a3..6c3e86d2f61 100644 --- a/versions.json +++ b/versions.json @@ -1,4 +1,5 @@ [ + "1.3.0", "1.2.0", "1.1.0", "1.0.0",