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 bd0bd5b09 feat(go): Add slice and map support to fory-go codegen
serialization (#2638)
bd0bd5b09 is described below
commit bd0bd5b090c8cc27aceaf76b72fbae2a3df4e104
Author: thisingl <[email protected]>
AuthorDate: Tue Sep 23 12:03:29 2025 +0800
feat(go): Add slice and map support to fory-go codegen serialization (#2638)
<!--
**Thanks for contributing to Apache Fory™.**
**If this is your first time opening a PR on fory, you can refer to
[CONTRIBUTING.md](https://github.com/apache/fory/blob/main/CONTRIBUTING.md).**
Contribution Checklist
- The **Apache Fory™** community has requirements on the naming of pr
titles. You can also find instructions in
[CONTRIBUTING.md](https://github.com/apache/fory/blob/main/CONTRIBUTING.md).
- Apache Fory™ has a strong focus on performance. If the PR you submit
will have an impact on performance, please benchmark it first and
provide the benchmark result here.
-->
## What does this PR do?
- Extends the ahead-of-time code generation to support serialization and
deserialization of slice types.
- Implements serialization and deserialization logic for map types.
- Enhances the generated Write and Read methods to cover these
collection types.
## Related issues
- #2227
<!--
Is there any related issue? If this PR closes them you say say
fix/closes:
- #xxxx0
- #xxxx1
- Fixes #xxxx2
-->
## Does this PR introduce any user-facing change?
<!--
If any user-facing interface changes, please [open an
issue](https://github.com/apache/fory/issues/new/choose) describing the
need to do so and update the document if necessary.
Delete section if not applicable.
-->
- [ ] Does this PR introduce any public API change?
- [ ] Does this PR introduce any binary protocol compatibility change?
## Benchmark
<!--
When the PR has an impact on performance (if you don't know whether the
PR will have an impact on performance, you can submit the PR first, and
if it will have impact on performance, the code reviewer will explain
it), be sure to attach a benchmark data here.
Delete section if not applicable.
-->
---
go/README.md | 24 +-
go/fory/codegen/decoder.go | 485 +++++++++++++++++++++++++++++++++-
go/fory/codegen/encoder.go | 453 ++++++++++++++++++++++++++++++-
go/fory/codegen/guard.go | 13 +-
go/fory/codegen/utils.go | 220 +++++++++++----
go/fory/struct.go | 36 ++-
go/fory/tests/generator_test.go | 162 +++++++++++-
go/fory/tests/generator_xlang_test.go | 291 ++++++++++++++++++++
go/fory/tests/structs.go | 35 ++-
go/fory/type.go | 64 +++--
10 files changed, 1680 insertions(+), 103 deletions(-)
diff --git a/go/README.md b/go/README.md
index 7dd44f912..bd0105ae4 100644
--- a/go/README.md
+++ b/go/README.md
@@ -2,7 +2,27 @@
Fory is a blazingly fast multi-language serialization framework powered by
just-in-time compilation and zero-copy.
-Currently, Fory Go is implemented using reflection. We have also implemented a
static code generator to generate serializer code ahead of time to speed up
serialization.
+Fory Go provides two serialization paths: a high-performance code generation
path and a reflection-based path. The code generation path is recommended for
production use as it offers better performance and broader type support.
+
+## Supported Types
+
+Fory Go supports the following types for both reflection-based serialization
and code generation:
+
+### Basic Data Types
+
+- `bool`
+- `int8`, `int16`, `int32`, `int64`, `int`
+- `uint8` (byte)
+- `float32`, `float64`
+- `string`
+
+### Collection Types
+
+- `[]bool`, `[]int16`, `[]int32`, `[]int64`
+- `[]float32`, `[]float64`
+- `[]string`
+- `[]interface{}` (dynamic slice)
+- `map[string]string`, `map[int]int`, `map[string]int`
## Fory Go Codegen (optional)
@@ -27,7 +47,7 @@ The generator binary is `fory`.
go install github.com/apache/fory/go/fory/cmd/fory@latest
```
-- Go 1.13+
+- Go 1.13+:
```bash
# Inside a module-enabled environment
diff --git a/go/fory/codegen/decoder.go b/go/fory/codegen/decoder.go
index 345f283c6..4cf048485 100644
--- a/go/fory/codegen/decoder.go
+++ b/go/fory/codegen/decoder.go
@@ -21,6 +21,8 @@ import (
"bytes"
"fmt"
"go/types"
+
+ "github.com/apache/fory/go/fory"
)
// generateReadTyped generates the strongly-typed Read method
@@ -110,15 +112,24 @@ func generateFieldReadTyped(buf *bytes.Buffer, field
*FieldInfo) error {
if basic, ok := field.Type.Underlying().(*types.Basic); ok {
switch basic.Kind() {
case types.Bool:
+ fmt.Fprintf(buf, "\tif flag := buf.ReadInt8(); flag !=
-1 {\n")
+ fmt.Fprintf(buf, "\t\treturn fmt.Errorf(\"expected
NotNullValueFlag for field %s, got %%d\", flag)\n", field.GoName)
+ fmt.Fprintf(buf, "\t}\n")
fmt.Fprintf(buf, "\t%s = buf.ReadBool()\n", fieldAccess)
case types.Int8:
fmt.Fprintf(buf, "\t%s = buf.ReadInt8()\n", fieldAccess)
case types.Int16:
fmt.Fprintf(buf, "\t%s = buf.ReadInt16()\n",
fieldAccess)
case types.Int32:
- fmt.Fprintf(buf, "\t%s = buf.ReadInt32()\n",
fieldAccess)
+ fmt.Fprintf(buf, "\tif flag := buf.ReadInt8(); flag !=
-1 {\n")
+ fmt.Fprintf(buf, "\t\treturn fmt.Errorf(\"expected
NotNullValueFlag for field %s, got %%d\", flag)\n", field.GoName)
+ fmt.Fprintf(buf, "\t}\n")
+ fmt.Fprintf(buf, "\t%s = buf.ReadVarint32()\n",
fieldAccess)
case types.Int, types.Int64:
- fmt.Fprintf(buf, "\t%s = buf.ReadInt64()\n",
fieldAccess)
+ fmt.Fprintf(buf, "\tif flag := buf.ReadInt8(); flag !=
-1 {\n")
+ fmt.Fprintf(buf, "\t\treturn fmt.Errorf(\"expected
NotNullValueFlag for field %s, got %%d\", flag)\n", field.GoName)
+ fmt.Fprintf(buf, "\t}\n")
+ fmt.Fprintf(buf, "\t%s = buf.ReadVarint64()\n",
fieldAccess)
case types.Uint8:
fmt.Fprintf(buf, "\t%s = buf.ReadByte_()\n",
fieldAccess)
case types.Uint16:
@@ -130,8 +141,14 @@ func generateFieldReadTyped(buf *bytes.Buffer, field
*FieldInfo) error {
case types.Float32:
fmt.Fprintf(buf, "\t%s = buf.ReadFloat32()\n",
fieldAccess)
case types.Float64:
+ fmt.Fprintf(buf, "\tif flag := buf.ReadInt8(); flag !=
-1 {\n")
+ fmt.Fprintf(buf, "\t\treturn fmt.Errorf(\"expected
NotNullValueFlag for field %s, got %%d\", flag)\n", field.GoName)
+ fmt.Fprintf(buf, "\t}\n")
fmt.Fprintf(buf, "\t%s = buf.ReadFloat64()\n",
fieldAccess)
case types.String:
+ fmt.Fprintf(buf, "\tif flag := buf.ReadInt8(); flag !=
0 {\n")
+ fmt.Fprintf(buf, "\t\treturn fmt.Errorf(\"expected
RefValueFlag for field %s, got %%d\", flag)\n", field.GoName)
+ fmt.Fprintf(buf, "\t}\n")
fmt.Fprintf(buf, "\t%s = fory.ReadString(buf)\n",
fieldAccess)
default:
fmt.Fprintf(buf, "\t// TODO: unsupported basic type
%s\n", basic.String())
@@ -139,6 +156,57 @@ func generateFieldReadTyped(buf *bytes.Buffer, field
*FieldInfo) error {
return nil
}
+ // Handle slice types
+ if slice, ok := field.Type.(*types.Slice); ok {
+ elemType := slice.Elem()
+ // Check if element type is interface{} (dynamic type)
+ if iface, ok := elemType.(*types.Interface); ok &&
iface.Empty() {
+ // For []interface{}, we need to manually implement the
deserialization
+ // to match our custom encoding
+ fmt.Fprintf(buf, "\t// Dynamic slice []interface{}
handling - manual deserialization\n")
+ fmt.Fprintf(buf, "\tif flag := buf.ReadInt8(); flag ==
-3 {\n")
+ fmt.Fprintf(buf, "\t\t%s = nil // null slice\n",
fieldAccess)
+ fmt.Fprintf(buf, "\t} else if flag == 0 {\n")
+ fmt.Fprintf(buf, "\t\t// Read slice length\n")
+ fmt.Fprintf(buf, "\t\tsliceLen :=
buf.ReadVarUint32()\n")
+ fmt.Fprintf(buf, "\t\t// Read collection flags (ignore
for now)\n")
+ fmt.Fprintf(buf, "\t\t_ = buf.ReadInt8()\n")
+ fmt.Fprintf(buf, "\t\t// Create slice with proper
capacity\n")
+ fmt.Fprintf(buf, "\t\t%s = make([]interface{},
sliceLen)\n", fieldAccess)
+ fmt.Fprintf(buf, "\t\t// Read each element using
ReadReferencable\n")
+ fmt.Fprintf(buf, "\t\tfor i := range %s {\n",
fieldAccess)
+ fmt.Fprintf(buf, "\t\t\tf.ReadReferencable(buf,
reflect.ValueOf(&%s[i]).Elem())\n", fieldAccess)
+ fmt.Fprintf(buf, "\t\t}\n")
+ fmt.Fprintf(buf, "\t} else {\n")
+ fmt.Fprintf(buf, "\t\treturn fmt.Errorf(\"expected
RefValueFlag or NullFlag for dynamic slice field %s, got %%d\", flag)\n",
field.GoName)
+ fmt.Fprintf(buf, "\t}\n")
+ return nil
+ }
+ // For static element types, use optimized inline generation
+ if err := generateSliceReadInline(buf, slice, fieldAccess); err
!= nil {
+ return err
+ }
+ return nil
+ }
+
+ // Handle map types
+ if mapType, ok := field.Type.(*types.Map); ok {
+ // For map types, we'll use manual deserialization following
the chunk-based format
+ if err := generateMapReadInline(buf, mapType, fieldAccess); err
!= nil {
+ return err
+ }
+ return nil
+ }
+
+ // Handle interface types
+ if iface, ok := field.Type.(*types.Interface); ok {
+ if iface.Empty() {
+ // For interface{}, use ReadReferencable for dynamic
type handling
+ fmt.Fprintf(buf, "\tf.ReadReferencable(buf,
reflect.ValueOf(&%s).Elem())\n", fieldAccess)
+ return nil
+ }
+ }
+
// Handle struct types
if _, ok := field.Type.Underlying().(*types.Struct); ok {
fmt.Fprintf(buf, "\tf.ReadReferencable(buf,
reflect.ValueOf(&%s).Elem())\n", fieldAccess)
@@ -148,3 +216,416 @@ func generateFieldReadTyped(buf *bytes.Buffer, field
*FieldInfo) error {
fmt.Fprintf(buf, "\t// TODO: unsupported type %s\n",
field.Type.String())
return nil
}
+
+// Note: generateSliceRead is no longer used since we use
WriteReferencable/ReadReferencable for slice fields
+// generateSliceRead generates code to deserialize a slice according to the
list format
+func generateSliceRead(buf *bytes.Buffer, sliceType *types.Slice, fieldAccess
string) error {
+ elemType := sliceType.Elem()
+
+ // Use block scope to avoid variable redeclaration across multiple
slice fields
+ fmt.Fprintf(buf, "\t// Read slice %s\n", fieldAccess)
+ fmt.Fprintf(buf, "\t{\n")
+ fmt.Fprintf(buf, "\t\tsliceLen := int(buf.ReadVarUint32())\n")
+ fmt.Fprintf(buf, "\t\tif sliceLen == 0 {\n")
+ fmt.Fprintf(buf, "\t\t\t// Empty slice - matching reflection behavior
where nil and empty are treated the same\n")
+ fmt.Fprintf(buf, "\t\t\t%s = nil\n", fieldAccess)
+ fmt.Fprintf(buf, "\t\t} else {\n")
+
+ // Read collection flags for non-empty slice
+ fmt.Fprintf(buf, "\t\t\t// Read collection flags\n")
+ fmt.Fprintf(buf, "\t\t\tcollectFlag := buf.ReadInt8()\n")
+ fmt.Fprintf(buf, "\t\t\t// Check if CollectionNotDeclElementType flag
is set\n")
+ fmt.Fprintf(buf, "\t\t\tif (collectFlag & 4) != 0 {\n")
+ fmt.Fprintf(buf, "\t\t\t\t// Read element type ID (we expect it but
don't need to validate it for codegen)\n")
+ fmt.Fprintf(buf, "\t\t\t\t_ = buf.ReadVarInt32()\n")
+ fmt.Fprintf(buf, "\t\t\t}\n")
+
+ // Create slice
+ fmt.Fprintf(buf, "\t\t\t%s = make(%s, sliceLen)\n", fieldAccess,
sliceType.String())
+
+ // Read elements
+ fmt.Fprintf(buf, "\t\t\tfor i := 0; i < sliceLen; i++ {\n")
+
+ // Generate element read code based on type
+ elemAccess := fmt.Sprintf("%s[i]", fieldAccess)
+ if err := generateSliceElementRead(buf, elemType, elemAccess); err !=
nil {
+ return err
+ }
+
+ fmt.Fprintf(buf, "\t\t\t}\n")
+ fmt.Fprintf(buf, "\t\t}\n")
+ fmt.Fprintf(buf, "\t}\n")
+
+ return nil
+}
+
+// generateSliceElementRead generates code to read a single slice element
+func generateSliceElementRead(buf *bytes.Buffer, elemType types.Type,
elemAccess string) error {
+ // Handle basic types
+ if basic, ok := elemType.Underlying().(*types.Basic); ok {
+ switch basic.Kind() {
+ case types.Bool:
+ fmt.Fprintf(buf, "\t\t\t\t%s = buf.ReadBool()\n",
elemAccess)
+ case types.Int8:
+ fmt.Fprintf(buf, "\t\t\t\t%s = buf.ReadInt8()\n",
elemAccess)
+ case types.Int16:
+ fmt.Fprintf(buf, "\t\t\t\t%s = buf.ReadInt16()\n",
elemAccess)
+ case types.Int32:
+ fmt.Fprintf(buf, "\t\t\t\tif flag := buf.ReadInt8();
flag != -1 {\n")
+ fmt.Fprintf(buf, "\t\t\t\t\treturn
fmt.Errorf(\"expected NotNullValueFlag for slice element, got %%d\", flag)\n")
+ fmt.Fprintf(buf, "\t\t\t\t}\n")
+ fmt.Fprintf(buf, "\t\t\t\t%s = buf.ReadVarint32()\n",
elemAccess)
+ case types.Int, types.Int64:
+ fmt.Fprintf(buf, "\t\t\t\tif flag := buf.ReadInt8();
flag != -1 {\n")
+ fmt.Fprintf(buf, "\t\t\t\t\treturn
fmt.Errorf(\"expected NotNullValueFlag for slice element, got %%d\", flag)\n")
+ fmt.Fprintf(buf, "\t\t\t\t}\n")
+ fmt.Fprintf(buf, "\t\t\t\t%s = buf.ReadVarint64()\n",
elemAccess)
+ case types.Uint8:
+ fmt.Fprintf(buf, "\t\t\t\t%s = buf.ReadByte_()\n",
elemAccess)
+ case types.Uint16:
+ fmt.Fprintf(buf, "\t\t\t\t%s =
uint16(buf.ReadInt16())\n", elemAccess)
+ case types.Uint32:
+ fmt.Fprintf(buf, "\t\t\t\t%s =
uint32(buf.ReadInt32())\n", elemAccess)
+ case types.Uint, types.Uint64:
+ fmt.Fprintf(buf, "\t\t\t\t%s =
uint64(buf.ReadInt64())\n", elemAccess)
+ case types.Float32:
+ fmt.Fprintf(buf, "\t\t\t\t%s = buf.ReadFloat32()\n",
elemAccess)
+ case types.Float64:
+ fmt.Fprintf(buf, "\t\t\t\t%s = buf.ReadFloat64()\n",
elemAccess)
+ case types.String:
+ fmt.Fprintf(buf, "\t\t\t\tif flag := buf.ReadInt8();
flag != 0 {\n")
+ fmt.Fprintf(buf, "\t\t\t\t\treturn
fmt.Errorf(\"expected RefValueFlag for string element, got %%d\", flag)\n")
+ fmt.Fprintf(buf, "\t\t\t\t}\n")
+ fmt.Fprintf(buf, "\t\t\t\t%s = fory.ReadString(buf)\n",
elemAccess)
+ default:
+ fmt.Fprintf(buf, "\t\t\t\t// TODO: unsupported basic
type %s\n", basic.String())
+ }
+ return nil
+ }
+
+ // Handle named types
+ if named, ok := elemType.(*types.Named); ok {
+ typeStr := named.String()
+ switch typeStr {
+ case "time.Time":
+ fmt.Fprintf(buf, "\t\t\t\tusec := buf.ReadInt64()\n")
+ fmt.Fprintf(buf, "\t\t\t\t%s =
fory.CreateTimeFromUnixMicro(usec)\n", elemAccess)
+ return nil
+ case "github.com/apache/fory/go/fory.Date":
+ fmt.Fprintf(buf, "\t\t\t\tdays := buf.ReadInt32()\n")
+ fmt.Fprintf(buf, "\t\t\t\t// Handle zero date marker\n")
+ fmt.Fprintf(buf, "\t\t\t\tif days == int32(-2147483648)
{\n")
+ fmt.Fprintf(buf, "\t\t\t\t\t%s = fory.Date{Year: 0,
Month: 0, Day: 0}\n", elemAccess)
+ fmt.Fprintf(buf, "\t\t\t\t} else {\n")
+ fmt.Fprintf(buf, "\t\t\t\t\tdiff := time.Duration(days)
* 24 * time.Hour\n")
+ fmt.Fprintf(buf, "\t\t\t\t\tt := time.Date(1970, 1, 1,
0, 0, 0, 0, time.Local).Add(diff)\n")
+ fmt.Fprintf(buf, "\t\t\t\t\t%s = fory.Date{Year:
t.Year(), Month: t.Month(), Day: t.Day()}\n", elemAccess)
+ fmt.Fprintf(buf, "\t\t\t\t}\n")
+ return nil
+ }
+ // Check if it's a struct
+ if _, ok := named.Underlying().(*types.Struct); ok {
+ fmt.Fprintf(buf, "\t\t\t\tf.ReadReferencable(buf,
reflect.ValueOf(&%s).Elem())\n", elemAccess)
+ return nil
+ }
+ }
+
+ // Handle struct types
+ if _, ok := elemType.Underlying().(*types.Struct); ok {
+ fmt.Fprintf(buf, "\t\t\t\tf.ReadReferencable(buf,
reflect.ValueOf(&%s).Elem())\n", elemAccess)
+ return nil
+ }
+
+ fmt.Fprintf(buf, "\t\t\t\t// TODO: unsupported element type %s\n",
elemType.String())
+ return nil
+}
+
+// generateSliceReadInline generates inline slice deserialization code to
match encoder behavior exactly
+func generateSliceReadInline(buf *bytes.Buffer, sliceType *types.Slice,
fieldAccess string) error {
+ elemType := sliceType.Elem()
+
+ // Read RefValueFlag first (slice is referencable)
+ fmt.Fprintf(buf, "\tif flag := buf.ReadInt8(); flag != 0 {\n")
+ fmt.Fprintf(buf, "\t\treturn fmt.Errorf(\"expected RefValueFlag for
slice field, got %%d\", flag)\n")
+ fmt.Fprintf(buf, "\t}\n")
+
+ // Read slice length - use block scope to avoid variable name conflicts
+ fmt.Fprintf(buf, "\t{\n")
+ fmt.Fprintf(buf, "\t\tsliceLen := int(buf.ReadVarUint32())\n")
+ fmt.Fprintf(buf, "\t\tif sliceLen == 0 {\n")
+ fmt.Fprintf(buf, "\t\t\t%s = nil\n", fieldAccess)
+ fmt.Fprintf(buf, "\t\t} else {\n")
+
+ // Read collection header
+ fmt.Fprintf(buf, "\t\t\tcollectFlag := buf.ReadInt8()\n")
+ fmt.Fprintf(buf, "\t\t\t// We expect 12 (no ref tracking) or 13 (with
ref tracking)\n")
+ fmt.Fprintf(buf, "\t\t\tif collectFlag != 12 && collectFlag != 13 {\n")
+ fmt.Fprintf(buf, "\t\t\t\treturn fmt.Errorf(\"unexpected collection
flag: %%d\", collectFlag)\n")
+ fmt.Fprintf(buf, "\t\t\t}\n")
+
+ // Create slice
+ fmt.Fprintf(buf, "\t\t\t%s = make(%s, sliceLen)\n", fieldAccess,
sliceType.String())
+
+ // Read elements
+ fmt.Fprintf(buf, "\t\t\tfor i := 0; i < sliceLen; i++ {\n")
+
+ // For each element, read NotNullValueFlag + TypeID + Value
+ fmt.Fprintf(buf, "\t\t\t\t// Read element NotNullValueFlag\n")
+ fmt.Fprintf(buf, "\t\t\t\tif flag := buf.ReadInt8(); flag != -1 {\n")
+ fmt.Fprintf(buf, "\t\t\t\t\treturn fmt.Errorf(\"expected
NotNullValueFlag for element, got %%d\", flag)\n")
+ fmt.Fprintf(buf, "\t\t\t\t}\n")
+
+ // Read and verify element type ID
+ if err := generateElementTypeIDReadInline(buf, elemType); err != nil {
+ return err
+ }
+
+ // Read element value
+ if err := generateSliceElementReadInline(buf, elemType,
fmt.Sprintf("%s[i]", fieldAccess)); err != nil {
+ return err
+ }
+
+ fmt.Fprintf(buf, "\t\t\t}\n")
+ fmt.Fprintf(buf, "\t\t}\n")
+ fmt.Fprintf(buf, "\t}\n")
+
+ return nil
+}
+
+// generateElementTypeIDReadInline generates element type ID verification
+func generateElementTypeIDReadInline(buf *bytes.Buffer, elemType types.Type)
error {
+ // Handle basic types - verify the expected type ID
+ if basic, ok := elemType.Underlying().(*types.Basic); ok {
+ var expectedTypeID int
+ switch basic.Kind() {
+ case types.Bool:
+ expectedTypeID = int(fory.BOOL)
+ case types.Int8:
+ expectedTypeID = int(fory.INT8)
+ case types.Int16:
+ expectedTypeID = int(fory.INT16)
+ case types.Int32:
+ expectedTypeID = int(fory.INT32)
+ case types.Int, types.Int64:
+ expectedTypeID = int(fory.INT64)
+ case types.Uint8:
+ expectedTypeID = int(fory.UINT8)
+ case types.Uint16:
+ expectedTypeID = int(fory.UINT16)
+ case types.Uint32:
+ expectedTypeID = int(fory.UINT32)
+ case types.Uint, types.Uint64:
+ expectedTypeID = int(fory.UINT64)
+ case types.Float32:
+ expectedTypeID = int(fory.FLOAT)
+ case types.Float64:
+ expectedTypeID = int(fory.DOUBLE)
+ case types.String:
+ expectedTypeID = int(fory.STRING)
+ default:
+ return fmt.Errorf("unsupported basic type for element
type ID read: %s", basic.String())
+ }
+
+ fmt.Fprintf(buf, "\t\t\t\t// Read and verify element type ID\n")
+ fmt.Fprintf(buf, "\t\t\t\tif typeID := buf.ReadVarInt32();
typeID != %d {\n", expectedTypeID)
+ fmt.Fprintf(buf, "\t\t\t\t\treturn fmt.Errorf(\"expected
element type ID %d, got %%d\", typeID)\n", expectedTypeID)
+ fmt.Fprintf(buf, "\t\t\t\t}\n")
+
+ return nil
+ }
+ return fmt.Errorf("unsupported element type for type ID read: %s",
elemType.String())
+}
+
+// generateSliceElementReadInline generates code to read a single slice
element value
+func generateSliceElementReadInline(buf *bytes.Buffer, elemType types.Type,
elemAccess string) error {
+ // Handle basic types - read the actual value (type ID already verified
above)
+ if basic, ok := elemType.Underlying().(*types.Basic); ok {
+ switch basic.Kind() {
+ case types.Bool:
+ fmt.Fprintf(buf, "\t\t\t\t%s = buf.ReadBool()\n",
elemAccess)
+ case types.Int8:
+ fmt.Fprintf(buf, "\t\t\t\t%s = buf.ReadInt8()\n",
elemAccess)
+ case types.Int16:
+ fmt.Fprintf(buf, "\t\t\t\t%s = buf.ReadInt16()\n",
elemAccess)
+ case types.Int32:
+ fmt.Fprintf(buf, "\t\t\t\t%s = buf.ReadVarint32()\n",
elemAccess)
+ case types.Int, types.Int64:
+ fmt.Fprintf(buf, "\t\t\t\t%s = buf.ReadVarint64()\n",
elemAccess)
+ case types.Uint8:
+ fmt.Fprintf(buf, "\t\t\t\t%s = buf.ReadByte_()\n",
elemAccess)
+ case types.Uint16:
+ fmt.Fprintf(buf, "\t\t\t\t%s =
uint16(buf.ReadInt16())\n", elemAccess)
+ case types.Uint32:
+ fmt.Fprintf(buf, "\t\t\t\t%s =
uint32(buf.ReadInt32())\n", elemAccess)
+ case types.Uint, types.Uint64:
+ fmt.Fprintf(buf, "\t\t\t\t%s =
uint64(buf.ReadInt64())\n", elemAccess)
+ case types.Float32:
+ fmt.Fprintf(buf, "\t\t\t\t%s = buf.ReadFloat32()\n",
elemAccess)
+ case types.Float64:
+ fmt.Fprintf(buf, "\t\t\t\t%s = buf.ReadFloat64()\n",
elemAccess)
+ case types.String:
+ fmt.Fprintf(buf, "\t\t\t\t%s = fory.ReadString(buf)\n",
elemAccess)
+ default:
+ return fmt.Errorf("unsupported basic type for element
read: %s", basic.String())
+ }
+ return nil
+ }
+
+ // Handle interface types
+ if iface, ok := elemType.(*types.Interface); ok {
+ if iface.Empty() {
+ // For interface{} elements, use ReadReferencable for
dynamic type handling
+ fmt.Fprintf(buf, "\t\t\t\tf.ReadReferencable(buf,
reflect.ValueOf(&%s).Elem())\n", elemAccess)
+ return nil
+ }
+ }
+
+ return fmt.Errorf("unsupported element type for read: %s",
elemType.String())
+}
+
+// generateMapReadInline generates inline map deserialization code following
the chunk-based format
+func generateMapReadInline(buf *bytes.Buffer, mapType *types.Map, fieldAccess
string) error {
+ keyType := mapType.Key()
+ valueType := mapType.Elem()
+
+ // Check if key or value types are interface{}
+ keyIsInterface := false
+ valueIsInterface := false
+ if iface, ok := keyType.(*types.Interface); ok && iface.Empty() {
+ keyIsInterface = true
+ }
+ if iface, ok := valueType.(*types.Interface); ok && iface.Empty() {
+ valueIsInterface = true
+ }
+
+ // Read RefValueFlag first (map is referencable)
+ fmt.Fprintf(buf, "\tif flag := buf.ReadInt8(); flag != 0 {\n")
+ fmt.Fprintf(buf, "\t\treturn fmt.Errorf(\"expected RefValueFlag for map
field, got %%d\", flag)\n")
+ fmt.Fprintf(buf, "\t}\n")
+
+ // Read map length
+ fmt.Fprintf(buf, "\t{\n")
+ fmt.Fprintf(buf, "\t\tmapLen := int(buf.ReadVarUint32())\n")
+ fmt.Fprintf(buf, "\t\tif mapLen == 0 {\n")
+ fmt.Fprintf(buf, "\t\t\t%s = make(%s)\n", fieldAccess, mapType.String())
+ fmt.Fprintf(buf, "\t\t} else {\n")
+ fmt.Fprintf(buf, "\t\t\t%s = make(%s, mapLen)\n", fieldAccess,
mapType.String())
+ fmt.Fprintf(buf, "\t\t\tmapSize := mapLen\n")
+
+ // Read chunks
+ fmt.Fprintf(buf, "\t\t\tfor mapSize > 0 {\n")
+ fmt.Fprintf(buf, "\t\t\t\t// Read KV header\n")
+ fmt.Fprintf(buf, "\t\t\t\tkvHeader := buf.ReadUint8()\n")
+ fmt.Fprintf(buf, "\t\t\t\tchunkSize := int(buf.ReadUint8())\n")
+
+ // Parse header flags
+ fmt.Fprintf(buf, "\t\t\t\ttrackKeyRef := (kvHeader & 0x1) != 0\n")
+ fmt.Fprintf(buf, "\t\t\t\tkeyNotDeclared := (kvHeader & 0x4) != 0\n")
+ fmt.Fprintf(buf, "\t\t\t\ttrackValueRef := (kvHeader & 0x8) != 0\n")
+ fmt.Fprintf(buf, "\t\t\t\tvalueNotDeclared := (kvHeader & 0x20) != 0\n")
+ fmt.Fprintf(buf, "\t\t\t\t_ = trackKeyRef\n")
+ fmt.Fprintf(buf, "\t\t\t\t_ = keyNotDeclared\n")
+ fmt.Fprintf(buf, "\t\t\t\t_ = trackValueRef\n")
+ fmt.Fprintf(buf, "\t\t\t\t_ = valueNotDeclared\n")
+
+ // Read key-value pairs in this chunk
+ fmt.Fprintf(buf, "\t\t\t\tfor i := 0; i < chunkSize; i++ {\n")
+
+ // Read key
+ if keyIsInterface {
+ fmt.Fprintf(buf, "\t\t\t\t\tvar mapKey interface{}\n")
+ fmt.Fprintf(buf, "\t\t\t\t\tf.ReadReferencable(buf,
reflect.ValueOf(&mapKey).Elem())\n")
+ } else {
+ // Declare key variable with appropriate type
+ keyVarType := getGoTypeString(keyType)
+ fmt.Fprintf(buf, "\t\t\t\t\tvar mapKey %s\n", keyVarType)
+ if err := generateMapKeyRead(buf, keyType, "mapKey"); err !=
nil {
+ return err
+ }
+ }
+
+ // Read value
+ if valueIsInterface {
+ fmt.Fprintf(buf, "\t\t\t\t\tvar mapValue interface{}\n")
+ fmt.Fprintf(buf, "\t\t\t\t\tf.ReadReferencable(buf,
reflect.ValueOf(&mapValue).Elem())\n")
+ } else {
+ // Declare value variable with appropriate type
+ valueVarType := getGoTypeString(valueType)
+ fmt.Fprintf(buf, "\t\t\t\t\tvar mapValue %s\n", valueVarType)
+ if err := generateMapValueRead(buf, valueType, "mapValue"); err
!= nil {
+ return err
+ }
+ }
+
+ // Set key-value pair in map
+ fmt.Fprintf(buf, "\t\t\t\t\t%s[mapKey] = mapValue\n", fieldAccess)
+
+ fmt.Fprintf(buf, "\t\t\t\t}\n") // end chunk loop
+ fmt.Fprintf(buf, "\t\t\t\tmapSize -= chunkSize\n")
+ fmt.Fprintf(buf, "\t\t\t}\n") // end mapSize > 0 loop
+
+ fmt.Fprintf(buf, "\t\t}\n") // end else (mapLen > 0)
+ fmt.Fprintf(buf, "\t}\n") // end block scope
+
+ return nil
+}
+
+// getGoTypeString returns the Go type string for a types.Type
+func getGoTypeString(t types.Type) string {
+ // Handle basic types
+ if basic, ok := t.Underlying().(*types.Basic); ok {
+ switch basic.Kind() {
+ case types.Int:
+ return "int"
+ case types.String:
+ return "string"
+ default:
+ return t.String()
+ }
+ }
+ return t.String()
+}
+
+// generateMapKeyRead generates code to read a map key
+func generateMapKeyRead(buf *bytes.Buffer, keyType types.Type, varName string)
error {
+ // For basic types, match reflection's serializer behavior
+ if basic, ok := keyType.Underlying().(*types.Basic); ok {
+ switch basic.Kind() {
+ case types.Int:
+ // intSerializer uses ReadInt64, not ReadVarint64
+ fmt.Fprintf(buf, "\t\t\t\t\t%s =
int(buf.ReadInt64())\n", varName)
+ case types.String:
+ // stringSerializer is referencable, need to use
ReadReferencable
+ fmt.Fprintf(buf, "\t\t\t\t\tf.ReadReferencable(buf,
reflect.ValueOf(&%s).Elem())\n", varName)
+ default:
+ return fmt.Errorf("unsupported map key type: %v",
keyType)
+ }
+ return nil
+ }
+
+ // For other types, use ReadReferencable
+ fmt.Fprintf(buf, "\t\t\t\t\tf.ReadReferencable(buf,
reflect.ValueOf(&%s).Elem())\n", varName)
+ return nil
+}
+
+// generateMapValueRead generates code to read a map value
+func generateMapValueRead(buf *bytes.Buffer, valueType types.Type, varName
string) error {
+ // For basic types, match reflection's serializer behavior
+ if basic, ok := valueType.Underlying().(*types.Basic); ok {
+ switch basic.Kind() {
+ case types.Int:
+ // intSerializer uses ReadInt64, not ReadVarint64
+ fmt.Fprintf(buf, "\t\t\t\t\t%s =
int(buf.ReadInt64())\n", varName)
+ case types.String:
+ // stringSerializer is referencable, need to use
ReadReferencable
+ fmt.Fprintf(buf, "\t\t\t\t\tf.ReadReferencable(buf,
reflect.ValueOf(&%s).Elem())\n", varName)
+ default:
+ return fmt.Errorf("unsupported map value type: %v",
valueType)
+ }
+ return nil
+ }
+
+ // For other types, use ReadReferencable
+ fmt.Fprintf(buf, "\t\t\t\t\tf.ReadReferencable(buf,
reflect.ValueOf(&%s).Elem())\n", varName)
+ return nil
+}
diff --git a/go/fory/codegen/encoder.go b/go/fory/codegen/encoder.go
index 72a5e94cd..804aac6d1 100644
--- a/go/fory/codegen/encoder.go
+++ b/go/fory/codegen/encoder.go
@@ -21,6 +21,8 @@ import (
"bytes"
"fmt"
"go/types"
+
+ "github.com/apache/fory/go/fory"
)
// generateWriteTyped generates the strongly-typed Write method
@@ -102,28 +104,40 @@ func generateFieldWriteTyped(buf *bytes.Buffer, field
*FieldInfo) error {
if basic, ok := field.Type.Underlying().(*types.Basic); ok {
switch basic.Kind() {
case types.Bool:
+ fmt.Fprintf(buf, "\tbuf.WriteInt8(-1) //
NotNullValueFlag\n")
fmt.Fprintf(buf, "\tbuf.WriteBool(%s)\n", fieldAccess)
case types.Int8:
+ fmt.Fprintf(buf, "\tbuf.WriteInt8(-1) //
NotNullValueFlag\n")
fmt.Fprintf(buf, "\tbuf.WriteInt8(%s)\n", fieldAccess)
case types.Int16:
+ fmt.Fprintf(buf, "\tbuf.WriteInt8(-1) //
NotNullValueFlag\n")
fmt.Fprintf(buf, "\tbuf.WriteInt16(%s)\n", fieldAccess)
case types.Int32:
- fmt.Fprintf(buf, "\tbuf.WriteInt32(%s)\n", fieldAccess)
+ fmt.Fprintf(buf, "\tbuf.WriteInt8(-1) //
NotNullValueFlag\n")
+ fmt.Fprintf(buf, "\tbuf.WriteVarint32(%s)\n",
fieldAccess)
case types.Int, types.Int64:
- fmt.Fprintf(buf, "\tbuf.WriteInt64(%s)\n", fieldAccess)
+ fmt.Fprintf(buf, "\tbuf.WriteInt8(-1) //
NotNullValueFlag\n")
+ fmt.Fprintf(buf, "\tbuf.WriteVarint64(%s)\n",
fieldAccess)
case types.Uint8:
+ fmt.Fprintf(buf, "\tbuf.WriteInt8(-1) //
NotNullValueFlag\n")
fmt.Fprintf(buf, "\tbuf.WriteByte_(%s)\n", fieldAccess)
case types.Uint16:
+ fmt.Fprintf(buf, "\tbuf.WriteInt8(-1) //
NotNullValueFlag\n")
fmt.Fprintf(buf, "\tbuf.WriteInt16(int16(%s))\n",
fieldAccess)
case types.Uint32:
+ fmt.Fprintf(buf, "\tbuf.WriteInt8(-1) //
NotNullValueFlag\n")
fmt.Fprintf(buf, "\tbuf.WriteInt32(int32(%s))\n",
fieldAccess)
case types.Uint, types.Uint64:
+ fmt.Fprintf(buf, "\tbuf.WriteInt8(-1) //
NotNullValueFlag\n")
fmt.Fprintf(buf, "\tbuf.WriteInt64(int64(%s))\n",
fieldAccess)
case types.Float32:
+ fmt.Fprintf(buf, "\tbuf.WriteInt8(-1) //
NotNullValueFlag\n")
fmt.Fprintf(buf, "\tbuf.WriteFloat32(%s)\n",
fieldAccess)
case types.Float64:
+ fmt.Fprintf(buf, "\tbuf.WriteInt8(-1) //
NotNullValueFlag\n")
fmt.Fprintf(buf, "\tbuf.WriteFloat64(%s)\n",
fieldAccess)
case types.String:
+ fmt.Fprintf(buf, "\tbuf.WriteInt8(0) // RefValueFlag\n")
fmt.Fprintf(buf, "\tfory.WriteString(buf, %s)\n",
fieldAccess)
default:
fmt.Fprintf(buf, "\t// TODO: unsupported basic type
%s\n", basic.String())
@@ -131,6 +145,57 @@ func generateFieldWriteTyped(buf *bytes.Buffer, field
*FieldInfo) error {
return nil
}
+ // Handle slice types
+ if slice, ok := field.Type.(*types.Slice); ok {
+ elemType := slice.Elem()
+ // Check if element type is interface{} (dynamic type)
+ if iface, ok := elemType.(*types.Interface); ok &&
iface.Empty() {
+ // For []interface{}, we need to manually implement the
serialization
+ // because WriteReferencable produces incorrect length
encoding
+ fmt.Fprintf(buf, "\t// Dynamic slice []interface{}
handling - manual serialization\n")
+ fmt.Fprintf(buf, "\tif %s == nil {\n", fieldAccess)
+ fmt.Fprintf(buf, "\t\tbuf.WriteInt8(-3) // null value
flag\n")
+ fmt.Fprintf(buf, "\t} else {\n")
+ fmt.Fprintf(buf, "\t\t// Write reference flag for the
slice itself\n")
+ fmt.Fprintf(buf, "\t\tbuf.WriteInt8(0) //
RefValueFlag\n")
+ fmt.Fprintf(buf, "\t\t// Write slice length\n")
+ fmt.Fprintf(buf,
"\t\tbuf.WriteVarUint32(uint32(len(%s)))\n", fieldAccess)
+ fmt.Fprintf(buf, "\t\t// Write collection flags (13 =
NotDeclElementType + NotSameType + TrackingRef for dynamic slices)\n")
+ fmt.Fprintf(buf, "\t\t// Always write collection flags
with tracking ref enabled (13)\n")
+ fmt.Fprintf(buf, "\t\t// This matches the reflection
implementation which uses NewFory(true)\n")
+ fmt.Fprintf(buf, "\t\tbuf.WriteInt8(13) // 12 + 1
(CollectionTrackingRef)\n")
+ fmt.Fprintf(buf, "\t\t// Write each element using
WriteReferencable\n")
+ fmt.Fprintf(buf, "\t\tfor _, elem := range %s {\n",
fieldAccess)
+ fmt.Fprintf(buf, "\t\t\tf.WriteReferencable(buf,
reflect.ValueOf(elem))\n")
+ fmt.Fprintf(buf, "\t\t}\n")
+ fmt.Fprintf(buf, "\t}\n")
+ return nil
+ }
+ // For static element types, use optimized inline generation
+ if err := generateSliceWriteInline(buf, slice, fieldAccess);
err != nil {
+ return err
+ }
+ return nil
+ }
+
+ // Handle map types
+ if mapType, ok := field.Type.(*types.Map); ok {
+ // For map types, we'll use manual serialization following the
chunk-based format
+ if err := generateMapWriteInline(buf, mapType, fieldAccess);
err != nil {
+ return err
+ }
+ return nil
+ }
+
+ // Handle interface types
+ if iface, ok := field.Type.(*types.Interface); ok {
+ if iface.Empty() {
+ // For interface{}, use WriteReferencable for dynamic
type handling
+ fmt.Fprintf(buf, "\tf.WriteReferencable(buf,
reflect.ValueOf(%s))\n", fieldAccess)
+ return nil
+ }
+ }
+
// Handle struct types
if _, ok := field.Type.Underlying().(*types.Struct); ok {
fmt.Fprintf(buf, "\tf.WriteReferencable(buf,
reflect.ValueOf(%s))\n", fieldAccess)
@@ -140,3 +205,387 @@ func generateFieldWriteTyped(buf *bytes.Buffer, field
*FieldInfo) error {
fmt.Fprintf(buf, "\t// TODO: unsupported type %s\n",
field.Type.String())
return nil
}
+
+// generateElementTypeIDWrite generates code to write the element type ID for
slice serialization
+func generateElementTypeIDWrite(buf *bytes.Buffer, elemType types.Type) error {
+ // Handle basic types
+ if basic, ok := elemType.Underlying().(*types.Basic); ok {
+ switch basic.Kind() {
+ case types.Bool:
+ fmt.Fprintf(buf, "\t\tbuf.WriteVarInt32(%d) // BOOL\n",
fory.BOOL)
+ case types.Int8:
+ fmt.Fprintf(buf, "\t\tbuf.WriteVarInt32(%d) // INT8\n",
fory.INT8)
+ case types.Int16:
+ fmt.Fprintf(buf, "\t\tbuf.WriteVarInt32(%d) //
INT16\n", fory.INT16)
+ case types.Int32:
+ fmt.Fprintf(buf, "\t\tbuf.WriteVarInt32(%d) //
INT32\n", fory.INT32)
+ case types.Int, types.Int64:
+ fmt.Fprintf(buf, "\t\tbuf.WriteVarInt32(%d) //
INT64\n", fory.INT64)
+ case types.Uint8:
+ fmt.Fprintf(buf, "\t\tbuf.WriteVarInt32(%d) //
UINT8\n", fory.UINT8)
+ case types.Uint16:
+ fmt.Fprintf(buf, "\t\tbuf.WriteVarInt32(%d) //
UINT16\n", fory.UINT16)
+ case types.Uint32:
+ fmt.Fprintf(buf, "\t\tbuf.WriteVarInt32(%d) //
UINT32\n", fory.UINT32)
+ case types.Uint, types.Uint64:
+ fmt.Fprintf(buf, "\t\tbuf.WriteVarInt32(%d) //
UINT64\n", fory.UINT64)
+ case types.Float32:
+ fmt.Fprintf(buf, "\t\tbuf.WriteVarInt32(%d) //
FLOAT\n", fory.FLOAT)
+ case types.Float64:
+ fmt.Fprintf(buf, "\t\tbuf.WriteVarInt32(%d) //
DOUBLE\n", fory.DOUBLE)
+ case types.String:
+ fmt.Fprintf(buf, "\t\tbuf.WriteVarInt32(%d) //
STRING\n", fory.STRING)
+ default:
+ return fmt.Errorf("unsupported basic type for element
type ID: %s", basic.String())
+ }
+ return nil
+ }
+
+ // Handle named types
+ if named, ok := elemType.(*types.Named); ok {
+ typeStr := named.String()
+ switch typeStr {
+ case "time.Time":
+ fmt.Fprintf(buf, "\t\tbuf.WriteVarInt32(%d) //
TIMESTAMP\n", fory.TIMESTAMP)
+ return nil
+ case "github.com/apache/fory/go/fory.Date":
+ fmt.Fprintf(buf, "\t\tbuf.WriteVarInt32(%d) //
LOCAL_DATE\n", fory.LOCAL_DATE)
+ return nil
+ }
+ // Check if it's a struct
+ if _, ok := named.Underlying().(*types.Struct); ok {
+ fmt.Fprintf(buf, "\t\tbuf.WriteVarInt32(%d) //
NAMED_STRUCT\n", fory.NAMED_STRUCT)
+ return nil
+ }
+ }
+
+ // Handle struct types
+ if _, ok := elemType.Underlying().(*types.Struct); ok {
+ fmt.Fprintf(buf, "\t\tbuf.WriteVarInt32(%d) // NAMED_STRUCT\n",
fory.NAMED_STRUCT)
+ return nil
+ }
+
+ return fmt.Errorf("unsupported element type for type ID: %s",
elemType.String())
+}
+
+// generateSliceWriteInline generates inline slice serialization code to match
reflection behavior exactly
+func generateSliceWriteInline(buf *bytes.Buffer, sliceType *types.Slice,
fieldAccess string) error {
+ elemType := sliceType.Elem()
+
+ // Write RefValueFlag first (slice is referencable)
+ fmt.Fprintf(buf, "\tbuf.WriteInt8(0) // RefValueFlag for slice\n")
+
+ // Write slice length - use block scope to avoid variable name conflicts
+ fmt.Fprintf(buf, "\t{\n")
+ fmt.Fprintf(buf, "\t\tsliceLen := 0\n")
+ fmt.Fprintf(buf, "\t\tif %s != nil {\n", fieldAccess)
+ fmt.Fprintf(buf, "\t\t\tsliceLen = len(%s)\n", fieldAccess)
+ fmt.Fprintf(buf, "\t\t}\n")
+ fmt.Fprintf(buf, "\t\tbuf.WriteVarUint32(uint32(sliceLen))\n")
+
+ // Write collection header and elements for non-empty slice
+ fmt.Fprintf(buf, "\t\tif sliceLen > 0 {\n")
+
+ // For codegen, follow reflection's behavior exactly:
+ // Set CollectionNotDeclElementType (0b0100 = 4) and
CollectionNotSameType (0b1000 = 8)
+ // Add CollectionTrackingRef (0b0001 = 1) when reference tracking is
enabled
+ fmt.Fprintf(buf, "\t\t\tcollectFlag := 12 //
CollectionNotDeclElementType + CollectionNotSameType\n")
+ fmt.Fprintf(buf, "\t\t\t// Access private field f.refTracking using
reflection to match behavior\n")
+ fmt.Fprintf(buf, "\t\t\tforyValue := reflect.ValueOf(f).Elem()\n")
+ fmt.Fprintf(buf, "\t\t\trefTrackingField :=
foryValue.FieldByName(\"refTracking\")\n")
+ fmt.Fprintf(buf, "\t\t\tif refTrackingField.IsValid() &&
refTrackingField.Bool() {\n")
+ fmt.Fprintf(buf, "\t\t\t\tcollectFlag |= 1 // Add
CollectionTrackingRef\n")
+ fmt.Fprintf(buf, "\t\t\t}\n")
+ fmt.Fprintf(buf, "\t\t\tbuf.WriteInt8(int8(collectFlag))\n")
+
+ // For each element, write type info + value (because
CollectionNotSameType is set)
+ fmt.Fprintf(buf, "\t\t\tfor _, elem := range %s {\n", fieldAccess)
+ fmt.Fprintf(buf, "\t\t\t\tbuf.WriteInt8(-1) // NotNullValueFlag\n")
+
+ // Write element type ID
+ if err := generateElementTypeIDWriteInline(buf, elemType); err != nil {
+ return err
+ }
+
+ // Write element value
+ if err := generateSliceElementWriteInline(buf, elemType, "elem"); err
!= nil {
+ return err
+ }
+
+ fmt.Fprintf(buf, "\t\t\t}\n")
+ fmt.Fprintf(buf, "\t\t}\n")
+ fmt.Fprintf(buf, "\t}\n")
+
+ return nil
+}
+
+// generateMapWriteInline generates inline map serialization code following
the chunk-based format
+func generateMapWriteInline(buf *bytes.Buffer, mapType *types.Map, fieldAccess
string) error {
+ keyType := mapType.Key()
+ valueType := mapType.Elem()
+
+ // Check if key or value types are interface{}
+ keyIsInterface := false
+ valueIsInterface := false
+ if iface, ok := keyType.(*types.Interface); ok && iface.Empty() {
+ keyIsInterface = true
+ }
+ if iface, ok := valueType.(*types.Interface); ok && iface.Empty() {
+ valueIsInterface = true
+ }
+
+ // Write RefValueFlag first (map is referencable)
+ fmt.Fprintf(buf, "\tbuf.WriteInt8(0) // RefValueFlag for map\n")
+
+ // Write map length
+ fmt.Fprintf(buf, "\t{\n")
+ fmt.Fprintf(buf, "\t\tmapLen := 0\n")
+ fmt.Fprintf(buf, "\t\tif %s != nil {\n", fieldAccess)
+ fmt.Fprintf(buf, "\t\t\tmapLen = len(%s)\n", fieldAccess)
+ fmt.Fprintf(buf, "\t\t}\n")
+ fmt.Fprintf(buf, "\t\tbuf.WriteVarUint32(uint32(mapLen))\n")
+
+ // Write chunks for non-empty map
+ fmt.Fprintf(buf, "\t\tif mapLen > 0 {\n")
+
+ // Calculate KV header based on types
+ fmt.Fprintf(buf, "\t\t\t// Calculate KV header flags\n")
+ fmt.Fprintf(buf, "\t\t\tkvHeader := uint8(0)\n")
+
+ // Check if ref tracking is enabled
+ fmt.Fprintf(buf, "\t\t\tforyValue := reflect.ValueOf(f).Elem()\n")
+ fmt.Fprintf(buf, "\t\t\trefTrackingField :=
foryValue.FieldByName(\"refTracking\")\n")
+ fmt.Fprintf(buf, "\t\t\tisRefTracking := refTrackingField.IsValid() &&
refTrackingField.Bool()\n")
+ fmt.Fprintf(buf, "\t\t\t_ = isRefTracking // Mark as used to avoid
warning\n")
+
+ // Set header flags based on type properties
+ if !keyIsInterface {
+ // For concrete key types, check if they're referencable
+ if isReferencableType(keyType) {
+ fmt.Fprintf(buf, "\t\t\tif isRefTracking {\n")
+ fmt.Fprintf(buf, "\t\t\t\tkvHeader |= 0x1 // track key
ref\n")
+ fmt.Fprintf(buf, "\t\t\t}\n")
+ }
+ } else {
+ // For interface{} keys, always set not declared type flag
+ fmt.Fprintf(buf, "\t\t\tkvHeader |= 0x4 // key type not
declared\n")
+ }
+
+ if !valueIsInterface {
+ // For concrete value types, check if they're referencable
+ if isReferencableType(valueType) {
+ fmt.Fprintf(buf, "\t\t\tif isRefTracking {\n")
+ fmt.Fprintf(buf, "\t\t\t\tkvHeader |= 0x8 // track
value ref\n")
+ fmt.Fprintf(buf, "\t\t\t}\n")
+ }
+ } else {
+ // For interface{} values, always set not declared type flag
+ fmt.Fprintf(buf, "\t\t\tkvHeader |= 0x20 // value type not
declared\n")
+ }
+
+ // Write map elements in chunks
+ fmt.Fprintf(buf, "\t\t\tchunkSize := 0\n")
+ fmt.Fprintf(buf, "\t\t\t_ = buf.WriterIndex() // chunkHeaderOffset\n")
+ fmt.Fprintf(buf, "\t\t\tbuf.WriteInt8(int8(kvHeader)) // KV header\n")
+ fmt.Fprintf(buf, "\t\t\tchunkSizeOffset := buf.WriterIndex()\n")
+ fmt.Fprintf(buf, "\t\t\tbuf.WriteInt8(0) // placeholder for chunk
size\n")
+
+ fmt.Fprintf(buf, "\t\t\tfor mapKey, mapValue := range %s {\n",
fieldAccess)
+
+ // Write key
+ if keyIsInterface {
+ fmt.Fprintf(buf, "\t\t\t\tf.WriteReferencable(buf,
reflect.ValueOf(mapKey))\n")
+ } else {
+ if err := generateMapKeyWrite(buf, keyType, "mapKey"); err !=
nil {
+ return err
+ }
+ }
+
+ // Write value
+ if valueIsInterface {
+ fmt.Fprintf(buf, "\t\t\t\tf.WriteReferencable(buf,
reflect.ValueOf(mapValue))\n")
+ } else {
+ if err := generateMapValueWrite(buf, valueType, "mapValue");
err != nil {
+ return err
+ }
+ }
+
+ fmt.Fprintf(buf, "\t\t\t\tchunkSize++\n")
+ fmt.Fprintf(buf, "\t\t\t\tif chunkSize >= 255 {\n")
+ fmt.Fprintf(buf, "\t\t\t\t\t// Write chunk size and start new chunk\n")
+ fmt.Fprintf(buf, "\t\t\t\t\tbuf.PutUint8(chunkSizeOffset,
uint8(chunkSize))\n")
+ fmt.Fprintf(buf, "\t\t\t\t\tif len(%s) > chunkSize {\n", fieldAccess)
+ fmt.Fprintf(buf, "\t\t\t\t\t\tchunkSize = 0\n")
+ fmt.Fprintf(buf, "\t\t\t\t\t\t_ = buf.WriterIndex() //
chunkHeaderOffset\n")
+ fmt.Fprintf(buf, "\t\t\t\t\t\tbuf.WriteInt8(int8(kvHeader)) // KV
header\n")
+ fmt.Fprintf(buf, "\t\t\t\t\t\tchunkSizeOffset = buf.WriterIndex()\n")
+ fmt.Fprintf(buf, "\t\t\t\t\t\tbuf.WriteInt8(0) // placeholder for chunk
size\n")
+ fmt.Fprintf(buf, "\t\t\t\t\t}\n")
+ fmt.Fprintf(buf, "\t\t\t\t}\n")
+
+ fmt.Fprintf(buf, "\t\t\t}\n") // end for loop
+
+ // Write final chunk size
+ fmt.Fprintf(buf, "\t\t\tif chunkSize > 0 {\n")
+ fmt.Fprintf(buf, "\t\t\t\tbuf.PutUint8(chunkSizeOffset,
uint8(chunkSize))\n")
+ fmt.Fprintf(buf, "\t\t\t}\n")
+
+ fmt.Fprintf(buf, "\t\t}\n") // end if mapLen > 0
+ fmt.Fprintf(buf, "\t}\n") // end block scope
+
+ return nil
+}
+
+// isReferencableType checks if a type is referencable (needs reference
tracking)
+func isReferencableType(t types.Type) bool {
+ // Handle pointer types
+ if _, ok := t.(*types.Pointer); ok {
+ return true
+ }
+
+ // Basic types and their underlying types
+ if basic, ok := t.Underlying().(*types.Basic); ok {
+ return basic.Kind() == types.String
+ }
+
+ // Slices, maps, and interfaces are referencable
+ switch t.Underlying().(type) {
+ case *types.Slice, *types.Map, *types.Interface:
+ return true
+ }
+
+ // Structs are referencable
+ if _, ok := t.Underlying().(*types.Struct); ok {
+ return true
+ }
+
+ return false
+}
+
+// generateMapKeyWrite generates code to write a map key
+func generateMapKeyWrite(buf *bytes.Buffer, keyType types.Type, varName
string) error {
+ // For basic types, match reflection's serializer behavior
+ if basic, ok := keyType.Underlying().(*types.Basic); ok {
+ switch basic.Kind() {
+ case types.Int:
+ // intSerializer uses WriteInt64, not WriteVarint64
+ fmt.Fprintf(buf, "\t\t\t\tbuf.WriteInt64(int64(%s))\n",
varName)
+ case types.String:
+ // stringSerializer is referencable, need to use
WriteReferencable
+ fmt.Fprintf(buf, "\t\t\t\tf.WriteReferencable(buf,
reflect.ValueOf(%s))\n", varName)
+ default:
+ return fmt.Errorf("unsupported map key type: %v",
keyType)
+ }
+ return nil
+ }
+
+ // For other types, use WriteReferencable
+ fmt.Fprintf(buf, "\t\t\t\tf.WriteReferencable(buf,
reflect.ValueOf(%s))\n", varName)
+ return nil
+}
+
+// generateMapValueWrite generates code to write a map value
+func generateMapValueWrite(buf *bytes.Buffer, valueType types.Type, varName
string) error {
+ // For basic types, match reflection's serializer behavior
+ if basic, ok := valueType.Underlying().(*types.Basic); ok {
+ switch basic.Kind() {
+ case types.Int:
+ // intSerializer uses WriteInt64, not WriteVarint64
+ fmt.Fprintf(buf, "\t\t\t\tbuf.WriteInt64(int64(%s))\n",
varName)
+ case types.String:
+ // stringSerializer is referencable, need to use
WriteReferencable
+ fmt.Fprintf(buf, "\t\t\t\tf.WriteReferencable(buf,
reflect.ValueOf(%s))\n", varName)
+ default:
+ return fmt.Errorf("unsupported map value type: %v",
valueType)
+ }
+ return nil
+ }
+
+ // For other types, use WriteReferencable
+ fmt.Fprintf(buf, "\t\t\t\tf.WriteReferencable(buf,
reflect.ValueOf(%s))\n", varName)
+ return nil
+}
+
+// generateElementTypeIDWriteInline generates element type ID write with
specific indentation
+func generateElementTypeIDWriteInline(buf *bytes.Buffer, elemType types.Type)
error {
+ // Handle basic types
+ if basic, ok := elemType.Underlying().(*types.Basic); ok {
+ switch basic.Kind() {
+ case types.Bool:
+ fmt.Fprintf(buf, "\t\t\t\tbuf.WriteVarInt32(%d) //
BOOL\n", fory.BOOL)
+ case types.Int8:
+ fmt.Fprintf(buf, "\t\t\t\tbuf.WriteVarInt32(%d) //
INT8\n", fory.INT8)
+ case types.Int16:
+ fmt.Fprintf(buf, "\t\t\t\tbuf.WriteVarInt32(%d) //
INT16\n", fory.INT16)
+ case types.Int32:
+ fmt.Fprintf(buf, "\t\t\t\tbuf.WriteVarInt32(%d) //
INT32\n", fory.INT32)
+ case types.Int, types.Int64:
+ fmt.Fprintf(buf, "\t\t\t\tbuf.WriteVarInt32(%d) //
INT64\n", fory.INT64)
+ case types.Uint8:
+ fmt.Fprintf(buf, "\t\t\t\tbuf.WriteVarInt32(%d) //
UINT8\n", fory.UINT8)
+ case types.Uint16:
+ fmt.Fprintf(buf, "\t\t\t\tbuf.WriteVarInt32(%d) //
UINT16\n", fory.UINT16)
+ case types.Uint32:
+ fmt.Fprintf(buf, "\t\t\t\tbuf.WriteVarInt32(%d) //
UINT32\n", fory.UINT32)
+ case types.Uint, types.Uint64:
+ fmt.Fprintf(buf, "\t\t\t\tbuf.WriteVarInt32(%d) //
UINT64\n", fory.UINT64)
+ case types.Float32:
+ fmt.Fprintf(buf, "\t\t\t\tbuf.WriteVarInt32(%d) //
FLOAT\n", fory.FLOAT)
+ case types.Float64:
+ fmt.Fprintf(buf, "\t\t\t\tbuf.WriteVarInt32(%d) //
DOUBLE\n", fory.DOUBLE)
+ case types.String:
+ fmt.Fprintf(buf, "\t\t\t\tbuf.WriteVarInt32(%d) //
STRING\n", fory.STRING)
+ default:
+ return fmt.Errorf("unsupported basic type for element
type ID: %s", basic.String())
+ }
+ return nil
+ }
+ return fmt.Errorf("unsupported element type for type ID: %s",
elemType.String())
+}
+
+// generateSliceElementWriteInline generates code to write a single slice
element value
+func generateSliceElementWriteInline(buf *bytes.Buffer, elemType types.Type,
elemAccess string) error {
+ // Handle basic types - write the actual value without type info (type
already written above)
+ if basic, ok := elemType.Underlying().(*types.Basic); ok {
+ switch basic.Kind() {
+ case types.Bool:
+ fmt.Fprintf(buf, "\t\t\t\tbuf.WriteBool(%s)\n",
elemAccess)
+ case types.Int8:
+ fmt.Fprintf(buf, "\t\t\t\tbuf.WriteInt8(%s)\n",
elemAccess)
+ case types.Int16:
+ fmt.Fprintf(buf, "\t\t\t\tbuf.WriteInt16(%s)\n",
elemAccess)
+ case types.Int32:
+ fmt.Fprintf(buf, "\t\t\t\tbuf.WriteVarint32(%s)\n",
elemAccess)
+ case types.Int, types.Int64:
+ fmt.Fprintf(buf, "\t\t\t\tbuf.WriteVarint64(%s)\n",
elemAccess)
+ case types.Uint8:
+ fmt.Fprintf(buf, "\t\t\t\tbuf.WriteByte_(%s)\n",
elemAccess)
+ case types.Uint16:
+ fmt.Fprintf(buf, "\t\t\t\tbuf.WriteInt16(int16(%s))\n",
elemAccess)
+ case types.Uint32:
+ fmt.Fprintf(buf, "\t\t\t\tbuf.WriteInt32(int32(%s))\n",
elemAccess)
+ case types.Uint, types.Uint64:
+ fmt.Fprintf(buf, "\t\t\t\tbuf.WriteInt64(int64(%s))\n",
elemAccess)
+ case types.Float32:
+ fmt.Fprintf(buf, "\t\t\t\tbuf.WriteFloat32(%s)\n",
elemAccess)
+ case types.Float64:
+ fmt.Fprintf(buf, "\t\t\t\tbuf.WriteFloat64(%s)\n",
elemAccess)
+ case types.String:
+ fmt.Fprintf(buf, "\t\t\t\tfory.WriteString(buf, %s)\n",
elemAccess)
+ default:
+ return fmt.Errorf("unsupported basic type for element
write: %s", basic.String())
+ }
+ return nil
+ }
+
+ // Handle interface types
+ if iface, ok := elemType.(*types.Interface); ok {
+ if iface.Empty() {
+ // For interface{} elements, use WriteReferencable for
dynamic type handling
+ fmt.Fprintf(buf, "\t\t\t\tf.WriteReferencable(buf,
reflect.ValueOf(%s))\n", elemAccess)
+ return nil
+ }
+ }
+
+ return fmt.Errorf("unsupported element type for write: %s",
elemType.String())
+}
diff --git a/go/fory/codegen/guard.go b/go/fory/codegen/guard.go
index c346f439d..b73aed571 100644
--- a/go/fory/codegen/guard.go
+++ b/go/fory/codegen/guard.go
@@ -53,14 +53,15 @@ func generateStructGuard(buf *bytes.Buffer, structInfo
StructInfo) {
buf.WriteString(fmt.Sprintf("// Snapshot of %s's underlying type at
generation time.\n", typeName))
buf.WriteString(fmt.Sprintf("type %s struct {\n", expectedTypeName))
- // Sort fields to ensure consistent ordering (using pointers)
- fields := make([]*FieldInfo, len(structInfo.Fields))
- copy(fields, structInfo.Fields)
- sort.Slice(fields, func(i, j int) bool {
- return fields[i].GoName < fields[j].GoName
+ // Sort fields by their original index to match the struct definition
+ // This is important for the compile-time guard to work correctly
+ originalFields := make([]*FieldInfo, len(structInfo.Fields))
+ copy(originalFields, structInfo.Fields)
+ sort.Slice(originalFields, func(i, j int) bool {
+ return originalFields[i].Index < originalFields[j].Index
})
- for _, field := range fields {
+ for _, field := range originalFields {
buf.WriteString(fmt.Sprintf("\t%s %s", field.GoName,
formatFieldType(*field)))
// Add struct tag if present (we'll extract it from the
original struct)
diff --git a/go/fory/codegen/utils.go b/go/fory/codegen/utils.go
index 00dde6424..18c5de055 100644
--- a/go/fory/codegen/utils.go
+++ b/go/fory/codegen/utils.go
@@ -18,11 +18,12 @@
package codegen
import (
- "crypto/md5"
- "encoding/binary"
+ "fmt"
"go/types"
"sort"
"unicode"
+
+ "github.com/apache/fory/go/fory"
)
// FieldInfo contains metadata about a struct field
@@ -62,6 +63,18 @@ func isSupportedFieldType(t types.Type) bool {
t = ptr.Elem()
}
+ // Check slice types
+ if slice, ok := t.(*types.Slice); ok {
+ // Check if element type is supported
+ return isSupportedFieldType(slice.Elem())
+ }
+
+ // Check map types
+ if mapType, ok := t.(*types.Map); ok {
+ // Check if both key and value types are supported
+ return isSupportedFieldType(mapType.Key()) &&
isSupportedFieldType(mapType.Elem())
+ }
+
// Check named types
if named, ok := t.(*types.Named); ok {
typeStr := named.String()
@@ -75,6 +88,14 @@ func isSupportedFieldType(t types.Type) bool {
}
}
+ // Check interface types
+ if iface, ok := t.(*types.Interface); ok {
+ // Support empty interface{} for dynamic types
+ if iface.Empty() {
+ return true
+ }
+ }
+
// Check basic types
if basic, ok := t.Underlying().(*types.Basic); ok {
switch basic.Kind() {
@@ -105,10 +126,8 @@ func isPrimitiveType(t types.Type) bool {
}
}
- // String is also considered primitive in Fory context but nullable
- if basic, ok := t.Underlying().(*types.Basic); ok && basic.Kind() ==
types.String {
- return true
- }
+ // String is NOT considered primitive for sorting purposes (it goes to
final group)
+ // This matches reflection's behavior where STRING goes to final group,
not boxed group
return false
}
@@ -120,6 +139,23 @@ func getTypeID(t types.Type) string {
t = ptr.Elem()
}
+ // Check slice types
+ if _, ok := t.(*types.Slice); ok {
+ return "LIST"
+ }
+
+ // Check map types
+ if _, ok := t.(*types.Map); ok {
+ return "MAP"
+ }
+
+ // Check interface types
+ if iface, ok := t.(*types.Interface); ok {
+ if iface.Empty() {
+ return "INTERFACE" // Use a placeholder for empty
interface{}
+ }
+ }
+
// Check named types first
if named, ok := t.(*types.Named); ok {
typeStr := named.String()
@@ -194,38 +230,55 @@ func getPrimitiveSize(t types.Type) int {
}
// getTypeIDValue returns numeric value for type ID for sorting
+// This uses the actual Fory TypeId constants for accuracy
func getTypeIDValue(typeID string) int {
- // Map Fory TypeIDs to numeric values for sorting
- typeIDMap := map[string]int{
- "BOOL": 1,
- "INT8": 2,
- "INT16": 3,
- "INT32": 4,
- "INT64": 5,
- "UINT8": 6,
- "UINT16": 7,
- "UINT32": 8,
- "UINT64": 9,
- "FLOAT32": 10,
- "FLOAT64": 11,
- "STRING": 12,
- "TIMESTAMP": 20,
- "LOCAL_DATE": 21,
- "NAMED_STRUCT": 30,
+ switch typeID {
+ case "BOOL":
+ return int(fory.BOOL) // 1
+ case "INT8":
+ return int(fory.INT8) // 2
+ case "INT16":
+ return int(fory.INT16) // 3
+ case "INT32":
+ return int(fory.INT32) // 4
+ case "INT64":
+ return int(fory.INT64) // 6
+ case "UINT8":
+ return int(fory.UINT8) // 100
+ case "UINT16":
+ return int(fory.UINT16) // 101
+ case "UINT32":
+ return int(fory.UINT32) // 102
+ case "UINT64":
+ return int(fory.UINT64) // 103
+ case "FLOAT32":
+ return int(fory.FLOAT) // 10
+ case "FLOAT64":
+ return int(fory.DOUBLE) // 11
+ case "STRING":
+ return int(fory.STRING) // 12
+ case "TIMESTAMP":
+ return int(fory.TIMESTAMP) // 25
+ case "LOCAL_DATE":
+ return int(fory.LOCAL_DATE) // 26
+ case "NAMED_STRUCT":
+ return int(fory.NAMED_STRUCT) // 17
+ case "LIST":
+ return int(fory.LIST) // 21
+ case "MAP":
+ return int(fory.MAP) // 23
+ default:
+ return 999 // Unknown types sort last
}
-
- if val, ok := typeIDMap[typeID]; ok {
- return val
- }
- return 999
}
-// sortFields sorts fields according to Fory protocol
+// sortFields sorts fields according to Fory protocol specification
+// This matches the reflection-based sorting exactly for cross-language
compatibility
func sortFields(fields []*FieldInfo) {
sort.Slice(fields, func(i, j int) bool {
f1, f2 := fields[i], fields[j]
- // Group primitives first
+ // Group primitives first (matching reflection's boxed group)
if f1.IsPrimitive && !f2.IsPrimitive {
return true
}
@@ -234,17 +287,28 @@ func sortFields(fields []*FieldInfo) {
}
if f1.IsPrimitive && f2.IsPrimitive {
- // Sort primitives by size (descending), then by type
ID, then by name
+ // Match reflection's boxed sorting logic exactly
+ // First: handle compression types
(INT32/INT64/VAR_INT32/VAR_INT64)
+ compressI := f1.TypeID == "INT32" || f1.TypeID ==
"INT64" ||
+ f1.TypeID == "VAR_INT32" || f1.TypeID ==
"VAR_INT64"
+ compressJ := f2.TypeID == "INT32" || f2.TypeID ==
"INT64" ||
+ f2.TypeID == "VAR_INT32" || f2.TypeID ==
"VAR_INT64"
+
+ if compressI != compressJ {
+ return !compressI && compressJ // non-compress
comes first
+ }
+
+ // Then: by size (descending)
if f1.PrimitiveSize != f2.PrimitiveSize {
return f1.PrimitiveSize > f2.PrimitiveSize
}
- if f1.TypeID != f2.TypeID {
- return getTypeIDValue(f1.TypeID) <
getTypeIDValue(f2.TypeID)
- }
+
+ // Finally: by name (ascending)
return f1.SnakeName < f2.SnakeName
}
- // Sort non-primitives by type ID, then by name
+ // For non-primitives: STRING comes in final group, others in
others group
+ // All sorted by type ID, then by name (matching reflection)
if f1.TypeID != f2.TypeID {
return getTypeIDValue(f1.TypeID) <
getTypeIDValue(f2.TypeID)
}
@@ -253,27 +317,83 @@ func sortFields(fields []*FieldInfo) {
}
// computeStructHash computes a hash for struct schema compatibility
+// This implementation aligns with the reflection-based hash calculation
func computeStructHash(s *StructInfo) int32 {
- h := md5.New()
+ // Use the same iterative algorithm as reflection
+ var hash int32 = 17
- // Write struct name
- h.Write([]byte(s.Name))
-
- // Write sorted field information
+ // Process fields in the same order as reflection
for _, field := range s.Fields {
- h.Write([]byte(field.SnakeName))
- h.Write([]byte(field.TypeID))
- // Add primitive size for better differentiation
- if field.IsPrimitive {
- sizeBytes := make([]byte, 4)
- binary.LittleEndian.PutUint32(sizeBytes,
uint32(field.PrimitiveSize))
- h.Write(sizeBytes)
+ id := getFieldHashID(field)
+
+ // Same algorithm as reflection: hash = hash * 31 + id
+ newHash := int64(hash)*31 + int64(id)
+
+ // Same overflow handling as reflection
+ const MaxInt32 = 2147483647
+ for newHash >= MaxInt32 {
+ newHash /= 7
}
+ hash = int32(newHash)
+ }
+
+ if hash == 0 {
+ // Same panic condition as reflection
+ panic(fmt.Errorf("hash for type %v is 0", s.Name))
+ }
+
+ return hash
+}
+
+// getFieldHashID computes the field ID for hash calculation, matching
reflection logic exactly
+func getFieldHashID(field *FieldInfo) int32 {
+ // Map Go types to Fory TypeIds (exactly matching reflection)
+ var tid int16
+
+ switch field.TypeID {
+ case "BOOL":
+ tid = fory.BOOL
+ case "INT8":
+ tid = fory.INT8
+ case "INT16":
+ tid = fory.INT16
+ case "INT32":
+ tid = fory.INT32
+ case "INT64":
+ tid = fory.INT64
+ case "UINT8":
+ tid = fory.UINT8
+ case "UINT16":
+ tid = fory.UINT16
+ case "UINT32":
+ tid = fory.UINT32
+ case "UINT64":
+ tid = fory.UINT64
+ case "FLOAT32":
+ tid = fory.FLOAT
+ case "FLOAT64":
+ tid = fory.DOUBLE
+ case "STRING":
+ tid = fory.STRING
+ case "TIMESTAMP":
+ tid = fory.TIMESTAMP
+ case "LOCAL_DATE":
+ tid = fory.LOCAL_DATE
+ case "NAMED_STRUCT":
+ tid = fory.NAMED_STRUCT
+ case "LIST":
+ tid = fory.LIST
+ case "MAP":
+ tid = fory.MAP
+ default:
+ tid = 0 // Unknown type
}
- hashBytes := h.Sum(nil)
- // Take first 4 bytes as int32
- return int32(binary.LittleEndian.Uint32(hashBytes[:4]))
+ // Same logic as reflection: handle negative TypeIds
+ if tid < 0 {
+ return -int32(tid)
+ }
+ return int32(tid)
}
// getStructNames extracts struct names from StructInfo slice
diff --git a/go/fory/struct.go b/go/fory/struct.go
index 02b230279..77e5b8056 100644
--- a/go/fory/struct.go
+++ b/go/fory/struct.go
@@ -26,11 +26,12 @@ import (
)
type structSerializer struct {
- typeTag string
- type_ reflect.Type
- fieldsInfo structFieldsInfo
- structHash int32
- fieldDefs []FieldDef // defs obtained during reading
+ typeTag string
+ type_ reflect.Type
+ fieldsInfo structFieldsInfo
+ structHash int32
+ fieldDefs []FieldDef // defs obtained during reading
+ codegenDelegate Serializer // Optional codegen serializer for
performance (like Python's approach)
}
var UNKNOWN_TYPE_ID = int16(-1)
@@ -44,6 +45,12 @@ func (s *structSerializer) NeedWriteRef() bool {
}
func (s *structSerializer) Write(f *Fory, buf *ByteBuffer, value
reflect.Value) error {
+ // If we have a codegen delegate, use it for optimal performance
+ if s.codegenDelegate != nil {
+ return s.codegenDelegate.Write(f, buf, value)
+ }
+
+ // Fall back to reflection-based serialization
// TODO support fields back and forward compatible. need to serialize
fields name too.
if s.fieldsInfo == nil {
if fieldsInfo, err := createStructFieldInfos(f, s.type_); err
!= nil {
@@ -78,6 +85,12 @@ func (s *structSerializer) Write(f *Fory, buf *ByteBuffer,
value reflect.Value)
}
func (s *structSerializer) Read(f *Fory, buf *ByteBuffer, type_ reflect.Type,
value reflect.Value) error {
+ // If we have a codegen delegate, use it for optimal performance
+ if s.codegenDelegate != nil {
+ return s.codegenDelegate.Read(f, buf, type_, value)
+ }
+
+ // Fall back to reflection-based deserialization
// struct value may be a value type if it's not a pointer, so we don't
invoke `refResolver.Reference` here,
// but invoke it in `ptrToStructSerializer` instead.
if value.Kind() == reflect.Ptr {
@@ -444,6 +457,7 @@ func (x structFieldsInfo) Swap(i, j int) { x[i], x[j] =
x[j], x[i] }
type ptrToStructSerializer struct {
type_ reflect.Type
structSerializer
+ codegenDelegate Serializer // Optional codegen serializer for
performance (like Python's approach)
}
func (s *ptrToStructSerializer) TypeId() TypeId {
@@ -455,10 +469,22 @@ func (s *ptrToStructSerializer) NeedWriteRef() bool {
}
func (s *ptrToStructSerializer) Write(f *Fory, buf *ByteBuffer, value
reflect.Value) error {
+ // If we have a codegen delegate, use it for optimal performance
(Python-style approach)
+ if s.codegenDelegate != nil {
+ return s.codegenDelegate.Write(f, buf, value)
+ }
+
+ // Fall back to reflection-based serialization
return s.structSerializer.Write(f, buf, value.Elem())
}
func (s *ptrToStructSerializer) Read(f *Fory, buf *ByteBuffer, type_
reflect.Type, value reflect.Value) error {
+ // If we have a codegen delegate, use it for optimal performance
+ if s.codegenDelegate != nil {
+ return s.codegenDelegate.Read(f, buf, type_, value)
+ }
+
+ // Fall back to reflection-based deserialization
newValue := reflect.New(type_.Elem())
value.Set(newValue)
elem := newValue.Elem()
diff --git a/go/fory/tests/generator_test.go b/go/fory/tests/generator_test.go
index 452554783..f5d0d120b 100644
--- a/go/fory/tests/generator_test.go
+++ b/go/fory/tests/generator_test.go
@@ -33,12 +33,16 @@ func TestValidationDemo(t *testing.T) {
A: 12345, // int32
B: "Hello Fory!", // string
C: 98765, // int64
+ D: 3.14159, // float64
+ E: true, // bool
}
// Validate original data structure
assert.Equal(t, int32(12345), original.A, "Original A should be 12345")
assert.Equal(t, "Hello Fory!", original.B, "Original B should be 'Hello
Fory!'")
assert.Equal(t, int64(98765), original.C, "Original C should be 98765")
+ assert.Equal(t, 3.14159, original.D, "Original D should be 3.14159")
+ assert.Equal(t, true, original.E, "Original E should be true")
// 2. Serialize using generated code
f := fory.NewFory(true)
@@ -53,11 +57,157 @@ func TestValidationDemo(t *testing.T) {
require.NoError(t, err, "Deserialization should not fail")
require.NotNil(t, result, "Deserialized result should not be nil")
- // 4. Validate round-trip serialization
- assert.Equal(t, original.A, result.A, "Field A should match after
round-trip")
- assert.Equal(t, original.B, result.B, "Field B should match after
round-trip")
- assert.Equal(t, original.C, result.C, "Field C should match after
round-trip")
+ // 4. Assert that serializer is the generated serializer
+ validationSerializer := NewSerializerFor_ValidationDemo()
+ _, ok := validationSerializer.(ValidationDemo_ForyGenSerializer)
+ assert.True(t, ok, "Serializer should be the generated
ValidationDemo_ForyGenSerializer")
+}
+
+func TestSliceDemo(t *testing.T) {
+ // 1. Create test instance with various slice types
+ original := &SliceDemo{
+ IntSlice: []int32{10, 20, 30, 40, 50},
+ StringSlice: []string{"hello", "world", "fory", "slice"},
+ FloatSlice: []float64{1.1, 2.2, 3.3, 4.4, 5.5},
+ BoolSlice: []bool{true, false, true, false},
+ }
+
+ // Validate original data structure (quick sanity check)
+ assert.NotEmpty(t, original.IntSlice, "IntSlice should not be empty")
+ assert.NotEmpty(t, original.StringSlice, "StringSlice should not be
empty")
+ assert.NotEmpty(t, original.FloatSlice, "FloatSlice should not be
empty")
+ assert.NotEmpty(t, original.BoolSlice, "BoolSlice should not be empty")
+
+ // 2. Serialize using generated code
+ f := fory.NewFory(true)
+ data, err := f.Marshal(original)
+ require.NoError(t, err, "Serialization should not fail")
+ require.NotEmpty(t, data, "Serialized data should not be empty")
+ assert.Greater(t, len(data), 0, "Serialized data should have positive
length")
+
+ // 3. Deserialize using generated code
+ var result *SliceDemo
+ err = f.Unmarshal(data, &result)
+ require.NoError(t, err, "Deserialization should not fail")
+ require.NotNil(t, result, "Deserialized result should not be nil")
+
+ // 4. Assert that serializer is the generated serializer
+ sliceSerializer := NewSerializerFor_SliceDemo()
+ _, ok := sliceSerializer.(SliceDemo_ForyGenSerializer)
+ assert.True(t, ok, "Serializer should be the generated
SliceDemo_ForyGenSerializer")
+}
+
+func TestDynamicSliceDemo(t *testing.T) {
+ // 1. Create test instance with various interface{} types
+ original := &DynamicSliceDemo{
+ DynamicSlice: []interface{}{
+ int32(42),
+ "hello",
+ float64(3.14),
+ true,
+ int64(12345),
+ },
+ }
+
+ // Validate original data structure (quick sanity check)
+ assert.Equal(t, 5, len(original.DynamicSlice), "DynamicSlice should
have 5 elements")
+ assert.Equal(t, int32(42), original.DynamicSlice[0], "First element
should be int32(42)")
+ assert.Equal(t, "hello", original.DynamicSlice[1], "Second element
should be 'hello'")
+ assert.Equal(t, float64(3.14), original.DynamicSlice[2], "Third element
should be float64(3.14)")
+ assert.Equal(t, true, original.DynamicSlice[3], "Fourth element should
be true")
+ assert.Equal(t, int64(12345), original.DynamicSlice[4], "Fifth element
should be int64(12345)")
+
+ // 2. Serialize using generated code
+ f := fory.NewFory(true)
+ data, err := f.Marshal(original)
+ require.NoError(t, err, "Serialization should not fail")
+ require.NotEmpty(t, data, "Serialized data should not be empty")
+ assert.Greater(t, len(data), 0, "Serialized data should have positive
length")
+
+ // 3. Deserialize using generated code
+ var result *DynamicSliceDemo
+ err = f.Unmarshal(data, &result)
+ require.NoError(t, err, "Deserialization should not fail")
+ require.NotNil(t, result, "Deserialized result should not be nil")
+
+ // 4. Assert that serializer is the generated serializer
+ dynamicSerializer := NewSerializerFor_DynamicSliceDemo()
+ _, ok := dynamicSerializer.(DynamicSliceDemo_ForyGenSerializer)
+ assert.True(t, ok, "Serializer should be the generated
DynamicSliceDemo_ForyGenSerializer")
+}
+
+func TestDynamicSliceDemoWithNilAndEmpty(t *testing.T) {
+ // Test with nil and empty dynamic slices
+ original := &DynamicSliceDemo{
+ DynamicSlice: nil, // nil slice
+ }
+
+ // Serialize using generated code
+ f := fory.NewFory(true)
+ data, err := f.Marshal(original)
+ require.NoError(t, err, "Serialization should not fail")
+ require.NotEmpty(t, data, "Serialized data should not be empty")
+
+ // Deserialize using generated code
+ var result *DynamicSliceDemo
+ err = f.Unmarshal(data, &result)
+ require.NoError(t, err, "Deserialization should not fail")
+ require.NotNil(t, result, "Deserialized result should not be nil")
+
+ // Validate nil slice handling
+ assert.Nil(t, result.DynamicSlice, "DynamicSlice should be nil after
round-trip")
+
+ // Test with empty slice
+ originalEmpty := &DynamicSliceDemo{
+ DynamicSlice: []interface{}{}, // empty slice
+ }
+
+ dataEmpty, err := f.Marshal(originalEmpty)
+ require.NoError(t, err, "Empty slice serialization should not fail")
+
+ var resultEmpty *DynamicSliceDemo
+ err = f.Unmarshal(dataEmpty, &resultEmpty)
+ require.NoError(t, err, "Empty slice deserialization should not fail")
+ require.NotNil(t, resultEmpty, "Deserialized result should not be nil")
+
+ // Empty slice should remain empty (or become nil, depending on
reflection behavior)
+ assert.Equal(t, 0, len(resultEmpty.DynamicSlice), "DynamicSlice should
be empty after round-trip")
+}
+
+// TestMapDemo tests basic map serialization and deserialization (including
nil maps)
+func TestMapDemo(t *testing.T) {
+ // Create test instance with various map types (including nil)
+ instance := &MapDemo{
+ StringMap: map[string]string{
+ "key1": "value1",
+ "key2": "value2",
+ },
+ IntMap: map[int]int{
+ 1: 100,
+ 2: 200,
+ 3: 300,
+ },
+ MixedMap: nil, // Test nil map handling
+ }
+
+ // Serialize with codegen
+ f := fory.NewFory(true)
+ data, err := f.Marshal(instance)
+ require.NoError(t, err, "Serialization failed")
+
+ // Deserialize back
+ var result MapDemo
+ err = f.Unmarshal(data, &result)
+ require.NoError(t, err, "Deserialization failed")
+
+ // Verify using generated serializer
+ serializer := NewSerializerFor_MapDemo()
+ assert.NotNil(t, serializer, "Generated serializer should exist")
- // 5. Validate data integrity
- assert.EqualValues(t, original, result, "Complete struct should match
after round-trip")
+ // Verify map contents
+ assert.EqualValues(t, instance.StringMap, result.StringMap, "StringMap
mismatch")
+ assert.EqualValues(t, instance.IntMap, result.IntMap, "IntMap mismatch")
+ // MixedMap was nil, should become empty after deserialization
+ assert.NotNil(t, result.MixedMap, "Expected non-nil MixedMap after
deserialization")
+ assert.Empty(t, result.MixedMap, "Expected empty MixedMap since
original was nil")
}
diff --git a/go/fory/tests/generator_xlang_test.go
b/go/fory/tests/generator_xlang_test.go
new file mode 100644
index 000000000..3bc49711c
--- /dev/null
+++ b/go/fory/tests/generator_xlang_test.go
@@ -0,0 +1,291 @@
+// 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.
+
+package fory
+
+import (
+ "reflect"
+ "testing"
+
+ forygo "github.com/apache/fory/go/fory"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+// TestActualCodegenName - Analyze actual type names used by codegen
+func TestActualCodegenName(t *testing.T) {
+ // From source code analysis:
+ // RegisterSerializerFactory calculates: typeTag := pkgPath + "." +
typeName
+
+ validationDemoType := reflect.TypeOf(ValidationDemo{})
+ pkgPath := validationDemoType.PkgPath()
+ typeName := validationDemoType.Name()
+ expectedTypeTag := pkgPath + "." + typeName
+
+ // Create test data
+ codegenInstance := &ValidationDemo{
+ A: 100,
+ B: "test_data",
+ C: 200,
+ D: 3.14159,
+ E: true,
+ }
+
+ type ReflectStruct struct {
+ A int32
+ B string
+ C int64
+ D float64
+ E bool
+ }
+
+ reflectInstance := &ReflectStruct{
+ A: 100,
+ B: "test_data",
+ C: 200,
+ D: 3.14159,
+ E: true,
+ }
+
+ // Codegen mode (automatically uses full name)
+ foryForCodegen := forygo.NewFory(true)
+
+ // Reflect mode (register with full name)
+ foryForReflect := forygo.NewFory(true)
+ err := foryForReflect.RegisterTagType(expectedTypeTag, ReflectStruct{})
+ require.NoError(t, err, "Should be able to register ReflectStruct with
full name")
+
+ // Serialization test
+ codegenData, err := foryForCodegen.Marshal(codegenInstance)
+ require.NoError(t, err, "Codegen serialization should not fail")
+
+ reflectData, err := foryForReflect.Marshal(reflectInstance)
+ require.NoError(t, err, "Reflect serialization should not fail")
+
+ // Use reflect to deserialize codegen data
+ var reflectResult *ReflectStruct
+ err = foryForReflect.Unmarshal(codegenData, &reflectResult)
+ require.NoError(t, err, "Reflect should be able to deserialize codegen
data")
+ require.NotNil(t, reflectResult, "Reflect result should not be nil")
+
+ // Verify content matches original
+ assert.EqualValues(t, codegenInstance, reflectResult, "Reflect
deserialized data should match original")
+
+ // Use codegen to deserialize reflect data
+ var codegenResult *ValidationDemo
+ err = foryForCodegen.Unmarshal(reflectData, &codegenResult)
+ require.NoError(t, err, "Codegen should be able to deserialize reflect
data")
+ require.NotNil(t, codegenResult, "Codegen result should not be nil")
+
+ // Verify content matches original
+ assert.EqualValues(t, reflectInstance, codegenResult, "Codegen
deserialized data should match original")
+}
+
+// TestSliceDemoXlang - Test cross-language compatibility of SliceDemo
+func TestSliceDemoXlang(t *testing.T) {
+ // Get SliceDemo type information
+ sliceDemoType := reflect.TypeOf(SliceDemo{})
+ pkgPath := sliceDemoType.PkgPath()
+ typeName := sliceDemoType.Name()
+ expectedTypeTag := pkgPath + "." + typeName
+
+ // Create test data
+ codegenInstance := &SliceDemo{
+ IntSlice: []int32{1, 2, 3, 4, 5},
+ StringSlice: []string{"hello", "world", "fory"},
+ FloatSlice: []float64{1.1, 2.2, 3.3},
+ BoolSlice: []bool{true, false, true},
+ }
+
+ // Define equivalent struct using reflection
+ type ReflectSliceStruct struct {
+ IntSlice []int32
+ StringSlice []string
+ FloatSlice []float64
+ BoolSlice []bool
+ }
+
+ reflectInstance := &ReflectSliceStruct{
+ IntSlice: []int32{1, 2, 3, 4, 5},
+ StringSlice: []string{"hello", "world", "fory"},
+ FloatSlice: []float64{1.1, 2.2, 3.3},
+ BoolSlice: []bool{true, false, true},
+ }
+
+ // Codegen mode - enable reference tracking
+ foryForCodegen := forygo.NewFory(true)
+
+ // Reflect mode - enable reference tracking
+ foryForReflect := forygo.NewFory(true)
+ err := foryForReflect.RegisterTagType(expectedTypeTag,
ReflectSliceStruct{})
+ require.NoError(t, err, "Should be able to register ReflectSliceStruct
with full name")
+
+ // Serialization test
+ codegenData, err := foryForCodegen.Marshal(codegenInstance)
+ require.NoError(t, err, "Codegen serialization should not fail")
+
+ reflectData, err := foryForReflect.Marshal(reflectInstance)
+ require.NoError(t, err, "Reflect serialization should not fail")
+
+ // Verify cross serialization
+
+ // Use reflect to deserialize codegen data
+ var reflectResult *ReflectSliceStruct
+ err = foryForReflect.Unmarshal(codegenData, &reflectResult)
+ require.NoError(t, err, "Reflect should be able to deserialize codegen
data")
+ require.NotNil(t, reflectResult, "Reflect result should not be nil")
+
+ // Verify content matches original
+ assert.EqualValues(t, codegenInstance.IntSlice, reflectResult.IntSlice,
"IntSlice mismatch")
+ assert.EqualValues(t, codegenInstance.StringSlice,
reflectResult.StringSlice, "StringSlice mismatch")
+ assert.EqualValues(t, codegenInstance.FloatSlice,
reflectResult.FloatSlice, "FloatSlice mismatch")
+ assert.EqualValues(t, codegenInstance.BoolSlice,
reflectResult.BoolSlice, "BoolSlice mismatch")
+
+ // Use codegen to deserialize reflect data
+ var codegenResult *SliceDemo
+ err = foryForCodegen.Unmarshal(reflectData, &codegenResult)
+ require.NoError(t, err, "Codegen should be able to deserialize reflect
data")
+ require.NotNil(t, codegenResult, "Codegen result should not be nil")
+
+ // Verify content matches original
+ assert.EqualValues(t, reflectInstance.IntSlice, codegenResult.IntSlice,
"IntSlice mismatch")
+ assert.EqualValues(t, reflectInstance.StringSlice,
codegenResult.StringSlice, "StringSlice mismatch")
+ assert.EqualValues(t, reflectInstance.FloatSlice,
codegenResult.FloatSlice, "FloatSlice mismatch")
+ assert.EqualValues(t, reflectInstance.BoolSlice,
codegenResult.BoolSlice, "BoolSlice mismatch")
+
+}
+
+// TestDynamicSliceDemoXlang - Test cross-language compatibility of
DynamicSliceDemo
+func TestDynamicSliceDemoXlang(t *testing.T) {
+ // Get DynamicSliceDemo type information
+ dynamicSliceType := reflect.TypeOf(DynamicSliceDemo{})
+ pkgPath := dynamicSliceType.PkgPath()
+ typeName := dynamicSliceType.Name()
+ expectedTypeTag := pkgPath + "." + typeName
+
+ // Create test data with simpler types to avoid reflection issues
+ codegenInstance := &DynamicSliceDemo{
+ DynamicSlice: []interface{}{
+ "first",
+ 200, // Testing mixed types in dynamic slice
+ "third",
+ },
+ }
+
+ // Define equivalent struct using reflection
+ type ReflectDynamicStruct struct {
+ DynamicSlice []interface{} `json:"dynamic_slice"`
+ }
+
+ reflectInstance := &ReflectDynamicStruct{
+ DynamicSlice: []interface{}{
+ "first",
+ 200, // Testing mixed types in dynamic slice
+ "third",
+ },
+ }
+
+ // Codegen mode - enable reference tracking
+ foryForCodegen := forygo.NewFory(true)
+
+ // Reflect mode - enable reference tracking
+ foryForReflect := forygo.NewFory(true)
+ err := foryForReflect.RegisterTagType(expectedTypeTag,
ReflectDynamicStruct{})
+ require.NoError(t, err, "Should be able to register
ReflectDynamicStruct with full name")
+
+ // Serialization test
+ codegenData, err := foryForCodegen.Marshal(codegenInstance)
+ require.NoError(t, err, "Codegen serialization should not fail")
+
+ reflectData, err := foryForReflect.Marshal(reflectInstance)
+ require.NoError(t, err, "Reflect serialization should not fail")
+
+ // Test cross deserialization - reflect deserializes codegen data
+ var reflectResult *ReflectDynamicStruct
+ err = foryForReflect.Unmarshal(codegenData, &reflectResult)
+ require.NoError(t, err, "Reflect should be able to deserialize codegen
data")
+ require.NotNil(t, reflectResult, "Reflect result should not be nil")
+
+ // Verify content matches original
+ assert.EqualValues(t, codegenInstance.DynamicSlice,
reflectResult.DynamicSlice, "DynamicSlice mismatch")
+
+ // Test opposite direction - codegen deserializes reflect data
+ var codegenResult *DynamicSliceDemo
+ err = foryForCodegen.Unmarshal(reflectData, &codegenResult)
+ require.NoError(t, err, "Codegen should be able to deserialize reflect
data")
+ require.NotNil(t, codegenResult, "Codegen result should not be nil")
+
+ // Verify content matches original
+ assert.EqualValues(t, reflectInstance.DynamicSlice,
codegenResult.DynamicSlice, "DynamicSlice mismatch")
+}
+
+// TestMapDemoXlang tests cross-language compatibility for map types
+func TestMapDemoXlang(t *testing.T) {
+ // Create test instance with same data for both codegen and reflection
+ codegenInstance := &MapDemo{
+ StringMap: map[string]string{
+ "key1": "value1",
+ "key2": "value2",
+ },
+ IntMap: map[int]int{
+ 1: 100,
+ 2: 200,
+ 3: 300,
+ },
+ MixedMap: map[string]int{
+ "one": 1,
+ "two": 2,
+ "three": 3,
+ },
+ }
+
+ // Use same instance for reflection (simplified test)
+ reflectInstance := codegenInstance
+
+ // Create Fory instances with reference tracking enabled
+ foryForCodegen := forygo.NewFory(true)
+ foryForReflect := forygo.NewFory(true)
+
+ // No need to register MapDemo - it has codegen serializer automatically
+
+ // Serialize both instances
+ codegenData, err := foryForCodegen.Marshal(codegenInstance)
+ require.NoError(t, err, "Codegen serialization should not fail")
+
+ reflectData, err := foryForReflect.Marshal(reflectInstance)
+ require.NoError(t, err, "Reflect serialization should not fail")
+
+ // Test cross deserialization - reflect deserializes codegen data
+ var reflectResult MapDemo
+ err = foryForReflect.Unmarshal(codegenData, &reflectResult)
+ require.NoError(t, err, "Reflect should be able to deserialize codegen
data")
+
+ // Verify content matches original
+ assert.EqualValues(t, codegenInstance.StringMap,
reflectResult.StringMap, "StringMap mismatch")
+ assert.EqualValues(t, codegenInstance.IntMap, reflectResult.IntMap,
"IntMap mismatch")
+ assert.EqualValues(t, codegenInstance.MixedMap, reflectResult.MixedMap,
"MixedMap mismatch")
+
+ // Test opposite direction - codegen deserializes reflect data
+ var codegenResult MapDemo
+ err = foryForCodegen.Unmarshal(reflectData, &codegenResult)
+ require.NoError(t, err, "Codegen should be able to deserialize reflect
data")
+
+ // Verify content matches original
+ assert.EqualValues(t, reflectInstance.StringMap,
codegenResult.StringMap, "StringMap mismatch")
+ assert.EqualValues(t, reflectInstance.IntMap, codegenResult.IntMap,
"IntMap mismatch")
+ assert.EqualValues(t, reflectInstance.MixedMap, codegenResult.MixedMap,
"MixedMap mismatch")
+}
diff --git a/go/fory/tests/structs.go b/go/fory/tests/structs.go
index 170c255e4..37399e064 100644
--- a/go/fory/tests/structs.go
+++ b/go/fory/tests/structs.go
@@ -18,11 +18,38 @@
package fory
// ValidationDemo is a simple struct for testing code generation
-// Contains only basic types since PR1 only supports basic types
+// Contains various basic types to validate comprehensive type support
// fory:gen
type ValidationDemo struct {
- A int32 `json:"a"` // int32 field
- B string `json:"b"` // string field
- C int64 `json:"c"` // int64 field (instead of array, as arrays are not
supported yet)
+ A int32 // int32 field
+ B string // string field
+ C int64 // int64 field
+ D float64 // float64 field
+ E bool // bool field
+}
+
+// SliceDemo is a struct for testing slice serialization
+// Contains various slice types
+
+// fory:gen
+type SliceDemo struct {
+ IntSlice []int32 // slice of int32
+ StringSlice []string // slice of string
+ FloatSlice []float64 // slice of float64
+ BoolSlice []bool // slice of bool
+}
+
+// DynamicSliceDemo is a struct for testing dynamic slice serialization
+// fory:gen
+type DynamicSliceDemo struct {
+ DynamicSlice []interface{} // slice of interface{}
+}
+
+// MapDemo demonstrates map field support
+// fory:gen
+type MapDemo struct {
+ StringMap map[string]string // map[string]string
+ IntMap map[int]int // map[int]int
+ MixedMap map[string]int // map[string]int
}
diff --git a/go/fory/type.go b/go/fory/type.go
index 60587af32..7d0508af7 100644
--- a/go/fory/type.go
+++ b/go/fory/type.go
@@ -372,38 +372,51 @@ func newTypeResolver(fory *Fory) *typeResolver {
}
r.initialize()
- // Register generated serializers from factories
+ // Register generated serializers from factories with complete type
information
generatedSerializerFactories.mu.RLock()
for type_, factory := range generatedSerializerFactories.factories {
- serializer := factory()
-
- // Use full registration process for generated types
+ codegenSerializer := factory()
pkgPath := type_.PkgPath()
typeName := type_.Name()
+ typeTag := pkgPath + "." + typeName
- // Register value type
- _, err := r.registerType(type_, int32(serializer.TypeId()),
pkgPath, typeName, serializer, true)
- if err != nil {
- fmt.Errorf("failed to register generated type %v: %v",
type_, err)
+ // Create structSerializer with codegen delegate (Python-style)
+ structSer := &structSerializer{
+ typeTag: typeTag,
+ type_: type_,
+ codegenDelegate: codegenSerializer, // Delegate to
codegen for performance
}
-
- // Also register pointer type in serializers map (without full
registration to avoid typeId conflict)
+ // Create ptrToStructSerializer with codegen delegate
(Python-style)
ptrType := reflect.PtrTo(type_)
- r.typeToSerializers[ptrType] = serializer
-
- // Create TypeInfo for pointer type and add to cache
- if typeInfo, exists := r.typesInfo[type_]; exists {
- ptrTypeInfo := TypeInfo{
- Type: ptrType,
- TypeID: -typeInfo.TypeID, // Use negative
ID for pointer type
- Serializer: serializer,
- PkgPathBytes: typeInfo.PkgPathBytes,
- NameBytes: typeInfo.NameBytes,
- IsDynamic: true,
- hashValue: calcTypeHash(ptrType),
- }
- r.typesInfo[ptrType] = ptrTypeInfo
+ ptrStructSer := &ptrToStructSerializer{
+ type_: ptrType,
+ structSerializer: *structSer,
+ codegenDelegate: codegenSerializer, // Delegate to
codegen for performance
}
+
+ // 1. Basic type mappings (same as reflection)
+ r.typeToSerializers[type_] = structSer // Value type ->
structSerializer with codegen
+ r.typeToSerializers[ptrType] = ptrStructSer // Pointer type ->
ptrToStructSerializer with codegen
+
+ // 2. Cross-language critical mapping (same as reflection)
+ r.typeTagToSerializers[typeTag] = ptrStructSer // "pkg.Type" ->
ptrToStructSerializer
+
+ // 3. Register complete type information (critical for proper
serialization)
+ _, err := r.registerType(type_,
int32(codegenSerializer.TypeId()), pkgPath, typeName, structSer, false)
+ if err != nil {
+ panic(fmt.Errorf("failed to register codegen type %s:
%v", typeTag, err))
+ }
+ // 4. Register pointer type information
+ _, err = r.registerType(ptrType,
-int32(codegenSerializer.TypeId()), pkgPath, typeName, ptrStructSer, false)
+ if err != nil {
+ panic(fmt.Errorf("failed to register codegen pointer
type %s: %v", typeTag, err))
+ }
+
+ // 5. Type info mappings (same as reflection) - these are
redundant if registerType is called
+ r.typeToTypeInfo[type_] = "@" + typeTag // Type ->
"@pkg.Type"
+ r.typeToTypeInfo[ptrType] = "*@" + typeTag // *Type ->
"*@pkg.Type"
+ r.typeInfoToType["@"+typeTag] = type_ // "@pkg.Type" ->
Type
+ r.typeInfoToType["*@"+typeTag] = ptrType // "*@pkg.Type" ->
*Type
}
generatedSerializerFactories.mu.RUnlock()
@@ -532,7 +545,7 @@ func (r *typeResolver) getTypeInfo(value reflect.Value,
create bool) (TypeInfo,
if info.Serializer == nil {
/*
Lazy initialize serializer if not created yet
- mapInStruct equals false because this path isn't
taken when extracting field info from structs;
+ mapInStruct equals false because this path isn’t
taken when extracting field info from structs;
for all other map cases, it remains false
*/
serializer, err := r.createSerializer(value.Type(),
false)
@@ -554,7 +567,6 @@ func (r *typeResolver) getTypeInfo(value reflect.Value,
create bool) (TypeInfo,
value = value.Elem()
}
type_ := value.Type()
-
// Get package path and type name for registration
var typeName string
var pkgPath string
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]