|
1 | 1 | # Apache Fory Dart |
2 | 2 |
|
3 | | -This package provides the Dart runtime for Apache Fory xlang serialization. |
| 3 | +Apache Fory Dart is the Dart xlang runtime for Apache Fory. It reads and writes |
| 4 | +Fory's cross-language wire format and is designed around generated serializers |
| 5 | +for annotated Dart models, with customized serializers available for advanced use |
| 6 | +cases. |
4 | 7 |
|
5 | | -For normal application code, use annotated objects plus the generated |
6 | | -library-level namespace such as `ExampleFory.register(fory, Person, id: 1)` or |
7 | | -`ExampleFory.register(fory, Person, namespace: 'example', typeName: 'Person')`. |
8 | | -Those generated helpers keep serializer metadata private to the source library |
9 | | -and register directly through generated serializer metadata, while manual |
10 | | -`Serializer` implementations |
11 | | -remain the advanced escape hatch for external types, custom wire behavior, or |
12 | | -manual union implementations through `Fory.registerSerializer(...)`. |
| 8 | +## Features |
13 | 9 |
|
14 | | -The runtime is built around a small public surface: |
| 10 | +- Cross-language serialization with the Fory xlang format |
| 11 | +- Generated serializers for annotated structs and enums |
| 12 | +- Compatible mode for schema evolution |
| 13 | +- Optional reference tracking for shared and circular object graphs |
| 14 | +- Manual serializers for external types, custom payloads, and unions |
| 15 | +- Explicit xlang value wrappers such as `Int32`, `UInt32`, `Float16`, |
| 16 | + `Float32`, `LocalDate`, and `Timestamp` |
| 17 | + |
| 18 | +## Getting Started |
| 19 | + |
| 20 | +Add `fory` to your package dependencies. |
| 21 | + |
| 22 | +```yaml |
| 23 | +dependencies: |
| 24 | + fory: ^0.17.0-dev |
| 25 | + |
| 26 | +dev_dependencies: |
| 27 | + build_runner: ^2.4.13 |
| 28 | +``` |
| 29 | +
|
| 30 | +## Basic Usage |
| 31 | +
|
| 32 | +Use `@ForyStruct()` for generated struct serializers and include the generated |
| 33 | +part file. |
| 34 | + |
| 35 | +```dart |
| 36 | +import 'package:fory/fory.dart'; |
| 37 | +
|
| 38 | +part 'person.fory.dart'; |
| 39 | +
|
| 40 | +enum Color { |
| 41 | + red, |
| 42 | + blue, |
| 43 | +} |
| 44 | +
|
| 45 | +@ForyStruct() |
| 46 | +class Person { |
| 47 | + Person(); |
| 48 | +
|
| 49 | + String name = ''; |
| 50 | + Int32 age = Int32(0); |
| 51 | + Color favoriteColor = Color.red; |
| 52 | + List<String> tags = <String>[]; |
| 53 | +} |
| 54 | +
|
| 55 | +void main() { |
| 56 | + final fory = Fory(); |
| 57 | +
|
| 58 | + PersonFory.register( |
| 59 | + fory, |
| 60 | + Color, |
| 61 | + namespace: 'example', |
| 62 | + typeName: 'Color', |
| 63 | + ); |
| 64 | + PersonFory.register( |
| 65 | + fory, |
| 66 | + Person, |
| 67 | + namespace: 'example', |
| 68 | + typeName: 'Person', |
| 69 | + ); |
| 70 | +
|
| 71 | + final person = Person() |
| 72 | + ..name = 'Ada' |
| 73 | + ..age = Int32(36) |
| 74 | + ..favoriteColor = Color.blue |
| 75 | + ..tags = <String>['engineer', 'mathematician']; |
| 76 | +
|
| 77 | + final bytes = fory.serialize(person); |
| 78 | + final roundTrip = fory.deserialize<Person>(bytes); |
| 79 | +
|
| 80 | + print(roundTrip.name); |
| 81 | +} |
| 82 | +``` |
| 83 | + |
| 84 | +Generate the companion file before running the program: |
| 85 | + |
| 86 | +```bash |
| 87 | +dart run build_runner build --delete-conflicting-outputs |
| 88 | +``` |
| 89 | + |
| 90 | +## Type Registration |
| 91 | + |
| 92 | +Generated types register through the generated library namespace. |
| 93 | + |
| 94 | +```dart |
| 95 | +PersonFory.register(fory, Person, id: 100); |
| 96 | +``` |
| 97 | + |
| 98 | +Or use namespace and type name registration: |
| 99 | + |
| 100 | +```dart |
| 101 | +PersonFory.register( |
| 102 | + fory, |
| 103 | + Person, |
| 104 | + namespace: 'example', |
| 105 | + typeName: 'Person', |
| 106 | +); |
| 107 | +``` |
| 108 | + |
| 109 | +Exactly one registration mode is required: |
| 110 | + |
| 111 | +- `id: ...` |
| 112 | +- `namespace: ...` and `typeName: ...` |
| 113 | + |
| 114 | +Keep the same registration identity on all runtimes that exchange the type. |
| 115 | + |
| 116 | +## Configuration |
| 117 | + |
| 118 | +Configure the runtime through `Config`. |
| 119 | + |
| 120 | +```dart |
| 121 | +final fory = Fory( |
| 122 | + compatible: true, |
| 123 | + maxDepth: 256, |
| 124 | + maxCollectionSize: 1 << 20, |
| 125 | + maxBinarySize: 64 * 1024 * 1024, |
| 126 | +); |
| 127 | +``` |
| 128 | + |
| 129 | +Key options: |
| 130 | + |
| 131 | +- `compatible`: enables compatible struct encoding and decoding |
| 132 | +- `checkStructVersion`: enables struct-version validation in |
| 133 | + schema-consistent mode |
| 134 | +- `maxDepth`: limits nesting depth for one operation |
| 135 | +- `maxCollectionSize`: limits collection and map payload sizes |
| 136 | +- `maxBinarySize`: limits binary payload size |
| 137 | + |
| 138 | +## Reference Tracking |
| 139 | + |
| 140 | +Enable root-level reference tracking only when the root value itself is a graph |
| 141 | +or container that needs shared-reference tracking. |
| 142 | + |
| 143 | +```dart |
| 144 | +final shared = String.fromCharCodes('shared'.codeUnits); |
| 145 | +final bytes = fory.serialize(<Object?>[shared, shared], trackRef: true); |
| 146 | +final roundTrip = fory.deserialize<List<Object?>>(bytes); |
| 147 | +``` |
| 148 | + |
| 149 | +For generated structs, prefer field-level reference metadata: |
| 150 | + |
| 151 | +```dart |
| 152 | +@ForyStruct() |
| 153 | +class NodeList { |
| 154 | + NodeList(); |
| 155 | +
|
| 156 | + @ForyField(ref: true) |
| 157 | + List<Object?> values = <Object?>[]; |
| 158 | +} |
| 159 | +``` |
| 160 | + |
| 161 | +## Customized Serializers |
| 162 | + |
| 163 | +Use `Serializer<T>` when a type cannot use generated struct support or when you |
| 164 | +need custom wire behavior. |
| 165 | + |
| 166 | +```dart |
| 167 | +import 'package:fory/fory.dart'; |
| 168 | +
|
| 169 | +final class Person { |
| 170 | + Person(this.name, this.age); |
| 171 | +
|
| 172 | + final String name; |
| 173 | + final int age; |
| 174 | +} |
| 175 | +
|
| 176 | +final class PersonSerializer extends Serializer<Person> { |
| 177 | + const PersonSerializer(); |
| 178 | +
|
| 179 | + @override |
| 180 | + void write(WriteContext context, Person value) { |
| 181 | + final buffer = context.buffer; |
| 182 | + buffer.writeUtf8(value.name); |
| 183 | + buffer.writeInt64(value.age); |
| 184 | + } |
| 185 | +
|
| 186 | + @override |
| 187 | + Person read(ReadContext context) { |
| 188 | + final buffer = context.buffer; |
| 189 | + return Person(buffer.readUtf8(), buffer.readInt64()); |
| 190 | + } |
| 191 | +} |
| 192 | +
|
| 193 | +void main() { |
| 194 | + final fory = Fory(); |
| 195 | + fory.registerSerializer( |
| 196 | + Person, |
| 197 | + const PersonSerializer(), |
| 198 | + namespace: 'example', |
| 199 | + typeName: 'Person', |
| 200 | + ); |
| 201 | +
|
| 202 | + final bytes = fory.serialize(Person('Ada', 36)); |
| 203 | + final roundTrip = fory.deserialize<Person>(bytes); |
| 204 | + print(roundTrip.name); |
| 205 | +} |
| 206 | +``` |
| 207 | + |
| 208 | +## Public API |
| 209 | + |
| 210 | +The main exported API includes: |
15 | 211 |
|
16 | 212 | - `Fory` |
17 | 213 | - `Config` |
18 | 214 | - `Buffer` |
19 | 215 | - `WriteContext` |
20 | 216 | - `ReadContext` |
21 | 217 | - `Serializer` |
| 218 | +- `UnionSerializer` |
22 | 219 | - `ForyStruct` |
23 | 220 | - `ForyField` |
| 221 | +- Numeric and temporal wrappers such as `Int8`, `Int16`, `Int32`, `UInt8`, |
| 222 | + `UInt16`, `UInt32`, `Float16`, `Float32`, `LocalDate`, and `Timestamp` |
24 | 223 |
|
25 | | -Generated structs and enums register through the generated library namespace. |
26 | | -Generated wrappers require an explicit registration mode: pass `id` for |
27 | | -id-based registration, or pass both `namespace` and `typeName` for name-based |
28 | | -registration. |
29 | | - |
30 | | -## Public API |
31 | | - |
32 | | -- `Fory`: root facade for xlang serialization, deserialization, generated type registration, and advanced manual serializer registration. |
33 | | -- `Config`: immutable runtime options for compatible mode and safety limits. |
34 | | -- `Buffer`: reusable byte buffer with explicit reader and writer indices. |
35 | | -- `WriteContext` and `ReadContext`: advanced context APIs used by generated and manual serializers. |
36 | | -- `Serializer`: low-level extension point for manual serializers and generated code. |
37 | | -- `ForyStruct` and `ForyField`: annotations for struct code generation. |
38 | | -- Numeric wrapper and time types such as `Int32`, `UInt32`, `Float16`, `LocalDate`, and `Timestamp`: xlang value types when Dart primitives are not precise enough to describe the wire type. |
39 | | - |
40 | | -Refer to the Dart doc comments on each exported symbol for the precise contract of each type and method. |
41 | | - |
42 | | -## Example |
43 | | - |
44 | | -The primary example uses generated serializers: |
45 | | - |
46 | | -1. Generate the example companion file: |
47 | | - `dart run build_runner build --delete-conflicting-outputs` |
48 | | -2. Run the example: |
49 | | - `dart run example/example.dart` |
| 224 | +## Cross-Language Notes |
50 | 225 |
|
51 | | -The example library exposes `ExampleFory.register(...)` for generated |
52 | | -registration, for example `ExampleFory.register(fory, Person, namespace: |
53 | | -'example', typeName: 'Person')`. |
| 226 | +- The Dart runtime only supports xlang payloads. |
| 227 | +- Register user-defined types before serialization or deserialization. |
| 228 | +- Keep numeric IDs or `namespace + typeName` mappings consistent across |
| 229 | + languages. |
| 230 | +- Use wrappers or numeric field annotations when the exact xlang wire type |
| 231 | + matters. |
54 | 232 |
|
55 | | -The advanced manual serializer example lives at `example/manual_serializer.dart`. |
| 233 | +For the xlang wire format and type mapping details, see the Apache Fory |
| 234 | +specification in the main repository. |
0 commit comments