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.git
The following commit(s) were added to refs/heads/main by this push:
new 8e0d5f36b feat(dart): Introduce id based enum serialization (#3482)
8e0d5f36b is described below
commit 8e0d5f36b2d76bcb1a8a331af9651cecc1dcc8cd
Author: Yash Agarwal <[email protected]>
AuthorDate: Thu Apr 9 15:01:38 2026 +0530
feat(dart): Introduce id based enum serialization (#3482)
## Why?
The Dart module, only supports ordinal enum serialization, where each
enum
value is serialized as its position index (0, 1, 2...), which breaks in
different
cases like adding or reordering values. This adds optional id based
serialization
with so that we can provide unique id to every while preserving ordinal
serialization
as the default fallback.
## What does this PR do?
- Adds @ForyEnumId(int id) for enum values
- Registers ForyEnumId in analysis type detection
- Updates enum analysis to collect IDs and emit build-time warnings for:
- partial @ForyEnumId usage
- duplicate IDs
- Falls back to ordinal serialization when annotations are incomplete or
duplicated
- Extends EnumSpec with an optional idToValue map
- Updates generated enum specs to include the ID map when valid
- Updates EnumSerializer to use ID-based read/write when the map is
present, otherwise keep ordinal behavior
- Adds codegen and serializer tests
## Related issues
## AI Contribution Checklist
AI Usage Disclosure
AI assistance was used to suggest tests and for better warnings in
enum_analyzer.
dart/packages/fory/lib/src/codegen/analyze/impl/struct/enum_analyzer_impl.dart
dart/packages/fory-test/test/
- [x] I included a completed [AI Contribution
Checklist](https://github.com/apache/fory/blob/main/AI_POLICY.md#9-contributor-checklist-for-ai-assisted-prs)
in this PR description and the required AI Usage Disclosure.
- [x] I can explain and defend all important changes without AI help.
- [x] I reviewed AI-assisted code changes line by line before
submission.
- [x] I ran adequate human verification and recorded evidence (checks
run locally or in CI, pass/fail summary, and confirmation I reviewed
results).
- [x] I added/updated tests and specs where required.
- [x] I validated protocol/performance impacts with evidence when
applicable.
- [x] I verified licensing and provenance compliance.
## Does this PR introduce any user-facing change?
- [x] Does this PR introduce any public API change?
`@ForyEnumId` is a new annotation users will write in their code
`ForyEnumId` class is publicly accessible
- [ ] Does this PR introduce any binary protocol compatibility change?
## Benchmark
General struct performance remained similar because those benchmarks do
not exercise enum serialization paths.
---
.../entity/enum_id_foo.dart} | 38 ++--
.../test/codegen_test/enum_codegen_test.dart | 17 ++
.../test/datatype_test/enum_serializer_test.dart | 216 +++++++++++++++++++++
.../fory/lib/src/annotation/fory_enum.dart | 39 ++++
.../codegen/analyze/analysis_type_identifier.dart | 20 +-
.../analyze/impl/struct/enum_analyzer_impl.dart | 107 +++++++++-
.../src/codegen/meta/impl/enum_spec_generator.dart | 37 +++-
.../fory/lib/src/meta/specs/enum_spec.dart | 8 +-
.../fory/lib/src/serializer/enum_serializer.dart | 64 +++++-
9 files changed, 509 insertions(+), 37 deletions(-)
diff --git a/dart/packages/fory-test/test/codegen_test/enum_codegen_test.dart
b/dart/packages/fory-test/lib/entity/enum_id_foo.dart
similarity index 61%
copy from dart/packages/fory-test/test/codegen_test/enum_codegen_test.dart
copy to dart/packages/fory-test/lib/entity/enum_id_foo.dart
index 86578cadd..6efdcaec0 100644
--- a/dart/packages/fory-test/test/codegen_test/enum_codegen_test.dart
+++ b/dart/packages/fory-test/lib/entity/enum_id_foo.dart
@@ -17,22 +17,28 @@
* under the License.
*/
-// @Skip()
-library;
-
-import 'package:checks/checks.dart';
import 'package:fory/fory.dart';
-import 'package:fory_test/entity/enum_foo.dart';
-import 'package:test/test.dart';
-void main() {
- group('Simple Enum Code Generation', () {
- test('test enum spec generation', () async {
- EnumSpec enumSpec = EnumSpec(EnumFoo, [EnumFoo.A, EnumFoo.B]);
- EnumSpec enumSubTypeSpec =
- EnumSpec(EnumSubClass, [EnumSubClass.A, EnumSubClass.B]);
- check($EnumFoo).equals(enumSpec);
- check($EnumSubClass).equals(enumSubTypeSpec);
- });
- });
+part '../generated/enum_id_foo.g.dart';
+
+@foryEnum
+enum EnumWithIds {
+ @ForyEnumId(10)
+ A,
+ @ForyEnumId(20)
+ B,
+ @ForyEnumId(30)
+ C,
+}
+
+@foryEnum
+enum EnumFieldBasedIds {
+ A(10),
+ B(20),
+ C(30);
+
+ @ForyEnumId()
+ final int code;
+ const EnumFieldBasedIds(this.code);
}
+
diff --git a/dart/packages/fory-test/test/codegen_test/enum_codegen_test.dart
b/dart/packages/fory-test/test/codegen_test/enum_codegen_test.dart
index 86578cadd..ac996d85d 100644
--- a/dart/packages/fory-test/test/codegen_test/enum_codegen_test.dart
+++ b/dart/packages/fory-test/test/codegen_test/enum_codegen_test.dart
@@ -22,6 +22,7 @@ library;
import 'package:checks/checks.dart';
import 'package:fory/fory.dart';
+import 'package:fory_test/entity/enum_id_foo.dart';
import 'package:fory_test/entity/enum_foo.dart';
import 'package:test/test.dart';
@@ -34,5 +35,21 @@ void main() {
check($EnumFoo).equals(enumSpec);
check($EnumSubClass).equals(enumSubTypeSpec);
});
+
+ test('test per-value @ForyEnumId spec generation', () {
+ EnumSpec enumWithIdsSpec = EnumSpec(EnumWithIds,
+ [EnumWithIds.A, EnumWithIds.B, EnumWithIds.C],
+ {10: EnumWithIds.A, 20: EnumWithIds.B, 30: EnumWithIds.C});
+
+ check($EnumWithIds).equals(enumWithIdsSpec);
+ });
+
+ test('test field-based @ForyEnumId spec generation', () {
+ EnumSpec enumFieldBasedIdsSpec = EnumSpec(EnumFieldBasedIds,
+ [EnumFieldBasedIds.A, EnumFieldBasedIds.B, EnumFieldBasedIds.C],
+ {10: EnumFieldBasedIds.A, 20: EnumFieldBasedIds.B, 30:
EnumFieldBasedIds.C});
+
+ check($EnumFieldBasedIds).equals(enumFieldBasedIdsSpec);
+ });
});
}
diff --git
a/dart/packages/fory-test/test/datatype_test/enum_serializer_test.dart
b/dart/packages/fory-test/test/datatype_test/enum_serializer_test.dart
new file mode 100644
index 000000000..a09667b42
--- /dev/null
+++ b/dart/packages/fory-test/test/datatype_test/enum_serializer_test.dart
@@ -0,0 +1,216 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+library;
+
+import 'package:checks/checks.dart';
+import 'package:fory/fory.dart';
+import 'package:fory/src/collection/stack.dart';
+import 'package:fory/src/config/fory_config.dart';
+import 'package:fory/src/deserialization_dispatcher.dart';
+import 'package:fory/src/deserialization_context.dart';
+import 'package:fory/src/fory_exception.dart';
+import 'package:fory/src/memory/byte_reader.dart';
+import 'package:fory/src/memory/byte_writer.dart';
+import 'package:fory/src/meta/spec_wraps/type_spec_wrap.dart';
+import 'package:fory/src/resolver/deserialization_ref_resolver.dart';
+import 'package:fory/src/resolver/meta_string_writing_resolver.dart';
+import 'package:fory/src/resolver/serialization_ref_resolver.dart';
+import 'package:fory/src/resolver/struct_hash_resolver.dart';
+import 'package:fory/src/resolver/type_resolver.dart';
+import 'package:fory/src/serialization_dispatcher.dart';
+import 'package:fory/src/serialization_context.dart';
+import 'package:fory/src/serializer/enum_serializer.dart';
+import 'package:fory_test/entity/enum_foo.dart';
+import 'package:fory_test/entity/enum_id_foo.dart';
+import 'package:test/test.dart';
+
+String _unusedTagLookup(Type _) => '';
+
+final ForyConfig _config = ForyConfig();
+final TypeResolver _typeResolver = TypeResolver.newOne(_config);
+
+SerializationContext _newSerializationContext() {
+ return SerializationContext(
+ StructHashResolver.inst,
+ _unusedTagLookup,
+ SerializationDispatcher.I,
+ _typeResolver,
+ SerializationRefResolver.getOne(false),
+ SerializationRefResolver.noRefResolver,
+ MetaStringWritingResolver.newInst,
+ Stack<TypeSpecWrap>(),
+ );
+}
+
+DeserializationContext _newDeserializationContext() {
+ return DeserializationContext(
+ StructHashResolver.inst,
+ _unusedTagLookup,
+ _config,
+ (isXLang: true, oobEnabled: false),
+ DeserializationDispatcher.I,
+ DeserializationRefResolver.getOne(false),
+ _typeResolver,
+ Stack<TypeSpecWrap>(),
+ );
+}
+
+void main() {
+ group('Enum serializer', () {
+ test('writes and reads annotated enum ids when all values are annotated',
+ () {
+ final EnumSerializer serializer = EnumSerializer(false, [
+ EnumWithIds.A,
+ EnumWithIds.B,
+ EnumWithIds.C,
+ ], {
+ 10: EnumWithIds.A,
+ 20: EnumWithIds.B,
+ 30: EnumWithIds.C,
+ });
+
+ final ByteWriter writer = ByteWriter();
+ serializer.write(
+ writer,
+ EnumWithIds.B,
+ _newSerializationContext(),
+ );
+ final ByteReader encodedIdReader =
+ ByteReader.forBytes(writer.takeBytes());
+ check(encodedIdReader.readVarUint32Small7()).equals(20);
+
+ final ByteWriter idWriter = ByteWriter();
+ idWriter.writeVarUint32Small7(30);
+ final Enum value = serializer.read(
+ ByteReader.forBytes(idWriter.takeBytes()),
+ 0,
+ _newDeserializationContext(),
+ );
+ check(value).equals(EnumWithIds.C);
+ });
+
+ test('uses ordinal serialization when no @ForyEnumId annotations are
present', () {
+ final EnumSerializer serializer = EnumSerializer(false, [
+ EnumFoo.A,
+ EnumFoo.B,
+ ]);
+
+ final ByteWriter writer = ByteWriter();
+ serializer.write(
+ writer,
+ EnumFoo.B,
+ _newSerializationContext(),
+ );
+ final ByteReader encodedIdReader =
+ ByteReader.forBytes(writer.takeBytes());
+ check(encodedIdReader.readVarUint32Small7()).equals(1);
+ });
+
+ test('throws on unknown annotated enum id', () {
+ final EnumSerializer serializer = EnumSerializer(false, [
+ EnumWithIds.A,
+ EnumWithIds.B,
+ EnumWithIds.C,
+ ], {
+ 10: EnumWithIds.A,
+ 20: EnumWithIds.B,
+ 30: EnumWithIds.C,
+ });
+
+ final ByteWriter writer = ByteWriter();
+ writer.writeVarUint32Small7(99);
+
+ check(
+ () => serializer.read(
+ ByteReader.forBytes(writer.takeBytes()),
+ 0,
+ _newDeserializationContext(),
+ ),
+ ).throws<DeserializationRangeException>();
+ });
+
+ test('writes and reads field-based enum ids', () {
+ final EnumSerializer serializer = EnumSerializer(false, [
+ EnumFieldBasedIds.A,
+ EnumFieldBasedIds.B,
+ EnumFieldBasedIds.C,
+ ], {
+ 10: EnumFieldBasedIds.A,
+ 20: EnumFieldBasedIds.B,
+ 30: EnumFieldBasedIds.C,
+ });
+
+ final ByteWriter writer = ByteWriter();
+ serializer.write(writer, EnumFieldBasedIds.B,
_newSerializationContext());
+ final ByteReader reader = ByteReader.forBytes(writer.takeBytes());
+ check(reader.readVarUint32Small7()).equals(20);
+
+ final ByteWriter idWriter = ByteWriter();
+ idWriter.writeVarUint32Small7(30);
+ final Enum value = serializer.read(
+ ByteReader.forBytes(idWriter.takeBytes()),
+ 0,
+ _newDeserializationContext(),
+ );
+ check(value).equals(EnumFieldBasedIds.C);
+ });
+
+ test('throws when enum id is outside unsigned 32-bit range', () {
+ final EnumSerializer serializer = EnumSerializer(false, [
+ EnumWithIds.A,
+ ], {
+ -1: EnumWithIds.A,
+ });
+
+ final ByteWriter writer = ByteWriter();
+ check(
+ () => serializer.write(
+ writer,
+ EnumWithIds.A,
+ _newSerializationContext(),
+ ),
+ ).throws<RangeError>();
+ });
+
+ test('round-trips every annotated enum value via Fory
serialize/deserialize',
+ () {
+ final Fory fory = Fory()..register(EnumWithIds);
+
+ for (final EnumWithIds value in EnumWithIds.values) {
+ final bytes = fory.serialize(value);
+ final EnumWithIds decoded = fory.deserialize(bytes) as EnumWithIds;
+ check(decoded).equals(value);
+ }
+ });
+
+ test(
+ 'round-trips every field-based annotated enum value via Fory
serialize/deserialize',
+ () {
+ final Fory fory = Fory()..register(EnumFieldBasedIds);
+
+ for (final EnumFieldBasedIds value in EnumFieldBasedIds.values) {
+ final bytes = fory.serialize(value);
+ final EnumFieldBasedIds decoded =
+ fory.deserialize(bytes) as EnumFieldBasedIds;
+ check(decoded).equals(value);
+ }
+ });
+ });
+}
diff --git a/dart/packages/fory/lib/src/annotation/fory_enum.dart
b/dart/packages/fory/lib/src/annotation/fory_enum.dart
index fe7aeab56..5145945cb 100644
--- a/dart/packages/fory/lib/src/annotation/fory_enum.dart
+++ b/dart/packages/fory/lib/src/annotation/fory_enum.dart
@@ -31,6 +31,7 @@ import 'fory_object.dart';
/// // enums
/// }
/// ```
+@Target({TargetKind.enumType})
class ForyEnum extends ForyObject {
static const String name = 'ForyEnum';
static const List<TargetKind> targets = [TargetKind.enumType];
@@ -41,3 +42,41 @@ class ForyEnum extends ForyObject {
/// A constant instance of [ForyEnum].
const ForyEnum foryEnum = ForyEnum();
+
+/// A class representing an enumeration id in the Fory framework.
+///
+/// This class extends [ForyObject] and is used to annotate enum ids
+/// within the Fory framework.
+///
+/// Can be used in two ways:
+///
+/// 1. On each enum value with an explicit id:
+/// ```
+/// @foryEnum
+/// enum Color {
+/// @ForyEnumId(5)
+/// blue,
+/// @ForyEnumId(10)
+/// white,
+/// }
+/// ```
+///
+/// 2. On an int field of an enhanced enum to use its value as the id:
+/// ```
+/// @foryEnum
+/// enum UserRole {
+/// green(0),
+/// blue(1),
+/// white(2);
+///
+/// @ForyEnumId()
+/// final int code;
+/// const Color(this.code);
+/// }
+/// ```
+@Target({TargetKind.enumValue, TargetKind.field})
+class ForyEnumId extends ForyObject {
+ final int? id;
+
+ const ForyEnumId([this.id]);
+}
\ No newline at end of file
diff --git
a/dart/packages/fory/lib/src/codegen/analyze/analysis_type_identifier.dart
b/dart/packages/fory/lib/src/codegen/analyze/analysis_type_identifier.dart
index 645f974fe..ec1671ef3 100644
--- a/dart/packages/fory/lib/src/codegen/analyze/analysis_type_identifier.dart
+++ b/dart/packages/fory/lib/src/codegen/analyze/analysis_type_identifier.dart
@@ -43,7 +43,8 @@ class AnalysisTypeIdentifier {
null,
null,
null,
- null
+ null,
+ null,
];
static final List<TypeStringKey> _keys = [
TypeStringKey(
@@ -66,6 +67,11 @@ class AnalysisTypeIdentifier {
'package',
'fory/src/annotation/fory_enum.dart',
),
+ TypeStringKey(
+ 'ForyEnumId',
+ 'package',
+ 'fory/src/annotation/fory_enum.dart',
+ ),
TypeStringKey(
'Uint8Type',
'package',
@@ -121,6 +127,10 @@ class AnalysisTypeIdentifier {
return _check(element, 3);
}
+ static bool isForyEnumId(ClassElement element) {
+ return _check(element, 4);
+ }
+
static void cacheForyEnumAnnotationId(int id) {
_ids[3] = id;
}
@@ -130,18 +140,18 @@ class AnalysisTypeIdentifier {
}
static bool isUint8Type(ClassElement element) {
- return _check(element, 4);
+ return _check(element, 5);
}
static bool isUint16Type(ClassElement element) {
- return _check(element, 5);
+ return _check(element, 6);
}
static bool isUint32Type(ClassElement element) {
- return _check(element, 6);
+ return _check(element, 7);
}
static bool isUint64Type(ClassElement element) {
- return _check(element, 7);
+ return _check(element, 8);
}
}
diff --git
a/dart/packages/fory/lib/src/codegen/analyze/impl/struct/enum_analyzer_impl.dart
b/dart/packages/fory/lib/src/codegen/analyze/impl/struct/enum_analyzer_impl.dart
index 621b15ebd..263c33986 100644
---
a/dart/packages/fory/lib/src/codegen/analyze/impl/struct/enum_analyzer_impl.dart
+++
b/dart/packages/fory/lib/src/codegen/analyze/impl/struct/enum_analyzer_impl.dart
@@ -17,26 +17,123 @@
* under the License.
*/
+import 'package:analyzer/dart/constant/value.dart';
import 'package:analyzer/dart/element/element.dart';
+import 'package:fory/src/codegen/analyze/analysis_type_identifier.dart';
import 'package:fory/src/codegen/analyze/interface/enum_analyzer.dart';
import 'package:fory/src/codegen/meta/impl/enum_spec_generator.dart';
+import 'package:fory/src/fory_exception.dart';
class EnumAnalyzerImpl implements EnumAnalyzer {
const EnumAnalyzerImpl();
+ static const int _minEnumId = 0;
+ static const int _maxEnumId = (1024 * 1024 * 1024 * 4) - 1; // 2^32 - 1
+
+ /// Finds a non-constant field annotated with @ForyEnumId().
+ String? _findIdField(EnumElement enumElement) {
+ for (final FieldElement field in enumElement.fields) {
+ if (field.isEnumConstant || field.isSynthetic) continue;
+ for (final ElementAnnotation annotation in field.metadata) {
+ final DartObject? annotationValue = annotation.computeConstantValue();
+ final Element? typeElement = annotationValue?.type?.element;
+ if (typeElement is ClassElement &&
+ AnalysisTypeIdentifier.isForyEnumId(typeElement)) {
+ return field.name;
+ }
+ }
+ }
+ return null;
+ }
+
+ /// Reads @ForyEnumId(id) from per-value annotation.
+ int? _readEnumId(FieldElement enumField) {
+ for (final ElementAnnotation annotation in enumField.metadata) {
+ final DartObject? annotationValue = annotation.computeConstantValue();
+ final Element? typeElement = annotationValue?.type?.element;
+ if (typeElement is ClassElement &&
+ AnalysisTypeIdentifier.isForyEnumId(typeElement)) {
+ return annotationValue?.getField('id')?.toIntValue();
+ }
+ }
+ return null;
+ }
+
@override
EnumSpecGenerator analyze(EnumElement enumElement) {
String packageName = enumElement.location!.components[0];
+ final String enumName = enumElement.name;
+ final List<FieldElement> enumFields =
+ enumElement.fields.where((FieldElement e) =>
e.isEnumConstant).toList();
+ final List<String> enumValues =
+ enumFields.map((FieldElement e) => e.name).toList();
+
+ final String? idFieldName = _findIdField(enumElement);
+ final Map<String, int> enumIds = <String, int>{};
+ final Map<int, String> seenIds = <int, String>{};
+ final List<String> duplicateIds = <String>[];
+ final List<String> missingIdValues = <String>[];
+ final List<String> outOfRangeIds = <String>[];
+
+ for (final FieldElement enumField in enumFields) {
+ final int? id = idFieldName != null
+ ?
enumField.computeConstantValue()?.getField(idFieldName)?.toIntValue()
+ : _readEnumId(enumField);
+
+ if (id == null) {
+ missingIdValues.add(enumField.name);
+ continue;
+ }
+
+ if (id < _minEnumId || id > _maxEnumId) {
+ outOfRangeIds.add('$id for ${enumField.name}');
+ continue;
+ }
+
+ final String? firstValueWithId = seenIds[id];
+ if (firstValueWithId != null) {
+ duplicateIds.add('$id for $firstValueWithId and ${enumField.name}');
+ continue;
+ }
+ seenIds[id] = enumField.name;
+ enumIds[enumField.name] = id;
+ }
+
+ // Any @ForyEnumId present means the whole configuration must be valid.
+ final bool anyAnnotationPresent = idFieldName != null ||
+ enumIds.isNotEmpty ||
+ outOfRangeIds.isNotEmpty ||
+ duplicateIds.isNotEmpty;
- List<String> enumValues = enumElement.fields
- .where((e) => e.isEnumConstant)
- .map((e) => e.name)
- .toList();
+ if (anyAnnotationPresent) {
+ if (outOfRangeIds.isNotEmpty) {
+ throw InvalidDataException(
+ 'Enum $enumName in $packageName has @ForyEnumId values outside the '
+ 'unsigned 32-bit range (0 to 4294967295). '
+ 'Offending values: ${outOfRangeIds.join('; ')}.',
+ );
+ }
+ if (duplicateIds.isNotEmpty) {
+ throw InvalidDataException(
+ 'Enum $enumName in $packageName has duplicate @ForyEnumId values '
+ '(${duplicateIds.join('; ')}). '
+ 'All ids must be unique.',
+ );
+ }
+ if (missingIdValues.isNotEmpty) {
+ throw InvalidDataException(
+ 'Enum $enumName in $packageName has partial @ForyEnumId annotations.
'
+ 'Missing values: ${missingIdValues.join(', ')}. '
+ 'All values must have an id when @ForyEnumId is used.',
+ );
+ }
+ }
return EnumSpecGenerator(
- enumElement.name,
+ enumName,
packageName,
enumValues,
+ anyAnnotationPresent ? enumIds : null,
);
}
}
diff --git
a/dart/packages/fory/lib/src/codegen/meta/impl/enum_spec_generator.dart
b/dart/packages/fory/lib/src/codegen/meta/impl/enum_spec_generator.dart
index c5d21755d..b726447f6 100644
--- a/dart/packages/fory/lib/src/codegen/meta/impl/enum_spec_generator.dart
+++ b/dart/packages/fory/lib/src/codegen/meta/impl/enum_spec_generator.dart
@@ -25,9 +25,15 @@ import 'package:meta/meta.dart';
@immutable
class EnumSpecGenerator extends CustomTypeSpecGenerator {
final List<String> _enumVarNames;
+ final Map<String, int>? _enumValueIds;
late final String _varName;
- EnumSpecGenerator(super.name, super.importPath, this._enumVarNames) {
+ EnumSpecGenerator(
+ super.name,
+ super.importPath,
+ this._enumVarNames,
+ this._enumValueIds,
+ ) {
_varName = "\$$name";
assert(_enumVarNames.isNotEmpty);
}
@@ -45,6 +51,34 @@ class EnumSpecGenerator extends CustomTypeSpecGenerator {
buf.write("],\n");
}
+ void _writeEnumIdMap(StringBuffer buf, int indentLevel) {
+ final Map<String, int>? enumValueIds = _enumValueIds;
+ if (enumValueIds == null) {
+ return;
+ }
+
+ final int totalIndent = indentLevel * CodegenStyle.indent;
+ CodegenTool.writeIndent(buf, totalIndent);
+ buf.write("{\n");
+
+ for (final String varName in _enumVarNames) {
+ final int? id = enumValueIds[varName];
+ if (id == null) {
+ continue;
+ }
+ CodegenTool.writeIndent(buf, totalIndent + CodegenStyle.indent);
+ buf.write(id);
+ buf.write(": ");
+ buf.write(name);
+ buf.write(".");
+ buf.write(varName);
+ buf.write(",\n");
+ }
+
+ CodegenTool.writeIndent(buf, totalIndent);
+ buf.write("},\n");
+ }
+
@override
void writeCode(StringBuffer buf, [int indentLevel = 0]) {
// buf.write(GenCodeStyle.magicSign);
@@ -75,6 +109,7 @@ class EnumSpecGenerator extends CustomTypeSpecGenerator {
// buf.write(",\n");
_writeFieldsStr(buf, indentLevel + 1);
+ _writeEnumIdMap(buf, indentLevel + 1);
// tail part
buf.write(");\n");
diff --git a/dart/packages/fory/lib/src/meta/specs/enum_spec.dart
b/dart/packages/fory/lib/src/meta/specs/enum_spec.dart
index 4a7cb969d..52cbf7804 100644
--- a/dart/packages/fory/lib/src/meta/specs/enum_spec.dart
+++ b/dart/packages/fory/lib/src/meta/specs/enum_spec.dart
@@ -27,9 +27,9 @@ import 'package:fory/src/const/types.dart';
@immutable
class EnumSpec extends CustomTypeSpec {
// final String tag;
- // TODO: Currently, enums only support using ordinal for transmission. There
is also support for ForyEnum annotation, such as using value, so we can
directly use the values array here.
final List<Enum> values;
- const EnumSpec(Type dartType, this.values)
+ final Map<int, Enum>? idToValue;
+ const EnumSpec(Type dartType, this.values, [this.idToValue])
: super(dartType, ObjType.NAMED_ENUM);
@override
@@ -38,7 +38,8 @@ class EnumSpec extends CustomTypeSpec {
other is EnumSpec &&
runtimeType == other.runtimeType &&
dartType == other.dartType &&
- values.equals(other.values);
+ values.equals(other.values) &&
+ const MapEquality<int, Enum>().equals(idToValue, other.idToValue);
}
@override
@@ -46,5 +47,6 @@ class EnumSpec extends CustomTypeSpec {
runtimeType,
dartType,
values,
+ const MapEquality<int, Enum>().hash(idToValue),
);
}
diff --git a/dart/packages/fory/lib/src/serializer/enum_serializer.dart
b/dart/packages/fory/lib/src/serializer/enum_serializer.dart
index 57d590338..554478840 100644
--- a/dart/packages/fory/lib/src/serializer/enum_serializer.dart
+++ b/dart/packages/fory/lib/src/serializer/enum_serializer.dart
@@ -41,7 +41,7 @@ final class _EnumSerializerCache extends SerializerCache {
return serializer;
}
// In foryJava, EnumSerializer does not perform reference tracking
- serializer = EnumSerializer(false, spec.values);
+ serializer = EnumSerializer(false, spec.values, spec.idToValue);
_cache[dartType] = serializer;
return serializer;
}
@@ -50,23 +50,73 @@ final class _EnumSerializerCache extends SerializerCache {
final class EnumSerializer extends CustomSerializer<Enum> {
static const SerializerCache cache = _EnumSerializerCache();
+ static const int _minEnumId = 0;
+ static const int _maxEnumId = (1024 * 1024 * 1024 * 4) - 1; // 2^32 - 1
+
final List<Enum> values;
- EnumSerializer(bool writeRef, this.values)
- : super(ObjType.NAMED_ENUM, writeRef);
+ final Map<int, Enum>? _idToValue;
+ final Map<Enum, int>? _valueToId;
+ final List<Object>? _idCandidates;
+
+ EnumSerializer(bool writeRef, this.values, [Map<int, Enum>? idToValue])
+ : _idToValue = idToValue,
+ _valueToId = idToValue == null
+ ? null
+ : <Enum, int>{
+ for (final MapEntry<int, Enum> entry in idToValue.entries)
+ entry.value: entry.key,
+ },
+ _idCandidates = idToValue == null
+ ? null
+ : List<Object>.unmodifiable(idToValue.keys),
+ super(ObjType.NAMED_ENUM, writeRef);
@override
Enum read(ByteReader br, int refId, DeserializationContext pack) {
- int index = br.readVarUint32Small7();
+ final int indexOrId = br.readVarUint32Small7();
+ final Map<int, Enum>? idToValue = _idToValue;
+ if (idToValue != null) {
+ final Enum? enumValue = idToValue[indexOrId];
+ if (enumValue == null) {
+ throw DeserializationRangeException(indexOrId, _idCandidates!);
+ }
+ return enumValue;
+ }
// foryJava supports deserializeUnknownEnumValueAsNull,
// but here in Dart, it will definitely throw an error if the index is out
of range
- if (index < 0 || index >= values.length) {
- throw DeserializationRangeException(index, values);
+ // This is for the ordinal-based deserailization only when previous check
fails
+ // and the variable here still means index, not id.
+ if (indexOrId < 0 || indexOrId >= values.length) {
+ throw DeserializationRangeException(indexOrId, values);
}
- return values[index];
+ return values[indexOrId];
}
@override
void write(ByteWriter bw, Enum v, SerializationContext pack) {
+ final Map<Enum, int>? valueToId = _valueToId;
+ if (valueToId != null) {
+ final int? id = valueToId[v];
+ if (id == null) {
+ throw ArgumentError.value(
+ v,
+ 'v',
+ 'Enum value is missing from EnumSpec.idToValue mapping.',
+ );
+ }
+ if (id < _minEnumId || id > _maxEnumId) {
+ throw RangeError.range(
+ id,
+ _minEnumId,
+ _maxEnumId,
+ 'id',
+ 'Enum id must be within unsigned 32-bit range.',
+ );
+ }
+ bw.writeVarUint32Small7(id);
+ return;
+ }
+
bw.writeVarUint32Small7(v.index);
}
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]