This is an automated email from the ASF dual-hosted git repository.
alexstocks pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/dubbo-go.git
The following commit(s) were added to refs/heads/develop by this push:
new 19d2affee Feat/generic-add-include-class-config (#3171)
19d2affee is described below
commit 19d2affee96d6defd0d81300d55dd4049d8dbfa3
Author: 翎 <[email protected]>
AuthorDate: Sun Jan 25 18:15:49 2026 +0800
Feat/generic-add-include-class-config (#3171)
* feat(key): add a const generic.include.class
* feat(generic): support generic.include.class config
* fix:(map)
: add case map[any]any
* fix(map): Improve the logic of realize method for CI
* feat(map): improve getGenericIncludeClass logic
* style: import
* style: import
* style: changed for CI
* style: import changed fo CI
* style: imports-formatter
* style changed for CI
* style: import
Reorganize import statements by removing and re-adding imports for
consistency.
* feat: add documentation to explain their purpose
* fix: map[any]any key comparison for "class" removal
* feat(map): add test cases
* style: imports-formatter
* style: imports-formatter
---
common/constant/key.go | 1 +
filter/generic/generalizer/map.go | 64 ++++++++++++++++
filter/generic/generalizer/map_test.go | 133 +++++++++++++++++++++++++++++++++
3 files changed, 198 insertions(+)
diff --git a/common/constant/key.go b/common/constant/key.go
index 2c6928492..4f40d9989 100644
--- a/common/constant/key.go
+++ b/common/constant/key.go
@@ -417,6 +417,7 @@ const (
GenericSerializationGson = "gson"
GenericSerializationProtobuf = "protobuf"
GenericSerializationProtobufJson = "protobuf-json"
+ GenericIncludeClassKey = "generic.include.class"
)
// AdaptiveService Filter
diff --git a/filter/generic/generalizer/map.go
b/filter/generic/generalizer/map.go
index 445a9daec..bc6021ea7 100644
--- a/filter/generic/generalizer/map.go
+++ b/filter/generic/generalizer/map.go
@@ -19,6 +19,7 @@ package generalizer
import (
"reflect"
+ "strconv"
"strings"
"sync"
"time"
@@ -35,6 +36,8 @@ import (
)
import (
+ "dubbo.apache.org/dubbo-go/v3/common/config"
+ "dubbo.apache.org/dubbo-go/v3/common/constant"
"dubbo.apache.org/dubbo-go/v3/protocol/dubbo/hessian2"
)
@@ -54,10 +57,16 @@ type MapGeneralizer struct{}
func (g *MapGeneralizer) Generalize(obj any) (gobj any, err error) {
gobj = objToMap(obj)
+ if !getGenericIncludeClass() {
+ gobj = removeClass(gobj)
+ }
return
}
func (g *MapGeneralizer) Realize(obj any, typ reflect.Type) (any, error) {
+ if !getGenericIncludeClass() {
+ obj = removeClass(obj)
+ }
newobj := reflect.New(typ).Interface()
err := mapstructure.Decode(obj, newobj)
if err != nil {
@@ -84,6 +93,61 @@ func (g *MapGeneralizer) GetType(obj any) (typ string, err
error) {
return
}
+// getGenericIncludeClass retrieves "generic.include.class" config value
(fallback to true)
+func getGenericIncludeClass() bool {
+ cfgList := config.GetEnvInstance().Configuration()
+ for e := cfgList.Front(); e != nil; e = e.Next() {
+ conf, ok := e.Value.(*config.InmemoryConfiguration)
+ if !ok {
+ continue
+ }
+
+ if exist, val :=
conf.GetProperty(constant.GenericIncludeClassKey); exist {
+ parsed, err := strconv.ParseBool(val)
+ if err != nil {
+ logger.Warnf("generic.include.class value %q is
invalid, fallback to true", val)
+ return true
+ }
+ return parsed
+ }
+ }
+
+ return true
+}
+
+// removeClass recursively removes "class" key from data (returns new copy, no
original modify)
+// obj: any data (map[string]any/map[any]any/[]any/basic type)
+func removeClass(obj any) any {
+ switch v := obj.(type) {
+ case map[string]any:
+ m := make(map[string]any, len(v))
+ for k, val := range v {
+ if k == "class" {
+ continue
+ }
+ m[k] = removeClass(val)
+ }
+ return m
+ case map[any]any:
+ m := make(map[any]any, len(v))
+ for k, val := range v {
+ if key, ok := k.(string); ok && key == "class" {
+ continue
+ }
+ m[k] = removeClass(val)
+ }
+ return m
+ case []any:
+ s := make([]any, 0, len(v))
+ for _, val := range v {
+ s = append(s, removeClass(val))
+ }
+ return s
+ default:
+ return obj
+ }
+}
+
// objToMap converts an object(any) to a map
func objToMap(obj any) any {
if obj == nil {
diff --git a/filter/generic/generalizer/map_test.go
b/filter/generic/generalizer/map_test.go
index efcc2a3dd..8a691d760 100644
--- a/filter/generic/generalizer/map_test.go
+++ b/filter/generic/generalizer/map_test.go
@@ -19,6 +19,7 @@ package generalizer
import (
"reflect"
+ "strconv"
"testing"
"time"
)
@@ -28,6 +29,11 @@ import (
"github.com/stretchr/testify/require"
)
+import (
+ "dubbo.apache.org/dubbo-go/v3/common/config"
+ "dubbo.apache.org/dubbo-go/v3/common/constant"
+)
+
type testPlainObj struct {
AaAa string `m:"aaAa"`
BaBa string
@@ -248,3 +254,130 @@ func TestNullField(t *testing.T) {
assert.True(t, ok)
assert.Nil(t, rMockParent.Child)
}
+
+func setGenericIncludeClass(t *testing.T, value *bool) {
+ env := config.GetEnvInstance()
+ if value == nil {
+ env.UpdateAppExternalConfigMap(map[string]string{})
+ env.UpdateExternalConfigMap(map[string]string{})
+ return
+ }
+
+ env.UpdateAppExternalConfigMap(map[string]string{
+ constant.GenericIncludeClassKey: strconv.FormatBool(*value),
+ })
+
+ t.Cleanup(func() {
+ env.UpdateAppExternalConfigMap(map[string]string{})
+ env.UpdateExternalConfigMap(map[string]string{})
+ })
+}
+
+func TestRemoveClass(t *testing.T) {
+ input := map[string]any{
+ "class": "root",
+ "name": "n",
+ "child": map[any]any{
+ "class": "child",
+ "x": 1,
+ 1: "keep",
+ },
+ "list": []any{
+ map[string]any{"class": "item", "y": 2},
+ "v",
+ },
+ }
+
+ output := removeClass(input).(map[string]any)
+ if _, ok := output["class"]; ok {
+ t.Fatalf("expected root class to be removed")
+ }
+
+ child := output["child"].(map[any]any)
+ if _, ok := child["class"]; ok {
+ t.Fatalf("expected child class to be removed")
+ }
+ assert.Equal(t, "keep", child[1])
+
+ list := output["list"].([]any)
+ item := list[0].(map[string]any)
+ if _, ok := item["class"]; ok {
+ t.Fatalf("expected list item class to be removed")
+ }
+}
+
+func TestGenericIncludeClass_ConfigTrue(t *testing.T) {
+ val := true
+ setGenericIncludeClass(t, &val)
+
+ child := &mockChild{
+ Age: 20,
+ Gender: "male",
+ Email: "[email protected]",
+ Name: "lmc",
+ }
+ parent := mockParent{
+ Age: 30,
+ Gender: "male",
+ Email: "[email protected]",
+ Name: "xavierniu",
+ Child: child,
+ }
+
+ m, err := mockMapGeneralizer.Generalize(parent)
+ require.NoError(t, err)
+ mMap := m.(map[string]any)
+ _, ok := mMap["class"]
+ assert.True(t, ok)
+ _, ok = mMap["child"].(map[string]any)["class"]
+ assert.True(t, ok)
+
+ r, err := mockMapGeneralizer.Realize(m, reflect.TypeOf(parent))
+ require.NoError(t, err)
+ rParent, ok := r.(mockParent)
+ assert.True(t, ok)
+ assert.Equal(t, "xavierniu", rParent.Name)
+ assert.Equal(t, 30, rParent.Age)
+ assert.Equal(t, "lmc", rParent.Child.Name)
+ assert.Equal(t, 20, rParent.Child.Age)
+}
+
+func TestGenericIncludeClass_ConfigFalse(t *testing.T) {
+ val := false
+ setGenericIncludeClass(t, &val)
+
+ child := &mockChild{
+ Age: 20,
+ Gender: "male",
+ Email: "[email protected]",
+ Name: "lmc",
+ }
+ parent := mockParent{
+ Age: 30,
+ Gender: "male",
+ Email: "[email protected]",
+ Name: "xavierniu",
+ Child: child,
+ }
+
+ m, err := mockMapGeneralizer.Generalize(parent)
+ require.NoError(t, err)
+ mMap := m.(map[string]any)
+ _, ok := mMap["class"]
+ assert.False(t, ok)
+ _, ok = mMap["child"].(map[string]any)["class"]
+ assert.False(t, ok)
+
+ // ensure Realize drops class if provided
+ mMap["class"] = "org.apache.dubbo.mockParent"
+ mMap["child"].(map[string]any)["class"] = "org.apache.dubbo.mockChild"
+
+ r, err := mockMapGeneralizer.Realize(m, reflect.TypeOf(parent))
+ require.NoError(t, err)
+ rParent, ok := r.(mockParent)
+ assert.True(t, ok)
+ assert.Equal(t, "xavierniu", rParent.Name)
+ assert.Equal(t, 30, rParent.Age)
+ assert.Equal(t, "lmc", rParent.Child.Name)
+ assert.Equal(t, 20, rParent.Child.Age)
+}