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 1b9c0d3cb feat(JavaScript): align xlang protocol (#3316)
1b9c0d3cb is described below
commit 1b9c0d3cb4bc4a3ce00abfb91226ff813b260a76
Author: weipeng <[email protected]>
AuthorDate: Mon Feb 9 23:23:57 2026 +0800
feat(JavaScript): align xlang protocol (#3316)
## Why?
## What does this PR do?
1. More test case were passed, the current progross is 26/39
## 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
---
.../org/apache/fory/xlang/JavaScriptXlangTest.java | 74 ++++++-
javascript/packages/fory/lib/gen/number.ts | 4 +-
javascript/packages/fory/lib/meta/TypeMeta.ts | 159 +++++++++-----
javascript/packages/fory/lib/type.ts | 13 ++
javascript/test/crossLanguage.test.ts | 235 +++++++++++++++------
5 files changed, 358 insertions(+), 127 deletions(-)
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 946777f00..8cb87be1c 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
@@ -22,10 +22,12 @@ package org.apache.fory.xlang;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
+import org.apache.fory.Fory;
+import org.apache.fory.config.CompatibleMode;
+import org.apache.fory.config.Language;
+import org.apache.fory.memory.MemoryBuffer;
+import org.testng.Assert;
import org.testng.SkipException;
import org.testng.annotations.Test;
@@ -244,7 +246,69 @@ public class JavaScriptXlangTest extends XlangTestBase {
@Override
@Test(dataProvider = "enableCodegen")
public void testNullableFieldCompatibleNull(boolean enableCodegen) throws
java.io.IOException {
- super.testNullableFieldCompatibleNull(enableCodegen);
+ // JavaScript properly supports Optional and sends actual null values,
+ // unlike Rust which sends default values. Override with
JavaScript-specific expectations.
+ String caseName = "test_nullable_field_compatible_null";
+ Fory fory =
+ Fory.builder()
+ .withLanguage(Language.XLANG)
+ .withCompatibleMode(CompatibleMode.COMPATIBLE)
+ .withCodegen(enableCodegen)
+ .withMetaCompressor(new NoOpMetaCompressor())
+ .build();
+ fory.register(NullableComprehensiveCompatible.class, 402);
+
+ NullableComprehensiveCompatible obj = new
NullableComprehensiveCompatible();
+ // Base non-nullable primitive fields - must have values
+ obj.byteField = 1;
+ obj.shortField = 2;
+ obj.intField = 42;
+ obj.longField = 123456789L;
+ obj.floatField = 1.5f;
+ obj.doubleField = 2.5;
+ obj.boolField = true;
+
+ // Base non-nullable boxed fields - must have values
+ obj.boxedInt = 10;
+ obj.boxedLong = 20L;
+ obj.boxedFloat = 1.1f;
+ obj.boxedDouble = 2.2;
+ obj.boxedBool = true;
+
+ // Base non-nullable reference fields - must have values
+ obj.stringField = "hello";
+ obj.listField = Arrays.asList("a", "b", "c");
+ obj.setField = new HashSet<>(Arrays.asList("x", "y"));
+ obj.mapField = new HashMap<>();
+ obj.mapField.put("key1", "value1");
+ obj.mapField.put("key2", "value2");
+
+ // Nullable group 1 - all null
+ obj.nullableInt1 = null;
+ obj.nullableLong1 = null;
+ obj.nullableFloat1 = null;
+ obj.nullableDouble1 = null;
+ obj.nullableBool1 = null;
+
+ // Nullable group 2 - all null
+ obj.nullableString2 = null;
+ obj.nullableList2 = null;
+ obj.nullableSet2 = null;
+ obj.nullableMap2 = null;
+
+ MemoryBuffer buffer = MemoryBuffer.newHeapBuffer(1024);
+ fory.serialize(buffer, obj);
+
+ ExecutionContext ctx = prepareExecution(caseName, buffer.getBytes(0,
buffer.writerIndex()));
+ runPeer(ctx);
+
+ MemoryBuffer buffer2 = readBuffer(ctx.dataFile());
+ NullableComprehensiveCompatible result =
+ (NullableComprehensiveCompatible) fory.deserialize(buffer2);
+
+ // JavaScript properly supports Optional and sends actual null values
+ // (unlike Rust which sends default values)
+ Assert.assertEquals(result, obj);
}
@Test(dataProvider = "enableCodegen")
diff --git a/javascript/packages/fory/lib/gen/number.ts
b/javascript/packages/fory/lib/gen/number.ts
index a33033975..4c5f6e3a0 100644
--- a/javascript/packages/fory/lib/gen/number.ts
+++ b/javascript/packages/fory/lib/gen/number.ts
@@ -158,7 +158,7 @@ CodegenRegistry.register(TypeId.TAGGED_UINT64,
CodegenRegistry.register(TypeId.VARINT64,
buildNumberSerializer(
- (builder, accessor) => builder.writer.varUInt64(accessor),
- builder => builder.reader.varUInt64()
+ (builder, accessor) => builder.writer.varInt64(accessor),
+ builder => builder.reader.varInt64()
)
);
diff --git a/javascript/packages/fory/lib/meta/TypeMeta.ts
b/javascript/packages/fory/lib/meta/TypeMeta.ts
index d132b5afe..63fbf99c3 100644
--- a/javascript/packages/fory/lib/meta/TypeMeta.ts
+++ b/javascript/packages/fory/lib/meta/TypeMeta.ts
@@ -25,17 +25,17 @@ import { TypeId } from "../type";
import { x64hash128 } from "../murmurHash3";
import { fromString } from "../platformBuffer";
-const fieldEncoder = new MetaStringEncoder("$", ".");
-const fieldDecoder = new MetaStringDecoder("$", ".");
-const pkgEncoder = new MetaStringEncoder("_", ".");
-const pkgDecoder = new MetaStringDecoder("_", ".");
-const typeNameEncoder = new MetaStringEncoder("_", ".");
-const typeNameDecoder = new MetaStringDecoder("_", ".");
+const fieldEncoder = new MetaStringEncoder("$", "_");
+const fieldDecoder = new MetaStringDecoder("$", "_");
+const pkgEncoder = new MetaStringEncoder(".", "_");
+const pkgDecoder = new MetaStringDecoder(".", "_");
+const typeNameEncoder = new MetaStringEncoder("$", ".");
+const typeNameDecoder = new MetaStringDecoder("$", ".");
// Constants from Java implementation
const COMPRESS_META_FLAG = 1n << 63n;
const HAS_FIELDS_META_FLAG = 1n << 62n;
-const META_SIZE_MASKS = 0xFFF; // 22 bits
+const META_SIZE_MASKS = 0xFF; // 22 bits
const NUM_HASH_BITS = 41;
const BIG_NAME_THRESHOLD = 0b111111;
@@ -55,29 +55,6 @@ export const refTrackingAbleTypeId = (typeId: number):
boolean => {
return PRIMITIVE_TYPE_IDS.includes(typeId as any) || [TypeId.DURATION,
TypeId.DATE, TypeId.TIMESTAMP, TypeId.STRING].includes(typeId as any);
};
-export const isInternalTypeId = (typeId: number): boolean => {
- return [
- TypeId.STRING,
- TypeId.TIMESTAMP,
- TypeId.DURATION,
- TypeId.DECIMAL,
- TypeId.BINARY,
- TypeId.BOOL_ARRAY,
- TypeId.INT8_ARRAY,
- TypeId.INT16_ARRAY,
- TypeId.INT32_ARRAY,
- TypeId.INT64_ARRAY,
- TypeId.FLOAT8_ARRAY,
- TypeId.FLOAT16_ARRAY,
- TypeId.BFLOAT16_ARRAY,
- TypeId.FLOAT32_ARRAY,
- TypeId.FLOAT64_ARRAY,
- TypeId.UINT16_ARRAY,
- TypeId.UINT32_ARRAY,
- TypeId.UINT64_ARRAY,
- ].includes(typeId as any);
-};
-
function getPrimitiveTypeSize(typeId: number) {
switch (typeId) {
case TypeId.BOOL:
@@ -370,7 +347,9 @@ export class TypeMeta {
} else {
// Read field name
const encoding = FieldInfo.u8ToEncoding(encodingFlags);
+
fieldName = fieldDecoder.decode(reader, size + 1, encoding ||
Encoding.UTF_8);
+ fieldName = TypeMeta.lowerUnderscoreToLowerCamelCase(fieldName);
}
return new FieldInfo(fieldName, typeId, userTypeId, trackingRef, nullable,
options, fieldId);
@@ -525,7 +504,7 @@ export class TypeMeta {
}
writeFieldName(writer: BinaryWriter, fieldName: string) {
- const name = this.lowerCamelToLowerUnderscore(fieldName);
+ const name = TypeMeta.lowerCamelToLowerUnderscore(fieldName);
const metaString = fieldEncoder.encodeByEncodings(name, fieldNameEncoding);
const encoded = metaString.getBytes();
const encoding = fieldNameEncoding.indexOf(metaString.getEncoding());
@@ -546,7 +525,7 @@ export class TypeMeta {
encodingFlags = 3; // TAG_ID encoding
} else {
// Convert camelCase to snake_case for xlang compatibility
- const fieldName =
this.lowerCamelToLowerUnderscore(fieldInfo.getFieldName());
+ const fieldName =
TypeMeta.lowerCamelToLowerUnderscore(fieldInfo.getFieldName());
const metaString = fieldEncoder.encodeByEncodings(fieldName,
fieldNameEncoding);
encodingFlags = fieldNameEncoding.indexOf(metaString.getEncoding());
encoded = metaString.getBytes();
@@ -573,8 +552,63 @@ export class TypeMeta {
}
}
- private lowerCamelToLowerUnderscore(str: string): string {
- return str.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`);
+ static lowerUnderscoreToLowerCamelCase(lowerUnderscore: string) {
+ let result = "";
+ const length = lowerUnderscore.length;
+
+ let fromIndex = 0;
+ let index;
+
+ while ((index = lowerUnderscore.indexOf("_", fromIndex)) !== -1) {
+ // 拼接下划线前的内容
+ result += lowerUnderscore.substring(fromIndex, index);
+
+ if (length > index + 1) {
+ const symbol = lowerUnderscore.charAt(index + 1);
+ // 判断是否为小写字母
+ if (symbol >= "a" && symbol <= "z") {
+ result += symbol.toUpperCase();
+ fromIndex = index + 2;
+ continue;
+ }
+ }
+
+ fromIndex = index + 1;
+ }
+
+ // 处理剩余部分
+ if (fromIndex < length) {
+ result += lowerUnderscore.substring(fromIndex, length);
+ }
+
+ return result;
+ }
+
+ static lowerCamelToLowerUnderscore(lowerCamel: string) {
+ let result = "";
+ const length = lowerCamel.length;
+ let fromIndex = 0;
+
+ for (let i = 0; i < length; i++) {
+ const symbol = lowerCamel.charAt(i);
+
+ // 检查是否为大写字母
+ if (symbol >= "A" && symbol <= "Z") {
+ // 拼接从上一个索引到当前大写字母前的部分,加下划线,加小写化后的字母
+ result += lowerCamel.substring(fromIndex, i);
+ result += "_";
+ result += symbol.toLowerCase();
+ // 更新起始索引
+ fromIndex = i + 1;
+ }
+ }
+
+ // 处理剩余部分
+ if (fromIndex < length) {
+ result += lowerCamel.substring(fromIndex, length);
+ }
+
+ return result;
}
private prependHeader(buffer: Uint8Array, isCompressed: boolean,
hasFieldsMeta: boolean): Uint8Array {
@@ -626,7 +660,14 @@ export class TypeMeta {
return result.join("");
}
- static groupFieldsByType<T extends { fieldName: string; nullable?: boolean;
typeId: number }>(typeInfos: Array<T>): Array<T> {
+ static getFieldSortKey(i: { fieldName: string; fieldId?: number }) {
+ if (i.fieldId !== undefined && i.fieldId !== null) {
+ return `${i.fieldId}`;
+ }
+ return TypeMeta.toSnakeCase(i.fieldName);
+ }
+
+ static groupFieldsByType<T extends { fieldName: string; nullable?: boolean;
typeId: number; fieldId?: number }>(typeInfos: Array<T>): Array<T> {
const primitiveFields: Array<T> = [];
const nullablePrimitiveFields: Array<T> = [];
const internalTypeFields: Array<T> = [];
@@ -650,7 +691,7 @@ export class TypeMeta {
}
// Categorize based on type_id
- if (isInternalTypeId(typeId)) {
+ if (TypeId.isBuiltin(typeId)) {
internalTypeFields.push(typeInfo);
} else if (typeId === TypeId.LIST) {
listFields.push(typeInfo);
@@ -664,38 +705,52 @@ export class TypeMeta {
}
// Sort functions
- const numericSorter = (a: T, b: T) => {
+ const primitiveComparator = (a: T, b: T) => {
// Sort by type_id descending, then by name ascending
-
- const sizea = getPrimitiveTypeSize(a.typeId);
- const sizeb = getPrimitiveTypeSize(b.typeId);
- if (sizea !== sizeb) {
- return sizeb - sizea;
+ const t1Compress = TypeId.isCompressedType(a.typeId);
+ const t2Compress = TypeId.isCompressedType(b.typeId);
+
+ if ((t1Compress && t2Compress) || (!t1Compress && !t2Compress)) {
+ const sizea = getPrimitiveTypeSize(a.typeId);
+ const sizeb = getPrimitiveTypeSize(b.typeId);
+ // return nameSorter(a, b);
+
+ let c = sizeb - sizea;
+ if (c === 0) {
+ c = b.typeId - a.typeId;
+ // noinspection Duplicates
+ if (c == 0) {
+ return nameSorter(a, b);
+ }
+ return c;
+ }
+ return c;
}
- if (a.typeId !== b.typeId) {
- return b.typeId - a.typeId;
+ if (t1Compress) {
+ return 1;
}
- return nameSorter(a, b);
+ // t2 compress
+ return -1;
};
const typeIdThenNameSorter = (a: T, b: T) => {
if (a.typeId !== b.typeId) {
- return b.typeId - a.typeId;
+ return a.typeId - b.typeId;
}
return nameSorter(a, b);
};
const nameSorter = (a: T, b: T) => {
- return
TypeMeta.toSnakeCase(a.fieldName).localeCompare(TypeMeta.toSnakeCase(b.fieldName));
+ return
TypeMeta.getFieldSortKey(a).localeCompare(TypeMeta.getFieldSortKey(b));
};
// Sort each group
- primitiveFields.sort(numericSorter);
- nullablePrimitiveFields.sort(numericSorter);
+ primitiveFields.sort(primitiveComparator);
+ nullablePrimitiveFields.sort(primitiveComparator);
internalTypeFields.sort(typeIdThenNameSorter);
- listFields.sort(nameSorter);
- setFields.sort(nameSorter);
- mapFields.sort(nameSorter);
+ listFields.sort(typeIdThenNameSorter);
+ setFields.sort(typeIdThenNameSorter);
+ mapFields.sort(typeIdThenNameSorter);
otherFields.sort(typeIdThenNameSorter);
return [
diff --git a/javascript/packages/fory/lib/type.ts
b/javascript/packages/fory/lib/type.ts
index 10d54d943..549f972e8 100644
--- a/javascript/packages/fory/lib/type.ts
+++ b/javascript/packages/fory/lib/type.ts
@@ -181,6 +181,19 @@ export const TypeId = {
TypeId.TYPED_UNION,
].includes(id as any);
},
+ isCompressedType(typeId: number) {
+ switch (typeId) {
+ case TypeId.VARINT32:
+ case TypeId.VAR_UINT32:
+ case TypeId.VARINT64:
+ case TypeId.VAR_UINT64:
+ case TypeId.TAGGED_INT64:
+ case TypeId.TAGGED_UINT64:
+ return true;
+ default:
+ return false;
+ }
+ },
} as const;
export enum ConfigFlags {
diff --git a/javascript/test/crossLanguage.test.ts
b/javascript/test/crossLanguage.test.ts
index 5c16b7782..3f4f0b2a7 100644
--- a/javascript/test/crossLanguage.test.ts
+++ b/javascript/test/crossLanguage.test.ts
@@ -183,7 +183,7 @@ describe("bool", () => {
writeToFile(writer.dump() as Buffer);
});
test("test_murmurhash3", () => {
- const { x64hash128 } = require("../packages/fory/lib/murmurHash3");
+ const { x64hash128 } = require("../packages/fory/lib/murmurHash3");
const reader = new BinaryReader({});
reader.reset(content);
let dataview = x64hash128(new Uint8Array([1, 2, 8]), 47);
@@ -898,7 +898,7 @@ describe("bool", () => {
f1: Type.string()
})
class OneStringFieldStruct {
- @ForyField({nullable: true})
+ @ForyField({ nullable: true })
f1: string | null = null;
}
fory.registerSerializer(OneStringFieldStruct);
@@ -921,7 +921,7 @@ describe("bool", () => {
f1: Type.string()
})
class OneStringFieldStruct {
- @ForyField({nullable: true})
+ @ForyField({ nullable: true })
f1: string | null = null;
}
fory.registerSerializer(OneStringFieldStruct);
@@ -1115,28 +1115,165 @@ describe("bool", () => {
writeToFile(serializedData as Buffer);
});
- test("test_nullable_field_schema_consistent_not_null", () => {
- if (Boolean("1")) { return; }
- const fory = new Fory({
- mode: Mode.Compatible
- });
+ const buildClass = (id = 402) => {
+ @Type.struct({ typeId: id })
+ class NullableComprehensiveCompatible {
+ // Base non-nullable primitive fields
+ @Type.int8()
+ byteField: number = 0;
+ @Type.int16()
+ shortField: number = 0;
+ @Type.varInt32()
+ intField: number = 0;
+ @Type.varInt64()
+ longField: number = 0;
+ @Type.float32()
+ floatField: number = 0;
+ @Type.float64()
+ doubleField: number = 0;
+ @Type.bool()
+ boolField: boolean = false;
+
+ // Base non-nullable boxed fields (not nullable by default in xlang)
+ @Type.varInt32()
+ boxedInt: number = 0;
+ @Type.varInt64()
+ boxedLong: number = 0;
+ @Type.float32()
+ boxedFloat: number = 0;
+ @Type.float64()
+ boxedDouble: number = 0;
+ @Type.bool()
+ boxedBool = false;
+
+ // Base non-nullable reference fields
+ @Type.string()
+ stringField: string = '';
+ @Type.array(Type.string())
+ listField: string[] = [];
+ @Type.set(Type.string())
+ setField: Set<string> = new Set();
+ @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()
+ nullableInt1: number | null = null;
- @Type.struct(401, {
- intField: Type.int32(),
- stringField: Type.string(),
- nullableInt: Type.int32(),
- nullableString: Type.string()
- })
- class NullableStruct {
+ @ForyField({ nullable: true })
+ @Type.varInt64()
+ nullableLong1: number | null = null;
+
+ @ForyField({ nullable: true })
+ @Type.float32()
+ nullableFloat1: number | null = null;
+
+ @ForyField({ nullable: true })
+ @Type.float64()
+ nullableDouble1: number | null = null;
+
+ @ForyField({ nullable: true })
+ @Type.bool()
+ nullableBool1: boolean | null = null;
+
+ // Nullable group 2 - reference types with @ForyField(nullable=true)
+ @ForyField({ nullable: true })
+ @Type.string()
+ nullableString2: string | null = null;
+
+ @ForyField({ nullable: true })
+ @Type.array(Type.string())
+ nullableList2: string[] | null = null;
+
+ @ForyField({ nullable: true })
+ @Type.set(Type.string())
+ nullableSet2: Set<string> | null = null;
+
+ @ForyField({ nullable: true })
+ @Type.map(Type.string(), Type.string())
+ nullableMap2: Map<string, string> | null = null;
+ }
+ return NullableComprehensiveCompatible;
+ }
+
+ const buildClassConsistent = (id = 401) => {
+ @Type.struct({ typeId: id })
+ class NullableComprehensiveConsistent {
+ // Base non-nullable primitive fields
+ @Type.int8()
+ byteField: number = 0;
+ @Type.int16()
+ shortField: number = 0;
+ @Type.varInt32()
intField: number = 0;
- stringField: string = "";
+ @Type.varInt64()
+ longField: number = 0;
+ @Type.float32()
+ floatField: number = 0;
+ @Type.float64()
+ doubleField: number = 0;
+ @Type.bool()
+ boolField: boolean = false;
+
+ // Base non-nullable reference fields
+ @Type.string()
+ stringField: string = '';
+ @Type.array(Type.string())
+ listField: string[] = [];
+ @Type.set(Type.string())
+ setField: Set<string> = new Set();
+ @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()
nullableInt: number | null = null;
+
+ @ForyField({ nullable: true })
+ @Type.varInt64()
+ nullableLong: number | null = null;
+
+ @ForyField({ nullable: true })
+ @Type.float32()
+ nullableFloat: number | null = null;
+
+ @ForyField({ nullable: true })
+ @Type.float64()
+ nullableDouble: number | null = null;
+
+ @ForyField({ nullable: true })
+ @Type.bool()
+ nullableBool: boolean | null = null;
+
+ // Nullable group 2 - reference types with @ForyField(nullable=true)
+ @ForyField({ nullable: true })
+ @Type.string()
nullableString: string | null = null;
+
+ @ForyField({ nullable: true })
+ @Type.array(Type.string())
+ nullableList: string[] | null = null;
+
+ @ForyField({ nullable: true })
+ @Type.set(Type.string())
+ nullableSet: Set<string> | null = null;
+
+ @ForyField({ nullable: true })
+ @Type.map(Type.string(), Type.string())
+ nullableMap: Map<string, string> | null = null;
}
- fory.registerSerializer(NullableStruct);
+ return NullableComprehensiveConsistent
- const reader = new BinaryReader({});
- reader.reset(content);
+ }
+
+ test("test_nullable_field_schema_consistent_not_null", () => {
+ const fory = new Fory({
+ mode: Mode.SchemaConsistent
+ });
+
+ fory.registerSerializer(buildClassConsistent(401));
// Deserialize struct from Java
let cursor = 0;
@@ -1148,25 +1285,12 @@ describe("bool", () => {
writeToFile(serializedData as Buffer);
});
+
test("test_nullable_field_schema_consistent_null", () => {
- if (Boolean("1")) { return; }
const fory = new Fory({
- mode: Mode.Compatible
+ mode: Mode.SchemaConsistent
});
-
- @Type.struct(401, {
- intField: Type.int32(),
- stringField: Type.string(),
- nullableInt: Type.int32(),
- nullableString: Type.string()
- })
- class NullableStruct {
- intField: number = 0;
- stringField: string = "";
- nullableInt: number | null = null;
- nullableString: string | null = null;
- }
- fory.registerSerializer(NullableStruct);
+ fory.registerSerializer(buildClassConsistent());
const reader = new BinaryReader({});
reader.reset(content);
@@ -1181,28 +1305,13 @@ describe("bool", () => {
writeToFile(serializedData as Buffer);
});
+
test("test_nullable_field_compatible_not_null", () => {
- if (Boolean("1")) { return; }
const fory = new Fory({
mode: Mode.Compatible
});
- @Type.struct(402, {
- intField: Type.int32(),
- stringField: Type.string(),
- nullableInt: Type.int32(),
- nullableString: Type.string()
- })
- class NullableStruct {
- intField: number = 0;
- stringField: string = "";
- nullableInt: number | null = null;
- nullableString: string | null = null;
- }
- fory.registerSerializer(NullableStruct);
-
- const reader = new BinaryReader({});
- reader.reset(content);
+ fory.registerSerializer(buildClass());
// Deserialize struct from Java
let cursor = 0;
@@ -1215,33 +1324,23 @@ describe("bool", () => {
});
test("test_nullable_field_compatible_null", () => {
- if (Boolean("1")) { return; }
const fory = new Fory({
mode: Mode.Compatible
});
- @Type.struct(402, {
- intField: Type.int32(),
- stringField: Type.string(),
- nullableInt: Type.int32(),
- nullableString: Type.string()
- })
- class NullableStruct {
- intField: number = 0;
- stringField: string = "";
- nullableInt: number | null = null;
- nullableString: string | null = null;
- }
- fory.registerSerializer(NullableStruct);
+ fory.registerSerializer(buildClass());
const reader = new BinaryReader({});
reader.reset(content);
// Deserialize struct from Java
let cursor = 0;
- const deserializedStruct = fory.deserialize(content.subarray(cursor));
+ const deserializedStruct: InstanceType<ReturnType<typeof buildClass>> |
null = fory.deserialize(content.subarray(cursor));
cursor += fory.binaryReader.getCursor();
+ if (deserializedStruct === null) {
+ throw new Error("deserializedStruct is null");
+ }
// Serialize the deserialized struct back
const serializedData = fory.serialize(deserializedStruct);
writeToFile(serializedData as Buffer);
@@ -1475,6 +1574,6 @@ describe("bool", () => {
// Serialize the deserialized struct back
const serializedData = fory.serialize(deserializedStruct);
- // writeToFile(serializedData as Buffer);
+ // writeToFile(serializedData as Buffer);
});
});
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]