我想递归更新结构中的所有字符串,包括结构中的所有字符串和任何切片。
func TestModifyStringsInStruct(t *testing.T) {
type MyStruct struct {
Field1 string
Field2 int
NestedStruct *MyStruct
SliceOfStrings []string
}
fn := func(s string) string {
return "replacement_" + s
}
input := &MyStruct{
Field1: "abc",
Field2: 42,
NestedStruct: &MyStruct{Field1: "def", Field2: 99},
SliceOfStrings: []string{"foo", "bar", "baz"},
}
expected := &MyStruct{
Field1: "replacement_abc",
Field2: 42,
NestedStruct: &MyStruct{Field1: "replacement_def", Field2: 99},
SliceOfStrings: []string{"replacement_foo", "replacement_bar", "replacement_baz"},
}
err := UpdateStringValues(input, fn)
assert.Assert(t, err == nil)
assert.Equal(t, input.Field1, expected.Field1)
assert.Equal(t, input.NestedStruct.Field1, expected.NestedStruct.Field1)
assert.DeepEqual(t, input.SliceOfStrings, expected.SliceOfStrings)
}
我写了这段代码,但它也未能通过我的单元测试:
func UpdateStringValues(structPtr interface{}, callback func(s string) string) error {
// Verify if structPtr is a pointer to a struct
inputParamStructType := reflect.TypeOf(structPtr)
if inputParamStructType == nil ||
inputParamStructType.Kind() != reflect.Ptr ||
inputParamStructType.Elem().Kind() != reflect.Struct {
return errors.New("invalid input structPtr param: should be a pointer to a struct")
}
// ValueOf is used to get the struct data in the reflect.Value form
structValue := reflect.ValueOf(structPtr)
if structValue.CanSet() {
return errors.New("struct values can't be changed")
}
// Elem is used to get the value pointed by the struct pointer
structValue = structValue.Elem()
// Update struct values, calling the callback on each string field
return updateStringValues(&structValue, callback)
}
func updateStringValues(structValue *reflect.Value, callback func(s string) string) error {
for i := 0; i < structValue.NumField(); i++ {
field := structValue.Field(i)
switch field.Kind() {
case reflect.Struct:
if err := updateStringValues(&field, callback); err != nil {
return fmt.Errorf("field error: %s", err.Error())
}
case reflect.String:
if field.CanSet() {
field.SetString(callback(field.String()))
}
case reflect.Slice, reflect.Array:
if err := updateStringValuesList(&field, callback); err != nil {
return fmt.Errorf("field error: %s", err.Error())
}
}
}
return nil
}
func updateStringValuesList(listValue *reflect.Value, callback func(s string) string) error {
for i := 0; i < listValue.Len(); i++ {
elem := listValue.Index(i)
switch elem.Kind() {
case reflect.Struct:
if err := updateStringValues(&elem, callback); err != nil {
return fmt.Errorf("field error: %s", err.Error())
}
case reflect.String:
if elem.CanSet() {
elem.SetString(callback(elem.String()))
}
case reflect.Slice, reflect.Array:
if err := updateStringValuesList(&elem, callback); err != nil {
return fmt.Errorf("field error: %s", err.Error())
}
}
}
return nil
}
如何正确地递归更新结构中的所有字符串,包括结构中任何切片中的所有字符串?
代码中的问题在于如何处理指向结构体字段和切片的指针。当遇到指向结构或切片的指针时,需要正确取消引用它以访问其底层结构或切片值。此外,您需要处理字段或切片元素是指向字符串的指针的情况。
试试这个:
func UpdateStringValues(structPtr interface{}, callback func(s string) string) error {
inputParamStructType := reflect.TypeOf(structPtr)
if inputParamStructType == nil ||
inputParamStructType.Kind() != reflect.Ptr ||
inputParamStructType.Elem().Kind() != reflect.Struct {
return errors.New("invalid input structPtr param: should be a pointer to a struct")
}
structValue := reflect.ValueOf(structPtr)
if !structValue.Elem().CanSet() {
return errors.New("struct values can't be changed")
}
structValue = structValue.Elem()
return updateStringValues(&structValue, callback)
}
func updateStringValues(structValue *reflect.Value, callback func(s string) string) error {
for i := 0; i < structValue.NumField(); i++ {
field := structValue.Field(i)
switch field.Kind() {
case reflect.Ptr:
if !field.IsNil() && field.Elem().Kind() == reflect.Struct {
if err := updateStringValues(&field.Elem(), callback); err != nil {
return fmt.Errorf("field error: %s", err.Error())
}
}
case reflect.Struct:
if err := updateStringValues(&field, callback); err != nil {
return fmt.Errorf("field error: %s", err.Error())
}
case reflect.String:
if field.CanSet() {
field.SetString(callback(field.String()))
}
case reflect.Slice, reflect.Array:
if err := updateStringValuesList(&field, callback); err != nil {
return fmt.Errorf("field error: %s", err.Error())
}
}
}
return nil
}
func updateStringValuesList(listValue *reflect.Value, callback func(s string) string) error {
for i := 0; i < listValue.Len(); i++ {
elem := listValue.Index(i)
switch elem.Kind() {
case reflect.Ptr:
if !elem.IsNil() && elem.Elem().Kind() == reflect.String {
elem.Elem().SetString(callback(elem.Elem().String()))
}
case reflect.Struct:
if err := updateStringValues(&elem, callback); err != nil {
return fmt.Errorf("field error: %s", err.Error())
}
case reflect.String:
if elem.CanSet() {
elem.SetString(callback(elem.String()))
}
case reflect.Slice, reflect.Array:
if err := updateStringValuesList(&elem, callback); err != nil {
return fmt.Errorf("field error: %s", err.Error())
}
}
}
return nil
}