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/fury.git


The following commit(s) were added to refs/heads/main by this push:
     new 0fe042af feat(javascript): simplify the definition of object type 
description (#2058)
0fe042af is described below

commit 0fe042af1fe59ab5e225ae8ecc15a6719ac3b2e8
Author: weipeng <[email protected]>
AuthorDate: Fri Feb 14 21:39:28 2025 +0800

    feat(javascript): simplify the definition of object type description (#2058)
    
    ## What does this PR do?
    It is hard to describe the type of a object, we should write some code
    like this:
    ```JavaScript
    Type.object("example.foo", {
        a: Type.string()
    })
    ```
     The `Type.xxx` APIs are very flexible but not easy to use.
    
    This PR add improve these APIS to help simplifing the type definition.
    Now we can definition object type like this:
    
    ```JavaScript
        @Type.object("example.foo")
        class Foo {
          @Type.int32()
          a: number;
        }
    
        const fury = new Fury({ refTracking: true });
        const { serialize, deserialize } = fury.registerSerializer(Foo);
        const foo = new Foo();
        foo.a = 123;
        const input = serialize(foo);
        const result = deserialize(
          input
        );
    
        expect(result instanceof Foo);
        expect(result).toEqual({ a: 123 })
    
    ```
    
    The result of `Type.xxx()` serves both as a decorator function and as a
    description object. It is totally compatible with
    `Type.object("example.foo", {
        a: Type.string()
    })`
    
    Note that decoration APIs can not detect the type of a anonymous object.
    providing anonymous object like `{a: 123}` still will cause error.
---
 javascript/jest.config.js                      |   3 +-
 javascript/packages/fury/lib/classResolver.ts  |  33 +++--
 javascript/packages/fury/lib/description.ts    | 178 ++++++++++++++++---------
 javascript/packages/fury/lib/fury.ts           |  37 ++++-
 javascript/packages/fury/lib/gen/any.ts        |   4 +-
 javascript/packages/fury/lib/gen/builder.ts    |   4 +
 javascript/packages/fury/lib/gen/index.ts      |  10 +-
 javascript/packages/fury/lib/gen/object.ts     |  19 ++-
 javascript/packages/fury/lib/gen/router.ts     |   8 +-
 javascript/packages/fury/lib/gen/serializer.ts |   5 +-
 javascript/packages/fury/lib/type.ts           |   8 ++
 javascript/packages/fury/tsconfig.json         |   2 +-
 javascript/test/object.test.ts                 |  37 +++++
 13 files changed, 254 insertions(+), 94 deletions(-)

diff --git a/javascript/jest.config.js b/javascript/jest.config.js
index 363bdd2b..9db8b752 100644
--- a/javascript/jest.config.js
+++ b/javascript/jest.config.js
@@ -32,7 +32,8 @@ module.exports = {
   transform: {
     '\\.ts$': ['ts-jest', {
       tsconfig: {
-        target: "ES2021"
+        target: "ES2021",
+        experimentalDecorators: true
       },
       diagnostics: {
         ignoreCodes: [151001]
diff --git a/javascript/packages/fury/lib/classResolver.ts 
b/javascript/packages/fury/lib/classResolver.ts
index 27dbbc64..600f5eec 100644
--- a/javascript/packages/fury/lib/classResolver.ts
+++ b/javascript/packages/fury/lib/classResolver.ts
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-import { InternalSerializerType, Serializer } from "./type";
+import { FuryClsInfoSymbol, InternalSerializerType, ObjectFuryClsInfo, 
Serializer } from "./type";
 import { fromString } from "./platformBuffer";
 import { x64hash128 } from "./murmurHash3";
 import { BinaryWriter } from "./writer";
@@ -78,9 +78,7 @@ const uninitSerialize = {
 
 export default class SerializerResolver {
   private internalSerializer: Serializer[] = new Array(300);
-  private customSerializer: { [key: string]: Serializer } = {
-  };
-
+  private customSerializer: Map<string | (new () => any), Serializer> = new 
Map();
   private readStringPool: LazyString[] = [];
   private writeStringCount = 0;
   private writeStringIndex: number[] = [];
@@ -161,17 +159,25 @@ export default class SerializerResolver {
     return this.internalSerializer[id];
   }
 
-  registerSerializerByTag(tag: string, serializer: Serializer = 
uninitSerialize) {
-    if (this.customSerializer[tag]) {
-      Object.assign(this.customSerializer[tag], serializer);
+  private registerCustomSerializer(tagOrConstructor: string | (new () => any), 
serializer: Serializer = uninitSerialize) {
+    if (this.customSerializer.has(tagOrConstructor)) {
+      Object.assign(this.customSerializer.get(tagOrConstructor)!, serializer);
     } else {
-      this.customSerializer[tag] = { ...serializer };
+      this.customSerializer.set(tagOrConstructor, { ...serializer });
     }
-    return this.customSerializer[tag];
+    return this.customSerializer.get(tagOrConstructor);
+  }
+
+  registerSerializerByTag(tag: string, serializer: Serializer = 
uninitSerialize) {
+    this.registerCustomSerializer(tag, serializer);
+  }
+
+  registerSerializerByConstructor(constructor: new () => any, serializer: 
Serializer = uninitSerialize) {
+    this.registerCustomSerializer(constructor, serializer);
   }
 
   getSerializerByTag(tag: string) {
-    return this.customSerializer[tag];
+    return this.customSerializer.get(tag)!;
   }
 
   static tagBuffer(tag: string) {
@@ -232,6 +238,7 @@ export default class SerializerResolver {
   }
 
   getSerializerByData(v: any) {
+    // internal types
     if (typeof v === "number") {
       return this.numberSerializer;
     }
@@ -263,6 +270,12 @@ export default class SerializerResolver {
     if (v instanceof Set) {
       return this.setSerializer;
     }
+
+    // custome types
+    if (typeof v === "object" && v !== null && FuryClsInfoSymbol in v) {
+      return this.customSerializer.get((v[FuryClsInfoSymbol] as 
ObjectFuryClsInfo).constructor)!;
+    }
+
     throw new Error(`Failed to detect the Fury type from JavaScript type: 
${typeof v}`);
   }
 
diff --git a/javascript/packages/fury/lib/description.ts 
b/javascript/packages/fury/lib/description.ts
index de837bad..78d0b276 100644
--- a/javascript/packages/fury/lib/description.ts
+++ b/javascript/packages/fury/lib/description.ts
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-import { InternalSerializerType } from "./type";
+import { FuryClsInfoSymbol, InternalSerializerType, ObjectFuryClsInfo } from 
"./type";
 
 export interface TypeDescription {
   type: InternalSerializerType;
@@ -28,6 +28,7 @@ export interface ObjectTypeDescription extends 
TypeDescription {
   options: {
     props: { [key: string]: TypeDescription };
     tag: string;
+    withConstructor?: false;
   };
 }
 
@@ -274,181 +275,234 @@ export type ResultType<T> = T extends {
                               type: InternalSerializerType.ONEOF;
                             } ? OneofResult<T> : unknown;
 
+type DecorationWithDescription<T> = ((target: any, key?: string | { name?: 
string }) => void) & T;
+
+const makeDescriptionWithDecoration = <T extends TypeDescription>(description: 
T): DecorationWithDescription<T> => {
+  function decoration(target: any, key?: string | { name?: string }) {
+    if (key === undefined) {
+      initMeta(target, description as unknown as ObjectTypeDescription);
+    } else {
+      const keyString = typeof key === "string" ? key : key?.name;
+      if (!keyString) {
+        throw new Error("Decorators can only be placed on classes and fields");
+      }
+      addField(target.constructor, keyString, description);
+    }
+  }
+  decoration.toJSON = function () {
+    return JSON.stringify(description);
+  };
+  Object.entries(description).map(([key, value]: any) => {
+    Object.defineProperty(decoration, key, {
+      enumerable: true,
+      get() {
+        return value;
+      },
+    });
+  });
+  return decoration as unknown as DecorationWithDescription<T>;
+};
+
 export const Type = {
   any() {
-    return {
+    return makeDescriptionWithDecoration({
       type: InternalSerializerType.ANY as const,
-    };
+    });
   },
   enum<T1 extends { [key: string]: any }>(t1: T1) {
-    return {
+    return makeDescriptionWithDecoration({
       type: InternalSerializerType.ENUM as const,
       options: {
         inner: t1,
       },
-    };
+    });
   },
   oneof<T extends { [key: string]: TypeDescription }>(inner?: T) {
-    return {
+    return makeDescriptionWithDecoration({
       type: InternalSerializerType.ONEOF as const,
       options: {
         inner,
       },
-    };
+    });
   },
   string() {
-    return {
+    return makeDescriptionWithDecoration({
       type: InternalSerializerType.STRING as const,
-    };
+    });
   },
   array<T extends TypeDescription>(def: T) {
-    return {
+    return makeDescriptionWithDecoration({
       type: InternalSerializerType.ARRAY as const,
       options: {
         inner: def,
       },
-    };
+    });
   },
   tuple<T1 extends readonly [...readonly TypeDescription[]]>(t1: T1) {
-    return {
+    return makeDescriptionWithDecoration({
       type: InternalSerializerType.TUPLE as const,
       options: {
         inner: t1,
       },
-    };
+    });
   },
   map<T1 extends TypeDescription, T2 extends TypeDescription>(
     key: T1,
     value: T2
   ) {
-    return {
+    return makeDescriptionWithDecoration({
       type: InternalSerializerType.MAP as const,
       options: {
         key,
         value,
       },
-    };
+    });
   },
   set<T extends TypeDescription>(key: T) {
-    return {
+    return makeDescriptionWithDecoration({
       type: InternalSerializerType.SET as const,
       options: {
         key,
       },
-    };
+    });
   },
   bool() {
-    return {
+    return makeDescriptionWithDecoration({
       type: InternalSerializerType.BOOL as const,
-    };
+    });
   },
-  object<T extends { [key: string]: TypeDescription }>(tag: string, props?: T) 
{
-    return {
+  object<T extends { [key: string]: TypeDescription }>(tag: string, props?: T, 
withConstructor = false) {
+    return makeDescriptionWithDecoration({
       type: InternalSerializerType.OBJECT as const,
       options: {
         tag,
         props,
+        withConstructor,
       },
-    };
+    });
   },
   int8() {
-    return {
+    return makeDescriptionWithDecoration({
       type: InternalSerializerType.INT8 as const,
-    };
+    });
   },
   int16() {
-    return {
+    return makeDescriptionWithDecoration({
       type: InternalSerializerType.INT16 as const,
-    };
+    });
   },
   int32() {
-    return {
+    return makeDescriptionWithDecoration({
       type: InternalSerializerType.INT32 as const,
-    };
+    });
   },
   varInt32() {
-    return {
+    return makeDescriptionWithDecoration({
       type: InternalSerializerType.VAR_INT32 as const,
-    };
+    });
   },
   int64() {
-    return {
+    return makeDescriptionWithDecoration({
       type: InternalSerializerType.INT64 as const,
-    };
+    });
   },
   sliInt64() {
-    return {
+    return makeDescriptionWithDecoration({
       type: InternalSerializerType.SLI_INT64 as const,
-    };
+    });
   },
   float16() {
-    return {
+    return makeDescriptionWithDecoration({
       type: InternalSerializerType.FLOAT16 as const,
-    };
+    });
   },
   float32() {
-    return {
+    return makeDescriptionWithDecoration({
       type: InternalSerializerType.FLOAT32 as const,
-    };
+    });
   },
   float64() {
-    return {
+    return makeDescriptionWithDecoration({
       type: InternalSerializerType.FLOAT64 as const,
-    };
+    });
   },
   binary() {
-    return {
+    return makeDescriptionWithDecoration({
       type: InternalSerializerType.BINARY as const,
-    };
+    });
   },
   duration() {
-    return {
+    return makeDescriptionWithDecoration({
       type: InternalSerializerType.DURATION as const,
-    };
+    });
   },
   timestamp() {
-    return {
+    return makeDescriptionWithDecoration({
       type: InternalSerializerType.TIMESTAMP as const,
-    };
+    });
   },
   boolArray() {
-    return {
+    return makeDescriptionWithDecoration({
       type: InternalSerializerType.BOOL_ARRAY as const,
-    };
+    });
   },
   int8Array() {
-    return {
+    return makeDescriptionWithDecoration({
       type: InternalSerializerType.INT8_ARRAY as const,
-    };
+    });
   },
   int16Array() {
-    return {
+    return makeDescriptionWithDecoration({
       type: InternalSerializerType.INT16_ARRAY as const,
-    };
+    });
   },
   int32Array() {
-    return {
+    return makeDescriptionWithDecoration({
       type: InternalSerializerType.INT32_ARRAY as const,
-    };
+    });
   },
   int64Array() {
-    return {
+    return makeDescriptionWithDecoration({
       type: InternalSerializerType.INT64_ARRAY as const,
-    };
+    });
   },
   float16Array() {
-    return {
+    return makeDescriptionWithDecoration({
       type: InternalSerializerType.FLOAT16_ARRAY as const,
-    };
+    });
   },
   float32Array() {
-    return {
+    return makeDescriptionWithDecoration({
       type: InternalSerializerType.FLOAT32_ARRAY as const,
-    };
+    });
   },
   float64Array() {
-    return {
+    return makeDescriptionWithDecoration({
       type: InternalSerializerType.FLOAT64_ARRAY as const,
-    };
+    });
   },
 };
+
+const initMeta = (target: new () => any, description: ObjectTypeDescription) 
=> {
+  if (!target.prototype) {
+    target.prototype = {};
+  }
+  target.prototype[FuryClsInfoSymbol] = {
+    toObjectDescription() {
+      if (targetFields.has(target)) {
+        return Type.object(description.options.tag, targetFields.get(target), 
true);
+      }
+      return Type.object(description.options.tag, {}, true);
+    },
+    constructor: target,
+  } as ObjectFuryClsInfo;
+};
+
+const targetFields = new WeakMap<new () => any, { [key: string]: 
TypeDescription }>();
+
+const addField = (target: new () => any, key: string, des: TypeDescription) => 
{
+  if (!targetFields.has(target)) {
+    targetFields.set(target, {});
+  }
+  targetFields.get(target)![key] = des;
+};
diff --git a/javascript/packages/fury/lib/fury.ts 
b/javascript/packages/fury/lib/fury.ts
index d4400f7f..910d8006 100644
--- a/javascript/packages/fury/lib/fury.ts
+++ b/javascript/packages/fury/lib/fury.ts
@@ -21,11 +21,12 @@ import ClassResolver from "./classResolver";
 import { BinaryWriter } from "./writer";
 import { BinaryReader } from "./reader";
 import { ReferenceResolver } from "./referenceResolver";
-import { ConfigFlags, Serializer, Config, Language, MAGIC_NUMBER, Mode } from 
"./type";
+import { ConfigFlags, Serializer, Config, Language, MAGIC_NUMBER, Mode, 
FuryClsInfoSymbol } from "./type";
 import { OwnershipError } from "./error";
 import { InputType, ResultType, TypeDescription } from "./description";
 import { generateSerializer, AnySerializer } from "./gen";
 import { TypeMeta } from "./meta/TypeMeta";
+import { PlatformBuffer } from "./platformBuffer";
 
 export default class {
   binaryReader: BinaryReader;
@@ -49,18 +50,42 @@ export default class {
     this.anySerializer = new AnySerializer(this);
   }
 
-  registerSerializer<T extends TypeDescription>(description: T) {
-    const serializer = generateSerializer(this, description);
+  registerSerializer<T extends new () => any>(description: T): {
+    serializer: Serializer;
+    serialize(data: Partial<InstanceType<T>> | null): PlatformBuffer;
+    serializeVolatile(data: Partial<InstanceType<T>>): {
+      get: () => Uint8Array;
+      dispose: () => void;
+    };
+    deserialize(bytes: Uint8Array): InstanceType<T> | null;
+  };
+  registerSerializer<T extends TypeDescription>(description: T): {
+    serializer: Serializer;
+    serialize(data: InputType<T> | null): PlatformBuffer;
+    serializeVolatile(data: InputType<T>): {
+      get: () => Uint8Array;
+      dispose: () => void;
+    };
+    deserialize(bytes: Uint8Array): ResultType<T>;
+  };
+  registerSerializer(description: any) {
+    let serializer: Serializer;
+    if (description.prototype?.[FuryClsInfoSymbol]) {
+      serializer = generateSerializer(this, 
description.prototype[FuryClsInfoSymbol].toObjectDescription(), { constructor: 
description });
+      this.classResolver.registerSerializerByConstructor(description, 
serializer);
+    } else {
+      serializer = generateSerializer(this, description);
+    }
     return {
       serializer,
-      serialize: (data: InputType<T>) => {
+      serialize: (data: any) => {
         return this.serialize(data, serializer);
       },
-      serializeVolatile: (data: InputType<T>) => {
+      serializeVolatile: (data: any) => {
         return this.serializeVolatile(data, serializer);
       },
       deserialize: (bytes: Uint8Array) => {
-        return this.deserialize(bytes, serializer) as ResultType<T>;
+        return this.deserialize(bytes, serializer);
       },
     };
   }
diff --git a/javascript/packages/fury/lib/gen/any.ts 
b/javascript/packages/fury/lib/gen/any.ts
index cc3900fb..16846720 100644
--- a/javascript/packages/fury/lib/gen/any.ts
+++ b/javascript/packages/fury/lib/gen/any.ts
@@ -44,7 +44,7 @@ export class AnySerializer {
 
   detectSerializer() {
     const typeId = this.fury.binaryReader.int16();
-    let serializer: Serializer;
+    let serializer: Serializer | undefined;
     if (typeId === 
SerializerResolver.getTypeIdByInternalSerializerType(InternalSerializerType.OBJECT))
 {
       const tag = this.fury.classResolver.readTag(this.fury.binaryReader)();
       serializer = this.fury.classResolver.getSerializerByTag(tag);
@@ -138,7 +138,7 @@ class AnySerializerGenerator extends 
BaseSerializerGenerator {
       };
     `;
     return `
-        return function (fury, external) {
+        return function (fury, external, options) {
             ${this.scope.generate()}
             ${declare}
             return {
diff --git a/javascript/packages/fury/lib/gen/builder.ts 
b/javascript/packages/fury/lib/gen/builder.ts
index f78d5a3a..d5e5b7a2 100644
--- a/javascript/packages/fury/lib/gen/builder.ts
+++ b/javascript/packages/fury/lib/gen/builder.ts
@@ -383,4 +383,8 @@ export class CodecBuilder {
   getExternal(key: string) {
     return `external.${key}`;
   }
+
+  getOptions(key: string) {
+    return `options.${key}`;
+  }
 }
diff --git a/javascript/packages/fury/lib/gen/index.ts 
b/javascript/packages/fury/lib/gen/index.ts
index ee449b75..827d1872 100644
--- a/javascript/packages/fury/lib/gen/index.ts
+++ b/javascript/packages/fury/lib/gen/index.ts
@@ -59,7 +59,7 @@ export const generate = (fury: Fury, description: 
TypeDescription) => {
   return new Function(funcString);
 };
 
-function regDependencies(fury: Fury, description: TypeDescription) {
+function regDependencies(fury: Fury, description: TypeDescription, regOptions: 
{ [key: string]: any } = {}) {
   if (description.type === InternalSerializerType.OBJECT) {
     const options = (<ObjectTypeDescription>description).options;
     if (options.props) {
@@ -68,7 +68,7 @@ function regDependencies(fury: Fury, description: 
TypeDescription) {
         regDependencies(fury, x);
       });
       const func = generate(fury, description);
-      fury.classResolver.registerSerializerByTag(options.tag, func()(fury, 
external));
+      fury.classResolver.registerSerializerByTag(options.tag, func()(fury, 
external, regOptions));
     }
   }
   if (description.type === InternalSerializerType.ARRAY) {
@@ -96,11 +96,11 @@ function regDependencies(fury: Fury, description: 
TypeDescription) {
   }
 }
 
-export const generateSerializer = (fury: Fury, description: TypeDescription) 
=> {
-  regDependencies(fury, description);
+export const generateSerializer = (fury: Fury, description: TypeDescription, 
options: { [key: string]: any } = {}) => {
+  regDependencies(fury, description, options);
   if (description.type === InternalSerializerType.OBJECT) {
     return 
fury.classResolver.getSerializerByTag((<ObjectTypeDescription>description).options.tag);
   }
   const func = generate(fury, description);
-  return func()(fury, external);
+  return func()(fury, external, options);
 };
diff --git a/javascript/packages/fury/lib/gen/object.ts 
b/javascript/packages/fury/lib/gen/object.ts
index 3602b6d6..19077e77 100644
--- a/javascript/packages/fury/lib/gen/object.ts
+++ b/javascript/packages/fury/lib/gen/object.ts
@@ -106,11 +106,20 @@ class ObjectSerializerGenerator extends 
BaseSerializerGenerator {
       if (${this.builder.reader.int32()} !== ${expectHash}) {
           throw new Error("got ${this.builder.reader.int32()} validate hash 
failed: ${this.safeTag()}. expect ${expectHash}");
       }
-      const ${result} = {
-        ${Object.entries(options.props).sort().map(([key]) => {
-          return `${CodecBuilder.safePropName(key)}: null`;
-        }).join(",\n")}
-      };
+      ${
+        this.description.options.withConstructor
+? `
+          const ${result} = new ${this.builder.getOptions("constructor")}();
+        `
+: `
+          const ${result} = {
+            ${Object.entries(options.props).sort().map(([key]) => {
+              return `${CodecBuilder.safePropName(key)}: null`;
+            }).join(",\n")}
+          };
+        `
+      }
+      
 
       ${this.maybeReference(result, refState)}
       ${
diff --git a/javascript/packages/fury/lib/gen/router.ts 
b/javascript/packages/fury/lib/gen/router.ts
index 0260767c..caee36f3 100644
--- a/javascript/packages/fury/lib/gen/router.ts
+++ b/javascript/packages/fury/lib/gen/router.ts
@@ -29,12 +29,18 @@ export class CodegenRegistry {
   static map = new Map<string, SerializerGeneratorConstructor>();
   static external = new Map<string, any>();
 
+  private static checkExists(name: string) {
+    if (this.external.has(name)) {
+      throw new Error(`${name} has been registered.`);
+    }
+  }
+
   static register(type: InternalSerializerType, generator: 
SerializerGeneratorConstructor) {
     this.map.set(InternalSerializerType[type], generator);
-    this.external.set(generator.name, generator);
   }
 
   static registerExternal(object: { name: string }) {
+    CodegenRegistry.checkExists(object.name);
     this.external.set(object.name, object);
   }
 
diff --git a/javascript/packages/fury/lib/gen/serializer.ts 
b/javascript/packages/fury/lib/gen/serializer.ts
index 3d8a067b..25a4478d 100644
--- a/javascript/packages/fury/lib/gen/serializer.ts
+++ b/javascript/packages/fury/lib/gen/serializer.ts
@@ -212,6 +212,9 @@ export abstract class BaseSerializerGenerator implements 
SerializerGenerator {
     this.scope.assertNameNotDuplicate("readInner");
     this.scope.assertNameNotDuplicate("write");
     this.scope.assertNameNotDuplicate("writeInner");
+    this.scope.assertNameNotDuplicate("fury");
+    this.scope.assertNameNotDuplicate("external");
+    this.scope.assertNameNotDuplicate("options");
 
     const declare = `
       const readInner = (fromRef) => {
@@ -228,7 +231,7 @@ export abstract class BaseSerializerGenerator implements 
SerializerGenerator {
       };
     `;
     return `
-        return function (fury, external) {
+        return function (fury, external, options) {
             ${this.scope.generate()}
             ${declare}
             return {
diff --git a/javascript/packages/fury/lib/type.ts 
b/javascript/packages/fury/lib/type.ts
index 6c76e9c0..89072f00 100644
--- a/javascript/packages/fury/lib/type.ts
+++ b/javascript/packages/fury/lib/type.ts
@@ -17,6 +17,7 @@
  * under the License.
  */
 
+import { ObjectTypeDescription } from "./description";
 import { Meta } from "./meta";
 
 export enum InternalSerializerType {
@@ -125,3 +126,10 @@ export enum Language {
 }
 
 export const MAGIC_NUMBER = 0x62D4;
+
+export interface ObjectFuryClsInfo {
+  constructor: new () => any;
+  toObjectDescription(): ObjectTypeDescription;
+}
+
+export const FuryClsInfoSymbol = Symbol("furyClsInfo");
diff --git a/javascript/packages/fury/tsconfig.json 
b/javascript/packages/fury/tsconfig.json
index 176ef55b..87cefde9 100644
--- a/javascript/packages/fury/tsconfig.json
+++ b/javascript/packages/fury/tsconfig.json
@@ -3,7 +3,7 @@
     "target": "ES2021",                                  /* Set the JavaScript 
language version for emitted JavaScript and include compatible library 
declarations. */
     // "lib": [],                                        /* Specify a set of 
bundled library declaration files that describe the target runtime environment. 
*/
     // "jsx": "preserve",                                /* Specify what JSX 
code is generated. */
-    // "experimentalDecorators": true,                   /* Enable 
experimental support for TC39 stage 2 draft decorators. */
+    "experimentalDecorators": true,                   /* Enable experimental 
support for TC39 stage 2 draft decorators. */
     // "emitDecoratorMetadata": true,                    /* Emit design-type 
metadata for decorated declarations in source files. */
     // "jsxFactory": "",                                 /* Specify the JSX 
factory function used when targeting React JSX emit, e.g. 'React.createElement' 
or 'h'. */
     // "jsxFragmentFactory": "",                         /* Specify the JSX 
Fragment reference used for fragments when targeting React JSX emit e.g. 
'React.Fragment' or 'Fragment'. */
diff --git a/javascript/test/object.test.ts b/javascript/test/object.test.ts
index 79e1f7dc..3c1b4df0 100644
--- a/javascript/test/object.test.ts
+++ b/javascript/test/object.test.ts
@@ -21,6 +21,43 @@ import Fury, { TypeDescription, InternalSerializerType, Type 
} from '../packages
 import { describe, expect, test } from '@jest/globals';
 
 describe('object', () => {
+  test('should descoration work', () => {
+    @Type.object("example.foo")
+    class Foo {
+      @Type.int32()
+      a: number;
+    }
+    const fury = new Fury({ refTracking: true });
+    const { serialize, deserialize } = fury.registerSerializer(Foo);
+    const foo = new Foo();
+    foo.a = 123;
+    const input = serialize(foo);
+    const result = deserialize(
+      input
+    );
+
+    expect(result instanceof Foo);
+    
+    expect(result).toEqual({ a: 123 })
+  });
+
+  test('should descoration work2', () => {
+    @Type.object("example.foo")
+    class Foo {
+      @Type.int32()
+      a: number;
+    }
+    const fury = new Fury({ refTracking: true });
+    fury.registerSerializer(Foo);
+
+    const foo = new Foo();
+    foo.a = 123;
+    const input = fury.serialize(foo)
+    const result = fury.deserialize(input);
+    expect(result instanceof Foo);
+    expect(result).toEqual({ a: 123 })
+  });
+
   test('should object work', () => {
     const description = {
       type: InternalSerializerType.OBJECT as const,


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

Reply via email to