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

chaokunyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fory.git


The following commit(s) were added to refs/heads/main by this push:
     new fb002e216 feat(js): add schema-based per-field nullable support for 
xlang (#3100)
fb002e216 is described below

commit fb002e216bb35cdb3fe5878b59017fb890796e76
Author: Harsh Patel <[email protected]>
AuthorDate: Sun Jan 4 08:05:23 2026 +0530

    feat(js): add schema-based per-field nullable support for xlang (#3100)
    
    ## Why?
    
    JavaScript xlang schema-based struct serialization currently treats all
    fields as nullable and always writes a per-field null flag.
    This introduces unnecessary overhead when a field is known to be
    non-nullable by schema design.
    
    ## What does this PR do?
    
    - Adds schema-based per-field `nullable` support for JS xlang struct
    serialization.
    - Preserves backward compatibility by treating fields as nullable by
    default.
    - When `nullable: false` is explicitly specified:
      - Skips writing the per-field null flag.
    - Throws a clear runtime error if the field value is `null` or
    `undefined` (includes field name).
    - Extends `StructTypeInfo` field schema typing to allow `nullable?:
    boolean`.
    - Adds a minimal unit test verifying:
      - Non-nullable fields throw on `null`.
      - Fields without `nullable` keep existing behavior.
    
    ## Related issues
    
    
    
    ## Does this PR introduce any user-facing change?
    
    Yes.
    This PR introduces an **opt-in** schema-level configuration (`nullable:
    false`) for JS xlang serialization.
    Existing schemas are unaffected unless `nullable: false` is explicitly
    set.
    
    - [x] Does this PR introduce any public API change?
    - [x] Does this PR introduce any binary protocol compatibility change?
    
    ## Benchmark
    
    This change is opt-in and only affects schema-based fields explicitly
    marked as `nullable: false`.
    No performance regression is expected for existing schemas.
    
    ---------
    
    Co-authored-by: Harsh Patel <[email protected]>
---
 javascript/packages/fory/lib/gen/struct.ts |  6 +++-
 javascript/packages/fory/lib/typeInfo.ts   |  2 +-
 javascript/test/protocol/struct.test.ts    | 48 +++++++++++++++++++++++++++++-
 3 files changed, 53 insertions(+), 3 deletions(-)

diff --git a/javascript/packages/fory/lib/gen/struct.ts 
b/javascript/packages/fory/lib/gen/struct.ts
index 43e93b326..1ef250829 100644
--- a/javascript/packages/fory/lib/gen/struct.ts
+++ b/javascript/packages/fory/lib/gen/struct.ts
@@ -76,7 +76,11 @@ class StructSerializerGenerator extends 
BaseSerializerGenerator {
             throw new Error(`${inner.type} generator not exists`);
         }
         const innerGenerator = new InnerGeneratorClass(inner, this.builder, 
this.scope);
-        return 
innerGenerator.toWriteEmbed(`${accessor}${CodecBuilder.safePropAccessor(key)}`);
+
+        const fieldAccessor = 
`${accessor}${CodecBuilder.safePropAccessor(key)}`;
+        return (inner as any).nullable === false
+          ? `if (${fieldAccessor} === null || ${fieldAccessor} === undefined) 
{ throw new Error("Field '${CodecBuilder.replaceBackslashAndQuote(key)}' is not 
nullable"); }\n${innerGenerator.toWriteEmbed(fieldAccessor, true)}`
+          : innerGenerator.toWriteEmbed(fieldAccessor);
       }).join(";\n")}
     `;
   }
diff --git a/javascript/packages/fory/lib/typeInfo.ts 
b/javascript/packages/fory/lib/typeInfo.ts
index 04af40392..d29c1b6f8 100644
--- a/javascript/packages/fory/lib/typeInfo.ts
+++ b/javascript/packages/fory/lib/typeInfo.ts
@@ -205,7 +205,7 @@ export class TypeInfo<T = unknown> extends 
ExtensibleFunction {
 
 export interface StructTypeInfo extends TypeInfo {
   options: {
-    props?: { [key: string]: TypeInfo };
+    props?: { [key: string]: TypeInfo & { nullable?: boolean } };
     withConstructor?: boolean;
   };
 }
diff --git a/javascript/test/protocol/struct.test.ts 
b/javascript/test/protocol/struct.test.ts
index 2b5633fdd..290e1effb 100644
--- a/javascript/test/protocol/struct.test.ts
+++ b/javascript/test/protocol/struct.test.ts
@@ -23,7 +23,7 @@ import { describe, expect, test } from '@jest/globals';
 
 describe('protocol', () => {
     test('should polymorphic work', () => {
-        
+
         const fory = new Fory({ refTracking: true });
         const { serialize, deserialize } = 
fory.registerSerializer(Type.struct({
             typeName: "example.foo"
@@ -45,6 +45,52 @@ describe('protocol', () => {
         const result = deserialize(bf);
         expect(result).toEqual(obj);
     });
+
+    test('should enforce nullable flag for schema-based structs', () => {
+        const fory = new Fory();
+
+        // 1) nullable: false => null must throw
+        const nonNullable = Type.struct({
+            typeName: "example.nonNullable"
+        }, {
+            a: Object.assign(Type.string(), { nullable: false }),
+        });
+        const nonNullableSer = fory.registerSerializer(nonNullable);
+        expect(() => nonNullableSer.serialize({ a: null })).toThrow(/Field 'a' 
is not nullable/);
+
+        // 2) nullable not specified => keep old behavior (null allowed)
+        const nullableUnspecified = Type.struct({
+            typeName: "example.nullableUnspecified"
+        }, {
+            a: Type.string(),
+        });
+        const { serialize, deserialize } = 
fory.registerSerializer(nullableUnspecified);
+        expect(deserialize(serialize({ a: null }))).toEqual({ a: null });
+    });
+
+    test('should enforce nullable flag in schema-consistent mode', () => {
+        const fory = new Fory({ mode: 'SCHEMA_CONSISTENT' as any });
+
+        const schema = Type.struct(
+            { typeName: 'example.schemaConsistentNullable' },
+            {
+                a: Object.assign(Type.string(), { nullable: false }),
+                b: Type.string(),
+            }
+        );
+
+        const { serialize, deserialize } = fory.registerSerializer(schema);
+
+        // non-nullable field must throw
+        expect(() => serialize({ a: null, b: 'ok' }))
+            .toThrow(/Field 'a' is not nullable/);
+
+        // unspecified nullable field keeps old behavior
+        expect(deserialize(serialize({ a: 'ok', b: null })))
+            .toEqual({ a: undefined, b: null });
+    });
 });
 
 
+
+


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

Reply via email to