This is an automated email from the ASF dual-hosted git repository.

wangweipeng 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 c080d7b4c feat(JavaScript): fix testcase (#3334)
c080d7b4c is described below

commit c080d7b4cbac6c2a2f27f01136e293a06d831727
Author: weipeng <[email protected]>
AuthorDate: Sat Feb 14 14:45:21 2026 +0800

    feat(JavaScript): fix testcase (#3334)
    
    ## Why?
    1. fix testcase, now all the testcases were passed.
    
    
    ## What does this PR do?
    
    
    
    ## Related issues
    #3133
    
    
    ## Does this PR introduce any user-facing change?
    
    
    
    - [ ] Does this PR introduce any public API change?
    - [ ] Does this PR introduce any binary protocol compatibility change?
    
    ## Benchmark
---
 .github/workflows/ci.yml                           |   4 +-
 .../org/apache/fory/xlang/JavaScriptXlangTest.java |   9 +-
 javascript/package.json                            |   1 +
 javascript/packages/fory/index.ts                  |   6 -
 javascript/packages/fory/lib/fory.ts               |   6 +-
 javascript/packages/fory/lib/gen/any.ts            |   2 +-
 javascript/packages/fory/lib/gen/array.ts          |   8 +-
 javascript/packages/fory/lib/gen/collection.ts     |  13 +-
 javascript/packages/fory/lib/gen/enum.ts           |  18 +-
 javascript/packages/fory/lib/gen/ext.ts            |  10 +-
 javascript/packages/fory/lib/gen/index.ts          |  23 ++-
 javascript/packages/fory/lib/gen/map.ts            |  40 ++--
 javascript/packages/fory/lib/gen/serializer.ts     |   9 +-
 javascript/packages/fory/lib/gen/set.ts            |  10 +-
 javascript/packages/fory/lib/gen/struct.ts         |  49 ++---
 javascript/packages/fory/lib/meta/TypeMeta.ts      |  25 +--
 javascript/packages/fory/lib/type.ts               |   5 +-
 javascript/packages/fory/lib/typeInfo.ts           | 228 ++++++++++-----------
 javascript/packages/fory/lib/typeMetaResolver.ts   |  74 ++++---
 javascript/packages/fory/lib/typeResolver.ts       |  30 +--
 javascript/test/crossLanguage.test.ts              | 191 ++++++++---------
 javascript/test/object.test.ts                     |  15 +-
 javascript/test/protocol/struct.test.ts            |  11 +-
 23 files changed, 386 insertions(+), 401 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index d4ddfe533..8071b79da 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -463,14 +463,14 @@ jobs:
             ${{ runner.os }}-maven-
       - name: Run JavaScript Xlang Test
         env:
-          FORY_JAVASCRIPT_JAVA_CI: "0"
+          FORY_JAVASCRIPT_JAVA_CI: "1"
         run: |
           cd javascript
           npm install
           cd ../java
           mvn -T16 --no-transfer-progress clean install -DskipTests
           cd fory-core
-          mvn -T16 --no-transfer-progress test 
-Dtest=org.apache.fory.xlang.JavaScriptXlangTest
+          mvn --no-transfer-progress test 
-Dtest=org.apache.fory.xlang.JavaScriptXlangTest -DforkCount=0
 
   cpp_examples:
     name: C++ Examples
diff --git 
a/java/fory-core/src/test/java/org/apache/fory/xlang/JavaScriptXlangTest.java 
b/java/fory-core/src/test/java/org/apache/fory/xlang/JavaScriptXlangTest.java
index 8cb87be1c..1ff742642 100644
--- 
a/java/fory-core/src/test/java/org/apache/fory/xlang/JavaScriptXlangTest.java
+++ 
b/java/fory-core/src/test/java/org/apache/fory/xlang/JavaScriptXlangTest.java
@@ -34,13 +34,12 @@ import org.testng.annotations.Test;
 /** Executes cross-language tests against the Rust implementation. */
 @Test
 public class JavaScriptXlangTest extends XlangTestBase {
-  private static final String NODE_EXECUTABLE = "npx";
-  private static final String NODE_MODULE = "crossLanguage.test.ts";
+  private static final String NODE_EXECUTABLE = "npm";
 
   private static final List<String> RUST_BASE_COMMAND =
-      Arrays.asList(NODE_EXECUTABLE, "jest", NODE_MODULE, "-t", "caseName");
+      Arrays.asList(NODE_EXECUTABLE, "run", "test:crosslanguage", "-s", "--", 
"caseName");
 
-  private static final int NODE_TESTCASE_INDEX = 4;
+  private static final int NODE_TESTCASE_INDEX = 5;
 
   @Override
   protected void ensurePeerReady() {
@@ -182,7 +181,7 @@ public class JavaScriptXlangTest extends XlangTestBase {
   @Override
   @Test(dataProvider = "enableCodegen")
   public void testCollectionElementRefOverride(boolean enableCodegen) throws 
java.io.IOException {
-    throw new SkipException("Skipping: JavaScript xlang test not implemented 
for this case");
+    super.testCollectionElementRefOverride(enableCodegen);
   }
 
   @Test(dataProvider = "enableCodegen")
diff --git a/javascript/package.json b/javascript/package.json
index 17deca025..fca8923ba 100644
--- a/javascript/package.json
+++ b/javascript/package.json
@@ -1,5 +1,6 @@
 {
   "scripts": {
+    "test:crosslanguage": "f() { npx jest crossLanguage.test.ts -t \"$@\" > 
/dev/null 2>&1; }; f",
     "test": "npm run build && jest",
     "clear": "rm -rf ./packages/fory/dist && rm -rf ./packages/hps/dist",
     "build": "npm run clear && npm run build -w packages/fory -w packages/hps",
diff --git a/javascript/packages/fory/index.ts 
b/javascript/packages/fory/index.ts
index efb460796..3e2a7a88c 100644
--- a/javascript/packages/fory/index.ts
+++ b/javascript/packages/fory/index.ts
@@ -18,11 +18,8 @@
  */
 
 import {
-  StructTypeInfo,
   TypeInfo,
-  ArrayTypeInfo,
   Type,
-  ForyField,
   Dynamic,
 } from "./lib/typeInfo";
 import { Serializer, Mode } from "./lib/type";
@@ -34,12 +31,9 @@ import { BFloat16, BFloat16Array } from "./lib/bfloat16";
 export {
   Serializer,
   TypeInfo,
-  ArrayTypeInfo,
-  StructTypeInfo,
   Type,
   Mode,
   BinaryWriter,
-  ForyField,
   Dynamic,
   BinaryReader,
   BFloat16,
diff --git a/javascript/packages/fory/lib/fory.ts 
b/javascript/packages/fory/lib/fory.ts
index 526a576f9..a554eb6df 100644
--- a/javascript/packages/fory/lib/fory.ts
+++ b/javascript/packages/fory/lib/fory.ts
@@ -23,7 +23,7 @@ import { BinaryReader } from "./reader";
 import { ReferenceResolver } from "./referenceResolver";
 import { ConfigFlags, Serializer, Config, ForyTypeInfoSymbol, WithForyClsInfo, 
TypeId, CustomSerializer } from "./type";
 import { OwnershipError } from "./error";
-import { InputType, ResultType, StructTypeInfo, TypeInfo } from "./typeInfo";
+import { InputType, ResultType, TypeInfo } from "./typeInfo";
 import { Gen } from "./gen";
 import { TypeMeta } from "./meta/TypeMeta";
 import { PlatformBuffer } from "./platformBuffer";
@@ -98,10 +98,12 @@ export default class {
     TypeInfo.attach(this);
     if (constructor.prototype?.[ForyTypeInfoSymbol]) {
       const typeInfo: TypeInfo = 
(<WithForyClsInfo>(constructor.prototype[ForyTypeInfoSymbol])).structTypeInfo;
+      typeInfo.freeze();
       serializer = new Gen(this, { creator: constructor, customSerializer 
}).generateSerializer(typeInfo);
       this.typeResolver.registerSerializer(typeInfo, serializer);
     } else {
       const typeInfo = constructor;
+      typeInfo.freeze();
       serializer = new Gen(this).generateSerializer(typeInfo);
       this.typeResolver.registerSerializer(typeInfo, serializer);
     }
@@ -125,7 +127,7 @@ export default class {
 
   replaceSerializerReader(typeInfo: TypeInfo) {
     TypeInfo.attach(this);
-    const serializer = new Gen(this, { creator: (typeInfo as 
StructTypeInfo).options.creator }).reGenerateSerializer(typeInfo);
+    const serializer = new Gen(this, { creator: (typeInfo).options!.creator! 
}).reGenerateSerializer(typeInfo);
     const result = this.typeResolver.registerSerializer(typeInfo, {
       getHash: serializer.getHash,
       read: serializer.read,
diff --git a/javascript/packages/fory/lib/gen/any.ts 
b/javascript/packages/fory/lib/gen/any.ts
index cf941ca60..9874cc6bc 100644
--- a/javascript/packages/fory/lib/gen/any.ts
+++ b/javascript/packages/fory/lib/gen/any.ts
@@ -41,7 +41,7 @@ export class AnyHelper {
       }
       const hash = serializer.getHash();
       if (hash !== typeMeta.getHash()) {
-        return fory.typeMetaResolver.genSerializerByTypeMetaRuntime(typeMeta);
+        return fory.typeMetaResolver.genSerializerByTypeMetaRuntime(typeMeta, 
serializer);
       }
       return serializer;
     }
diff --git a/javascript/packages/fory/lib/gen/array.ts 
b/javascript/packages/fory/lib/gen/array.ts
index 0b7d59e9c..7027353f1 100644
--- a/javascript/packages/fory/lib/gen/array.ts
+++ b/javascript/packages/fory/lib/gen/array.ts
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-import { ArrayTypeInfo, TypeInfo } from "../typeInfo";
+import { TypeInfo } from "../typeInfo";
 import { CodecBuilder } from "./builder";
 import { CodegenRegistry } from "./router";
 import { TypeId } from "../type";
@@ -25,15 +25,15 @@ import { Scope } from "./scope";
 import { CollectionSerializerGenerator } from "./collection";
 
 class ArraySerializerGenerator extends CollectionSerializerGenerator {
-  typeInfo: ArrayTypeInfo;
+  typeInfo: TypeInfo;
 
   constructor(typeInfo: TypeInfo, builder: CodecBuilder, scope: Scope) {
     super(typeInfo, builder, scope);
-    this.typeInfo = <ArrayTypeInfo>typeInfo;
+    this.typeInfo = typeInfo;
   }
 
   genericTypeDescriptin(): TypeInfo {
-    return this.typeInfo.options.inner;
+    return this.typeInfo.options!.inner!;
   }
 
   sizeProp() {
diff --git a/javascript/packages/fory/lib/gen/collection.ts 
b/javascript/packages/fory/lib/gen/collection.ts
index 16cc6689e..c8b7566e4 100644
--- a/javascript/packages/fory/lib/gen/collection.ts
+++ b/javascript/packages/fory/lib/gen/collection.ts
@@ -214,14 +214,14 @@ export abstract class CollectionSerializerGenerator 
extends BaseSerializerGenera
   constructor(typeInfo: TypeInfo, builder: CodecBuilder, scope: Scope) {
     super(typeInfo, builder, scope);
     this.typeInfo = typeInfo;
-    const inner = this.genericTypeDescriptin();
+    const inner = this.genericTypeDescriptin()!;
     this.innerGenerator = CodegenRegistry.newGeneratorByTypeInfo(inner, 
this.builder, this.scope);
   }
 
-  abstract genericTypeDescriptin(): TypeInfo;
+  abstract genericTypeDescriptin(): TypeInfo | undefined;
 
   private isAny() {
-    return this.genericTypeDescriptin().typeId === TypeId.UNKNOWN;
+    return this.genericTypeDescriptin()?.typeId === TypeId.UNKNOWN;
   }
 
   abstract newCollection(lenAccessor: string): string;
@@ -295,7 +295,6 @@ export abstract class CollectionSerializerGenerator extends 
BaseSerializerGenera
     const flags = this.scope.uniqueName("flags");
     const idx = this.scope.uniqueName("idx");
     const refFlag = this.scope.uniqueName("refFlag");
-
     return `
             const ${len} = ${this.builder.reader.readVarUint32Small7()};
             const ${flags} = ${this.builder.reader.uint8()};
@@ -307,7 +306,7 @@ export abstract class CollectionSerializerGenerator extends 
BaseSerializerGenera
                     switch (${refFlag}) {
                         case ${RefFlags.NotNullValueFlag}:
                         case ${RefFlags.RefValueFlag}:
-                            ${this.innerGenerator.read(x => 
`${this.putAccessor(result, x, idx)}`, `${refFlag} === 
${RefFlags.RefValueFlag}`)}
+                            ${this.innerGenerator.readEmbed().read((x: any) => 
`${this.putAccessor(result, x, idx)}`, `${refFlag} === 
${RefFlags.RefValueFlag}`)}
                             break;
                         case ${RefFlags.RefFlag}:
                             ${this.putAccessor(result, 
this.builder.referenceResolver.getReadObject(this.builder.reader.varUInt32()), 
idx)}
@@ -322,13 +321,13 @@ export abstract class CollectionSerializerGenerator 
extends BaseSerializerGenera
                     if (${this.builder.reader.int8()} == ${RefFlags.NullFlag}) 
{
                         ${this.putAccessor(result, "null", idx)}
                     } else {
-                        ${this.innerGenerator.read(x => 
`${this.putAccessor(result, x, idx)}`, "false")}
+                        ${this.innerGenerator.readEmbed().read((x: any) => 
`${this.putAccessor(result, x, idx)}`, "false")}
                     }
                 }
 
             } else {
                 for (let ${idx} = 0; ${idx} < ${len}; ${idx}++) {
-                    ${this.innerGenerator.read(x => 
`${this.putAccessor(result, x, idx)}`, "false")}
+                    ${this.innerGenerator.readEmbed().read((x: any) => 
`${this.putAccessor(result, x, idx)}`, "false")}
                 }
             }
             ${accessor(result)}
diff --git a/javascript/packages/fory/lib/gen/enum.ts 
b/javascript/packages/fory/lib/gen/enum.ts
index 6a7218780..cc2a31498 100644
--- a/javascript/packages/fory/lib/gen/enum.ts
+++ b/javascript/packages/fory/lib/gen/enum.ts
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-import { EnumTypeInfo, TypeInfo } from "../typeInfo";
+import { TypeInfo } from "../typeInfo";
 import { CodecBuilder } from "./builder";
 import { BaseSerializerGenerator } from "./serializer";
 import { CodegenRegistry } from "./router";
@@ -25,22 +25,22 @@ import { TypeId, MaxUInt32 } from "../type";
 import { Scope } from "./scope";
 
 class EnumSerializerGenerator extends BaseSerializerGenerator {
-  typeInfo: EnumTypeInfo;
+  typeInfo: TypeInfo;
 
   constructor(typeInfo: TypeInfo, builder: CodecBuilder, scope: Scope) {
     super(typeInfo, builder, scope);
-    this.typeInfo = <EnumTypeInfo>typeInfo;
+    this.typeInfo = typeInfo;
   }
 
   write(accessor: string): string {
-    if (!this.typeInfo.options?.inner) {
+    if (!this.typeInfo.options?.enumProps) {
       return this.builder.writer.varUInt32(accessor);
     }
-    if (Object.values(this.typeInfo.options.inner).length < 1) {
+    if (Object.values(this.typeInfo.options.enumProps).length < 1) {
       throw new Error("An enum must contain at least one field");
     }
     return `
-        ${Object.values(this.typeInfo.options.inner).map((value, index) => {
+        ${Object.values(this.typeInfo.options.enumProps).map((value, index) => 
{
       if (typeof value !== "string" && typeof value !== "number") {
         throw new Error("Enum value must be string or number");
       }
@@ -104,7 +104,7 @@ class EnumSerializerGenerator extends 
BaseSerializerGenerator {
         break;
       case TypeId.NAMED_ENUM:
         {
-          const typeInfo = this.typeInfo.castToStruct();
+          const typeInfo = this.typeInfo;
           const nsBytes = this.scope.declare("nsBytes", 
this.builder.metaStringResolver.encodeNamespace(CodecBuilder.replaceBackslashAndQuote(typeInfo.namespace)));
           const typeNameBytes = this.scope.declare("typeNameBytes", 
this.builder.metaStringResolver.encodeTypeName(CodecBuilder.replaceBackslashAndQuote(typeInfo.typeName)));
           typeMeta = `
@@ -124,14 +124,14 @@ class EnumSerializerGenerator extends 
BaseSerializerGenerator {
   }
 
   read(accessor: (expr: string) => string): string {
-    if (!this.typeInfo.options?.inner) {
+    if (!this.typeInfo.options?.enumProps) {
       return accessor(this.builder.reader.varUInt32());
     }
     const enumValue = this.scope.uniqueName("enum_v");
     return `
         const ${enumValue} = ${this.builder.reader.varUInt32()};
         switch(${enumValue}) {
-            ${Object.values(this.typeInfo.options.inner).map((value, index) => 
{
+            ${Object.values(this.typeInfo.options.enumProps).map((value, 
index) => {
       if (typeof value !== "string" && typeof value !== "number") {
         throw new Error("Enum value must be string or number");
       }
diff --git a/javascript/packages/fory/lib/gen/ext.ts 
b/javascript/packages/fory/lib/gen/ext.ts
index 72081a10d..4569b202a 100644
--- a/javascript/packages/fory/lib/gen/ext.ts
+++ b/javascript/packages/fory/lib/gen/ext.ts
@@ -20,18 +20,18 @@
 import { TypeId } from "../type";
 import { Scope } from "./scope";
 import { CodecBuilder } from "./builder";
-import { StructTypeInfo, TypeInfo } from "../typeInfo";
+import { TypeInfo } from "../typeInfo";
 import { CodegenRegistry } from "./router";
 import { BaseSerializerGenerator } from "./serializer";
 import { TypeMeta } from "../meta/TypeMeta";
 
 class ExtSerializerGenerator extends BaseSerializerGenerator {
-  typeInfo: StructTypeInfo;
+  typeInfo: TypeInfo;
   typeMeta: TypeMeta;
 
   constructor(typeInfo: TypeInfo, builder: CodecBuilder, scope: Scope) {
     super(typeInfo, builder, scope);
-    this.typeInfo = <StructTypeInfo>typeInfo;
+    this.typeInfo = typeInfo;
     this.typeMeta = TypeMeta.fromTypeInfo(this.typeInfo);
   }
 
@@ -44,7 +44,7 @@ class ExtSerializerGenerator extends BaseSerializerGenerator {
   read(accessor: (expr: string) => string, refState: string): string {
     const result = this.scope.uniqueName("result");
     return `
-      ${this.typeInfo.options.withConstructor
+      ${this.typeInfo.options!.withConstructor
         ? `
           const ${result} = new ${this.builder.getOptions("creator")}();
         `
@@ -158,7 +158,7 @@ class ExtSerializerGenerator extends 
BaseSerializerGenerator {
             
${this.builder.metaStringResolver.writeBytes(this.builder.writer.ownName(), 
typeNameBytes)}
           `;
         } else {
-          const bytes = this.scope.declare("typeInfoBytes", `new 
Uint8Array([${TypeMeta.fromTypeInfo(<StructTypeInfo> 
this.typeInfo).toBytes().join(",")}])`);
+          const bytes = this.scope.declare("typeInfoBytes", `new 
Uint8Array([${TypeMeta.fromTypeInfo(this.typeInfo).toBytes().join(",")}])`);
           typeMeta = 
this.builder.typeMetaResolver.writeTypeMeta(this.builder.getTypeInfo(), 
this.builder.writer.ownName(), bytes);
         }
         break;
diff --git a/javascript/packages/fory/lib/gen/index.ts 
b/javascript/packages/fory/lib/gen/index.ts
index 6df62034e..df757d974 100644
--- a/javascript/packages/fory/lib/gen/index.ts
+++ b/javascript/packages/fory/lib/gen/index.ts
@@ -18,7 +18,7 @@
  */
 
 import { TypeId, Serializer } from "../type";
-import { ArrayTypeInfo, MapTypeInfo, StructTypeInfo, SetTypeInfo, TypeInfo } 
from "../typeInfo";
+import { TypeInfo } from "../typeInfo";
 import { CodegenRegistry } from "./router";
 import { CodecBuilder } from "./builder";
 import { Scope } from "./scope";
@@ -62,12 +62,12 @@ export class Gen {
     return new Function(funcString);
   }
 
-  private register(typeInfo: StructTypeInfo, serializer?: Serializer) {
+  private register(typeInfo: TypeInfo, serializer?: Serializer) {
     this.fory.typeResolver.registerSerializer(typeInfo, serializer);
   }
 
   private isRegistered(typeInfo: TypeInfo) {
-    return !!this.fory.typeResolver.typeInfoExists(typeInfo);
+    return !!this.fory.typeResolver.getSerializerByTypeInfo(typeInfo);
   }
 
   private traversalContainer(typeInfo: TypeInfo) {
@@ -75,25 +75,28 @@ export class Gen {
       if (this.isRegistered(typeInfo)) {
         return;
       }
-      const options = (<StructTypeInfo>typeInfo).options;
+      const options = (typeInfo).options;
       if (options?.props) {
-        this.register(<StructTypeInfo>typeInfo);
+        this.register(typeInfo);
         Object.values(options.props).forEach((x) => {
           this.traversalContainer(x);
         });
         const func = this.generate(typeInfo);
-        this.register(<StructTypeInfo>typeInfo, func()(this.fory, 
Gen.external, typeInfo, this.regOptions));
+        this.register(typeInfo, func()(this.fory, Gen.external, typeInfo, 
this.regOptions));
       }
     }
     if (typeInfo.typeId === TypeId.LIST) {
-      this.traversalContainer((<ArrayTypeInfo>typeInfo).options.inner);
+      this.traversalContainer(typeInfo.options!.inner!);
     }
     if (typeInfo.typeId === TypeId.SET) {
-      this.traversalContainer((<SetTypeInfo>typeInfo).options.key);
+      this.traversalContainer((typeInfo).options!.key!);
     }
     if (typeInfo.typeId === TypeId.MAP) {
-      this.traversalContainer((<MapTypeInfo>typeInfo).options.key);
-      this.traversalContainer((<MapTypeInfo>typeInfo).options.value);
+      if (!typeInfo.options?.key || !typeInfo.options?.value) {
+        throw new Error("map type must have key and value");
+      }
+      this.traversalContainer((typeInfo).options!.key!);
+      this.traversalContainer((typeInfo).options!.value!);
     }
   }
 
diff --git a/javascript/packages/fory/lib/gen/map.ts 
b/javascript/packages/fory/lib/gen/map.ts
index 372ac5c73..f1af7b1e8 100644
--- a/javascript/packages/fory/lib/gen/map.ts
+++ b/javascript/packages/fory/lib/gen/map.ts
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-import { MapTypeInfo, TypeInfo } from "../typeInfo";
+import { TypeInfo } from "../typeInfo";
 import { CodecBuilder } from "./builder";
 import { BaseSerializerGenerator, SerializerGenerator } from "./serializer";
 import { CodegenRegistry } from "./router";
@@ -118,8 +118,8 @@ class MapChunkWriter {
     // max size of chunk is 255
     if (this.chunkSize == 255
       || this.chunkOffset == 0
-      || keyInfo.equalTo(this.preKeyInfo)
-      || valueInfo.equalTo(this.preValueInfo)
+      || !keyInfo.equalTo(this.preKeyInfo)
+      || !valueInfo.equalTo(this.preValueInfo)
     ) {
       // new chunk
       this.endChunk();
@@ -153,7 +153,7 @@ class MapAnySerializer {
       const keyRef = this.fory.referenceResolver.existsWriteObject(v);
       if (keyRef !== undefined) {
         this.fory.binaryWriter.int8(RefFlags.RefFlag);
-        this.fory.binaryWriter.uint16(keyRef);
+        this.fory.binaryWriter.varUInt32(keyRef);
         return true;
       } else {
         this.fory.binaryWriter.int8(RefFlags.RefValueFlag);
@@ -277,19 +277,19 @@ class MapAnySerializer {
 }
 
 export class MapSerializerGenerator extends BaseSerializerGenerator {
-  typeInfo: MapTypeInfo;
+  typeInfo: TypeInfo;
   keyGenerator: SerializerGenerator;
   valueGenerator: SerializerGenerator;
 
   constructor(typeInfo: TypeInfo, builder: CodecBuilder, scope: Scope) {
     super(typeInfo, builder, scope);
-    this.typeInfo = <MapTypeInfo>typeInfo;
-    this.keyGenerator = 
CodegenRegistry.newGeneratorByTypeInfo(this.typeInfo.options.key, this.builder, 
this.scope);
-    this.valueGenerator = 
CodegenRegistry.newGeneratorByTypeInfo(this.typeInfo.options.value, 
this.builder, this.scope);
+    this.typeInfo = typeInfo;
+    this.keyGenerator = 
CodegenRegistry.newGeneratorByTypeInfo(this.typeInfo.options!.key!, 
this.builder, this.scope);
+    this.valueGenerator = 
CodegenRegistry.newGeneratorByTypeInfo(this.typeInfo.options!.value!, 
this.builder, this.scope);
   }
 
   private isAny() {
-    return this.typeInfo.options.key.typeId === TypeId.UNKNOWN || 
this.typeInfo.options.value.typeId === TypeId.UNKNOWN;
+    return this.typeInfo.options?.key!.typeId === TypeId.UNKNOWN || 
this.typeInfo.options?.value!.typeId === TypeId.UNKNOWN || 
!this.typeInfo.options?.key?.isMonomorphic() || 
!this.typeInfo.options?.value?.isMonomorphic();
   }
 
   private writeSpecificType(accessor: string) {
@@ -342,7 +342,7 @@ export class MapSerializerGenerator extends 
BaseSerializerGenerator {
               const ${keyRef} = 
${this.builder.referenceResolver.existsWriteObject(v)};
               if (${keyRef} !== undefined) {
                 ${this.builder.writer.int8(RefFlags.RefFlag)};
-                ${this.builder.writer.uint16(keyRef)};
+                ${this.builder.writer.varUInt32(keyRef)};
               } else {
                 ${this.builder.writer.int8(RefFlags.RefValueFlag)};
                 ${this.keyGenerator.writeEmbed().write(k)}
@@ -357,7 +357,7 @@ export class MapSerializerGenerator extends 
BaseSerializerGenerator {
               const ${valueRef} = 
${this.builder.referenceResolver.existsWriteObject(v)};
               if (${valueRef} !== undefined) {
                 ${this.builder.writer.int8(RefFlags.RefFlag)};
-                ${this.builder.writer.uint16(valueRef)};
+                ${this.builder.writer.varUInt32(valueRef)};
               } else {
                 ${this.builder.writer.int8(RefFlags.RefValueFlag)};
                 ${this.valueGenerator.writeEmbed().write(v)};
@@ -381,6 +381,9 @@ export class MapSerializerGenerator extends 
BaseSerializerGenerator {
       return this.writeSpecificType(accessor);
     }
     const innerSerializer = (innerTypeInfo: TypeInfo) => {
+      if (!innerTypeInfo.isMonomorphic()) {
+        return null;
+      }
       return this.scope.declare(
         "map_inner_ser",
         TypeId.isNamedType(innerTypeInfo.typeId)
@@ -388,8 +391,8 @@ export class MapSerializerGenerator extends 
BaseSerializerGenerator {
           : this.builder.typeResolver.getSerializerById(innerTypeInfo.typeId, 
innerTypeInfo.userTypeId)
       );
     };
-    return `new (${anySerializer})(${this.builder.getForyName()}, 
${this.typeInfo.options.key.typeId !== TypeId.UNKNOWN ? 
innerSerializer(this.typeInfo.options.key) : null
-      }, ${this.typeInfo.options.value.typeId !== TypeId.UNKNOWN ? 
innerSerializer(this.typeInfo.options.value) : null
+    return `new (${anySerializer})(${this.builder.getForyName()}, 
${this.typeInfo.options!.key!.typeId !== TypeId.UNKNOWN ? 
innerSerializer(this.typeInfo.options!.key!) : null
+      }, ${this.typeInfo.options!.value!.typeId !== TypeId.UNKNOWN ? 
innerSerializer(this.typeInfo.options!.value!) : null
       }).write(${accessor})`;
   }
 
@@ -427,7 +430,7 @@ export class MapSerializerGenerator extends 
BaseSerializerGenerator {
                 ${this.keyGenerator.read(x => `key = ${x}`, "true")}
                 break;
               case ${RefFlags.RefFlag}:
-                key = 
${this.builder.referenceResolver.getReadObject(this.builder.reader.varInt32())}
+                key = 
${this.builder.referenceResolver.getReadObject(this.builder.reader.varUInt32())}
                 break;
               case ${RefFlags.NullFlag}:
                 key = null;
@@ -449,7 +452,7 @@ export class MapSerializerGenerator extends 
BaseSerializerGenerator {
                 ${this.valueGenerator.read(x => `value = ${x}`, "true")}
                 break;
               case ${RefFlags.RefFlag}:
-                value = 
${this.builder.referenceResolver.getReadObject(this.builder.reader.varInt32())}
+                value = 
${this.builder.referenceResolver.getReadObject(this.builder.reader.varUInt32())}
                 break;
               case ${RefFlags.NullFlag}:
                 value = null;
@@ -479,6 +482,9 @@ export class MapSerializerGenerator extends 
BaseSerializerGenerator {
       return this.readSpecificType(accessor, refState);
     }
     const innerSerializer = (innerTypeInfo: TypeInfo) => {
+      if (!innerTypeInfo.isMonomorphic()) {
+        return null;
+      }
       return this.scope.declare(
         "map_inner_ser",
         TypeId.isNamedType(innerTypeInfo.typeId)
@@ -486,8 +492,8 @@ export class MapSerializerGenerator extends 
BaseSerializerGenerator {
           : this.builder.typeResolver.getSerializerById(innerTypeInfo.typeId, 
innerTypeInfo.userTypeId)
       );
     };
-    return accessor(`new (${anySerializer})(${this.builder.getForyName()}, 
${this.typeInfo.options.key.typeId !== TypeId.UNKNOWN ? 
innerSerializer(this.typeInfo.options.key) : null
-      }, ${this.typeInfo.options.value.typeId !== TypeId.UNKNOWN ? 
innerSerializer(this.typeInfo.options.value) : null
+    return accessor(`new (${anySerializer})(${this.builder.getForyName()}, 
${this.typeInfo.options!.key!.typeId! !== TypeId.UNKNOWN ? 
innerSerializer(this.typeInfo.options!.key!) : null
+      }, ${this.typeInfo.options!.value!.typeId !== TypeId.UNKNOWN ? 
innerSerializer(this.typeInfo.options!.value!) : null
       }).read(${refState})`);
   }
 
diff --git a/javascript/packages/fory/lib/gen/serializer.ts 
b/javascript/packages/fory/lib/gen/serializer.ts
index 0923fddaa..df8fd499c 100644
--- a/javascript/packages/fory/lib/gen/serializer.ts
+++ b/javascript/packages/fory/lib/gen/serializer.ts
@@ -76,7 +76,13 @@ export abstract class BaseSerializerGenerator implements 
SerializerGenerator {
     if (refTrackingUnableTypeId(this.typeInfo.typeId)) {
       return false;
     }
-    return this.builder.fory.config.refTracking === true;
+    if (this.builder.fory.config.refTracking !== true) {
+      return false;
+    }
+    if (typeof this.typeInfo.trackingRef === "boolean") {
+      return this.typeInfo.trackingRef;
+    }
+    return true;
   }
 
   abstract write(accessor: string): string;
@@ -308,6 +314,7 @@ export abstract class BaseSerializerGenerator implements 
SerializerGenerator {
               needToWriteRef: () => ${this.needToWriteRef()},
               getTypeId: () => ${this.getTypeId()},
               getUserTypeId: () => ${this.getUserTypeId()},
+              getTypeInfo: () => typeInfo,
               getHash,
 
               write,
diff --git a/javascript/packages/fory/lib/gen/set.ts 
b/javascript/packages/fory/lib/gen/set.ts
index 916e4cb53..f6a9ffb52 100644
--- a/javascript/packages/fory/lib/gen/set.ts
+++ b/javascript/packages/fory/lib/gen/set.ts
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-import { SetTypeInfo, TypeInfo } from "../typeInfo";
+import { TypeInfo } from "../typeInfo";
 import { CodecBuilder } from "./builder";
 import { CodegenRegistry } from "./router";
 import { TypeId } from "../type";
@@ -25,15 +25,15 @@ import { Scope } from "./scope";
 import { CollectionSerializerGenerator } from "./collection";
 
 class SetSerializerGenerator extends CollectionSerializerGenerator {
-  typeInfo: SetTypeInfo;
+  typeInfo: TypeInfo;
 
   constructor(typeInfo: TypeInfo, builder: CodecBuilder, scope: Scope) {
     super(typeInfo, builder, scope);
-    this.typeInfo = <SetTypeInfo>typeInfo;
+    this.typeInfo = typeInfo;
   }
 
-  genericTypeDescriptin(): TypeInfo {
-    return this.typeInfo.options.key;
+  genericTypeDescriptin(): TypeInfo | undefined {
+    return this.typeInfo.options?.key;
   }
 
   newCollection(): string {
diff --git a/javascript/packages/fory/lib/gen/struct.ts 
b/javascript/packages/fory/lib/gen/struct.ts
index e715f7f73..d753e16ae 100644
--- a/javascript/packages/fory/lib/gen/struct.ts
+++ b/javascript/packages/fory/lib/gen/struct.ts
@@ -20,14 +20,14 @@
 import { TypeId, RefFlags } from "../type";
 import { Scope } from "./scope";
 import { CodecBuilder } from "./builder";
-import { StructTypeInfo, TypeInfo } from "../typeInfo";
+import { TypeInfo } from "../typeInfo";
 import { CodegenRegistry } from "./router";
 import { BaseSerializerGenerator, SerializerGenerator } from "./serializer";
 import { TypeMeta } from "../meta/TypeMeta";
 
-const sortProps = (typeInfo: StructTypeInfo) => {
+const sortProps = (typeInfo: TypeInfo) => {
   const names = TypeMeta.fromTypeInfo(typeInfo).getFieldInfo();
-  const props = typeInfo.options.props;
+  const props = typeInfo.options!.props;
   return names.map((x) => {
     return {
       key: x.fieldName,
@@ -48,7 +48,7 @@ enum RefMode {
 
 }
 
-function toRefMode(trackingRef: boolean, nullable: boolean) {
+function toRefMode(trackingRef?: boolean, nullable?: boolean) {
   if (trackingRef) {
     return RefMode.TRACKING;
   } else if (nullable) {
@@ -59,26 +59,21 @@ function toRefMode(trackingRef: boolean, nullable: boolean) 
{
 }
 
 class StructSerializerGenerator extends BaseSerializerGenerator {
-  typeInfo: StructTypeInfo;
+  typeInfo: TypeInfo;
   sortedProps: { key: string; typeInfo: TypeInfo }[];
   metaChangedSerializer: string;
   typeMeta: TypeMeta;
 
   constructor(typeInfo: TypeInfo, builder: CodecBuilder, scope: Scope) {
     super(typeInfo, builder, scope);
-    this.typeInfo = <StructTypeInfo>typeInfo;
+    this.typeInfo = typeInfo;
     this.sortedProps = sortProps(this.typeInfo);
     this.metaChangedSerializer = 
this.scope.declareVar("metaChangedSerializer", "null");
     this.typeMeta = TypeMeta.fromTypeInfo(this.typeInfo);
   }
 
-  readField(fieldName: string, fieldTypeInfo: TypeInfo, assignStmt: (expr: 
string) => string, embedGenerator: SerializerGenerator, needToWriteRef: 
boolean) {
-    const { nullable = false } = this.typeInfo.options.fieldInfo?.[fieldName] 
|| {};
-    let { trackingRef } = this.typeInfo.options.fieldInfo?.[fieldName] || {};
-    const { dynamic } = this.typeInfo.options.fieldInfo?.[fieldName] || {};
-    if (typeof trackingRef !== "boolean") {
-      trackingRef = needToWriteRef;
-    }
+  readField(fieldTypeInfo: TypeInfo, assignStmt: (expr: string) => string, 
embedGenerator: SerializerGenerator) {
+    const { nullable = false, dynamic, trackingRef } = fieldTypeInfo;
     const refMode = toRefMode(trackingRef, nullable);
     let stmt = "";
     // polymorphic type
@@ -100,13 +95,8 @@ class StructSerializerGenerator extends 
BaseSerializerGenerator {
     return stmt;
   }
 
-  writeField(fieldName: string, fieldTypeInfo: TypeInfo, fieldAccessor: 
string, embedGenerator: SerializerGenerator, needToWriteRef: boolean) {
-    const { nullable = false } = this.typeInfo.options.fieldInfo?.[fieldName] 
|| {};
-    let { trackingRef } = this.typeInfo.options.fieldInfo?.[fieldName] || {};
-    const { dynamic } = this.typeInfo.options.fieldInfo?.[fieldName] || {};
-    if (typeof trackingRef !== "boolean") {
-      trackingRef = needToWriteRef;
-    }
+  writeField(fieldName: string, fieldTypeInfo: TypeInfo, fieldAccessor: 
string, embedGenerator: SerializerGenerator) {
+    const { nullable = false, dynamic, trackingRef } = fieldTypeInfo;
     const refMode = toRefMode(trackingRef, nullable);
     let stmt = "";
     // polymorphic type
@@ -175,7 +165,7 @@ class StructSerializerGenerator extends 
BaseSerializerGenerator {
       const innerGenerator = new InnerGeneratorClass(typeInfo, this.builder, 
this.scope);
 
       const fieldAccessor = `${accessor}${CodecBuilder.safePropAccessor(key)}`;
-      return this.writeField(key, typeInfo, fieldAccessor, 
innerGenerator.writeEmbed(), innerGenerator.needToWriteRef());
+      return this.writeField(key, typeInfo, fieldAccessor, 
innerGenerator.writeEmbed());
     }).join(";\n")}
     `;
   }
@@ -191,7 +181,7 @@ class StructSerializerGenerator extends 
BaseSerializerGenerator {
         }
       `
 : ""}
-      ${this.typeInfo.options.withConstructor
+      ${this.typeInfo.options!.withConstructor
         ? `
           const ${result} = new ${this.builder.getOptions("creator")}();
         `
@@ -210,8 +200,7 @@ class StructSerializerGenerator extends 
BaseSerializerGenerator {
           throw new Error(`${typeInfo.typeId} generator not exists`);
         }
         const innerGenerator = new InnerGeneratorClass(typeInfo, this.builder, 
this.scope);
-        const needToWriteRef = innerGenerator.needToWriteRef();
-        return this.readField(key, typeInfo, expr => 
`${result}${CodecBuilder.safePropAccessor(key)} = ${expr}`, 
innerGenerator.readEmbed(), needToWriteRef);
+        return this.readField(typeInfo, expr => 
`${result}${CodecBuilder.safePropAccessor(key)} = ${expr}`, 
innerGenerator.readEmbed());
       }).join(";\n")}
       ${accessor(result)}
     `;
@@ -337,13 +326,13 @@ class StructSerializerGenerator extends 
BaseSerializerGenerator {
       case TypeId.NAMED_COMPATIBLE_STRUCT:
       case TypeId.COMPATIBLE_STRUCT:
         {
-          const bytes = this.scope.declare("typeInfoBytes", `new 
Uint8Array([${TypeMeta.fromTypeInfo(<StructTypeInfo> 
this.typeInfo).toBytes().join(",")}])`);
+          const bytes = this.scope.declare("typeInfoBytes", `new 
Uint8Array([${TypeMeta.fromTypeInfo(this.typeInfo).toBytes().join(",")}])`);
           typeMeta = 
this.builder.typeMetaResolver.writeTypeMeta(this.builder.getTypeInfo(), 
this.builder.writer.ownName(), bytes);
         }
         break;
       case TypeId.NAMED_STRUCT:
         if (!this.builder.fory.isCompatible()) {
-          const typeInfo = this.typeInfo.castToStruct();
+          const typeInfo = this.typeInfo;
           const nsBytes = this.scope.declare("nsBytes", 
this.builder.metaStringResolver.encodeNamespace(CodecBuilder.replaceBackslashAndQuote(typeInfo.namespace)));
           const typeNameBytes = this.scope.declare("typeNameBytes", 
this.builder.metaStringResolver.encodeTypeName(CodecBuilder.replaceBackslashAndQuote(typeInfo.typeName)));
           typeMeta = `
@@ -351,7 +340,7 @@ class StructSerializerGenerator extends 
BaseSerializerGenerator {
             
${this.builder.metaStringResolver.writeBytes(this.builder.writer.ownName(), 
typeNameBytes)}
           `;
         } else {
-          const bytes = this.scope.declare("typeInfoBytes", `new 
Uint8Array([${TypeMeta.fromTypeInfo(<StructTypeInfo> 
this.typeInfo).toBytes().join(",")}])`);
+          const bytes = this.scope.declare("typeInfoBytes", `new 
Uint8Array([${TypeMeta.fromTypeInfo(this.typeInfo).toBytes().join(",")}])`);
           typeMeta = 
this.builder.typeMetaResolver.writeTypeMeta(this.builder.getTypeInfo(), 
this.builder.writer.ownName(), bytes);
         }
         break;
@@ -366,11 +355,11 @@ class StructSerializerGenerator extends 
BaseSerializerGenerator {
   }
 
   getFixedSize(): number {
-    const typeInfo = <StructTypeInfo> this.typeInfo;
+    const typeInfo = this.typeInfo;
     const options = typeInfo.options;
     let fixedSize = 8;
-    if (options.props) {
-      Object.values(options.props).forEach((x) => {
+    if (options!.props) {
+      Object.values(options!.props).forEach((x) => {
         const propGenerator = new (CodegenRegistry.get(x.typeId)!)(x, 
this.builder, this.scope);
         fixedSize += propGenerator.getFixedSize();
       });
diff --git a/javascript/packages/fory/lib/meta/TypeMeta.ts 
b/javascript/packages/fory/lib/meta/TypeMeta.ts
index f52114baa..d407c6622 100644
--- a/javascript/packages/fory/lib/meta/TypeMeta.ts
+++ b/javascript/packages/fory/lib/meta/TypeMeta.ts
@@ -20,7 +20,7 @@
 import { BinaryWriter } from "../writer";
 import { BinaryReader } from "../reader";
 import { Encoding, MetaStringDecoder, MetaStringEncoder } from "./MetaString";
-import { StructTypeInfo, TypeInfo } from "../typeInfo";
+import { TypeInfo } from "../typeInfo";
 import { TypeId } from "../type";
 import { x64hash128 } from "../murmurHash3";
 import { fromString } from "../platformBuffer";
@@ -102,16 +102,16 @@ function getPrimitiveTypeSize(typeId: number) {
   }
 }
 
-type InnerFieldInfoOptions = { key?: InnerFieldInfo; value?: InnerFieldInfo; 
inner?: InnerFieldInfo };
-interface InnerFieldInfo {
+export type InnerFieldInfoOptions = { key?: InnerFieldInfo; value?: 
InnerFieldInfo; inner?: InnerFieldInfo };
+export interface InnerFieldInfo {
   typeId: number;
   userTypeId: number;
-  trackingRef: boolean;
-  nullable: boolean;
+  trackingRef?: boolean;
+  nullable?: boolean;
   options?: InnerFieldInfoOptions;
   fieldId?: number;
 }
-class FieldInfo {
+export class FieldInfo {
   constructor(
     public fieldName: string,
     public typeId: number,
@@ -242,28 +242,29 @@ export class TypeMeta {
     const fingerprint = this.computeStructFingerprint(fields);
     const bytes = fromString(fingerprint);
     const hashLong = x64hash128(bytes, 47).getBigInt64(0);
-    return Number(BigInt.asIntN(32, hashLong));
+    const result = Number(BigInt.asIntN(32, hashLong));
+    return result;
   }
 
   static fromTypeInfo(typeInfo: TypeInfo) {
     let fieldInfo: FieldInfo[] = [];
     if (TypeId.structType(typeInfo.typeId)) {
-      const structTypeInfo = typeInfo as StructTypeInfo;
-      fieldInfo = 
Object.entries(structTypeInfo.options.props!).map(([fieldName, typeInfo]) => {
+      const structTypeInfo = typeInfo;
+      fieldInfo = 
Object.entries(structTypeInfo.options!.props!).map(([fieldName, typeInfo]) => {
         let fieldTypeId = typeInfo.typeId;
         if (fieldTypeId === TypeId.NAMED_ENUM) {
           fieldTypeId = TypeId.ENUM;
         } else if (fieldTypeId === TypeId.NAMED_UNION || fieldTypeId === 
TypeId.TYPED_UNION) {
           fieldTypeId = TypeId.UNION;
         }
-        const { trackingRef, nullable, id } = 
structTypeInfo.options.fieldInfo?.[fieldName] || {};
+        const { trackingRef, nullable, id, userTypeId, options } = typeInfo;
         return new FieldInfo(
           fieldName,
           fieldTypeId,
-          typeInfo.userTypeId,
+          userTypeId,
           trackingRef,
           nullable,
-          typeInfo.options,
+          options!,
           id
         );
       });
diff --git a/javascript/packages/fory/lib/type.ts 
b/javascript/packages/fory/lib/type.ts
index 8141b20fa..983c4797c 100644
--- a/javascript/packages/fory/lib/type.ts
+++ b/javascript/packages/fory/lib/type.ts
@@ -19,7 +19,7 @@
 
 import Fory from "./fory";
 import { BinaryReader } from "./reader";
-import { StructTypeInfo } from "./typeInfo";
+import { TypeInfo } from "./typeInfo";
 import { BinaryWriter } from "./writer";
 
 export const TypeId = {
@@ -210,6 +210,7 @@ export type CustomSerializer<T> = {
 // read, write
 export type Serializer<T = any> = {
   fixedSize: number;
+  getTypeInfo: () => TypeInfo;
   needToWriteRef: () => boolean;
   getTypeId: () => number;
   getUserTypeId: () => number;
@@ -270,7 +271,7 @@ export interface Config {
 }
 
 export interface WithForyClsInfo {
-  structTypeInfo: StructTypeInfo;
+  structTypeInfo: TypeInfo;
 }
 
 export const ForyTypeInfoSymbol = Symbol("foryTypeInfo");
diff --git a/javascript/packages/fory/lib/typeInfo.ts 
b/javascript/packages/fory/lib/typeInfo.ts
index cfa6e69fb..21f54afaf 100644
--- a/javascript/packages/fory/lib/typeInfo.ts
+++ b/javascript/packages/fory/lib/typeInfo.ts
@@ -18,64 +18,10 @@
  */
 
 import Fory from "./fory";
+import { refTrackingUnableTypeId } from "./meta/TypeMeta";
 import { ForyTypeInfoSymbol, TypeId, Mode } from "./type";
 import { BFloat16 } from "./bfloat16";
 
-const targetFieldInfo = new WeakMap<new () => any, { [key: string]: 
StructFieldInfo }>();
-
-export const ForyField = (fieldInfo: {
-  nullable?: boolean,
-  trackingRef?: boolean,
-  id?: number,
-  dynamic?: Dynamic,
-}) => {
-  return (target: any, key: string | {name?: string}) => {
-    const creator = target.constructor;
-    if (!targetFieldInfo.has(creator)) {
-      targetFieldInfo.set(creator, {});
-    }
-    const keyString = typeof key === "string" ? key : key?.name;
-    if (!keyString) {
-      throw new Error("Decorators can only be placed on classes and fields");
-    }
-    targetFieldInfo.get(creator)![keyString] = fieldInfo;
-  };
-}
-
-const initMeta = (target: new () => any, typeInfo: TypeInfo) => {
-  if (!target.prototype) {
-    target.prototype = {};
-  }
-  if (!typeInfo.options) {
-    typeInfo.options = {};
-  }
-  typeInfo.options.withConstructor = true;
-  typeInfo.options.creator = target;
-  if (!typeInfo.options.props) {
-    typeInfo.options.props = {}
-  }
-  if(targetFieldInfo.has(target)) {
-    const structTypeInfo = (typeInfo as StructTypeInfo);
-    if (!structTypeInfo.options.fieldInfo) {
-      structTypeInfo.options.fieldInfo = {};
-    }
-    Object.assign(structTypeInfo.options.fieldInfo, 
targetFieldInfo.get(target));
-  }
-  Object.assign(typeInfo.options.props, targetFields.get(target) || {})
-  Object.defineProperties(target.prototype, {
-    [ForyTypeInfoSymbol]: {
-      get() {
-        return {
-          structTypeInfo: typeInfo
-        };
-      },
-      enumerable: false,
-      set(_) {
-        throw new Error("fory type info is readonly")
-      },
-    },
-  })
-};
 
 const targetFields = new WeakMap<new () => any, { [key: string]: TypeInfo }>();
 
@@ -94,6 +40,16 @@ class ExtensibleFunction extends Function {
   }
 }
 
+interface TypeInfoOptions {
+  props?: { [key: string]: TypeInfo };
+  withConstructor?: boolean;
+  creator?: Function;
+  key?: TypeInfo;
+  value?: TypeInfo;
+  inner?: TypeInfo;
+  enumProps?: { [key: string]: number };
+}
+
 /**
  * T is for type matching
  */
@@ -103,23 +59,93 @@ export class TypeInfo<T = unknown> extends 
ExtensibleFunction {
   named = "";
   namespace = "";
   typeName = "";
-  // Stored as unsigned 32-bit; -1 (0xffffffff) means "unset".
   userTypeId = -1;
-  options?: any;
+  options?: TypeInfoOptions;
+  _typeId: number;
+  nullable: boolean = false;
+
+  setNullable(v: boolean) {
+    this.nullable = v;
+    return this;
+  }
+  trackingRef?: boolean;
+  setTrackingRef(v: boolean) {
+    this.trackingRef = v;
+    return this;
+  }
+  id?: number;
+  setId(v: number | undefined) {
+    this.id = v;
+    return this;
+  }
+  dynamic?: Dynamic;
+  setDynamic(v: Dynamic) {
+    this.dynamic = v;
+    return this;
+  }
+
   static fory: WeakRef<Fory> | null = null;
 
   static attach(fory: Fory) {
     TypeInfo.fory = new WeakRef(fory);
   }
-  
+
   static detach() {
     TypeInfo.fory = null;
   }
 
-  public constructor(private _typeId: number, userTypeId = -1) {
+  initMeta(target: new () => any) {
+    if (!target.prototype) {
+      target.prototype = {};
+    }
+    if (!this.options) {
+      this.options = {}
+    }
+    this.options!.withConstructor = true;
+    this.options!.creator = target;
+    if (!this.options!.props) {
+      this.options!.props = {};
+    }
+    Object.assign(this.options!.props, targetFields.get(target) || {})
+    const that = this;
+    Object.defineProperties(target.prototype, {
+      [ForyTypeInfoSymbol]: {
+        get() {
+          return {
+            structTypeInfo: that
+          };
+        },
+        enumerable: false,
+        set(_) {
+          throw new Error("fory type info is readonly")
+        },
+      },
+    });
+  }
+
+  public freeze() {
+    Object.defineProperties(this, {
+      'named': { writable: false, configurable: false },
+      'namespace': { writable: false, configurable: false },
+      'typeName': { writable: false, configurable: false },
+      'userTypeId': { writable: false, configurable: false },
+      'options': { writable: false, configurable: false },
+      '_typeId': { writable: false, configurable: false },
+      'nullable': { writable: false, configurable: false },
+    });
+    Object.freeze(this.options);
+    if (this.options?.props) {
+      Object.freeze(this.options!.props);
+    }
+    if (this.options?.enumProps) {
+      Object.freeze(this.options!.enumProps);
+    }
+  }
+
+  public constructor(typeId: number, userTypeId = -1) {
     super(function (target: any, key?: string | { name?: string }) {
       if (key === undefined) {
-        initMeta(target, that as unknown as StructTypeInfo);
+        that.initMeta(target);
       } else {
         const keyString = typeof key === "string" ? key : key?.name;
         if (!keyString) {
@@ -135,9 +161,25 @@ export class TypeInfo<T = unknown> extends 
ExtensibleFunction {
         throw new Error("userTypeId must be in range [0, 0xfffffffe]");
       }
     }
+    this._typeId = typeId;
     this.userTypeId = userTypeId;
   }
 
+  clone() {
+    const result = new TypeInfo(this._typeId, this.userTypeId);
+    result.named = this.named;
+    result.namespace = this.namespace;
+    result.typeName = this.typeName;
+    result.options = { ...this.options };
+    result.dynamicTypeId = this.dynamicTypeId;
+    result.nullable = this.nullable;
+    result.trackingRef = this.trackingRef;
+    result.id = this.id;
+    result.dynamic = this.dynamic;
+    return result;
+  }
+
+
   computeTypeId(fory?: Fory) {
     const internalTypeId = this._typeId;
     if (internalTypeId !== TypeId.STRUCT && internalTypeId !== 
TypeId.NAMED_STRUCT) {
@@ -238,7 +280,7 @@ export class TypeInfo<T = unknown> extends 
ExtensibleFunction {
     } else {
       finalTypeId = TypeId.NAMED_EXT;
     }
-    const typeInfo = new TypeInfo<T>(finalTypeId, 
userTypeId).cast<StructTypeInfo>();
+    const typeInfo = new TypeInfo<T>(finalTypeId, userTypeId)
     typeInfo.options = {
       withConstructor,
     };
@@ -254,10 +296,8 @@ export class TypeInfo<T = unknown> extends 
ExtensibleFunction {
     typeName?: string;
   } | string | number, props?: Record<string, TypeInfo>, {
     withConstructor = false,
-    fieldInfo = {},
   }: {
     withConstructor?: boolean;
-    fieldInfo?: Record<string, StructFieldInfo>
   } = {}) {
     let typeId: number | undefined;
     let namespace: string | undefined;
@@ -294,11 +334,10 @@ export class TypeInfo<T = unknown> extends 
ExtensibleFunction {
     } else {
       finalTypeId = TypeId.NAMED_STRUCT;
     }
-    const typeInfo = new TypeInfo<T>(finalTypeId, 
userTypeId).cast<StructTypeInfo>();
+    const typeInfo = new TypeInfo<T>(finalTypeId, userTypeId);
     typeInfo.options = {
       props: props || {},
       withConstructor,
-      fieldInfo
     };
     typeInfo.namespace = namespace || "";
     typeInfo.typeName = typeId !== undefined ? "" : typeName!;
@@ -311,7 +350,7 @@ export class TypeInfo<T = unknown> extends 
ExtensibleFunction {
       type: T;
       options: T2;
     }>(typeId);
-    typeInfo.options = options;
+    typeInfo.options = options as any;
     return typeInfo;
   }
 
@@ -319,7 +358,7 @@ export class TypeInfo<T = unknown> extends 
ExtensibleFunction {
     typeId?: number;
     namespace?: string;
     typeName?: string;
-  } | string | number, props: { [key: string]: any }) {
+  } | string | number, props?: { [key: string]: any }) {
     let typeId: number | undefined;
     let namespace: string | undefined;
     let typeName: string | undefined;
@@ -350,22 +389,14 @@ export class TypeInfo<T = unknown> extends 
ExtensibleFunction {
     const finalTypeId = typeId !== undefined ? TypeId.ENUM : TypeId.NAMED_ENUM;
     const userTypeId = typeId !== undefined ? typeId : -1;
     const typeInfo = new TypeInfo<T>(finalTypeId, userTypeId);
-    typeInfo.cast<EnumTypeInfo>().options = {
-      inner: props,
+    typeInfo.options = {
+      enumProps: props,
     };
     typeInfo.namespace = namespace || "";
     typeInfo.typeName = typeId !== undefined ? "" : typeName!;
     typeInfo.named = `${typeInfo.namespace}$${typeInfo.typeName}`;
     return typeInfo;
   }
-
-  castToStruct() {
-    return this as unknown as StructTypeInfo;
-  }
-
-  cast<T>() {
-    return this as unknown as T;
-  }
 }
 
 export enum Dynamic {
@@ -374,40 +405,6 @@ export enum Dynamic {
   AUTO = "AUTO"
 }
 
-type StructFieldInfo = {nullable?: boolean, trackingRef?: boolean, id?: 
number, dynamic?: Dynamic}
-export interface StructTypeInfo extends TypeInfo {
-  options: {
-    props?: { [key: string]: TypeInfo };
-    fieldInfo?: {[key: string]: StructFieldInfo};
-    withConstructor?: boolean;
-    creator?: Function;
-  };
-}
-
-export interface EnumTypeInfo extends TypeInfo {
-  options: {
-    inner: { [key: string]: any };
-  };
-}
-
-export interface ArrayTypeInfo extends TypeInfo {
-  options: {
-    inner: TypeInfo;
-  };
-}
-
-export interface SetTypeInfo extends TypeInfo {
-  options: {
-    key: TypeInfo;
-  };
-}
-
-export interface MapTypeInfo extends TypeInfo {
-  options: {
-    key: TypeInfo;
-    value: TypeInfo;
-  };
-}
 
 type Props<T> = T extends {
   options: {
@@ -600,7 +597,7 @@ export type HintResult<T> = T extends never ? any : T 
extends {
   ? Uint8Array : T extends {
     type: typeof TypeId.ENUM;
   }
-  ? EnumProps<T>: unknown;
+  ? EnumProps<T> : unknown;
 
 export const Type = {
   any() {
@@ -634,11 +631,11 @@ export const Type = {
     typeId?: number;
     namespace?: string;
     typeName?: string;
-  } | string | number, t1: T1) {
+  } | string | number, t1?: T1) {
     return TypeInfo.fromEnum<{
       type: typeof TypeId.ENUM;
       options: {
-        inner: T1;
+        enumProps: T1;
       };
     }>(nameInfo, t1);
   },
@@ -666,10 +663,8 @@ export const Type = {
     typeName?: string;
   } | string | number, props?: T, {
     withConstructor = false,
-    fieldInfo,
   }: {
     withConstructor?: boolean;
-    fieldInfo?: Record<string, StructFieldInfo>
   } = {}) {
     return TypeInfo.fromStruct<{
       type: typeof TypeId.STRUCT;
@@ -678,7 +673,6 @@ export const Type = {
       };
     }>(nameInfo, props, {
       withConstructor,
-      fieldInfo
     });
   },
   string() {
diff --git a/javascript/packages/fory/lib/typeMetaResolver.ts 
b/javascript/packages/fory/lib/typeMetaResolver.ts
index f7de94461..6db2fe148 100644
--- a/javascript/packages/fory/lib/typeMetaResolver.ts
+++ b/javascript/packages/fory/lib/typeMetaResolver.ts
@@ -17,15 +17,15 @@
  * under the License.
  */
 
-import { StructTypeInfo, Type, TypeInfo } from "./typeInfo";
+import { Type, TypeInfo } from "./typeInfo";
 import fory from "./fory";
-import { TypeMeta } from "./meta/TypeMeta";
+import { InnerFieldInfo, TypeMeta } from "./meta/TypeMeta";
 import { BinaryReader } from "./reader";
 import { Serializer, TypeId } from "./type";
 import { BinaryWriter } from "./writer";
 
 export class TypeMetaResolver {
-  private disposeTypeInfo: StructTypeInfo[] = [];
+  private disposeTypeInfo: TypeInfo[] = [];
   private dynamicTypeId = 0;
   private typeMeta: TypeMeta[] = [];
 
@@ -33,46 +33,56 @@ export class TypeMetaResolver {
 
   }
 
-  private updateTypeInfo(typeMeta: TypeMeta, typeInfo: TypeInfo) {
-    typeInfo.options.props = 
Object.fromEntries(typeMeta.getFieldInfo().map((x) => {
-      const typeId = x.getTypeId();
-      const fieldName = x.getFieldName();
-      const declared = typeInfo.options.props?.[fieldName];
-      let fieldTypeInfo = declared ?? 
this.fory.typeResolver.getTypeInfo(typeId);
-      if (!fieldTypeInfo) {
-        fieldTypeInfo = Type.any();
-      }
-      if (!typeInfo.options.fieldInfo) {
-        typeInfo.options.fieldInfo = {};
+  private fieldInfoToTypeInfo(fieldInfo: InnerFieldInfo): TypeInfo {
+    const typeId = fieldInfo.typeId;
+
+    switch (typeId) {
+      case TypeId.MAP:
+        return Type.map(this.fieldInfoToTypeInfo(fieldInfo.options!.key!), 
this.fieldInfoToTypeInfo(fieldInfo.options!.value!));
+      case TypeId.LIST:
+        return Type.array(this.fieldInfoToTypeInfo(fieldInfo.options!.inner!));
+      case TypeId.SET:
+        return Type.set(this.fieldInfoToTypeInfo(fieldInfo.options!.key!));
+      default:
+      {
+        const serializer = this.fory.typeResolver.getSerializerById(typeId, 
fieldInfo.userTypeId);
+        // registered type
+        if (serializer) {
+          return serializer.getTypeInfo().clone();
+        }
+        return Type.any();
       }
-      typeInfo.options.fieldInfo[x.fieldName] = {
-        nullable: x.nullable,
-        trackingRef: x.trackingRef,
-        ...typeInfo.options.fieldInfo[x.fieldName],
-      };
-      return [fieldName, fieldTypeInfo];
-    }));
+    }
   }
 
-  genSerializerByTypeMetaRuntime(typeMeta: TypeMeta): Serializer {
+  genSerializerByTypeMetaRuntime(typeMeta: TypeMeta, original?: Serializer): 
Serializer {
     const typeName = typeMeta.getTypeName();
     const ns = typeMeta.getNs();
     const typeId = typeMeta.getTypeId();
     const userTypeId = typeMeta.getUserTypeId();
+    if (!TypeId.structType(typeId)) {
+      throw new Error("only support reconstructor struct type");
+    }
     let typeInfo;
-    if (!TypeId.isNamedType(typeId)) {
-      typeInfo = this.fory.typeResolver.getTypeInfo(typeId, userTypeId);
-      if (!typeInfo) {
-        typeInfo = Type.struct({ typeId });
-      }
+    if (original) {
+      typeInfo = original.getTypeInfo().clone();
     } else {
-      typeInfo = this.fory.typeResolver.getTypeInfo(`${ns}$${typeName}`);
-      if (!typeInfo) {
+      if (!TypeId.isNamedType(typeId)) {
+        typeInfo = Type.struct(userTypeId);
+      } else {
         typeInfo = Type.struct({ typeName, namespace: ns });
       }
     }
-
-    this.updateTypeInfo(typeMeta, typeInfo);
+    // rebuild props
+    const props = (Object.fromEntries(typeMeta.getFieldInfo().map((x) => {
+      const fieldName = x.getFieldName();
+      const fieldTypeInfo = 
this.fieldInfoToTypeInfo(x).setNullable(x.nullable).setTrackingRef(x.trackingRef).setId(x.fieldId);
+      return [fieldName, fieldTypeInfo];
+    })));
+    typeInfo.options! = {
+      ...typeInfo.options,
+      props,
+    };
     return this.fory.replaceSerializerReader(typeInfo);
   }
 
@@ -88,7 +98,7 @@ export class TypeMetaResolver {
     }
   }
 
-  writeTypeMeta(typeInfo: StructTypeInfo, writer: BinaryWriter, bytes: 
Uint8Array) {
+  writeTypeMeta(typeInfo: TypeInfo, writer: BinaryWriter, bytes: Uint8Array) {
     if (typeInfo.dynamicTypeId !== -1) {
       // Reference to previously written type: (index << 1) | 1, LSB=1
       writer.varUInt32((typeInfo.dynamicTypeId << 1) | 1);
diff --git a/javascript/packages/fory/lib/typeResolver.ts 
b/javascript/packages/fory/lib/typeResolver.ts
index a06c9e7f8..380ae8ed3 100644
--- a/javascript/packages/fory/lib/typeResolver.ts
+++ b/javascript/packages/fory/lib/typeResolver.ts
@@ -25,6 +25,9 @@ import Fory from "./fory";
 const uninitSerialize = {
   // for writer
   fixedSize: 0,
+  getTypeInfo: () => {
+    throw new Error("uninitSerialize");
+  },
   getTypeId: () => {
     throw new Error("uninitSerialize");
   },
@@ -79,7 +82,6 @@ const uninitSerialize = {
 export default class TypeResolver {
   private internalSerializer: Serializer[] = new Array(300);
   private customSerializer: Map<number | string, Serializer> = new Map();
-  private typeInfoMap: Map<number | string, TypeInfo> = new Map();
 
   private initInternalSerializer() {
     const registerSerializer = (typeInfo: TypeInfo) => {
@@ -185,14 +187,9 @@ export default class TypeResolver {
   }
 
   init() {
+    TypeInfo.attach(this.fory);
     this.initInternalSerializer();
-  }
-
-  getTypeInfo(typeIdOrName: number | string, userTypeId?: number) {
-    if (typeof typeIdOrName === "number" && userTypeId !== undefined && 
TypeId.needsUserTypeId(typeIdOrName)) {
-      return this.typeInfoMap.get(this.makeUserTypeKey(userTypeId));
-    }
-    return this.typeInfoMap.get(typeIdOrName);
+    TypeInfo.detach();
   }
 
   registerSerializer(typeInfo: TypeInfo, serializer: Serializer = 
uninitSerialize) {
@@ -200,7 +197,6 @@ export default class TypeResolver {
     if (!TypeId.isNamedType(typeId)) {
       if (TypeId.needsUserTypeId(typeId) && typeInfo.userTypeId !== -1) {
         const key = this.makeUserTypeKey(typeInfo.userTypeId);
-        this.typeInfoMap.set(key, typeInfo);
         if (this.customSerializer.has(key)) {
           Object.assign(this.customSerializer.get(key)!, serializer);
         } else {
@@ -209,7 +205,6 @@ export default class TypeResolver {
         return this.customSerializer.get(key);
       }
       const id = typeId;
-      this.typeInfoMap.set(id, typeInfo);
       if (id <= 0xFF) {
         if (this.internalSerializer[id]) {
           Object.assign(this.internalSerializer[id], serializer);
@@ -225,32 +220,21 @@ export default class TypeResolver {
       }
       return this.customSerializer.get(id);
     } else {
-      const namedTypeInfo = typeInfo.castToStruct();
+      const namedTypeInfo = typeInfo;
       const name = namedTypeInfo.named!;
       if (this.customSerializer.has(name)) {
         Object.assign(this.customSerializer.get(name)!, serializer);
       } else {
         this.customSerializer.set(name, { ...serializer });
       }
-      this.typeInfoMap.set(name, typeInfo);
       return this.customSerializer.get(name);
     }
   }
 
-  typeInfoExists(typeInfo: TypeInfo) {
-    if (typeInfo.isNamedType()) {
-      return this.typeInfoMap.has((typeInfo.castToStruct()).named!);
-    }
-    if (TypeId.needsUserTypeId(typeInfo.typeId) && typeInfo.userTypeId !== -1) 
{
-      return this.typeInfoMap.has(this.makeUserTypeKey(typeInfo.userTypeId));
-    }
-    return this.typeInfoMap.has(typeInfo.typeId);
-  }
-
   getSerializerByTypeInfo(typeInfo: TypeInfo) {
     const typeId = typeInfo.computeTypeId(this.fory);
     if (TypeId.isNamedType(typeId)) {
-      return this.customSerializer.get((typeInfo.castToStruct()).named!);
+      return this.customSerializer.get((typeInfo).named!);
     }
     return this.getSerializerById(typeId, typeInfo.userTypeId);
   }
diff --git a/javascript/test/crossLanguage.test.ts 
b/javascript/test/crossLanguage.test.ts
index af5129efb..27c099760 100644
--- a/javascript/test/crossLanguage.test.ts
+++ b/javascript/test/crossLanguage.test.ts
@@ -20,7 +20,6 @@
 import Fory, {
   BinaryReader,
   BinaryWriter,
-  ForyField,
   Type,
   Dynamic,
 } from "../packages/fory/index";
@@ -639,6 +638,57 @@ describe("bool", () => {
     writeToFile(writer.dump() as Buffer);
   });
 
+  test("test_collection_element_ref_override", () => {
+    const fory = new Fory({
+      compatible: false,
+      refTracking: true,
+      hooks: {
+        afterCodeGenerated: (code) => {
+          return beautify.js(code, { indent_size: 2, space_in_empty_paren: 
true, indent_empty_lines: true });
+        }
+      }
+    });
+
+    const Type701 = Type.struct(701, {
+      id: Type.varInt32(),
+      name: Type.string()
+    });
+
+    @Type701
+    class RefOverrideElement {
+      id: number = 0;
+      name: string = "";
+    }
+
+    @Type.struct(702, {
+      list_field: Type.array(Type701.setTrackingRef(true)),
+      map_field: Type.map(Type.string(), Type701.setTrackingRef(true))
+    })
+    class RefOverrideContainer {
+      list_field: RefOverrideElement[] = [];
+      map_field: Map<string, RefOverrideElement> = new Map();
+    }
+
+    fory.registerSerializer(RefOverrideElement);
+    fory.registerSerializer(RefOverrideContainer);
+
+    const outer = fory.deserialize(content);
+    console.log("Deserialized:", outer);
+
+    expect(outer.list_field).toBeTruthy();
+    expect(outer.list_field.length).toBeGreaterThan(0);
+    const shared = outer.list_field[0];
+    const newOuter = new RefOverrideContainer();
+    newOuter.list_field = [shared, shared];
+    newOuter.map_field = new Map([
+      ["k1", shared],
+      ["k2", shared]
+    ]);
+
+    const newBytes = fory.serialize(newOuter);
+    writeToFile(newBytes as Buffer)
+  });
+
   test("test_skip_id_custom", () => {
     const fory1 = new Fory({
       compatible: true
@@ -877,12 +927,11 @@ describe("bool", () => {
 
     @Type.struct(201, {
       f1: Type.varInt32(),
-      f2: Type.string(),
+      f2: Type.string().setNullable(true),
       f3: Type.float64()
     })
     class VersionCheckStruct {
       f1: number = 0;
-      @ForyField({ nullable: true })
       f2: string | null = null;
       f3: number = 0;
     }
@@ -909,11 +958,10 @@ describe("bool", () => {
     // Define Animal interface implementations
     @Type.struct(302, {
       age: Type.varInt32(),
-      name: Type.string()
+      name: Type.string().setNullable(true)
     })
     class Dog {
       age: number = 0;
-      @ForyField({ nullable: true })
       name: string | null = null;
     }
     fory.registerSerializer(Dog);
@@ -969,11 +1017,10 @@ describe("bool", () => {
     // Define Animal interface implementations
     @Type.struct(302, {
       age: Type.varInt32(),
-      name: Type.string()
+      name: Type.string().setNullable(true)
     })
     class Dog {
       age: number = 0;
-      @ForyField({ nullable: true })
       name: string | null = null;
     }
     fory.registerSerializer(Dog);
@@ -1028,7 +1075,7 @@ describe("bool", () => {
       f1: Type.string()
     })
     class OneStringFieldStruct {
-      @ForyField({ nullable: true })
+      @Type.string().setNullable(true)
       f1: string | null = null;
     }
     fory.registerSerializer(OneStringFieldStruct);
@@ -1048,10 +1095,9 @@ describe("bool", () => {
     });
 
     @Type.struct(200, {
-      f1: Type.string()
+      f1: Type.string().setNullable(true)
     })
     class OneStringFieldStruct {
-      @ForyField({ nullable: true })
       f1: string | null = null;
     }
     fory.registerSerializer(OneStringFieldStruct);
@@ -1336,42 +1382,33 @@ describe("bool", () => {
       @Type.map(Type.string(), Type.string())
       mapField: Map<string, string> = new Map();
 
-      // Nullable group 1 - boxed types with @ForyField(nullable=true)
-      @ForyField({ nullable: true })
-      @Type.varInt32()
+      // Nullable group 1 - boxed types with nullable type decorators
+      @(Type.varInt32().setNullable(true))
       nullableInt1: number | null = null;
 
-      @ForyField({ nullable: true })
-      @Type.varInt64()
+      @(Type.varInt64().setNullable(true))
       nullableLong1: number | null = null;
 
-      @ForyField({ nullable: true })
-      @Type.float32()
+      @(Type.float32().setNullable(true))
       nullableFloat1: number | null = null;
 
-      @ForyField({ nullable: true })
-      @Type.float64()
+      @(Type.float64().setNullable(true))
       nullableDouble1: number | null = null;
 
-      @ForyField({ nullable: true })
-      @Type.bool()
+      @(Type.bool().setNullable(true))
       nullableBool1: boolean | null = null;
 
-      // Nullable group 2 - reference types with @ForyField(nullable=true)
-      @ForyField({ nullable: true })
-      @Type.string()
+      // Nullable group 2 - reference types with nullable type decorators
+      @(Type.string().setNullable(true))
       nullableString2: string | null = null;
 
-      @ForyField({ nullable: true })
-      @Type.array(Type.string())
+      @(Type.array(Type.string()).setNullable(true))
       nullableList2: string[] | null = null;
 
-      @ForyField({ nullable: true })
-      @Type.set(Type.string())
+      @(Type.set(Type.string()).setNullable(true))
       nullableSet2: Set<string> | null = null;
 
-      @ForyField({ nullable: true })
-      @Type.map(Type.string(), Type.string())
+      @(Type.map(Type.string(), Type.string()).setNullable(true))
       nullableMap2: Map<string, string> | null = null;
     }
     return NullableComprehensiveCompatible;
@@ -1406,42 +1443,33 @@ describe("bool", () => {
       @Type.map(Type.string(), Type.string())
       mapField: Map<string, string> = new Map();
 
-      // Nullable group 1 - boxed types with @ForyField(nullable=true)
-      @ForyField({ nullable: true })
-      @Type.varInt32()
+      // Nullable group 1 - boxed types with nullable type decorators
+      @(Type.varInt32().setNullable(true))
       nullableInt: number | null = null;
 
-      @ForyField({ nullable: true })
-      @Type.varInt64()
+      @(Type.varInt64().setNullable(true))
       nullableLong: number | null = null;
 
-      @ForyField({ nullable: true })
-      @Type.float32()
+      @(Type.float32().setNullable(true))
       nullableFloat: number | null = null;
 
-      @ForyField({ nullable: true })
-      @Type.float64()
+      @(Type.float64().setNullable(true))
       nullableDouble: number | null = null;
 
-      @ForyField({ nullable: true })
-      @Type.bool()
+      @(Type.bool().setNullable(true))
       nullableBool: boolean | null = null;
 
-      // Nullable group 2 - reference types with @ForyField(nullable=true)
-      @ForyField({ nullable: true })
-      @Type.string()
+      // Nullable group 2 - reference types with nullable type decorators
+      @(Type.string().setNullable(true))
       nullableString: string | null = null;
 
-      @ForyField({ nullable: true })
-      @Type.array(Type.string())
+      @(Type.array(Type.string()).setNullable(true))
       nullableList: string[] | null = null;
 
-      @ForyField({ nullable: true })
-      @Type.set(Type.string())
+      @(Type.set(Type.string()).setNullable(true))
       nullableSet: Set<string> | null = null;
 
-      @ForyField({ nullable: true })
-      @Type.map(Type.string(), Type.string())
+      @(Type.map(Type.string(), Type.string()).setNullable(true))
       nullableMap: Map<string, string> | null = null;
     }
     return NullableComprehensiveConsistent
@@ -1543,14 +1571,12 @@ describe("bool", () => {
     fory.registerSerializer(RefInner);
 
     @Type.struct(502, {
-      inner1: Type.struct(501),
-      inner2: Type.struct(501)
+      inner1: 
Type.struct(501).setTrackingRef(true).setNullable(true).setDynamic(Dynamic.FALSE),
+      inner2: 
Type.struct(501).setTrackingRef(true).setNullable(true).setDynamic(Dynamic.FALSE),
     })
     class RefOuter {
-      @ForyField({ trackingRef: true, nullable: true, dynamic: Dynamic.FALSE })
       inner1: RefInner | null = null;
 
-      @ForyField({ trackingRef: true, nullable: true, dynamic: Dynamic.FALSE })
       inner2: RefInner | null = null;
     }
     fory.registerSerializer(RefOuter);
@@ -1582,13 +1608,11 @@ describe("bool", () => {
     fory.registerSerializer(RefInner);
 
     @Type.struct(504, {
-      inner1: Type.struct(503),
-      inner2: Type.struct(503)
+      inner1: Type.struct(503).setTrackingRef(true).setNullable(true),
+      inner2: Type.struct(503).setTrackingRef(true).setNullable(true),
     })
     class RefOuter {
-      @ForyField({ trackingRef: true, nullable: true })
       inner1: RefInner | null = null;
-      @ForyField({ trackingRef: true, nullable: true })
       inner2: RefInner | null = null;
     }
     fory.registerSerializer(RefOuter);
@@ -1612,11 +1636,10 @@ describe("bool", () => {
 
     @Type.struct(601, {
       name: Type.string(),
-      selfRef: Type.struct(601)
+      selfRef: Type.struct(601).setNullable(true).setTrackingRef(true)
     })
     class CircularRefStruct {
       name: string = "";
-      @ForyField({ nullable: true, trackingRef: true })
       selfRef: CircularRefStruct | null = null;
     }
     fory.registerSerializer(CircularRefStruct);
@@ -1642,11 +1665,10 @@ describe("bool", () => {
 
     @Type.struct(602, {
       name: Type.string(),
-      selfRef: Type.struct(602)
+      selfRef: Type.struct(602).setNullable(true).setTrackingRef(true)
     })
     class CircularRefStruct {
       name: string = "";
-      @ForyField({ nullable: true, trackingRef: true })
       selfRef: CircularRefStruct | null = null;
     }
     fory.registerSerializer(CircularRefStruct);
@@ -1668,12 +1690,11 @@ describe("bool", () => {
 
     @Type.struct(1, {
       u64Tagged: Type.taggedUInt64(),
-      u64TaggedNullable: Type.taggedUInt64()
+      u64TaggedNullable: Type.taggedUInt64().setNullable(true)
     })
     class UnsignedSchemaConsistentSimple {
       u64Tagged: bigint = 0n;
 
-      @ForyField({ nullable: true })
       u64TaggedNullable: bigint | null = null;
     }
     fory.registerSerializer(UnsignedSchemaConsistentSimple);
@@ -1711,32 +1732,25 @@ describe("bool", () => {
       u64FixedField: bigint = 0n;
       u64TaggedField: bigint = 0n;
 
-      @ForyField({ nullable: true })
-      @Type.uint8()
+      @(Type.uint8().setNullable(true))
       u8NullableField: number = 0;
 
-      @ForyField({ nullable: true })
-      @Type.uint16()
+      @(Type.uint16().setNullable(true))
       u16NullableField: number = 0;
 
-      @ForyField({ nullable: true })
-      @Type.varUInt32()
+      @(Type.varUInt32().setNullable(true))
       u32VarNullableField: number = 0;
 
-      @ForyField({ nullable: true })
-      @Type.uint32()
+      @(Type.uint32().setNullable(true))
       u32FixedNullableField: number = 0;
 
-      @ForyField({ nullable: true })
-      @Type.varUInt64()
+      @(Type.varUInt64().setNullable(true))
       u64VarNullableField: bigint = 0n;
 
-      @ForyField({ nullable: true })
-      @Type.uint64()
+      @(Type.uint64().setNullable(true))
       u64FixedNullableField: bigint = 0n;
 
-      @ForyField({ nullable: true })
-      @Type.taggedUInt64()
+      @(Type.taggedUInt64().setNullable(true))
       u64TaggedNullableField: bigint = 0n;
     }
     fory.registerSerializer(UnsignedSchemaConsistent);
@@ -1774,32 +1788,25 @@ describe("bool", () => {
       u64FixedField1: bigint = 0n;
       u64TaggedField1: bigint = 0n;
 
-      @ForyField({ nullable: true })
-      @Type.uint8()
+      @(Type.uint8().setNullable(true))
       u8Field2: number = 0;
 
-      @ForyField({ nullable: true })
-      @Type.uint16()
+      @(Type.uint16().setNullable(true))
       u16Field2: number = 0;
 
-      @ForyField({ nullable: true })
-      @Type.varUInt32()
+      @(Type.varUInt32().setNullable(true))
       u32VarField2: number = 0;
 
-      @ForyField({ nullable: true })
-      @Type.uint32()
+      @(Type.uint32().setNullable(true))
       u32FixedField2: number = 0;
 
-      @ForyField({ nullable: true })
-      @Type.varUInt64()
+      @(Type.varUInt64().setNullable(true))
       u64VarField2: bigint = 0n;
 
-      @ForyField({ nullable: true })
-      @Type.uint64()
+      @(Type.uint64().setNullable(true))
       u64FixedField2: bigint = 0n;
 
-      @ForyField({ nullable: true })
-      @Type.taggedUInt64()
+      @(Type.taggedUInt64().setNullable(true))
       u64TaggedField2: bigint = 0n;
     }
     fory.registerSerializer(UnsignedSchemaCompatible);
diff --git a/javascript/test/object.test.ts b/javascript/test/object.test.ts
index 7270270b0..6c45f8fed 100644
--- a/javascript/test/object.test.ts
+++ b/javascript/test/object.test.ts
@@ -82,9 +82,7 @@ describe('object', () => {
     const typeInfo = Type.struct("example.foo", {
       a: Type.struct("example.bar", {
         b: Type.string()
-      })
-    }, {
-      fieldInfo: { a: { nullable: true } }
+      }).setNullable(true)
     })
     const fory = new Fory({ refTracking: true });
     const { serialize, deserialize } = fory.registerSerializer(typeInfo);
@@ -137,11 +135,12 @@ describe('object', () => {
       a: Type.struct("example.bar", {
         b: Type.string(),
       }),
-      a2: Type.struct("example.foo")
+      a2: Type.struct("example.foo").setTrackingRef(true)
     })
 
     const fory = new Fory({
-      refTracking: true, hooks: {
+      refTracking: true, 
+      hooks: {
         afterCodeGenerated: (code) => {
           return beautify.js(code, { indent_size: 2, space_in_empty_paren: 
true, indent_empty_lines: true });
         }
@@ -202,11 +201,7 @@ describe('object', () => {
     const hps = undefined;
     const typeInfo = Type.struct('ws-channel-protocol', {
       kind: Type.string(),
-      path: Type.string(),
-    }, {
-      fieldInfo: {
-        path: { nullable: true }
-      }
+      path: Type.string().setNullable(true),
     });
 
     const fory = new Fory({ hps });
diff --git a/javascript/test/protocol/struct.test.ts 
b/javascript/test/protocol/struct.test.ts
index a1fb70760..2593675a2 100644
--- a/javascript/test/protocol/struct.test.ts
+++ b/javascript/test/protocol/struct.test.ts
@@ -60,9 +60,7 @@ describe('protocol', () => {
         const nullableUnspecified = Type.struct({
             typeName: "example.nullableUnspecified"
         }, {
-            a: Type.string(),
-        }, {
-            fieldInfo: {a: { nullable: true }}
+            a: Type.string().setNullable(true),
         });
         const { serialize, deserialize } = 
fory.registerSerializer(nullableUnspecified);
         expect(deserialize(serialize({ a: null }))).toEqual({ a: null });
@@ -75,13 +73,8 @@ describe('protocol', () => {
             { typeName: 'example.schemaConsistentNullable' },
             {
                 a: Type.string(),
-                b: Type.string(),
+                b: Type.string().setNullable(true),
             },
-            {
-                fieldInfo: {
-                    b: { nullable: true}
-                }
-            }
         );
 
         const { serialize, deserialize } = fory.registerSerializer(schema);


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to