chaokunyang opened a new issue, #3006:
URL: https://github.com/apache/fory/issues/3006
## Feature Request
Create a `@ForyField` annotation for Dart to provide field-level metadata
for performance and space optimization during xlang serialization.
## Is your feature request related to a problem? Please describe
Currently, Fory's Dart xlang serialization treats all class fields uniformly:
1. **Null checks are always performed** - Even for fields that are never
null, Fory writes a null/ref flag (1 byte per field)
2. **Reference tracking is always applied** (when enabled globally) - Even
for fields that won't be shared/cyclic, objects are tracked with hash lookup
cost
3. **Field names use meta string encoding** - In schema evolution mode,
field names are encoded using meta string compression, but for fields with long
names, this still takes space
These defaults ensure correctness but introduce unnecessary overhead when
the developer has more specific knowledge about their data model.
## Describe the solution you'd like
Add a `@ForyField` annotation that works with Dart's class system:
```dart
import 'package:fory/fory.dart';
@foryClass
class Foo {
// Field f1: non-nullable (default), no ref tracking (default)
// Tag ID 0 provides compact encoding in schema evolution mode
@ForyField(id: 0)
String f1;
// Field f2: non-nullable (default), no ref tracking (default)
@ForyField(id: 1)
Bar f2;
// Field f3: nullable field that may contain null values
@ForyField(id: 2, nullable: true)
String? f3;
// Field parent: shared reference that needs tracking (e.g., for circular
refs)
@ForyField(id: 3, ref: true, nullable: true)
Node? parent;
// Field with long name: tag ID provides significant space savings
@ForyField(id: 4)
String veryLongFieldNameThatWouldTakeManyBytes;
// Explicit opt-out: use field name encoding but get nullable optimization
@ForyField(id: -1, nullable: true)
String? optionalField;
Foo({
required this.f1,
required this.f2,
this.f3,
this.parent,
required this.veryLongFieldNameThatWouldTakeManyBytes,
this.optionalField,
});
}
```
### Annotation API
```dart
/// Annotation to provide field-level metadata for Fory serialization.
class ForyField {
/// Field tag ID for schema evolution mode (REQUIRED).
/// - When >= 0: Uses numeric ID instead of field name for compact encoding
/// - When -1: Explicitly opt-out, use field name with meta string encoding
/// Must be unique within the class (except -1) and stable across versions.
final int id;
/// Whether this field can be null.
/// When false (default), Fory skips writing the null flag (saves 1 byte).
/// When true, Fory writes null flag for nullable fields.
/// Default: false (aligned with xlang protocol defaults)
final bool nullable;
/// Whether to track references for this field.
/// When false (default):
/// - Avoids adding the object to reference tracking (saves hash overhead)
/// - Skips writing ref tracking flag
/// When true, enables reference tracking for shared/circular references.
/// Default: false (aligned with xlang protocol defaults)
final bool ref;
const ForyField({
required this.id,
this.nullable = false,
this.ref = false,
});
}
```
### Design Decision: Required `id`
The `id` parameter is **required**:
- `id: 0` to `id: N`: Use tag ID encoding (compact)
- `id: -1`: Explicit opt-out, use field name encoding
Rationale:
1. **Explicit control**: Using `@ForyField` means opting into explicit
control
2. **Build-time validation**: Can check for duplicate IDs during code
generation
3. **Proven pattern**: Similar to protobuf field numbers
### Optimization Details
#### 1. `nullable: false` (Default) Optimization
When `nullable: false` (default):
- Skip writing the null flag entirely (1 byte saved per field)
- Directly serialize the field value
- **Throw error** if nullable type (`T?`) used without `nullable: true`
#### 2. `ref: false` (Default) Optimization
When `ref: false` (default):
- Skip reference tracking map operations
- Skip ref flag when combined with `nullable: false`
#### 3. Tag ID Optimization
When `id >= 0`:
- Field name encoded as varint instead of meta string
- Significant space savings for long field names
**Space savings:**
| Field Name | Meta String (approx) | Tag ID |
|------------|---------------------|--------|
| `f1` | ~2 bytes | 1 byte |
| `userName` | ~6 bytes | 1 byte |
| `transactionId` | ~10 bytes | 1 byte |
### Implementation Notes
1. **Code Generation with build_runner**:
```dart
// fory_generator.dart
class ForyGenerator extends GeneratorForAnnotation<ForyClass> {
@override
String generateForAnnotatedElement(Element element, ...) {
// Parse @ForyField annotations
// Generate optimized serialization code
}
}
```
2. **Generated Serializer**:
```dart
// foo.fory.dart (generated)
class FooSerializer extends ForySerializer<Foo> {
@override
void write(ForyWriter writer, Foo value) {
// f1: id=0, non-nullable, no ref
writer.writeTagId(0);
writer.writeString(value.f1);
// f3: id=2, nullable, no ref
writer.writeTagId(2);
if (value.f3 == null) {
writer.writeNull();
} else {
writer.writeNotNull();
writer.writeString(value.f3!);
}
// ...
}
}
```
3. **Validation**:
- **Build error** if duplicate tag IDs (>= 0) in same class
- **Build error** if `id < -1`
- **Build error** if nullable Dart type (`T?`) without `nullable: true`
- **Runtime error** if `nullable: false` but field is null
4. **Dart Null Safety Integration**:
- Non-nullable Dart type (`T`) should have `nullable: false` (default)
- Nullable Dart type (`T?`) must have `nullable: true`
- Analyzer can warn about mismatches
### Performance Impact
For a class with 10 fields using default settings (`nullable: false`, `ref:
false`):
- **Space savings**: ~20 bytes per object (null + ref flags)
- **CPU savings**: 10 fewer reference tracking operations per serialization
## Additional context
This is the Dart equivalent of Java's `@ForyField` annotation. See [Java
issue #3000](https://github.com/apache/fory/issues/3000) for the original
design discussion.
Protocol spec:
https://fory.apache.org/docs/specification/fory_xlang_serialization_spec
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]