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)
+}

Reply via email to