You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fory.apache.org by ch...@apache.org on 2026/07/27 03:02:49 UTC
(fory-site) branch main updated (0310b1129d -> 10fe5918ef)
This is an automated email from the ASF dual-hosted git repository.
chaokunyang pushed a change to branch main
in repository https://gitbox.apache.org/repos/asf/fory-site.git
from 0310b1129d 🔄 synced local 'docs/guide/' with remote 'docs/guide/'
new 29c8070ce0 🔄 synced local 'docs/guide/' with remote 'docs/guide/'
new 10fe5918ef 🔄 synced local 'docs/specification/' with remote 'docs/specification/'
The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
docs/guide/dart/code-generation.md | 12 +-
docs/guide/dart/custom-serializers.md | 18 +-
docs/guide/dart/external-types.md | 310 +++++++++++++++++++++++
docs/guide/dart/grpc-support.md | 2 +-
docs/guide/dart/index.md | 32 +--
docs/guide/dart/schema-evolution.md | 7 +-
docs/guide/dart/schema-metadata.md | 6 +-
docs/guide/dart/supported-types.md | 6 +-
docs/guide/dart/troubleshooting.md | 22 +-
docs/guide/dart/type-registration.md | 25 +-
docs/guide/dart/web-platform-support.md | 7 +-
docs/guide/dart/xlang-serialization.md | 29 ++-
docs/specification/xlang_implementation_guide.md | 34 +++
13 files changed, 471 insertions(+), 39 deletions(-)
create mode 100644 docs/guide/dart/external-types.md
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@fory.apache.org
For additional commands, e-mail: commits-help@fory.apache.org
(fory-site) 01/02: 🔄 synced local 'docs/guide/' with remote 'docs/guide/'
Posted by ch...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
chaokunyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fory-site.git
commit 29c8070ce0b18cf75e8bd63781beffc0faf7dad8
Author: chaokunyang <sh...@gmail.com>
AuthorDate: Mon Jul 27 03:02:44 2026 +0000
🔄 synced local 'docs/guide/' with remote 'docs/guide/'
---
docs/guide/dart/code-generation.md | 12 +-
docs/guide/dart/custom-serializers.md | 18 +-
docs/guide/dart/external-types.md | 310 ++++++++++++++++++++++++++++++++
docs/guide/dart/grpc-support.md | 2 +-
docs/guide/dart/index.md | 32 ++--
docs/guide/dart/schema-evolution.md | 7 +-
docs/guide/dart/schema-metadata.md | 6 +-
docs/guide/dart/supported-types.md | 6 +-
docs/guide/dart/troubleshooting.md | 22 ++-
docs/guide/dart/type-registration.md | 25 ++-
docs/guide/dart/web-platform-support.md | 7 +-
docs/guide/dart/xlang-serialization.md | 29 ++-
12 files changed, 437 insertions(+), 39 deletions(-)
diff --git a/docs/guide/dart/code-generation.md b/docs/guide/dart/code-generation.md
index 09d8ee7df9..01624e3007 100644
--- a/docs/guide/dart/code-generation.md
+++ b/docs/guide/dart/code-generation.md
@@ -52,6 +52,10 @@ class User {
Enums defined in the same file are automatically included in the generated registration.
+For a class owned by another library, define an
+[external structural serializer](external-types.md) with
+`@ForyStruct(target: ExternalType)`.
+
## Step 2 — Run the Generator
From the directory that contains your `pubspec.yaml`:
@@ -103,12 +107,16 @@ class Event {
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
+## Choosing Generated or Manual Serialization
-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.
+Use an [external structural serializer](external-types.md) when another
+package's class exposes matching public getters and a safe public construction
+path. Use a [manual serializer](custom-serializers.md) when the wire body,
+field names, values, or construction need custom logic.
## Related Topics
- [Type Registration](type-registration.md)
+- [External-Type Serialization](external-types.md)
- [Schema Metadata](schema-metadata.md)
- [Schema Evolution](schema-evolution.md)
diff --git a/docs/guide/dart/custom-serializers.md b/docs/guide/dart/custom-serializers.md
index 4e76736411..14c677fde5 100644
--- a/docs/guide/dart/custom-serializers.md
+++ b/docs/guide/dart/custom-serializers.md
@@ -1,6 +1,6 @@
---
-title: Custom Serializers
-sidebar_position: 9
+title: Manual Serializers
+sidebar_position: 10
id: custom_serializers
license: |
Licensed to the Apache Software Foundation (ASF) under one or more
@@ -19,13 +19,18 @@ license: |
limitations under the License.
---
-A custom serializer lets you control exactly how a type is encoded and decoded. You only need one when:
+A manual serializer lets you control exactly how a type is encoded and decoded.
+Use one when:
-- the type comes from a package you cannot modify and cannot be annotated with `@ForyStruct()`
- you need a completely custom binary layout
+- the target requires field-name translation or value conversion
+- the target exposes only factory or private construction
+- the target's complete state is not available through matching public members
- you are implementing a union/discriminated type
-For your own models, `@ForyStruct()` with code generation is almost always the better choice.
+Use `@ForyStruct()` for your own models. For a structurally matching class in
+another package, use an
+[external structural serializer](external-types.md).
## Implement `Serializer<T>`
@@ -113,7 +118,7 @@ final class ShapeSerializer extends UnionSerializer<Shape> {
}
```
-## Circular References in Custom Serializers
+## Circular References in Manual Serializers
If your serializer can encounter circular object graphs, bind the object to the reference tracker **before** reading its nested fields:
@@ -134,5 +139,6 @@ Skipping this step causes back-references to that object to resolve to `null`.
## Related Topics
- [Type Registration](type-registration.md)
+- [External-Type Serialization](external-types.md)
- [Xlang Serialization](xlang-serialization.md)
- [Troubleshooting](troubleshooting.md)
diff --git a/docs/guide/dart/external-types.md b/docs/guide/dart/external-types.md
new file mode 100644
index 0000000000..0d9e96c6c5
--- /dev/null
+++ b/docs/guide/dart/external-types.md
@@ -0,0 +1,310 @@
+---
+title: External-Type Serialization
+sidebar_position: 4
+id: external_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.
+---
+
+External-type serialization generates a Fory struct serializer for a class
+owned by another Dart library or package. Define a local serializer declaration
+with the same fields and name the external class with `ForyStruct.target`.
+
+## Define an External Structural Serializer
+
+Suppose a dependency owns this class:
+
+```dart
+final class User {
+ const User({
+ required this.name,
+ required this.age,
+ });
+
+ final String name;
+ final int age;
+}
+```
+
+Add the serializer declaration in your package:
+
+```dart
+import 'package:fory/fory.dart';
+import 'package:third_party/models.dart' as third_party;
+
+part 'external_serializers.fory.dart';
+
+@ForyStruct(target: third_party.User)
+abstract final class UserSerializer {
+ @ForyField(id: 1)
+ late final String name;
+
+ @ForyField(id: 2, type: Int32Type())
+ late final int age;
+}
+```
+
+The declaration must be `abstract final`, cannot have type parameters, and
+must declare each schema field as `late final` without an initializer. Each
+field name and Dart type, including nullability and generic arguments, must
+exactly match an accessible getter on the target.
+
+Run the generator as usual:
+
+```bash
+dart run build_runner build --delete-conflicting-outputs
+```
+
+## Register and Use the Target
+
+Register the external target through the generated module:
+
+```dart
+final fory = Fory();
+ExternalSerializersForyModule.register(
+ fory,
+ third_party.User,
+ name: 'example.User',
+);
+
+final bytes = fory.serialize(
+ const third_party.User(name: 'Ada', age: 36),
+);
+final user = fory.deserialize<third_party.User>(bytes);
+```
+
+Numeric IDs work the same way:
+
+```dart
+ExternalSerializersForyModule.register(
+ fory,
+ third_party.User,
+ id: 100,
+);
+```
+
+Register the target `third_party.User`, not `UserSerializer`.
+
+## Constructors and Mutable Targets
+
+Without a `constructor` option, generation uses the target's public unnamed
+generative constructor. Constructor parameters map to schema fields by name
+and must use exactly the same Dart types.
+
+For example, suppose the dependency exposes this immutable class:
+
+```dart
+final class Money {
+ const Money.fromParts({
+ required this.currency,
+ required this.units,
+ });
+
+ final String currency;
+ final int units;
+}
+```
+
+Select its public named generative constructor in the serializer declaration:
+
+```dart
+@ForyStruct(
+ target: third_party.Money,
+ constructor: 'fromParts',
+)
+abstract final class MoneySerializer {
+ late final String currency;
+
+ @ForyField(type: Int64Type())
+ late final int units;
+}
+```
+
+After decoding the fields, the generated serializer calls
+`third_party.Money.fromParts(currency: ..., units: ...)`.
+
+For a mutable target, a public generative constructor with no required
+arguments plus matching setters allows Fory to construct the target first and
+then assign its fields. This also supports cycles when reference tracking is
+enabled. For example, suppose the dependency exposes:
+
+```dart
+final class Node {
+ Node.empty();
+
+ late String label;
+ Node? next;
+}
+```
+
+Select `Node.empty` in the serializer declaration:
+
+```dart
+@ForyStruct(
+ target: third_party.Node,
+ constructor: 'empty',
+)
+abstract final class NodeSerializer {
+ late final String label;
+
+ @ForyField(ref: true)
+ late final third_party.Node? next;
+}
+```
+
+The generated serializer can call `third_party.Node.empty()`, publish the new
+node for reference tracking, and then assign `label` and `next`.
+
+A constructor-based target cannot decode a statically known reference-tracked
+path back to itself because the target does not exist until its constructor
+arguments have been read. This includes the target nested as a `List` or `Set`
+element or as a `Map` key or value. Generation rejects these schemas. Use a
+mutable two-phase target or a manual serializer when cycles are required.
+Indirect cycles that cannot be determined from the declaration are also
+unsupported for constructor-based targets.
+
+Factory constructors, private constructors, abstract targets, external enums,
+external unions, records, extension types, and built-in collection types are
+not external struct targets.
+
+## Fields and Collections
+
+After registration, the target works anywhere an ordinary registered struct
+works. A containing generated class does not select the serializer declaration:
+
+```dart
+@ForyStruct()
+final class Group {
+ Group();
+
+ third_party.User? owner;
+
+ @ListField(element: DeclaredType())
+ List<third_party.User> users = <third_party.User>[];
+
+ @MapField(value: DeclaredType())
+ Map<String, third_party.User> usersByName =
+ <String, third_party.User>{};
+}
+```
+
+Nested lists, sets, and maps resolve the registered target recursively.
+Dynamic fields and heterogeneous collections also resolve each concrete target
+by its registered type:
+
+```dart
+@ForyField(dynamic: true)
+Object? value;
+```
+
+Register every concrete external type that can appear dynamically.
+
+Non-empty root lists, sets, and maps decode their elements, keys, and values as
+the registered external targets. Dart root collections retain their existing
+runtime shapes, so read a root collection as `Object?` and cast its outer
+carrier:
+
+```dart
+final decoded =
+ fory.deserialize<Object?>(
+ fory.serialize(<third_party.User>[user]),
+ )
+ as List<Object?>;
+final first = decoded.first as third_party.User;
+```
+
+An empty root collection contains no element type identity.
+
+## Closed Generic Targets
+
+Suppose the dependency exposes this generic class:
+
+```dart
+final class Box<T> {
+ const Box(this.value);
+
+ final T value;
+}
+```
+
+`Box<T>` is open because `T` is unresolved. Supplying a concrete type argument,
+such as `Box<String>`, produces a closed generic instantiation that code
+generation can analyze:
+
+```dart
+@ForyStruct(target: third_party.Box<String>)
+abstract final class StringBoxSerializer {
+ late final String value;
+}
+```
+
+The generated serializer reconstructs values with
+`third_party.Box<String>(...)` and applies only to `Box<String>`. Register and
+use that exact target type directly:
+
+```dart
+final fory = Fory();
+ExternalSerializersForyModule.register(
+ fory,
+ third_party.Box<String>,
+ id: 102,
+);
+
+final input = const third_party.Box<String>('hello');
+final bytes = fory.serialize(input);
+final output = fory.deserialize<third_party.Box<String>>(bytes);
+
+print(output.value); // hello
+```
+
+Register `third_party.Box<String>`, not `StringBoxSerializer`. A different
+instantiation such as `Box<int>` needs its own serializer declaration and
+registration; one declaration does not cover every `Box<T>`.
+
+## Schema Evolution
+
+`evolving` and field IDs work exactly as they do for an ordinary generated
+struct:
+
+```dart
+@ForyStruct(
+ target: third_party.User,
+ evolving: true,
+)
+abstract final class UserSerializer {
+ @ForyField(id: 1)
+ late final String name;
+}
+```
+
+Keep field IDs and the registered type identity stable across peers. A field
+name must still match the corresponding property on the local target class.
+
+## When to Use a Manual Serializer
+
+Use a [manual serializer](custom-serializers.md) when the target needs a custom
+wire body, field-name translation, value conversion, factory-only
+construction, private state, or any reconstruction rule that cannot be
+expressed through matching public getters, constructor parameters, and
+setters.
+
+## Related Topics
+
+- [Code Generation](code-generation.md)
+- [Type Registration](type-registration.md)
+- [Schema Metadata](schema-metadata.md)
+- [Schema Evolution](schema-evolution.md)
+- [Manual Serializers](custom-serializers.md)
diff --git a/docs/guide/dart/grpc-support.md b/docs/guide/dart/grpc-support.md
index ee6bd8634c..43f2a3a17e 100644
--- a/docs/guide/dart/grpc-support.md
+++ b/docs/guide/dart/grpc-support.md
@@ -1,6 +1,6 @@
---
title: gRPC Support
-sidebar_position: 12
+sidebar_position: 13
id: grpc_support
license: |
Licensed to the Apache Software Foundation (ASF) under one or more
diff --git a/docs/guide/dart/index.md b/docs/guide/dart/index.md
index bf62639e01..8c201f6ab3 100644
--- a/docs/guide/dart/index.md
+++ b/docs/guide/dart/index.md
@@ -37,7 +37,7 @@ languages.
### Requirements
-- Dart SDK 3.6 or later
+- Dart SDK 3.7 or later
- `build_runner` (generates the serializer code)
### Install
@@ -117,6 +117,7 @@ dart run build_runner build --delete-conflicting-outputs
- `fory.serialize(value)` — returns `Uint8List` bytes
- `fory.deserialize<T>(bytes)` — returns a `T`
- `@ForyStruct()` — marks a class for code generation
+- `@ForyStruct(target: Type)` — generates an external structural serializer
- `@ForyField(...)` — per-field options and canonical `type:` overrides
- `@ListField(...)`, `@SetField(...)`, `@MapField(...)` — container sugar for nested `type:` trees
- Exact-value wrappers: `Int64`, `Uint64`, `Float32`
@@ -126,20 +127,21 @@ dart run build_runner build --delete-conflicting-outputs
## 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<T>` 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 |
+| 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 |
+| [External-Type Serialization](external-types.md) | Generated serializers for classes owned by another package |
+| [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 |
+| [Manual Serializers](custom-serializers.md) | Manual `Serializer<T>` 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
diff --git a/docs/guide/dart/schema-evolution.md b/docs/guide/dart/schema-evolution.md
index 46410de5a2..6239dc45c3 100644
--- a/docs/guide/dart/schema-evolution.md
+++ b/docs/guide/dart/schema-evolution.md
@@ -1,6 +1,6 @@
---
title: Schema Evolution
-sidebar_position: 8
+sidebar_position: 9
id: schema_evolution
license: |
Licensed to the Apache Software Foundation (ASF) under one or more
@@ -62,6 +62,10 @@ class UserProfile {
If you add field IDs after payloads are already in production, existing stored messages won't have them and evolution won't work correctly.
+For an [external structural serializer](external-types.md), the local
+serializer declaration supplies the evolving schema and each declaration field
+must match the corresponding target property.
+
## What You Can Safely Change
**Safe changes** (compatible on both sides):
@@ -101,5 +105,6 @@ final fory = Fory(compatible: false);
## Related Topics
- [Configuration](configuration.md)
+- [External-Type Serialization](external-types.md)
- [Schema Metadata](schema-metadata.md)
- [Xlang Serialization](xlang-serialization.md)
diff --git a/docs/guide/dart/schema-metadata.md b/docs/guide/dart/schema-metadata.md
index b24597ddbb..63d4da5952 100644
--- a/docs/guide/dart/schema-metadata.md
+++ b/docs/guide/dart/schema-metadata.md
@@ -1,6 +1,6 @@
---
title: Schema Metadata
-sidebar_position: 5
+sidebar_position: 6
id: schema_metadata
license: |
Licensed to the Apache Software Foundation (ASF) under one or more
@@ -21,6 +21,9 @@ license: |
Add `@ForyField(...)` to a field inside a `@ForyStruct()` class to change how that field is serialized.
+The same annotations apply to fields in an
+[external structural serializer declaration](external-types.md).
+
## Quick Reference
```dart
@@ -141,5 +144,6 @@ When the same model is defined in multiple languages:
## Related Topics
- [Code Generation](code-generation.md)
+- [External-Type Serialization](external-types.md)
- [Schema Evolution](schema-evolution.md)
- [Xlang Serialization](xlang-serialization.md)
diff --git a/docs/guide/dart/supported-types.md b/docs/guide/dart/supported-types.md
index 220ed988cd..55f3858b86 100644
--- a/docs/guide/dart/supported-types.md
+++ b/docs/guide/dart/supported-types.md
@@ -1,6 +1,6 @@
---
title: Supported Types
-sidebar_position: 7
+sidebar_position: 8
id: supported_types
license: |
Licensed to the Apache Software Foundation (ASF) under one or more
@@ -132,6 +132,10 @@ class User {
See [Code Generation](code-generation.md).
+Classes owned by another library can use an
+[external structural serializer](external-types.md) when their public fields
+and construction match a local Fory schema declaration.
+
## Collections
Fory supports `List<T>`, `Set<T>`, and `Map<K, V>`. Element and key types must
diff --git a/docs/guide/dart/troubleshooting.md b/docs/guide/dart/troubleshooting.md
index 5f066920a2..fba1ef59bf 100644
--- a/docs/guide/dart/troubleshooting.md
+++ b/docs/guide/dart/troubleshooting.md
@@ -1,6 +1,6 @@
---
title: Troubleshooting
-sidebar_position: 11
+sidebar_position: 12
id: troubleshooting
license: |
Licensed to the Apache Software Foundation (ASF) under one or more
@@ -48,6 +48,22 @@ dart run build_runner build --delete-conflicting-outputs
If you moved files or renamed types, rebuild before re-running analysis or tests.
+## External target generation fails
+
+An external structural serializer requires:
+
+- an `abstract final` serializer declaration with `late final` schema fields;
+- a concrete imported target class;
+- an accessible target getter with the same name and exact Dart type for every
+ field;
+- a public generative constructor whose parameters map to fields, or a
+ zero-required-argument constructor plus matching setters.
+
+Select a public named constructor with
+`@ForyStruct(target: Type, constructor: 'name')`. Use a
+[manual serializer](custom-serializers.md) when the target requires a factory,
+private state, field conversion, or name translation.
+
## `Deserialized value has type ..., expected ...`
The payload describes a different type than `T` in `deserialize<T>`. Common causes:
@@ -64,7 +80,7 @@ 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.
+- In a manual serializer, use `context.writeRef` / `context.readRef` and call `context.reference(obj)` before reading nested fields.
## Cross-language field mismatch (missing data or wrong values)
@@ -158,6 +174,6 @@ separate protobuf service endpoint for generic protobuf clients.
- [Xlang Serialization](xlang-serialization.md)
- [Code Generation](code-generation.md)
-- [Custom Serializers](custom-serializers.md)
+- [Manual Serializers](custom-serializers.md)
- [Web Platform Support](web-platform-support.md)
- [gRPC Support](grpc-support.md)
diff --git a/docs/guide/dart/type-registration.md b/docs/guide/dart/type-registration.md
index 61e3c20ce8..7948e7afae 100644
--- a/docs/guide/dart/type-registration.md
+++ b/docs/guide/dart/type-registration.md
@@ -1,6 +1,6 @@
---
title: Type Registration
-sidebar_position: 6
+sidebar_position: 7
id: type_registration
license: |
Licensed to the Apache Software Foundation (ASF) under one or more
@@ -65,9 +65,23 @@ Call the generated `register` function from the `.fory.dart` file. It installs a
UserModelsForyModule.register(fory, User, id: 100);
```
-## Registering a Custom Serializer
+External structural serializers use the same generated registration API. Pass
+the external target type:
-For types that you cannot annotate with `@ForyStruct()`, pass a serializer instance directly:
+```dart
+ExternalSerializersForyModule.register(
+ fory,
+ third_party.User,
+ id: 100,
+);
+```
+
+See [External-Type Serialization](external-types.md) for the declaration.
+
+## Registering a Manual Serializer
+
+Pass a serializer instance directly when a type needs custom wire or
+construction logic:
```dart
fory.registerSerializer(
@@ -77,7 +91,7 @@ fory.registerSerializer(
);
```
-See [Custom Serializers](custom-serializers.md) for how to implement a serializer.
+See [Manual Serializers](custom-serializers.md) for how to implement a serializer.
## Rules to Follow
@@ -93,5 +107,6 @@ The same numeric ID or name must be used in every peer that reads or writes the
## Related Topics
- [Code Generation](code-generation.md)
+- [External-Type Serialization](external-types.md)
- [Xlang Serialization](xlang-serialization.md)
-- [Custom Serializers](custom-serializers.md)
+- [Manual Serializers](custom-serializers.md)
diff --git a/docs/guide/dart/web-platform-support.md b/docs/guide/dart/web-platform-support.md
index 2000450992..f329be4d58 100644
--- a/docs/guide/dart/web-platform-support.md
+++ b/docs/guide/dart/web-platform-support.md
@@ -1,6 +1,6 @@
---
title: Web Platform Support
-sidebar_position: 10
+sidebar_position: 11
id: web_platform_support
license: |
Licensed to the Apache Software Foundation (ASF) under one or more
@@ -36,6 +36,7 @@ Fory Dart supports:
- Flutter web applications.
- Generated `@ForyStruct` serializers and manually registered serializers on
all supported targets.
+- External structural serializers generated with `@ForyStruct(target: ...)`.
## Code Generation Is Required
@@ -135,9 +136,9 @@ class StorageExtent {
}
```
-## Custom Serializers
+## Manual Serializers
-Custom serializers can use the same `Buffer`, `WriteContext`, and `ReadContext`
+Manual 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
diff --git a/docs/guide/dart/xlang-serialization.md b/docs/guide/dart/xlang-serialization.md
index 8b1a5874d3..639878b5d1 100644
--- a/docs/guide/dart/xlang-serialization.md
+++ b/docs/guide/dart/xlang-serialization.md
@@ -1,6 +1,6 @@
---
title: Xlang Serialization
-sidebar_position: 4
+sidebar_position: 5
id: xlang_serialization
license: |
Licensed to the Apache Software Foundation (ASF) under one or more
@@ -59,6 +59,32 @@ ModelsForyModule.register(
Do not mix the two strategies for the same type across implementations.
+## External Types
+
+For a struct class owned by another Dart package, define an
+[external structural serializer](external-types.md) and register the target
+with the same ID or name used by every peer:
+
+```dart
+@ForyStruct(target: third_party.User)
+abstract final class UserSerializer {
+ @ForyField(id: 1)
+ late final String name;
+
+ @ForyField(id: 2, type: Int32Type())
+ late final int age;
+}
+
+ExternalSerializersForyModule.register(
+ fory,
+ third_party.User,
+ id: 100,
+);
+```
+
+The declaration's field IDs, names, nullability, and wire-width annotations
+define the Dart-side xlang schema.
+
## Dart to Java Example
### Dart
@@ -215,5 +241,6 @@ dart test
## Related Topics
- [Type Registration](type-registration.md)
+- [External-Type Serialization](external-types.md)
- [Schema Evolution](schema-evolution.md)
- [Xlang guide](../xlang/index.md)
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@fory.apache.org
For additional commands, e-mail: commits-help@fory.apache.org
(fory-site) 02/02: 🔄 synced local 'docs/specification/' with remote 'docs/specification/'
Posted by ch...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
chaokunyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fory-site.git
commit 10fe5918efa36fd7c7847d0ae482ec1d559ef459
Author: chaokunyang <sh...@gmail.com>
AuthorDate: Mon Jul 27 03:02:44 2026 +0000
🔄 synced local 'docs/specification/' with remote 'docs/specification/'
---
docs/specification/xlang_implementation_guide.md | 34 ++++++++++++++++++++++++
1 file changed, 34 insertions(+)
diff --git a/docs/specification/xlang_implementation_guide.md b/docs/specification/xlang_implementation_guide.md
index 06cdf5c618..b336d83a8e 100644
--- a/docs/specification/xlang_implementation_guide.md
+++ b/docs/specification/xlang_implementation_guide.md
@@ -984,6 +984,40 @@ 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.
+### Dart External Structural Serializers
+
+Dart external-type serialization extends `ForyStruct` with an optional
+compile-time `target` and named generative `constructor`. The annotated
+`abstract final` class is a schema declaration only. It has no runtime value or
+registration identity.
+
+The generator must analyze ordinary and external structs through one struct
+model and one emitter. Private generated symbol names come from the declaration
+name. Every runtime type position uses the target type: `Serializer<Target>`,
+`GeneratedStructSchema<Target>`, read and write signatures, constructor calls,
+schema `type`, and generated-module dispatch.
+
+For each declaration field, resolve an accessible target getter with the same
+name and exact instantiated Dart type. Constructor parameters and any
+post-construction setter must also match exactly. A public named generative
+constructor is selected only when the annotation names it. Factory
+constructors, abstract targets, open target types, and constructor-based
+reference-tracked paths back to the target are rejected during generation.
+The recursive check includes target elements, keys, and values nested in the
+supported list, set, and map field metadata.
+
+Generated code reads getters and invokes target constructors or setters
+directly. It must not allocate the declaration, copy values through an
+intermediate object, invoke runtime callbacks, perform member-name lookup, or
+branch on whether the struct is external. The existing generated struct,
+registration, resolver, field, collection, map, compatible-read, and reference
+paths remain the only runtime paths.
+
+Registration is keyed by `Target` through the generated module and the existing
+generated registration API. Direct roots, generated fields, dynamic values,
+and recursive collection/map children resolve the same target registration.
+Dart root collections retain their existing untyped outer runtime shapes.
+
## Directory Layout
Under each Dart package `lib/` tree, only one nested source layer is allowed.
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@fory.apache.org
For additional commands, e-mail: commits-help@fory.apache.org