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]

Reply via email to