如何递归更新 Go 结构中的所有字符串?

问题描述 投票:0回答:1

我想递归更新结构中的所有字符串,包括结构中的所有字符串和任何切片。

我尝试了这个代码这个答案,但都没有通过我的这个单元测试:

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
}

如何正确地递归更新结构中的所有字符串,包括结构中任何切片中的所有字符串?

go reflection
1个回答
0
投票

代码中的问题在于如何处理指向结构体字段和切片的指针。当遇到指向结构或切片的指针时,需要正确取消引用它以访问其底层结构或切片值。此外,您需要处理字段或切片元素是指向字符串的指针的情况。

试试这个:

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
}
© www.soinside.com 2019 - 2024. All rights reserved.