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 96684971c feat(JavaScript): Support EXT,NAMED_EXT (#3312)
96684971c is described below
commit 96684971cdbf6238717223fcccf88426227b19e4
Author: weipeng <[email protected]>
AuthorDate: Mon Feb 9 14:53:28 2026 +0800
feat(JavaScript): Support EXT,NAMED_EXT (#3312)
## Why?
## What does this PR do?
1. support EXT,NAMED_EXT and classVersionHash
2. Add `computeStructFingerprint`
3. More xlang testcases in crossLanguage.test.ts were passed.
4. Support null chunk in Map
## 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
---
javascript/packages/fory/lib/fory.ts | 27 +-
javascript/packages/fory/lib/gen/collection.ts | 4 +-
javascript/packages/fory/lib/gen/ext.ts | 185 ++++++++++++++
javascript/packages/fory/lib/gen/index.ts | 1 +
javascript/packages/fory/lib/gen/map.ts | 172 ++++++++-----
javascript/packages/fory/lib/gen/struct.ts | 15 +-
javascript/packages/fory/lib/meta/TypeMeta.ts | 122 +++++----
javascript/packages/fory/lib/type.ts | 9 +
javascript/packages/fory/lib/typeInfo.ts | 79 +++++-
javascript/packages/fory/lib/typeResolver.ts | 12 +
javascript/test/crossLanguage.test.ts | 328 ++++++++++++++++---------
11 files changed, 724 insertions(+), 230 deletions(-)
diff --git a/javascript/packages/fory/lib/fory.ts
b/javascript/packages/fory/lib/fory.ts
index 88ab42a6f..fbef0e3ff 100644
--- a/javascript/packages/fory/lib/fory.ts
+++ b/javascript/packages/fory/lib/fory.ts
@@ -21,7 +21,7 @@ import TypeResolver from "./typeResolver";
import { BinaryWriter } from "./writer";
import { BinaryReader } from "./reader";
import { ReferenceResolver } from "./referenceResolver";
-import { ConfigFlags, Serializer, Config, Mode, ForyTypeInfoSymbol,
WithForyClsInfo, TypeId } from "./type";
+import { ConfigFlags, Serializer, Config, Mode, ForyTypeInfoSymbol,
WithForyClsInfo, TypeId, CustomSerializer } from "./type";
import { OwnershipError } from "./error";
import { InputType, ResultType, StructTypeInfo, TypeInfo } from "./typeInfo";
import { Gen } from "./gen";
@@ -59,6 +59,7 @@ export default class {
useSliceString: Boolean(config?.useSliceString),
hooks: config?.hooks || {},
mode: config?.mode || Mode.SchemaConsistent,
+ classVersionHash: config?.classVersionHash || false,
};
}
@@ -66,14 +67,14 @@ export default class {
return this.config.mode === Mode.Compatible;
}
- registerSerializer<T extends new () => any>(constructor: T): {
+ registerSerializer<T>(constructor: new () => T, customSerializer:
CustomSerializer<T>): {
serializer: Serializer;
- serialize(data: Partial<InstanceType<T>> | null): PlatformBuffer;
- serializeVolatile(data: Partial<InstanceType<T>>): {
+ serialize(data: InputType<T> | null): PlatformBuffer;
+ serializeVolatile(data: InputType<T>): {
get: () => Uint8Array;
dispose: () => void;
};
- deserialize(bytes: Uint8Array): InstanceType<T> | null;
+ deserialize(bytes: Uint8Array): ResultType<T>;
};
registerSerializer<T extends TypeInfo>(typeInfo: T): {
serializer: Serializer;
@@ -84,12 +85,21 @@ export default class {
};
deserialize(bytes: Uint8Array): ResultType<T>;
};
- registerSerializer(constructor: any) {
+ registerSerializer<T extends new () => any>(constructor: 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(constructor: any, customSerializer?:
CustomSerializer<any>) {
let serializer: Serializer;
TypeInfo.attach(this);
if (constructor.prototype?.[ForyTypeInfoSymbol]) {
const typeInfo: TypeInfo =
(<WithForyClsInfo>(constructor.prototype[ForyTypeInfoSymbol])).structTypeInfo;
- serializer = new Gen(this, { constructor }).generateSerializer(typeInfo);
+ serializer = new Gen(this, { creator: constructor, customSerializer
}).generateSerializer(typeInfo);
this.typeResolver.registerSerializer(typeInfo, serializer);
} else {
const typeInfo = constructor;
@@ -116,7 +126,7 @@ export default class {
replaceSerializerReader(typeInfo: TypeInfo) {
TypeInfo.attach(this);
- const serializer = new Gen(this, { constroctor: (typeInfo as
StructTypeInfo).options.constructor }).reGenerateSerializer(typeInfo);
+ const serializer = new Gen(this, { creator: (typeInfo as
StructTypeInfo).options.creator }).reGenerateSerializer(typeInfo);
const result = this.typeResolver.registerSerializer(typeInfo, {
getHash: serializer.getHash,
read: serializer.read,
@@ -158,6 +168,7 @@ export default class {
throw e;
}
this.referenceResolver.reset();
+ this.metaStringResolver.reset();
let bitmap = 0;
if (data === null) {
bitmap |= ConfigFlags.isNullFlag;
diff --git a/javascript/packages/fory/lib/gen/collection.ts
b/javascript/packages/fory/lib/gen/collection.ts
index 2d7941bfa..808d92546 100644
--- a/javascript/packages/fory/lib/gen/collection.ts
+++ b/javascript/packages/fory/lib/gen/collection.ts
@@ -257,7 +257,7 @@ export abstract class CollectionSerializerGenerator extends
BaseSerializerGenera
${this.builder.writer.reserve(`${this.innerGenerator.getFixedSize()} *
${accessor}.${this.sizeProp()}`)};
if (${flags} & ${CollectionFlags.TRACKING_REF}) {
for (const ${item} of ${accessor}) {
- if (${accessor} !== null && ${accessor} !== undefined) {
+ if (${item} !== null && ${item} !== undefined) {
const ${existsId} =
${this.builder.referenceResolver.existsWriteObject(item)};
if (typeof ${existsId} === "number") {
${this.builder.writer.int8(RefFlags.RefFlag)}
@@ -273,7 +273,7 @@ export abstract class CollectionSerializerGenerator extends
BaseSerializerGenera
}
} else if (${flags} & ${CollectionFlags.HAS_NULL}) {
for (const ${item} of ${accessor}) {
- if (${accessor} !== null && ${accessor} !== undefined) {
+ if (${item} !== null && ${item} !== undefined) {
${this.builder.writer.int8(RefFlags.NotNullValueFlag)};
${this.innerGenerator.writeEmbed().write(item)}
} else {
diff --git a/javascript/packages/fory/lib/gen/ext.ts
b/javascript/packages/fory/lib/gen/ext.ts
new file mode 100644
index 000000000..835e1f525
--- /dev/null
+++ b/javascript/packages/fory/lib/gen/ext.ts
@@ -0,0 +1,185 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { TypeId, Mode } from "../type";
+import { Scope } from "./scope";
+import { CodecBuilder } from "./builder";
+import { StructTypeInfo, TypeInfo } from "../typeInfo";
+import { CodegenRegistry } from "./router";
+import { BaseSerializerGenerator } from "./serializer";
+import { TypeMeta } from "../meta/TypeMeta";
+
+class ExtSerializerGenerator extends BaseSerializerGenerator {
+ typeInfo: StructTypeInfo;
+ typeMeta: TypeMeta;
+
+ constructor(typeInfo: TypeInfo, builder: CodecBuilder, scope: Scope) {
+ super(typeInfo, builder, scope);
+ this.typeInfo = <StructTypeInfo>typeInfo;
+ this.typeMeta = TypeMeta.fromTypeInfo(this.typeInfo);
+ }
+
+ write(accessor: string): string {
+ return `
+ ${this.builder.getOptions("customSerializer")}.write(${accessor},
${this.builder.writer.ownName()}, ${this.builder.getForyName()}, )
+ `;
+ }
+
+ read(accessor: (expr: string) => string, refState: string): string {
+ const result = this.scope.uniqueName("result");
+ return `
+ ${this.typeInfo.options.withConstructor
+ ? `
+ const ${result} = new ${this.builder.getOptions("creator")}();
+ `
+ : `
+ const ${result} = {};
+ `
+ }
+ ${this.maybeReference(result, refState)}
+ ${this.builder.getOptions("customSerializer")}.read(${result},
${this.builder.reader.ownName()}, ${this.builder.getForyName()})
+ ${
+ accessor(result)
+ }
+ `;
+ }
+
+ readNoRef(assignStmt: (v: string) => string, refState: string): string {
+ return `
+ ${this.readTypeInfo()}
+ ${this.read(assignStmt, refState)};
+ `;
+ }
+
+ readTypeInfo(): string {
+ const typeMeta = this.scope.uniqueName("typeMeta");
+ const internalTypeId = this.getInternalTypeId();
+ let namesStmt = "";
+ let typeMetaStmt = "";
+ let readUserTypeIdStmt = "";
+ switch (internalTypeId) {
+ case TypeId.EXT:
+ readUserTypeIdStmt = `${this.builder.reader.readVarUint32Small7()};`;
+ break;
+ case TypeId.NAMED_EXT:
+ if (!this.builder.fory.isCompatible()) {
+ namesStmt = `
+ ${
+
this.builder.metaStringResolver.readNamespace(this.builder.reader.ownName())
+ };
+ ${
+
this.builder.metaStringResolver.readTypeName(this.builder.reader.ownName())
+ };
+ `;
+ } else {
+ typeMetaStmt = `
+ const ${typeMeta} =
${this.builder.typeMetaResolver.readTypeMeta(this.builder.reader.ownName())};
+ `;
+ }
+ break;
+ }
+ return `
+ ${
+ this.builder.reader.uint8()
+ };
+ ${readUserTypeIdStmt}
+ ${
+ namesStmt
+ }
+ ${
+ typeMetaStmt
+ }
+ `;
+ }
+
+ readEmbed() {
+ return new Proxy({}, {
+ get: (target, prop: string) => {
+ return (accessor: (expr: string) => string, ...args: string[]) => {
+ const name = this.scope.declare(
+ "ext_ser",
+ TypeId.isNamedType(this.typeInfo.typeId)
+ ?
this.builder.typeResolver.getSerializerByName(CodecBuilder.replaceBackslashAndQuote(this.typeInfo.named!))
+ :
this.builder.typeResolver.getSerializerById(this.typeInfo.typeId,
this.typeInfo.userTypeId)
+ );
+ return accessor(`${name}.${prop}(${args.join(",")})`);
+ };
+ },
+ });
+ }
+
+ writeEmbed() {
+ return new Proxy({}, {
+ get: (target, prop: string) => {
+ return (accessor: string) => {
+ const name = this.scope.declare(
+ "ext_ser",
+ TypeId.isNamedType(this.typeInfo.typeId)
+ ?
this.builder.typeResolver.getSerializerByName(CodecBuilder.replaceBackslashAndQuote(this.typeInfo.named!))
+ :
this.builder.typeResolver.getSerializerById(this.typeInfo.typeId,
this.typeInfo.userTypeId)
+ );
+ return `${name}.${prop}(${accessor})`;
+ };
+ },
+ });
+ }
+
+ writeTypeInfo(): string {
+ const internalTypeId = this.getInternalTypeId();
+ let typeMeta = "";
+ let writeUserTypeIdStmt = "";
+ switch (internalTypeId) {
+ case TypeId.EXT:
+ writeUserTypeIdStmt =
this.builder.writer.writeVarUint32Small7(this.typeInfo.userTypeId);
+ break;
+ case TypeId.NAMED_EXT:
+ if (this.builder.fory.config.mode !== Mode.Compatible) {
+ 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 = `
+
${this.builder.metaStringResolver.writeBytes(this.builder.writer.ownName(),
nsBytes)}
+
${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(",")}])`);
+ typeMeta =
this.builder.typeMetaResolver.writeTypeMeta(this.builder.getTypeInfo(),
this.builder.writer.ownName(), bytes);
+ }
+ break;
+ default:
+ break;
+ }
+ return `
+ ${this.builder.writer.uint8(this.getTypeId())};
+ ${writeUserTypeIdStmt}
+ ${typeMeta}
+ `;
+ }
+
+ getFixedSize(): number {
+ return 5;
+ }
+
+ getHash(): string {
+ return "0";
+ }
+}
+
+CodegenRegistry.register(TypeId.EXT, ExtSerializerGenerator);
+CodegenRegistry.register(TypeId.NAMED_EXT, ExtSerializerGenerator);
diff --git a/javascript/packages/fory/lib/gen/index.ts
b/javascript/packages/fory/lib/gen/index.ts
index 1234148ae..64529f060 100644
--- a/javascript/packages/fory/lib/gen/index.ts
+++ b/javascript/packages/fory/lib/gen/index.ts
@@ -35,6 +35,7 @@ import "./struct";
import "./typedArray";
import "./enum";
import "./any";
+import "./ext";
import Fory from "../fory";
export class Gen {
diff --git a/javascript/packages/fory/lib/gen/map.ts
b/javascript/packages/fory/lib/gen/map.ts
index 89066af8d..e7f2a2d19 100644
--- a/javascript/packages/fory/lib/gen/map.ts
+++ b/javascript/packages/fory/lib/gen/map.ts
@@ -89,13 +89,19 @@ class MapChunkWriter {
return flag;
}
- private writeHead(keyInfo: number, valueInfo: number) {
- // chunkSize, max 255
- this.chunkOffset = this.fory.binaryWriter.getCursor();
+ private writeHead(keyInfo: number, valueInfo: number, withOutSize = false) {
// KV header
const header = this.getHead(keyInfo, valueInfo);
// chunkSize default 0 | KV header
- this.fory.binaryWriter.uint16(header);
+ this.fory.binaryWriter.uint8(header);
+
+ if (!withOutSize) {
+ // chunkSize, max 255
+ this.chunkOffset = this.fory.binaryWriter.getCursor();
+ this.fory.binaryWriter.uint8(0);
+ } else {
+ this.chunkOffset = 0;
+ }
if (this.keySerializer) {
this.keySerializer.writeTypeInfo(null);
}
@@ -105,7 +111,18 @@ class MapChunkWriter {
return header;
}
+ public isFirst() {
+ return this.chunkSize === 0 || this.chunkSize === 1;
+ }
+
next(keyInfo: number, valueInfo: number) {
+ if (keyInfo & MapFlags.HAS_NULL || valueInfo & MapFlags.HAS_NULL) {
+ this.endChunk();
+ this.header = this.writeHead(keyInfo, valueInfo, true);
+ this.preKeyInfo = keyInfo;
+ this.preValueInfo = valueInfo;
+ return this.header;
+ }
// max size of chunk is 255
if (this.chunkSize == 255
|| this.chunkOffset == 0
@@ -125,7 +142,7 @@ class MapChunkWriter {
endChunk() {
if (this.chunkOffset > 0) {
- this.fory.binaryWriter.setUint8Position(this.chunkOffset + 1,
this.chunkSize);
+ this.fory.binaryWriter.setUint8Position(this.chunkOffset,
this.chunkSize);
this.chunkSize = 0;
}
}
@@ -145,26 +162,17 @@ class MapAnySerializer {
}
private writeFlag(header: number, v: any) {
+ if (header & MapFlags.HAS_NULL) {
+ return true;
+ }
if (header & MapFlags.TRACKING_REF) {
- if (v === null || v === undefined) {
- this.fory.binaryWriter.uint8(RefFlags.NullFlag);
- return true;
- }
const keyRef = this.fory.referenceResolver.existsWriteObject(v);
if (keyRef !== undefined) {
- this.fory.binaryWriter.uint8(RefFlags.RefFlag);
+ this.fory.binaryWriter.int8(RefFlags.RefFlag);
this.fory.binaryWriter.uint16(keyRef);
return true;
} else {
- this.fory.binaryWriter.uint8(RefFlags.RefValueFlag);
- return false;
- }
- } else if (header & MapFlags.HAS_NULL) {
- if (v === null || v === undefined) {
- this.fory.binaryWriter.uint8(RefFlags.NullFlag);
- return true;
- } else {
- this.fory.binaryWriter.uint8(RefFlags.NotNullValueFlag);
+ this.fory.binaryWriter.int8(RefFlags.RefValueFlag);
return false;
}
}
@@ -179,18 +187,32 @@ class MapAnySerializer {
const valueSerializer = this.valueSerializer !== null ?
this.valueSerializer : this.fory.typeResolver.getSerializerByData(v);
const header = mapChunkWriter.next(
- MapHeadUtil.elementInfo(keySerializer!.getTypeId()!, k == null ? 1 :
0, keySerializer!.needToWriteRef() ? 1 : 0),
- MapHeadUtil.elementInfo(valueSerializer!.getTypeId()!, v == null ? 1 :
0, valueSerializer!.needToWriteRef() ? 1 : 0)
+ MapHeadUtil.elementInfo(keySerializer?.getTypeId() ?? 0, k == null ? 1
: 0, keySerializer?.needToWriteRef() ? 1 : 0),
+ MapHeadUtil.elementInfo(valueSerializer?.getTypeId() ?? 0, v == null ?
1 : 0, valueSerializer?.needToWriteRef() ? 1 : 0)
);
- if (!this.writeFlag(header & 0b00001111, k)) {
- if (this.keySerializer) {
+ const keyHeader = header & 0b111;
+ const valueHeader = (header >> 3);
+ if (mapChunkWriter.isFirst()) {
+ if (!(keyHeader & MapFlags.HAS_NULL) && !(valueHeader &
MapFlags.HAS_NULL)) {
+ if (!(keyHeader & MapFlags.DECL_ELEMENT_TYPE)) {
+ keySerializer?.writeTypeInfo(null);
+ }
+ if (!(valueHeader & MapFlags.DECL_ELEMENT_TYPE)) {
+ valueSerializer?.writeTypeInfo(null);
+ }
+ }
+ }
+
+ const includeNone = (keyHeader & MapFlags.HAS_NULL) || (valueHeader &
MapFlags.HAS_NULL);
+ if (!this.writeFlag(keyHeader, k)) {
+ if (!includeNone) {
keySerializer!.write(k);
} else {
keySerializer!.writeNoRef(k);
}
}
- if (!this.writeFlag(header >> 4, v)) {
- if (this.valueSerializer) {
+ if (!this.writeFlag(valueHeader, v)) {
+ if (!includeNone) {
valueSerializer!.write(v);
} else {
valueSerializer!.writeNoRef(v);
@@ -201,25 +223,28 @@ class MapAnySerializer {
}
private readElement(header: number, serializer: Serializer | null) {
- const declared = header & MapFlags.DECL_ELEMENT_TYPE;
const includeNone = header & MapFlags.HAS_NULL;
const trackingRef = header & MapFlags.TRACKING_REF;
- if (!declared) {
- serializer = AnyHelper.detectSerializer(this.fory);
+ if (includeNone) {
+ return null;
}
- if (!trackingRef && !includeNone) {
+ if (!trackingRef) {
+ serializer = serializer == null ? AnyHelper.detectSerializer(this.fory)
: serializer;
return serializer!.read(false);
}
- const flag = this.fory.binaryReader.uint8();
+
+ const flag = this.fory.binaryReader.int8();
switch (flag) {
case RefFlags.RefValueFlag:
+ serializer = serializer == null ?
AnyHelper.detectSerializer(this.fory) : serializer;
return serializer!.read(true);
case RefFlags.RefFlag:
return
this.fory.referenceResolver.getReadObject(this.fory.binaryReader.varUInt32());
case RefFlags.NullFlag:
return null;
case RefFlags.NotNullValueFlag:
+ serializer = serializer == null ?
AnyHelper.detectSerializer(this.fory) : serializer;
return serializer!.read(false);
}
}
@@ -231,20 +256,28 @@ class MapAnySerializer {
this.fory.referenceResolver.reference(result);
}
while (count > 0) {
- const header = this.fory.binaryReader.uint16();
+ const header = this.fory.binaryReader.uint8();
const valueHeader = (header >> 3) & 0b111;
const keyHeader = header & 0b111;
- const chunkSize = header >> 8;
+ let chunkSize = 0;
+ if ((valueHeader & MapFlags.HAS_NULL) || (keyHeader &
MapFlags.HAS_NULL)) {
+ chunkSize = 1;
+ } else {
+ chunkSize = this.fory.binaryReader.uint8();
+ }
+ let keySerializer = this.keySerializer;
+ let valueSerializer = this.valueSerializer;
- let keySerializer = null;
- let valueSerializer = null;
+ if (!(keyHeader & MapFlags.HAS_NULL) && !(valueHeader &
MapFlags.HAS_NULL)) {
+ if (!(keyHeader & MapFlags.DECL_ELEMENT_TYPE)) {
+ keySerializer = AnyHelper.detectSerializer(this.fory);
+ }
- if (keyHeader & MapFlags.DECL_ELEMENT_TYPE) {
- keySerializer = AnyHelper.detectSerializer(this.fory);
- }
- if (valueHeader & MapFlags.DECL_ELEMENT_TYPE) {
- valueSerializer = AnyHelper.detectSerializer(this.fory);
+ if (!(valueHeader & MapFlags.DECL_ELEMENT_TYPE)) {
+ valueSerializer = AnyHelper.detectSerializer(this.fory);
+ }
}
+
for (let index = 0; index < chunkSize; index++) {
const key = this.readElement(keyHeader, keySerializer);
const value = this.readElement(valueHeader, valueSerializer);
@@ -299,57 +332,61 @@ export class MapSerializerGenerator extends
BaseSerializerGenerator {
for (const [${k}, ${v}] of ${accessor}.entries()) {
let keyIsNull = ${k} === null || ${k} === undefined;
let valueIsNull = ${v} === null || ${v} === undefined;
- if (${lastKeyIsNull} !== keyIsNull || ${lastValueIsNull} !==
valueIsNull || ${chunkSize} === 0 || ${chunkSize} === 255) {
+ if (${lastKeyIsNull} !== keyIsNull || ${lastValueIsNull} !==
valueIsNull || ${chunkSize} === 0 || ${chunkSize} === 255 || keyIsNull ||
valueIsNull) {
if (${chunkSize} > 0) {
- ${this.builder.writer.setUint8Position(`${chunkSizeOffset} + 1`,
chunkSize)};
+ ${this.builder.writer.setUint8Position(`${chunkSizeOffset}`,
chunkSize)};
${chunkSize} = 0;
}
- ${chunkSizeOffset} = ${this.builder.writer.getCursor()}
- ${this.builder.writer.uint16(
- `((${valueHeader} | (valueIsNull ? ${MapFlags.HAS_NULL} : 0)) <<
3) | (${keyHeader} | (keyIsNull ? ${MapFlags.HAS_NULL} : 0))`
- )
+ if (keyIsNull || valueIsNull) {
+ ${this.builder.writer.uint8(
+ `((${valueHeader} | (valueIsNull ? ${MapFlags.HAS_NULL} : 0))
<< 3) | (${keyHeader} | (keyIsNull ? ${MapFlags.HAS_NULL} : 0))`
+ )
+ }
+ } else {
+ ${chunkSizeOffset} = ${this.builder.writer.getCursor()} + 1;
+ ${this.builder.writer.uint16(
+ `((${valueHeader} | (valueIsNull ? ${MapFlags.HAS_NULL} : 0))
<< 3) | (${keyHeader} | (keyIsNull ? ${MapFlags.HAS_NULL} : 0))`
+ )
+ }
}
${lastKeyIsNull} = keyIsNull;
${lastValueIsNull} = valueIsNull;
}
- if (keyIsNull) {
- ${this.builder.writer.uint8(RefFlags.NullFlag)}
- } else {
+ if (!keyIsNull) {
${this.keyGenerator.needToWriteRef()
? `
const ${keyRef} =
${this.builder.referenceResolver.existsWriteObject(v)};
if (${keyRef} !== undefined) {
- ${this.builder.writer.uint8(RefFlags.RefFlag)};
+ ${this.builder.writer.int8(RefFlags.RefFlag)};
${this.builder.writer.uint16(keyRef)};
} else {
- ${this.builder.writer.uint8(RefFlags.RefValueFlag)};
+ ${this.builder.writer.int8(RefFlags.RefValueFlag)};
${this.keyGenerator.writeEmbed().write(k)}
}
`
: this.keyGenerator.writeEmbed().write(k)}
}
- if (valueIsNull) {
- ${this.builder.writer.uint8(RefFlags.NullFlag)}
- } else {
+ if (!valueIsNull) {
${this.valueGenerator.needToWriteRef()
? `
const ${valueRef} =
${this.builder.referenceResolver.existsWriteObject(v)};
if (${valueRef} !== undefined) {
- ${this.builder.writer.uint8(RefFlags.RefFlag)};
+ ${this.builder.writer.int8(RefFlags.RefFlag)};
${this.builder.writer.uint16(valueRef)};
} else {
- ${this.builder.writer.uint8(RefFlags.RefValueFlag)};
+ ${this.builder.writer.int8(RefFlags.RefValueFlag)};
${this.valueGenerator.writeEmbed().write(v)};
}
`
: this.valueGenerator.writeEmbed().write(v)}
}
-
- ${chunkSize}++;
+ if (!keyIsNull && !valueIsNull) {
+ ${chunkSize}++;
+ }
}
if (${chunkSize} > 0) {
- ${this.builder.writer.setUint8Position(`${chunkSizeOffset} + 1`,
chunkSize)};
+ ${this.builder.writer.setUint8Position(`${chunkSizeOffset}`,
chunkSize)};
}
`;
}
@@ -375,19 +412,24 @@ export class MapSerializerGenerator extends
BaseSerializerGenerator {
${this.builder.referenceResolver.reference(result)}
}
while (${count} > 0) {
- const header = ${this.builder.reader.uint16()};
+ const header = ${this.builder.reader.uint8()};
const keyHeader = header & 0b111;
const valueHeader = (header >> 3) & 0b111;
- const chunkSize = header >> 8;
const keyIncludeNone = keyHeader & ${MapFlags.HAS_NULL};
const keyTrackingRef = keyHeader & ${MapFlags.TRACKING_REF};
const valueIncludeNone = valueHeader & ${MapFlags.HAS_NULL};
const valueTrackingRef = valueHeader & ${MapFlags.TRACKING_REF};
+ let chunkSize = 1;
+ if (!keyIncludeNone && !valueIncludeNone) {
+ chunkSize = ${this.builder.reader.uint8()};
+ }
for (let index = 0; index < chunkSize; index++) {
let key;
let value;
- if (keyTrackingRef || keyIncludeNone) {
- const flag = ${this.builder.reader.uint8()};
+ if (keyIncludeNone) {
+ key = null;
+ } else if (keyTrackingRef) {
+ const flag = ${this.builder.reader.int8()};
switch (flag) {
case ${RefFlags.RefValueFlag}:
${this.keyGenerator.read(x => `key = ${x}`, "true")}
@@ -406,8 +448,10 @@ export class MapSerializerGenerator extends
BaseSerializerGenerator {
${this.keyGenerator.read(x => `key = ${x}`, "false")}
}
- if (valueTrackingRef || valueIncludeNone) {
- const flag = ${this.builder.reader.uint8()};
+ if (valueIncludeNone) {
+ value = null;
+ } else if (valueTrackingRef) {
+ const flag = ${this.builder.reader.int8()};
switch (flag) {
case ${RefFlags.RefValueFlag}:
${this.valueGenerator.read(x => `value = ${x}`, "true")}
diff --git a/javascript/packages/fory/lib/gen/struct.ts
b/javascript/packages/fory/lib/gen/struct.ts
index b18173dea..f86ab2180 100644
--- a/javascript/packages/fory/lib/gen/struct.ts
+++ b/javascript/packages/fory/lib/gen/struct.ts
@@ -62,11 +62,14 @@ class StructSerializerGenerator extends
BaseSerializerGenerator {
typeInfo: StructTypeInfo;
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.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) {
@@ -159,7 +162,9 @@ class StructSerializerGenerator extends
BaseSerializerGenerator {
}
write(accessor: string): string {
+ const hash = this.typeMeta.computeStructHash();
return `
+ ${this.builder.fory.config.classVersionHash ?
this.builder.writer.int32(hash) : ""}
${this.sortedProps.map(({ key, typeInfo }) => {
const InnerGeneratorClass = CodegenRegistry.get(typeInfo.typeId);
if (!InnerGeneratorClass) {
@@ -175,10 +180,18 @@ class StructSerializerGenerator extends
BaseSerializerGenerator {
read(accessor: (expr: string) => string, refState: string): string {
const result = this.scope.uniqueName("result");
+ const hash = this.typeMeta.computeStructHash();
return `
+ ${this.builder.fory.config.classVersionHash
+? `
+ if(${this.builder.reader.int32()} !== ${hash}) {
+ throw new Error("Read class version is not consistent with ${hash} ")
+ }
+ `
+: ""}
${this.typeInfo.options.withConstructor
? `
- const ${result} = new ${this.builder.getOptions("constructor")}();
+ const ${result} = new ${this.builder.getOptions("creator")}();
`
: `
const ${result} = {
diff --git a/javascript/packages/fory/lib/meta/TypeMeta.ts
b/javascript/packages/fory/lib/meta/TypeMeta.ts
index 985710c6f..8b5dce8d3 100644
--- a/javascript/packages/fory/lib/meta/TypeMeta.ts
+++ b/javascript/packages/fory/lib/meta/TypeMeta.ts
@@ -20,9 +20,10 @@
import { BinaryWriter } from "../writer";
import { BinaryReader } from "../reader";
import { Encoding, MetaStringDecoder, MetaStringEncoder } from "./MetaString";
-import { StructTypeInfo } from "../typeInfo";
+import { StructTypeInfo, TypeInfo } from "../typeInfo";
import { TypeId } from "../type";
import { x64hash128 } from "../murmurHash3";
+import { fromString } from "../platformBuffer";
const fieldEncoder = new MetaStringEncoder("$", ".");
const fieldDecoder = new MetaStringDecoder("$", ".");
@@ -227,26 +228,61 @@ export class TypeMeta {
return this.fields.length;
}
- static fromTypeInfo(typeInfo: StructTypeInfo) {
- const structTypeInfo = typeInfo;
- let fieldInfo = Object.entries(typeInfo.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;
+ computeStructFingerprint(fields: FieldInfo[]) {
+ let fieldInfos = [];
+ for (const field of fields) {
+ const typeId = field.getTypeId();
+ let fieldIdentifier = "";
+ if (field.getFieldId()) {
+ fieldIdentifier = `${field.getFieldId()}`;
+ } else {
+ fieldIdentifier = TypeMeta.toSnakeCase(field.getFieldName());
}
- const { trackingRef, nullable } =
structTypeInfo.options.fieldInfo?.[fieldName] || {};
- return new FieldInfo(
- fieldName,
- fieldTypeId,
- typeInfo.userTypeId,
- trackingRef,
- nullable,
- typeInfo.options,
- );
- });
+ const ref = field.trackingRef ? "1" : "0";
+ const nullable = field.nullable ? "1" : "0";
+ fieldInfos.push([fieldIdentifier, `${typeId}`, ref, nullable]);
+ }
+ fieldInfos = fieldInfos.sort((a, b) => a[0].localeCompare(b[0]));
+ let result = "";
+ for (const fieldInfo of fieldInfos) {
+ result += [fieldInfo[0], fieldInfo[1], fieldInfo[2],
fieldInfo[3]].join(",");
+ result += ";";
+ }
+ return result;
+ }
+
+ computeStructHash() {
+ const fields = TypeMeta.groupFieldsByType(this.fields);
+ const fingerprint = this.computeStructFingerprint(fields);
+ const bytes = fromString(fingerprint);
+ const hashLong = x64hash128(bytes, 47).getBigInt64(0);
+ return Number(BigInt.asIntN(32, hashLong));
+ }
+
+ 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]) => {
+ 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 } =
structTypeInfo.options.fieldInfo?.[fieldName] || {};
+ return new FieldInfo(
+ fieldName,
+ fieldTypeId,
+ typeInfo.userTypeId,
+ trackingRef,
+ nullable,
+ typeInfo.options,
+ );
+ });
+ }
fieldInfo = TypeMeta.groupFieldsByType(fieldInfo);
+
return new TypeMeta(fieldInfo, {
typeId: typeInfo.typeId,
namespace: typeInfo.namespace,
@@ -561,6 +597,29 @@ export class TypeMeta {
return writer.dump();
}
+ static toSnakeCase(name: string) {
+ const result = [];
+ const chars = Array.from(name);
+
+ for (let i = 0; i < chars.length; i++) {
+ const c = chars[i];
+ if (c >= "A" && c <= "Z") {
+ if (i > 0) {
+ const prevUpper = chars[i - 1] >= "A" && chars[i - 1] <= "Z";
+ const nextUpperOrEnd = i + 1 >= chars.length || (chars[i + 1] >= "A"
&& chars[i + 1] <= "Z");
+
+ if (!prevUpper || !nextUpperOrEnd) {
+ result.push("_");
+ }
+ }
+ result.push(c.toLowerCase());
+ } else {
+ result.push(c);
+ }
+ }
+ return result.join("");
+ }
+
static groupFieldsByType<T extends { fieldName: string; nullable?: boolean;
typeId: number }>(typeInfos: Array<T>): Array<T> {
const primitiveFields: Array<T> = [];
const nullablePrimitiveFields: Array<T> = [];
@@ -570,29 +629,6 @@ export class TypeMeta {
const mapFields: Array<T> = [];
const otherFields: Array<T> = [];
- const toSnakeCase = (name: string) => {
- const result = [];
- const chars = Array.from(name);
-
- for (let i = 0; i < chars.length; i++) {
- const c = chars[i];
- if (c >= "A" && c <= "Z") {
- if (i > 0) {
- const prevUpper = chars[i - 1] >= "A" && chars[i - 1] <= "Z";
- const nextUpperOrEnd = i + 1 >= chars.length || (chars[i + 1] >=
"A" && chars[i + 1] <= "Z");
-
- if (!prevUpper || !nextUpperOrEnd) {
- result.push("_");
- }
- }
- result.push(c.toLowerCase());
- } else {
- result.push(c);
- }
- }
- return result.join("");
- };
-
for (const typeInfo of typeInfos) {
const typeId = typeInfo.typeId;
@@ -644,7 +680,7 @@ export class TypeMeta {
};
const nameSorter = (a: T, b: T) => {
- return toSnakeCase(a.fieldName).localeCompare(toSnakeCase(b.fieldName));
+ return
TypeMeta.toSnakeCase(a.fieldName).localeCompare(TypeMeta.toSnakeCase(b.fieldName));
};
// Sort each group
diff --git a/javascript/packages/fory/lib/type.ts
b/javascript/packages/fory/lib/type.ts
index 9cbdf5759..278157732 100644
--- a/javascript/packages/fory/lib/type.ts
+++ b/javascript/packages/fory/lib/type.ts
@@ -17,7 +17,10 @@
* under the License.
*/
+import Fory from "./fory";
+import { BinaryReader } from "./reader";
import { StructTypeInfo } from "./typeInfo";
+import { BinaryWriter } from "./writer";
export const TypeId = {
// Unknown/polymorphic type marker.
@@ -186,6 +189,11 @@ export enum ConfigFlags {
isOutOfBandFlag = 1 << 2,
}
+export type CustomSerializer<T> = {
+ read: (result: T, reader: BinaryReader, fory: Fory) => void;
+ write: (v: T, writer: BinaryWriter, fory: Fory) => void;
+};
+
// read, write
export type Serializer<T = any> = {
fixedSize: number;
@@ -245,6 +253,7 @@ export interface Config {
afterCodeGenerated?: (code: string) => string;
};
mode: Mode;
+ classVersionHash?: boolean;
}
export interface WithForyClsInfo {
diff --git a/javascript/packages/fory/lib/typeInfo.ts
b/javascript/packages/fory/lib/typeInfo.ts
index 9a1b519cb..f3b846f9a 100644
--- a/javascript/packages/fory/lib/typeInfo.ts
+++ b/javascript/packages/fory/lib/typeInfo.ts
@@ -28,7 +28,10 @@ const initMeta = (target: new () => any, typeInfo: TypeInfo)
=> {
typeInfo.options = {};
}
typeInfo.options.withConstructor = true;
- typeInfo.options.constructor = target;
+ typeInfo.options.creator = target;
+ if (!typeInfo.options.props) {
+ typeInfo.options.props = {}
+ }
Object.assign(typeInfo.options.props, targetFields.get(target) || {})
Object.defineProperties(target.prototype, {
[ForyTypeInfoSymbol]: {
@@ -163,6 +166,60 @@ export class TypeInfo<T = unknown> extends
ExtensibleFunction {
}>(typeId);
}
+ static fromExt<T = any>(nameInfo: {
+ typeId?: number;
+ namespace?: string;
+ typeName?: string;
+ } | string | number, {
+ withConstructor = false,
+ }: {
+ withConstructor?: boolean;
+ } = {}) {
+ let typeId: number | undefined;
+ let namespace: string | undefined;
+ let typeName: string | undefined;
+ if (typeof nameInfo === "string") {
+ typeName = nameInfo;
+ } else if (typeof nameInfo === "number") {
+ typeId = nameInfo;
+ } else {
+ namespace = nameInfo.namespace;
+ typeName = nameInfo.typeName;
+ typeId = nameInfo.typeId;
+ }
+ if (typeId !== undefined && typeName !== undefined) {
+ throw new Error(`type name ${typeName} and id ${typeId} should not be
set at the same time`);
+ }
+ if (!typeId) {
+ if (!typeName) {
+ throw new Error(`type name and type id should be set at least one`);
+ }
+ }
+ if (!namespace && typeName) {
+ const splits = typeName!.split(".");
+ if (splits.length > 1) {
+ namespace = splits[0];
+ typeName = splits.slice(1).join(".");
+ }
+ }
+ let finalTypeId = 0;
+ let userTypeId = -1;
+ if (typeId !== undefined) {
+ finalTypeId = TypeId.EXT;
+ userTypeId = typeId;
+ } else {
+ finalTypeId = TypeId.NAMED_EXT;
+ }
+ const typeInfo = new TypeInfo<T>(finalTypeId,
userTypeId).cast<StructTypeInfo>();
+ typeInfo.options = {
+ withConstructor,
+ };
+ typeInfo.namespace = namespace || "";
+ typeInfo.typeName = typeId !== undefined ? "" : typeName!;
+ typeInfo.named = `${typeInfo.namespace}$${typeInfo.typeName}`;
+ return typeInfo as TypeInfo<T>;
+ }
+
static fromStruct<T = any>(nameInfo: {
typeId?: number;
namespace?: string;
@@ -289,7 +346,7 @@ export interface StructTypeInfo extends TypeInfo {
props?: { [key: string]: TypeInfo };
fieldInfo?: {[key: string]: StructFieldInfo};
withConstructor?: boolean;
- constructor?: Function;
+ creator?: Function;
};
}
@@ -545,6 +602,24 @@ export const Type = {
};
}>(nameInfo, t1);
},
+ ext<T extends { [key: string]: TypeInfo }>(nameInfo: {
+ typeId?: number;
+ namespace?: string;
+ typeName?: string;
+ } | string | number, {
+ withConstructor = false,
+ }: {
+ withConstructor?: boolean;
+ } = {}) {
+ return TypeInfo.fromExt<{
+ type: typeof TypeId.EXT;
+ options: {
+ props: T;
+ };
+ }>(nameInfo, {
+ withConstructor,
+ });
+ },
struct<T extends { [key: string]: TypeInfo }>(nameInfo: {
typeId?: number;
namespace?: string;
diff --git a/javascript/packages/fory/lib/typeResolver.ts
b/javascript/packages/fory/lib/typeResolver.ts
index f4c5f5423..71a03be25 100644
--- a/javascript/packages/fory/lib/typeResolver.ts
+++ b/javascript/packages/fory/lib/typeResolver.ts
@@ -94,6 +94,15 @@ export default class TypeResolver {
registerSerializer(Type.varUInt64());
registerSerializer(Type.varInt64());
registerSerializer(Type.int64());
+ registerSerializer(Type.uint8());
+ registerSerializer(Type.uint16());
+ registerSerializer(Type.uint32());
+ registerSerializer(Type.uint64());
+ registerSerializer(Type.varInt32());
+ registerSerializer(Type.varUInt32());
+ registerSerializer(Type.varUInt64());
+ registerSerializer(Type.varInt64());
+ registerSerializer(Type.taggedUInt64());
registerSerializer(Type.sliInt64());
registerSerializer(Type.float16());
registerSerializer(Type.float32());
@@ -246,6 +255,9 @@ export default class TypeResolver {
}
getSerializerByData(v: any) {
+ if (v === null || v === undefined) {
+ return null;
+ }
// internal types
if (typeof v === "number") {
if (Number.isInteger(v)) {
diff --git a/javascript/test/crossLanguage.test.ts
b/javascript/test/crossLanguage.test.ts
index bc1e8aa67..f5650b91e 100644
--- a/javascript/test/crossLanguage.test.ts
+++ b/javascript/test/crossLanguage.test.ts
@@ -239,8 +239,9 @@ describe("bool", () => {
writeToFile(Buffer.concat(bfs));
});
test("test_cross_language_serializer", () => {
- if (Boolean("1")) { return; }
- const fory = new Fory();
+ const fory = new Fory({
+ mode: Mode.Compatible
+ });
// Define and register Color enum
const Color = {
@@ -250,27 +251,24 @@ describe("bool", () => {
White: 3,
};
fory.registerSerializer(Type.enum(101, Color));
-
- const reader = new BinaryReader({});
- reader.reset(content);
-
// Deserialize various data types from Java
const deserializedData = [];
+ let cursor = 0;
for (let i = 0; i < 28; i++) { // 28 serialized items from Java
- const deserializedItem =
fory.deserialize(reader.buffer(reader.varUInt32()));
+ const deserializedItem = fory.deserialize(content.subarray(cursor));
+ cursor += fory.binaryReader.getCursor();
deserializedData.push(deserializedItem);
}
- const writer = new BinaryWriter();
- writer.reserve(1024);
+ const bfs = []
// Serialize each deserialized item back
for (const item of deserializedData) {
const serializedData = fory.serialize(item);
- writer.buffer(serializedData);
+ bfs.push(serializedData);
}
- writeToFile(writer.dump() as Buffer);
+ writeToFile(Buffer.concat(bfs));
});
test("test_simple_struct", () => {
const fory = new Fory({
@@ -426,8 +424,9 @@ describe("bool", () => {
});
test("test_map", () => {
- if (Boolean("1")) { return; }
- const fory = new Fory();
+ const fory = new Fory({
+ mode: Mode.Compatible
+ });
@Type.struct(102, {
name: Type.string()
@@ -438,26 +437,24 @@ describe("bool", () => {
fory.registerSerializer(Item);
- const reader = new BinaryReader({});
- reader.reset(content);
-
// Deserialize maps from Java
const deserializedMaps = [];
+ let cursor = 0;
for (let i = 0; i < 2; i++) { // 2 maps
- const deserializedMap =
fory.deserialize(reader.buffer(reader.varUInt32()));
+ const deserializedMap = fory.deserialize(content.subarray(cursor));
+ cursor += fory.binaryReader.getCursor();
deserializedMaps.push(deserializedMap);
}
- const writer = new BinaryWriter();
- writer.reserve(512);
-
+ const bfs = []
// Serialize each deserialized map back
for (const map of deserializedMaps) {
const serializedData = fory.serialize(map);
- writer.buffer(serializedData);
+ fory.deserialize(serializedData);
+ bfs.push(serializedData);
}
- writeToFile(writer.dump() as Buffer);
+ writeToFile(Buffer.concat(bfs));
});
test("test_integer", () => {
@@ -505,8 +502,9 @@ describe("bool", () => {
});
test("test_item", () => {
- if (Boolean("1")) { return; }
- const fory = new Fory();
+ const fory = new Fory({
+ mode: Mode.Compatible
+ });
@Type.struct(102, {
name: Type.string()
@@ -521,26 +519,27 @@ describe("bool", () => {
// Deserialize items from Java
const deserializedItems = [];
+ let cursor = 0;
for (let i = 0; i < 3; i++) { // 3 items
- const deserializedItem =
fory.deserialize(reader.buffer(reader.varUInt32()));
+ const deserializedItem = fory.deserialize(content.subarray(cursor));
+ cursor += fory.binaryReader.getCursor();
deserializedItems.push(deserializedItem);
}
- const writer = new BinaryWriter();
- writer.reserve(256);
-
+ const bfs = []
// Serialize each deserialized item back
for (const item of deserializedItems) {
const serializedData = fory.serialize(item);
- writer.buffer(serializedData);
+ bfs.push(serializedData);
}
- writeToFile(writer.dump() as Buffer);
+ writeToFile(Buffer.concat(bfs));
});
test("test_color", () => {
- if (Boolean("1")) { return; }
- const fory = new Fory();
+ const fory = new Fory({
+ mode: Mode.Compatible
+ });
// Define and register Color enum
const Color = {
@@ -549,15 +548,17 @@ describe("bool", () => {
Blue: 2,
White: 3,
};
- fory.registerSerializer(Type.enum(101, Color));
+ const { serialize: enumSerialize } =
fory.registerSerializer(Type.enum(101, Color));
const reader = new BinaryReader({});
reader.reset(content);
// Deserialize colors from Java
const deserializedColors = [];
+ let cursor = 0;
for (let i = 0; i < 4; i++) { // 4 colors
- const deserializedColor =
fory.deserialize(reader.buffer(reader.varUInt32()));
+ const deserializedColor = fory.deserialize(content.subarray(cursor));
+ cursor += fory.binaryReader.getCursor();
deserializedColors.push(deserializedColor);
}
@@ -566,15 +567,16 @@ describe("bool", () => {
// Serialize each deserialized color back
for (const color of deserializedColors) {
- const serializedData = fory.serialize(color);
+ const serializedData = enumSerialize(color);
writer.buffer(serializedData);
}
writeToFile(writer.dump() as Buffer);
});
test("test_struct_with_list", () => {
- if (Boolean("1")) { return; }
- const fory = new Fory();
+ const fory = new Fory({
+ mode: Mode.Compatible
+ });
@Type.struct(201, {
items: Type.array(Type.string())
@@ -589,8 +591,10 @@ describe("bool", () => {
// Deserialize structs from Java
const deserializedStructs = [];
+ let cursor = 0;
for (let i = 0; i < 2; i++) { // 2 structs
- const deserializedStruct =
fory.deserialize(reader.buffer(reader.varUInt32()));
+ const deserializedStruct = fory.deserialize(content.subarray(cursor));
+ cursor += fory.binaryReader.getCursor();
deserializedStructs.push(deserializedStruct);
}
@@ -607,8 +611,9 @@ describe("bool", () => {
});
test("test_struct_with_map", () => {
- if (Boolean("1")) { return; }
- const fory = new Fory();
+ const fory = new Fory({
+ mode: Mode.Compatible
+ });
@Type.struct(202, {
data: Type.map(Type.string(), Type.string())
@@ -623,8 +628,10 @@ describe("bool", () => {
// Deserialize structs from Java
const deserializedStructs = [];
+ let cursor = 0;
for (let i = 0; i < 2; i++) { // 2 structs
- const deserializedStruct =
fory.deserialize(reader.buffer(reader.varUInt32()));
+ const deserializedStruct = fory.deserialize(content.subarray(cursor));
+ cursor += fory.binaryReader.getCursor();
deserializedStructs.push(deserializedStruct);
}
@@ -642,7 +649,9 @@ describe("bool", () => {
test("test_skip_id_custom", () => {
if (Boolean("1")) { return; }
- const fory = new Fory();
+ const fory = new Fory({
+ mode: Mode.Compatible
+ });
// Define empty wrapper for deserialization
@Type.struct(104)
@@ -653,7 +662,9 @@ describe("bool", () => {
reader.reset(content);
// Deserialize empty wrapper from Java
- const deserializedWrapper =
fory.deserialize(reader.buffer(reader.varUInt32()));
+ let cursor = 0;
+ const deserializedWrapper = fory.deserialize(content.subarray(cursor));
+ cursor += fory.binaryReader.getCursor();
// Serialize the deserialized wrapper back
const serializedData = fory.serialize(deserializedWrapper);
@@ -662,7 +673,9 @@ describe("bool", () => {
test("test_skip_name_custom", () => {
if (Boolean("1")) { return; }
- const fory = new Fory();
+ const fory = new Fory({
+ mode: Mode.Compatible
+ });
// Define empty wrapper for deserialization
@Type.struct({ namespace: "", typeName: "my_wrapper" })
@@ -673,7 +686,9 @@ describe("bool", () => {
reader.reset(content);
// Deserialize empty wrapper from Java
- const deserializedWrapper =
fory.deserialize(reader.buffer(reader.varUInt32()));
+ let cursor = 0;
+ const deserializedWrapper = fory.deserialize(content.subarray(cursor));
+ cursor += fory.binaryReader.getCursor();
// Serialize the deserialized wrapper back
const serializedData = fory.serialize(deserializedWrapper);
@@ -681,8 +696,10 @@ describe("bool", () => {
});
test("test_consistent_named", () => {
- if (Boolean("1")) { return; }
- const fory = new Fory();
+ const fory = new Fory({
+ mode: Mode.SchemaConsistent,
+ classVersionHash: true,
+ });
// Define and register Color enum
const Color = {
@@ -691,45 +708,48 @@ describe("bool", () => {
Blue: 2,
White: 3,
};
- fory.registerSerializer(Type.enum({ namespace: "", typeName: "color" },
Color));
+ const { serialize: enumSerialize } = fory.registerSerializer(Type.enum({
namespace: "", typeName: "color" }, Color));
@Type.struct({ namespace: "", typeName: "my_struct" }, {
- id: Type.int32()
+ id: Type.varInt32()
})
class MyStruct {
id: number = 0;
- constructor(id: number = 0) {
- this.id = id;
- }
}
fory.registerSerializer(MyStruct);
- @Type.struct({ namespace: "", typeName: "my_ext" }, {
- id: Type.int32()
- })
+ @Type.ext({ namespace: "", typeName: "my_ext" })
class MyExt {
id: number = 0;
- constructor(id: number = 0) {
- this.id = id;
- }
}
- fory.registerSerializer(MyExt);
-
- const reader = new BinaryReader({});
- reader.reset(content);
+ fory.registerSerializer(MyExt, {
+ write: (value: MyExt, writer: BinaryWriter, fory: Fory) => {
+ writer.varInt32(value.id);
+ },
+ read: (result: MyExt, reader: BinaryReader, fory: Fory) => {
+ result.id = reader.varInt32();
+ }
+ });
// Deserialize multiple instances from Java
const deserializedData = [];
+ let cursor = 0;
for (let i = 0; i < 9; i++) { // 3 colors + 3 structs + 3 exts
- const deserializedItem =
fory.deserialize(reader.buffer(reader.varUInt32()));
+ const deserializedItem = fory.deserialize(content.subarray(cursor));
+ cursor += fory.binaryReader.getCursor();
deserializedData.push(deserializedItem);
}
const writer = new BinaryWriter();
writer.reserve(256);
- // Serialize each deserialized item back
- for (const item of deserializedData) {
+ for (let index = 0; index < 3; index++) {
+ const element = deserializedData[index];
+ const serializedData = enumSerialize(element);
+ writer.buffer(serializedData);
+ }
+ for (let index = 3; index < deserializedData.length; index++) {
+ const item = deserializedData[index];
const serializedData = fory.serialize(item);
writer.buffer(serializedData);
}
@@ -739,7 +759,9 @@ describe("bool", () => {
test("test_struct_version_check", () => {
if (Boolean("1")) { return; }
- const fory = new Fory();
+ const fory = new Fory({
+ mode: Mode.Compatible
+ });
@Type.struct(201, {
f1: Type.int32(),
@@ -757,7 +779,9 @@ describe("bool", () => {
reader.reset(content);
// Deserialize struct from Java
- const deserializedStruct =
fory.deserialize(reader.buffer(reader.varUInt32()));
+ let cursor = 0;
+ const deserializedStruct = fory.deserialize(content.subarray(cursor));
+ cursor += fory.binaryReader.getCursor();
// Serialize the deserialized struct back
const serializedData = fory.serialize(deserializedStruct);
@@ -766,7 +790,9 @@ describe("bool", () => {
test("test_polymorphic_list", () => {
if (Boolean("1")) { return; }
- const fory = new Fory();
+ const fory = new Fory({
+ mode: Mode.Compatible
+ });
// Define Animal interface implementations
@Type.struct(302, {
@@ -808,8 +834,10 @@ describe("bool", () => {
// Deserialize polymorphic data from Java
const deserializedData = [];
+ let cursor = 0;
for (let i = 0; i < 2; i++) { // animals array + holder
- const deserializedItem =
fory.deserialize(reader.buffer(reader.varUInt32()));
+ const deserializedItem = fory.deserialize(content.subarray(cursor));
+ cursor += fory.binaryReader.getCursor();
deserializedData.push(deserializedItem);
}
@@ -827,7 +855,9 @@ describe("bool", () => {
test("test_polymorphic_map", () => {
if (Boolean("1")) { return; }
- const fory = new Fory();
+ const fory = new Fory({
+ mode: Mode.Compatible
+ });
// Define Animal interface implementations
@Type.struct(302, {
@@ -869,8 +899,10 @@ describe("bool", () => {
// Deserialize polymorphic data from Java
const deserializedData = [];
+ let cursor = 0;
for (let i = 0; i < 2; i++) { // animal map + holder
- const deserializedItem =
fory.deserialize(reader.buffer(reader.varUInt32()));
+ const deserializedItem = fory.deserialize(content.subarray(cursor));
+ cursor += fory.binaryReader.getCursor();
deserializedData.push(deserializedItem);
}
@@ -887,7 +919,9 @@ describe("bool", () => {
});
test("test_one_string_field_schema", () => {
if (Boolean("1")) { return; }
- const fory = new Fory();
+ const fory = new Fory({
+ mode: Mode.Compatible
+ });
@Type.struct(200, {
f1: Type.string()
@@ -901,7 +935,9 @@ describe("bool", () => {
reader.reset(content);
// Deserialize struct from Java
- const deserializedStruct =
fory.deserialize(reader.buffer(reader.varUInt32()));
+ let cursor = 0;
+ const deserializedStruct = fory.deserialize(content.subarray(cursor));
+ cursor += fory.binaryReader.getCursor();
// Serialize the deserialized struct back
const serializedData = fory.serialize(deserializedStruct);
@@ -909,7 +945,9 @@ describe("bool", () => {
});
test("test_one_string_field_compatible", () => {
if (Boolean("1")) { return; }
- const fory = new Fory();
+ const fory = new Fory({
+ mode: Mode.Compatible
+ });
@Type.struct(200, {
f1: Type.string()
@@ -923,7 +961,9 @@ describe("bool", () => {
reader.reset(content);
// Deserialize struct from Java
- const deserializedStruct =
fory.deserialize(reader.buffer(reader.varUInt32()));
+ let cursor = 0;
+ const deserializedStruct = fory.deserialize(content.subarray(cursor));
+ cursor += fory.binaryReader.getCursor();
// Serialize the deserialized struct back
const serializedData = fory.serialize(deserializedStruct);
@@ -932,7 +972,9 @@ describe("bool", () => {
test("test_two_string_field_compatible", () => {
if (Boolean("1")) { return; }
- const fory = new Fory();
+ const fory = new Fory({
+ mode: Mode.Compatible
+ });
@Type.struct(201, {
f1: Type.string(),
@@ -948,7 +990,9 @@ describe("bool", () => {
reader.reset(content);
// Deserialize struct from Java
- const deserializedStruct =
fory.deserialize(reader.buffer(reader.varUInt32()));
+ let cursor = 0;
+ const deserializedStruct = fory.deserialize(content.subarray(cursor));
+ cursor += fory.binaryReader.getCursor();
// Serialize the deserialized struct back
const serializedData = fory.serialize(deserializedStruct);
@@ -957,7 +1001,9 @@ describe("bool", () => {
test("test_schema_evolution_compatible", () => {
if (Boolean("1")) { return; }
- const fory = new Fory();
+ const fory = new Fory({
+ mode: Mode.Compatible
+ });
@Type.struct(200)
class EmptyStruct { }
@@ -967,7 +1013,9 @@ describe("bool", () => {
reader.reset(content);
// Deserialize empty struct from Java
- const deserializedStruct =
fory.deserialize(reader.buffer(reader.varUInt32()));
+ let cursor = 0;
+ const deserializedStruct = fory.deserialize(content.subarray(cursor));
+ cursor += fory.binaryReader.getCursor();
// Serialize the deserialized struct back
const serializedData = fory.serialize(deserializedStruct);
@@ -975,7 +1023,9 @@ describe("bool", () => {
});
test("test_one_enum_field_schema", () => {
if (Boolean("1")) { return; }
- const fory = new Fory();
+ const fory = new Fory({
+ mode: Mode.Compatible
+ });
// Define and register TestEnum
const TestEnum = {
@@ -997,7 +1047,9 @@ describe("bool", () => {
reader.reset(content);
// Deserialize struct from Java
- const deserializedStruct =
fory.deserialize(reader.buffer(reader.varUInt32()));
+ let cursor = 0;
+ const deserializedStruct = fory.deserialize(content.subarray(cursor));
+ cursor += fory.binaryReader.getCursor();
// Serialize the deserialized struct back
const serializedData = fory.serialize(deserializedStruct);
@@ -1006,7 +1058,9 @@ describe("bool", () => {
test("test_one_enum_field_compatible", () => {
if (Boolean("1")) { return; }
- const fory = new Fory();
+ const fory = new Fory({
+ mode: Mode.Compatible
+ });
// Define and register TestEnum
const TestEnum = {
@@ -1028,7 +1082,9 @@ describe("bool", () => {
reader.reset(content);
// Deserialize struct from Java
- const deserializedStruct =
fory.deserialize(reader.buffer(reader.varUInt32()));
+ let cursor = 0;
+ const deserializedStruct = fory.deserialize(content.subarray(cursor));
+ cursor += fory.binaryReader.getCursor();
// Serialize the deserialized struct back
const serializedData = fory.serialize(deserializedStruct);
@@ -1037,7 +1093,9 @@ describe("bool", () => {
test("test_two_enum_field_compatible", () => {
if (Boolean("1")) { return; }
- const fory = new Fory();
+ const fory = new Fory({
+ mode: Mode.Compatible
+ });
// Define and register TestEnum
const TestEnum = {
@@ -1061,7 +1119,9 @@ describe("bool", () => {
reader.reset(content);
// Deserialize struct from Java
- const deserializedStruct =
fory.deserialize(reader.buffer(reader.varUInt32()));
+ let cursor = 0;
+ const deserializedStruct = fory.deserialize(content.subarray(cursor));
+ cursor += fory.binaryReader.getCursor();
// Serialize the deserialized struct back
const serializedData = fory.serialize(deserializedStruct);
@@ -1070,7 +1130,9 @@ describe("bool", () => {
test("test_enum_schema_evolution_compatible", () => {
if (Boolean("1")) { return; }
- const fory = new Fory();
+ const fory = new Fory({
+ mode: Mode.Compatible
+ });
// Register TestEnum
const TestEnum = {
@@ -1088,7 +1150,9 @@ describe("bool", () => {
reader.reset(content);
// Deserialize empty struct from Java
- const deserializedStruct =
fory.deserialize(reader.buffer(reader.varUInt32()));
+ let cursor = 0;
+ const deserializedStruct = fory.deserialize(content.subarray(cursor));
+ cursor += fory.binaryReader.getCursor();
// Serialize the deserialized struct back
const serializedData = fory.serialize(deserializedStruct);
@@ -1097,7 +1161,9 @@ describe("bool", () => {
test("test_nullable_field_schema_consistent_not_null", () => {
if (Boolean("1")) { return; }
- const fory = new Fory();
+ const fory = new Fory({
+ mode: Mode.Compatible
+ });
@Type.struct(401, {
intField: Type.int32(),
@@ -1117,7 +1183,9 @@ describe("bool", () => {
reader.reset(content);
// Deserialize struct from Java
- const deserializedStruct =
fory.deserialize(reader.buffer(reader.varUInt32()));
+ let cursor = 0;
+ const deserializedStruct = fory.deserialize(content.subarray(cursor));
+ cursor += fory.binaryReader.getCursor();
// Serialize the deserialized struct back
const serializedData = fory.serialize(deserializedStruct);
@@ -1126,7 +1194,9 @@ describe("bool", () => {
test("test_nullable_field_schema_consistent_null", () => {
if (Boolean("1")) { return; }
- const fory = new Fory();
+ const fory = new Fory({
+ mode: Mode.Compatible
+ });
@Type.struct(401, {
intField: Type.int32(),
@@ -1146,7 +1216,9 @@ describe("bool", () => {
reader.reset(content);
// Deserialize struct from Java
- const deserializedStruct =
fory.deserialize(reader.buffer(reader.varUInt32()));
+ let cursor = 0;
+ const deserializedStruct = fory.deserialize(content.subarray(cursor));
+ cursor += fory.binaryReader.getCursor();
// Serialize the deserialized struct back
const serializedData = fory.serialize(deserializedStruct);
@@ -1155,7 +1227,9 @@ describe("bool", () => {
test("test_nullable_field_compatible_not_null", () => {
if (Boolean("1")) { return; }
- const fory = new Fory();
+ const fory = new Fory({
+ mode: Mode.Compatible
+ });
@Type.struct(402, {
intField: Type.int32(),
@@ -1175,7 +1249,9 @@ describe("bool", () => {
reader.reset(content);
// Deserialize struct from Java
- const deserializedStruct =
fory.deserialize(reader.buffer(reader.varUInt32()));
+ let cursor = 0;
+ const deserializedStruct = fory.deserialize(content.subarray(cursor));
+ cursor += fory.binaryReader.getCursor();
// Serialize the deserialized struct back
const serializedData = fory.serialize(deserializedStruct);
@@ -1184,7 +1260,9 @@ describe("bool", () => {
test("test_nullable_field_compatible_null", () => {
if (Boolean("1")) { return; }
- const fory = new Fory();
+ const fory = new Fory({
+ mode: Mode.Compatible
+ });
@Type.struct(402, {
intField: Type.int32(),
@@ -1204,7 +1282,9 @@ describe("bool", () => {
reader.reset(content);
// Deserialize struct from Java
- const deserializedStruct =
fory.deserialize(reader.buffer(reader.varUInt32()));
+ let cursor = 0;
+ const deserializedStruct = fory.deserialize(content.subarray(cursor));
+ cursor += fory.binaryReader.getCursor();
// Serialize the deserialized struct back
const serializedData = fory.serialize(deserializedStruct);
@@ -1213,7 +1293,9 @@ describe("bool", () => {
test("test_ref_schema_consistent", () => {
if (Boolean("1")) { return; }
- const fory = new Fory();
+ const fory = new Fory({
+ mode: Mode.Compatible
+ });
@Type.struct(501, {
id: Type.int32(),
@@ -1239,7 +1321,9 @@ describe("bool", () => {
reader.reset(content);
// Deserialize outer struct from Java
- const deserializedOuter =
fory.deserialize(reader.buffer(reader.varUInt32()));
+ let cursor = 0;
+ const deserializedOuter = fory.deserialize(content.subarray(cursor));
+ cursor += fory.binaryReader.getCursor();
// Serialize the deserialized outer struct back
const serializedData = fory.serialize(deserializedOuter);
@@ -1248,7 +1332,9 @@ describe("bool", () => {
test("test_ref_compatible", () => {
if (Boolean("1")) { return; }
- const fory = new Fory();
+ const fory = new Fory({
+ mode: Mode.Compatible
+ });
@Type.struct(503, {
id: Type.int32(),
@@ -1274,7 +1360,9 @@ describe("bool", () => {
reader.reset(content);
// Deserialize outer struct from Java
- const deserializedOuter =
fory.deserialize(reader.buffer(reader.varUInt32()));
+ let cursor = 0;
+ const deserializedOuter = fory.deserialize(content.subarray(cursor));
+ cursor += fory.binaryReader.getCursor();
// Serialize the deserialized outer struct back
const serializedData = fory.serialize(deserializedOuter);
@@ -1283,7 +1371,9 @@ describe("bool", () => {
test("test_circular_ref_schema_consistent", () => {
if (Boolean("1")) { return; }
- const fory = new Fory();
+ const fory = new Fory({
+ mode: Mode.Compatible
+ });
@Type.struct(601, {
name: Type.string(),
@@ -1299,7 +1389,9 @@ describe("bool", () => {
reader.reset(content);
// Deserialize circular struct from Java
- const deserializedStruct =
fory.deserialize(reader.buffer(reader.varUInt32()));
+ let cursor = 0;
+ const deserializedStruct = fory.deserialize(content.subarray(cursor));
+ cursor += fory.binaryReader.getCursor();
// Serialize the deserialized struct back
const serializedData = fory.serialize(deserializedStruct);
@@ -1308,7 +1400,9 @@ describe("bool", () => {
test("test_circular_ref_compatible", () => {
if (Boolean("1")) { return; }
- const fory = new Fory();
+ const fory = new Fory({
+ mode: Mode.Compatible
+ });
@Type.struct(602, {
name: Type.string(),
@@ -1324,7 +1418,9 @@ describe("bool", () => {
reader.reset(content);
// Deserialize circular struct from Java
- const deserializedStruct =
fory.deserialize(reader.buffer(reader.varUInt32()));
+ let cursor = 0;
+ const deserializedStruct = fory.deserialize(content.subarray(cursor));
+ cursor += fory.binaryReader.getCursor();
// Serialize the deserialized struct back
const serializedData = fory.serialize(deserializedStruct);
@@ -1333,7 +1429,9 @@ describe("bool", () => {
test("test_unsigned_schema_consistent_simple", () => {
if (Boolean("1")) { return; }
- const fory = new Fory();
+ const fory = new Fory({
+ mode: Mode.Compatible
+ });
@Type.struct(1, {
u64Tagged: Type.int64(),
@@ -1349,7 +1447,9 @@ describe("bool", () => {
reader.reset(content);
// Deserialize struct from Java
- const deserializedStruct =
fory.deserialize(reader.buffer(reader.varUInt32()));
+ let cursor = 0;
+ const deserializedStruct = fory.deserialize(content.subarray(cursor));
+ cursor += fory.binaryReader.getCursor();
// Serialize the deserialized struct back
const serializedData = fory.serialize(deserializedStruct);
@@ -1358,7 +1458,9 @@ describe("bool", () => {
test("test_unsigned_schema_consistent", () => {
if (Boolean("1")) { return; }
- const fory = new Fory();
+ const fory = new Fory({
+ mode: Mode.Compatible
+ });
@Type.struct(501, {
u8Field: Type.uint8(),
@@ -1378,7 +1480,9 @@ describe("bool", () => {
reader.reset(content);
// Deserialize struct from Java
- const deserializedStruct =
fory.deserialize(reader.buffer(reader.varUInt32()));
+ let cursor = 0;
+ const deserializedStruct = fory.deserialize(content.subarray(cursor));
+ cursor += fory.binaryReader.getCursor();
// Serialize the deserialized struct back
const serializedData = fory.serialize(deserializedStruct);
@@ -1387,7 +1491,9 @@ describe("bool", () => {
test("test_unsigned_schema_compatible", () => {
if (Boolean("1")) { return; }
- const fory = new Fory();
+ const fory = new Fory({
+ mode: Mode.Compatible
+ });
@Type.struct(502, {
u8Field: Type.uint8(),
@@ -1407,10 +1513,12 @@ describe("bool", () => {
reader.reset(content);
// Deserialize struct from Java
- const deserializedStruct =
fory.deserialize(reader.buffer(reader.varUInt32()));
+ let cursor = 0;
+ const deserializedStruct = fory.deserialize(content.subarray(cursor));
+ cursor += fory.binaryReader.getCursor();
// 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]