diff --git a/util/gconv/gconv_struct.go b/util/gconv/gconv_struct.go index b9eb2f95c..b7efd2385 100644 --- a/util/gconv/gconv_struct.go +++ b/util/gconv/gconv_struct.go @@ -534,14 +534,20 @@ func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}, ma structFieldValue.Set(reflectArray) case reflect.Ptr: - item := reflect.New(structFieldValue.Type().Elem()) - if err, ok = bindVarToReflectValueWithInterfaceCheck(item, value); ok { - structFieldValue.Set(item) - return err - } - elem := item.Elem() - if err = bindVarToReflectValue(elem, value, mapping); err == nil { - structFieldValue.Set(elem.Addr()) + if structFieldValue.IsNil() || structFieldValue.IsZero() { + // Nil or empty pointer, it creates a new one. + item := reflect.New(structFieldValue.Type().Elem()) + if err, ok = bindVarToReflectValueWithInterfaceCheck(item, value); ok { + structFieldValue.Set(item) + return err + } + elem := item.Elem() + if err = bindVarToReflectValue(elem, value, mapping); err == nil { + structFieldValue.Set(elem.Addr()) + } + } else { + // Not empty pointer, it assigns values to it. + return bindVarToReflectValue(structFieldValue.Elem(), value, mapping) } // It mainly and specially handles the interface of nil value. diff --git a/util/gconv/gconv_z_unit_all_test.go b/util/gconv/gconv_z_unit_all_test.go index c11ee17f1..d7aeb67bd 100644 --- a/util/gconv/gconv_z_unit_all_test.go +++ b/util/gconv/gconv_z_unit_all_test.go @@ -11,6 +11,7 @@ import ( "testing" "time" + "github.com/gogf/gf/v2/container/gtype" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/os/gtime" "github.com/gogf/gf/v2/test/gtest" @@ -1480,3 +1481,76 @@ func Test_Struct_Time_All(t *testing.T) { t.Assert(user.CreateTime.Time.UTC().String(), now.UTC().String()) }) } + +func Test_Issue1946(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + type B struct { + init *gtype.Bool + Name string + } + type A struct { + B *B + } + a := &A{ + B: &B{ + init: gtype.NewBool(true), + }, + } + err := gconv.Struct(g.Map{ + "B": g.Map{ + "Name": "init", + }, + }, a) + t.AssertNil(err) + t.Assert(a.B.Name, "init") + t.Assert(a.B.init.Val(), true) + }) + // It cannot change private attribute. + gtest.C(t, func(t *gtest.T) { + type B struct { + init *gtype.Bool + Name string + } + type A struct { + B *B + } + a := &A{ + B: &B{ + init: gtype.NewBool(true), + }, + } + err := gconv.Struct(g.Map{ + "B": g.Map{ + "init": 0, + "Name": "init", + }, + }, a) + t.AssertNil(err) + t.Assert(a.B.Name, "init") + t.Assert(a.B.init.Val(), true) + }) + // It can change public attribute. + gtest.C(t, func(t *gtest.T) { + type B struct { + Init *gtype.Bool + Name string + } + type A struct { + B *B + } + a := &A{ + B: &B{ + Init: gtype.NewBool(), + }, + } + err := gconv.Struct(g.Map{ + "B": g.Map{ + "Init": 1, + "Name": "init", + }, + }, a) + t.AssertNil(err) + t.Assert(a.B.Name, "init") + t.Assert(a.B.Init.Val(), true) + }) +}