This is an automated email from the ASF dual-hosted git repository.
pandalee 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 646cd5400 refactor(go): Replace legacy RegisterTagType api call (#2696)
646cd5400 is described below
commit 646cd5400d2ebdfd6b7b3c1d3abb204d10de3f5e
Author: Zhong Junjie <[email protected]>
AuthorDate: Fri Oct 10 01:09:28 2025 +0800
refactor(go): Replace legacy RegisterTagType api call (#2696)
<!--
**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.
-->
## Why?
<!-- Describe the purpose of this PR. -->
fory-go was using RegisterTagType as register api which is already
abandoned in implements in java and python. We need to switch to
"typename + namespace" pattern registration.
## What does this PR do?
<!-- Describe the details of this PR. -->
Add two register-api called RegisterType and Register, they all do the
same things. The RegisterType will register type on "typename +
namespace", and the Register will register type using
"namespace.typename" which is less complicated.
Remove the old api RegisterTagType and all its usages.
## Related issues
<!--
Is there any related issue? If this PR closes them you say say
fix/closes:
- #xxxx0
- #xxxx1
- Fixes #xxxx2
-->
#2611
## 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.
-->
- [x] Does this PR introduce any public API change? yes
- [x] Does this PR introduce any binary protocol compatibility change?
no
## 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.
-->
---
README.md | 2 +-
docs/guide/xlang_serialization_guide.md | 6 ++--
go/README.md | 17 ++++++++++
go/fory/fory.go | 25 +++++++++++++--
go/fory/fory_metashare_test.go | 12 +++----
go/fory/fory_test.go | 42 +++++++++++++++----------
go/fory/fory_xlang_test.go | 11 ++++---
go/fory/tests/generator_xlang_test.go | 6 ++--
go/fory/type.go | 55 ++++++++++++++++++++++++++++-----
go/fory/type_def_encoder_test.go | 2 +-
go/fory/type_test.go | 4 +--
11 files changed, 136 insertions(+), 46 deletions(-)
diff --git a/README.md b/README.md
index 6d6714e54..7445de3c8 100644
--- a/README.md
+++ b/README.md
@@ -293,7 +293,7 @@ func main() {
F3 map[string]string
}
fory := forygo.NewFory(true)
- if err := fory.RegisterTagType("example.SomeClass", SomeClass{}); err != nil {
+ if err := fory.RegisterNamedType(SomeClass{}, "example.SomeClass"); err !=
nil {
panic(err)
}
value := &SomeClass{F2: map[string]string{"k1": "v1", "k2": "v2"}}
diff --git a/docs/guide/xlang_serialization_guide.md
b/docs/guide/xlang_serialization_guide.md
index 8ba8c379d..a03490988 100644
--- a/docs/guide/xlang_serialization_guide.md
+++ b/docs/guide/xlang_serialization_guide.md
@@ -289,10 +289,10 @@ func main() {
F2 map[int8]int32
}
fory := forygo.NewFory()
- if err := fory.RegisterTagType("example.SomeClass1", SomeClass1{}); err !=
nil {
+ if err := fory.RegisterNamedType(SomeClass1{}, "example.SomeClass1"); err !=
nil {
panic(err)
}
- if err := fory.RegisterTagType("example.SomeClass2", SomeClass2{}); err !=
nil {
+ if err := fory.RegisterNamedType(SomeClass2{}, "example.SomeClass2"); err !=
nil {
panic(err)
}
obj1 := &SomeClass1{}
@@ -481,7 +481,7 @@ func main() {
F3 map[string]string
}
fory := forygo.NewFory(true)
- if err := fory.RegisterTagType("example.SomeClass", SomeClass{}); err != nil {
+ if err := fory.Register(SomeClass{}, 65); err != nil {
panic(err)
}
value := &SomeClass{F2: map[string]string{"k1": "v1", "k2": "v2"}}
diff --git a/go/README.md b/go/README.md
index 0b0db3d0e..8a58e3d4c 100644
--- a/go/README.md
+++ b/go/README.md
@@ -198,6 +198,23 @@ fory := NewForyWithOptions(
)
```
+## Best Practices
+
+### Type Registration Patterns
+
+Choose the right registration approach for your use case:
+
+```go
+// Register using an explicit namespace and type name pair.
+func (f *Fory) RegisterByNamespace(User{}, "example", "user") error
+
+// Register using a name
+func (f *Fory) RegisterNamedType(User{}, "example.user") error
+
+// Register using a pre-assigned numeric type identifier.
+func (f *Fory) Register(User{}, 101) error
+```
+
## How to test
```bash
diff --git a/go/fory/fory.go b/go/fory/fory.go
index 3751452c8..3e77eda68 100644
--- a/go/fory/fory.go
+++ b/go/fory/fory.go
@@ -174,8 +174,29 @@ type Fory struct {
metaContext *MetaContext
}
-func (f *Fory) RegisterTagType(tag string, v interface{}) error {
- return f.typeResolver.RegisterTypeTag(reflect.ValueOf(v), tag)
+// RegisterByNamespace registers a type using a namespace and type name tag.
+func (f *Fory) RegisterByNamespace(
+ v interface{},
+ namespace string,
+ typeName string,
+) error {
+ return f.typeResolver.RegisterNamedType(reflect.TypeOf(v), 0,
namespace, typeName)
+}
+
+// RegisterNamedType register by name
+func (f *Fory) RegisterNamedType(
+ v interface{},
+ name string,
+) error {
+ return f.typeResolver.RegisterNamedType(reflect.TypeOf(v), 0, "", name)
+}
+
+// Register reister by typeId
+func (f *Fory) Register(
+ v interface{},
+ typeId int32,
+) error {
+ return f.typeResolver.RegisterNamedType(reflect.TypeOf(v), typeId, "",
"")
}
func (f *Fory) Marshal(v interface{}) ([]byte, error) {
diff --git a/go/fory/fory_metashare_test.go b/go/fory/fory_metashare_test.go
index 2e2dd16bd..e8e32ba27 100644
--- a/go/fory/fory_metashare_test.go
+++ b/go/fory/fory_metashare_test.go
@@ -284,13 +284,13 @@ func TestCompatibleSerializationScenarios(t *testing.T) {
Inner: SimpleDataClass{Name: "inner", Age: 18,
Active: true},
},
writerSetup: func(f *Fory) error {
- if err := f.RegisterTagType("SimpleDataClass",
SimpleDataClass{}); err != nil {
+ if err :=
f.RegisterNamedType(SimpleDataClass{}, "SimpleDataClass"); err != nil {
return err
}
return nil
},
readerSetup: func(f *Fory) error {
- if err := f.RegisterTagType("SimpleDataClass",
SimpleDataClass{}); err != nil {
+ if err :=
f.RegisterNamedType(SimpleDataClass{}, "SimpleDataClass"); err != nil {
return err
}
return nil
@@ -312,13 +312,13 @@ func TestCompatibleSerializationScenarios(t *testing.T) {
Inner: SimpleDataClass{Name: "inner", Age: 18,
Active: true},
},
writerSetup: func(f *Fory) error {
- if err := f.RegisterTagType("SimpleDataClass",
SimpleDataClass{}); err != nil {
+ if err :=
f.RegisterNamedType(SimpleDataClass{}, "SimpleDataClass"); err != nil {
return err
}
return nil
},
readerSetup: func(f *Fory) error {
- if err := f.RegisterTagType("SimpleDataClass",
InconsistentDataClass{}); err != nil {
+ if err :=
f.RegisterNamedType(InconsistentDataClass{}, "SimpleDataClass"); err != nil {
return err
}
return nil
@@ -360,7 +360,7 @@ func runCompatibilityCase(t *testing.T, tc
compatibilityCase) {
err := tc.writerSetup(writer)
assert.NoError(t, err)
}
- err := writer.RegisterTagType(tc.tag, tc.writeType)
+ err := writer.RegisterNamedType(tc.writeType, tc.tag)
assert.NoError(t, err)
data, err := writer.Marshal(tc.input)
@@ -371,7 +371,7 @@ func runCompatibilityCase(t *testing.T, tc
compatibilityCase) {
err = tc.readerSetup(reader)
assert.NoError(t, err)
}
- err = reader.RegisterTagType(tc.tag, tc.readType)
+ err = reader.RegisterNamedType(tc.readType, tc.tag)
assert.NoError(t, err)
target := reflect.New(reflect.TypeOf(tc.readType))
diff --git a/go/fory/fory_test.go b/go/fory/fory_test.go
index 9c4981617..b3c1fe5f7 100644
--- a/go/fory/fory_test.go
+++ b/go/fory/fory_test.go
@@ -19,10 +19,11 @@ package fory
import (
"fmt"
- "github.com/stretchr/testify/require"
"reflect"
"testing"
"unsafe"
+
+ "github.com/stretchr/testify/require"
)
func primitiveData() []interface{} {
@@ -221,7 +222,7 @@ func TestSerializeStructSimple(t *testing.T) {
type A struct {
F1 []string
}
- require.Nil(t, fory.RegisterTagType("example.A", A{}))
+ require.Nil(t, fory.RegisterNamedType(A{}, "example.A"))
serde(t, fory, A{})
serde(t, fory, &A{})
serde(t, fory, A{F1: []string{"str1", "", "str2"}})
@@ -231,7 +232,7 @@ func TestSerializeStructSimple(t *testing.T) {
F1 []string
F2 map[string]int32
}
- require.Nil(t, fory.RegisterTagType("example.B", B{}))
+ require.Nil(t, fory.RegisterNamedType(B{}, "example.B"))
serde(t, fory, B{})
serde(t, fory, B{
F1: []string{"str1", "", "str2"},
@@ -243,6 +244,15 @@ func TestSerializeStructSimple(t *testing.T) {
}
}
+func TestRegisterById(t *testing.T) {
+ fory := NewFory(false)
+ type simple struct {
+ Field string
+ }
+ require.NoError(t, fory.Register(simple{}, 100))
+ serde(t, fory, simple{Field: "value"})
+}
+
func TestSerializeBeginWithMagicNumber(t *testing.T) {
strSlice := []string{"str1", "str1", "", "", "str2"}
fory := NewFory(true)
@@ -291,7 +301,7 @@ func newFoo() Foo {
func TestSerializeStruct(t *testing.T) {
for _, referenceTracking := range []bool{false, true} {
fory := NewFory(referenceTracking)
- require.Nil(t, fory.RegisterTagType("example.Bar", Bar{}))
+ require.Nil(t, fory.RegisterNamedType(Bar{}, "example.Bar"))
serde(t, fory, &Bar{})
bar := Bar{F1: 1, F2: "str"}
serde(t, fory, bar)
@@ -301,13 +311,13 @@ func TestSerializeStruct(t *testing.T) {
F1 Bar
F2 interface{}
}
- require.Nil(t, fory.RegisterTagType("example.A", A{}))
+ require.Nil(t, fory.RegisterNamedType(A{}, "example.A"))
serde(t, fory, A{})
serde(t, fory, &A{})
serde(t, fory, A{F1: Bar{F1: 1, F2: "str"}, F2: -1})
serde(t, fory, &A{F1: Bar{F1: 1, F2: "str"}, F2: -1})
- require.Nil(t, fory.RegisterTagType("example.Foo", Foo{}))
+ require.Nil(t, fory.RegisterNamedType(Foo{}, "example.Foo"))
foo := newFoo()
serde(t, fory, foo)
serde(t, fory, &foo)
@@ -320,7 +330,7 @@ func TestSerializeCircularReference(t *testing.T) {
type A struct {
A1 *A
}
- require.Nil(t, fory.RegisterTagType("example.A", A{}))
+ require.Nil(t, fory.RegisterNamedType(A{}, "example.A"))
// If use `A{}` instead of `&A{}` and pass `a` instead of `&a`,
there will be serialization data duplication
// and can't be deserialized by other languages too.
// TODO(chaokunyang) If pass by value(have a copy) and there
are some inner value reference, return a readable
@@ -340,7 +350,7 @@ func TestSerializeCircularReference(t *testing.T) {
F2 *B
F3 *B
}
- require.Nil(t, fory.RegisterTagType("example.B", B{}))
+ require.Nil(t, fory.RegisterNamedType(B{}, "example.B"))
b := &B{F1: "str"}
b.F2 = b
b.F3 = b
@@ -368,8 +378,8 @@ func TestSerializeComplexReference(t *testing.T) {
F3 *A
F4 *B
}
- require.Nil(t, fory.RegisterTagType("example.A", A{}))
- require.Nil(t, fory.RegisterTagType("example.B", B{}))
+ require.Nil(t, fory.RegisterNamedType(A{}, "example.A"))
+ require.Nil(t, fory.RegisterNamedType(B{}, "example.B"))
a := &A{F1: "str"}
a.F2 = a
@@ -484,8 +494,8 @@ func serde(t *testing.T, fory *Fory, value interface{}) {
func BenchmarkMarshal(b *testing.B) {
fory := NewFory(true)
- require.Nil(b, fory.RegisterTagType("example.Foo", Foo{}))
- require.Nil(b, fory.RegisterTagType("example.Bar", Bar{}))
+ require.Nil(b, fory.RegisterNamedType(Foo{}, "example.Foo"))
+ require.Nil(b, fory.RegisterNamedType(Bar{}, "example.Bar"))
value := benchData()
for i := 0; i < b.N; i++ {
_, err := fory.Marshal(value)
@@ -497,8 +507,8 @@ func BenchmarkMarshal(b *testing.B) {
func BenchmarkUnmarshal(b *testing.B) {
fory := NewFory(true)
- require.Nil(b, fory.RegisterTagType("example.Foo", Foo{}))
- require.Nil(b, fory.RegisterTagType("example.Bar", Bar{}))
+ require.Nil(b, fory.RegisterNamedType(Foo{}, "example.Foo"))
+ require.Nil(b, fory.RegisterNamedType(Bar{}, "example.Bar"))
value := benchData()
data, err := fory.Marshal(value)
if err != nil {
@@ -610,10 +620,10 @@ func TestStructWithNestedSlice(t *testing.T) {
}
fory := NewFory(true)
- if err := fory.RegisterTagType("Example", Example{}); err != nil {
+ if err := fory.RegisterNamedType(Example{}, "Example"); err != nil {
panic(err)
}
- if err := fory.RegisterTagType("Item", Item{}); err != nil {
+ if err := fory.RegisterNamedType(Item{}, "Item"); err != nil {
panic(err)
}
diff --git a/go/fory/fory_xlang_test.go b/go/fory/fory_xlang_test.go
index d923d7a02..0921b67ae 100644
--- a/go/fory/fory_xlang_test.go
+++ b/go/fory/fory_xlang_test.go
@@ -19,8 +19,6 @@ package fory_test
import (
"fmt"
- "github.com/apache/fory/go/fory"
- "github.com/stretchr/testify/require"
"io/ioutil"
"os"
"os/exec"
@@ -28,6 +26,9 @@ import (
"testing"
"time"
"unsafe"
+
+ "github.com/apache/fory/go/fory"
+ "github.com/stretchr/testify/require"
)
const pythonModule = "pyfory.tests.test_cross_language"
@@ -228,7 +229,7 @@ func TestSerializeSimpleStruct(t *testing.T) {
// Temporarily disabled
// t.Skip()
fory_ := fory.NewFory(true)
- require.Nil(t, fory_.RegisterTagType("test.ComplexObject2",
ComplexObject2{}))
+ require.Nil(t, fory_.RegisterNamedType(ComplexObject2{},
"test.ComplexObject2"))
obj2 := ComplexObject2{}
obj2.F1 = true
obj2.F2 = map[int8]int32{-1: 2}
@@ -239,8 +240,8 @@ func TestSerializeComplexStruct(t *testing.T) {
// Temporarily disabled
// t.Skip()
fory_ := fory.NewFory(true)
- require.Nil(t, fory_.RegisterTagType("test.ComplexObject1",
ComplexObject1{}))
- require.Nil(t, fory_.RegisterTagType("test.ComplexObject2",
ComplexObject2{}))
+ require.Nil(t, fory_.RegisterNamedType(ComplexObject1{},
"test.ComplexObject1"))
+ require.Nil(t, fory_.RegisterNamedType(ComplexObject2{},
"test.ComplexObject2"))
obj2 := ComplexObject2{}
obj2.F1 = true
obj2.F2 = map[int8]int32{-1: 2}
diff --git a/go/fory/tests/generator_xlang_test.go
b/go/fory/tests/generator_xlang_test.go
index 85704c60f..01da9c8a7 100644
--- a/go/fory/tests/generator_xlang_test.go
+++ b/go/fory/tests/generator_xlang_test.go
@@ -66,7 +66,7 @@ func TestValidationDemoXlang(t *testing.T) {
// Reflect mode (register with full name)
foryForReflect := forygo.NewFory(true)
- err := foryForReflect.RegisterTagType(expectedTypeTag, ReflectStruct{})
+ err := foryForReflect.RegisterNamedType(ReflectStruct{},
expectedTypeTag)
require.NoError(t, err, "Should be able to register ReflectStruct with
full name")
// Serialization test
@@ -131,7 +131,7 @@ func TestSliceDemoXlang(t *testing.T) {
// Reflect mode - enable reference tracking
foryForReflect := forygo.NewFory(true)
- err := foryForReflect.RegisterTagType(expectedTypeTag,
ReflectSliceStruct{})
+ err := foryForReflect.RegisterNamedType(ReflectSliceStruct{},
expectedTypeTag)
require.NoError(t, err, "Should be able to register ReflectSliceStruct
with full name")
// Serialization test
@@ -204,7 +204,7 @@ func TestDynamicSliceDemoXlang(t *testing.T) {
// Reflect mode - enable reference tracking
foryForReflect := forygo.NewFory(true)
- err := foryForReflect.RegisterTagType(expectedTypeTag,
ReflectDynamicStruct{})
+ err := foryForReflect.RegisterNamedType(ReflectDynamicStruct{},
expectedTypeTag)
require.NoError(t, err, "Should be able to register
ReflectDynamicStruct with full name")
// Serialization test
diff --git a/go/fory/type.go b/go/fory/type.go
index d754a258e..2e49e54fd 100644
--- a/go/fory/type.go
+++ b/go/fory/type.go
@@ -184,6 +184,7 @@ var namedTypes = map[TypeId]struct{}{
// IsNamespacedType checks whether the given type ID is a namespace type
func IsNamespacedType(typeID TypeId) bool {
+ typeID = typeID & 0xFF
_, exists := namedTypes[typeID]
return exists
}
@@ -479,11 +480,34 @@ func (r *typeResolver) RegisterSerializer(type_
reflect.Type, s Serializer) erro
return nil
}
-func (r *typeResolver) RegisterTypeTag(value reflect.Value, tag string) error {
- type_ := value.Type()
+func (r *typeResolver) RegisterNamedType(
+ type_ reflect.Type,
+ typeId int32,
+ namespace string,
+ typeName string,
+) error {
if prev, ok := r.typeToSerializers[type_]; ok {
return fmt.Errorf("type %s already has a serializer %s
registered", type_, prev)
}
+ registerById := (typeId != 0)
+ if registerById && typeName != "" {
+ return fmt.Errorf("typename %s and typeId %d cannot be both
register", typeName, typeId)
+ }
+ if namespace == "" {
+ if idx := strings.LastIndex(typeName, "."); idx != -1 {
+ namespace = typeName[:idx]
+ typeName = typeName[idx+1:]
+ }
+ }
+ if typeName == "" && namespace != "" {
+ return fmt.Errorf("typeName cannot be empty if namespace is
provided")
+ }
+ var tag string
+ if namespace == "" {
+ tag = typeName
+ } else {
+ tag = namespace + "." + typeName
+ }
serializer := &structSerializer{type_: type_, typeTag: tag}
r.typeToSerializers[type_] = serializer
// multiple struct with same name defined inside function will have
same `type_.String()`, but they are
@@ -493,21 +517,38 @@ func (r *typeResolver) RegisterTypeTag(value
reflect.Value, tag string) error {
r.typeInfoToType["@"+tag] = type_
ptrType := reflect.PtrTo(type_)
- ptrValue := reflect.New(type_)
ptrSerializer := &ptrToStructSerializer{structSerializer: *serializer,
type_: ptrType}
r.typeToSerializers[ptrType] = ptrSerializer
// use `ptrToStructSerializer` as default deserializer when
deserializing data from other languages.
r.typeTagToSerializers[tag] = ptrSerializer
r.typeToTypeInfo[ptrType] = "*@" + tag
r.typeInfoToType["*@"+tag] = ptrType
+ if typeId == 0 {
+ if r.metaShareEnabled() {
+ typeId = (typeId << 8) + NAMED_COMPATIBLE_STRUCT
+ } else {
+ typeId = (typeId << 8) + NAMED_STRUCT
+ }
+ } else {
+ if r.metaShareEnabled() {
+ typeId = COMPATIBLE_STRUCT
+ } else {
+ typeId = STRUCT
+ }
+ }
+ if registerById {
+ if info, ok := r.typeIDToTypeInfo[typeId]; ok {
+ return fmt.Errorf("type %s with id %d has been
registered", info.Type, typeId)
+ }
+ }
// For named structs, directly register both their value and pointer
types
- info, err := r.getTypeInfo(value, true)
+ _, err := r.registerType(type_, typeId, namespace, typeName, nil, false)
if err != nil {
- return fmt.Errorf("failed to register named structs: info is
%v", info)
+ return fmt.Errorf("failed to register named structs: %w", err)
}
- info, err = r.getTypeInfo(ptrValue, true)
+ _, err = r.registerType(ptrType, -typeId, namespace, typeName, nil,
false)
if err != nil {
- return fmt.Errorf("failed to register named structs: info is
%v", info)
+ return fmt.Errorf("failed to register named structs: %w", err)
}
return nil
}
diff --git a/go/fory/type_def_encoder_test.go b/go/fory/type_def_encoder_test.go
index ec7c19219..2fe96dd2e 100644
--- a/go/fory/type_def_encoder_test.go
+++ b/go/fory/type_def_encoder_test.go
@@ -108,7 +108,7 @@ func TestTypeDefEncodingDecoding(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
fory := NewFory(false)
- if err := fory.RegisterTagType(tt.tagName,
tt.testStruct); err != nil {
+ if err := fory.RegisterNamedType(tt.testStruct,
tt.tagName); err != nil {
t.Fatalf("Failed to register tag type: %v", err)
}
diff --git a/go/fory/type_test.go b/go/fory/type_test.go
index b26c5674a..5866f0f8e 100644
--- a/go/fory/type_test.go
+++ b/go/fory/type_test.go
@@ -34,8 +34,8 @@ func TestTypeResolver(t *testing.T) {
type A struct {
F1 string
}
- require.Nil(t, typeResolver.RegisterTypeTag(reflect.ValueOf(A{}),
"example.A"))
- require.Error(t, typeResolver.RegisterTypeTag(reflect.ValueOf(A{}),
"example.A"))
+ require.Nil(t, typeResolver.RegisterNamedType(reflect.TypeOf(A{}), 0,
"", "example.A"))
+ require.Error(t, typeResolver.RegisterNamedType(reflect.TypeOf(A{}), 0,
"", "example.A"))
var tests = []struct {
type_ reflect.Type
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]