首页 > 解决方案 > Golang - 创建一个与传递的相同类型的对象

问题描述

我正在尝试构建一个通用函数,它将输入(以 JSON 格式)解析为指定的结构。根据传递给函数的参数,结构在运行时可能会有所不同。我目前正在尝试通过传递正确类型的对象并使用 reflect.New() 创建相同类型的新输出对象来实现这一点。

然后我将 JSON 解析为这个对象,并扫描这些字段。

如果我创建对象并在代码中指定类型,那么一切正常。如果我传递一个对象并尝试创建一个副本,我会在几步之后得到一个“无效的间接”错误(参见代码)。

import (
    "fmt"
    "reflect"
    "encoding/json"
    "strings"
)

type Test struct {
    FirstName   *string `json:"FirstName"`
    LastName    *string `json:"LastName"`
}

func genericParser(incomingData *strings.Reader, inputStructure interface{}) (interface{}, error) {
    //******* Use the line below and things work *******
    //parsedInput := new(Test)


    //******* Use vvv the line below and things don't work *******
    parsedInput := reflect.New(reflect.TypeOf(inputStructure))

    decoder := json.NewDecoder(incomingData)
    err := decoder.Decode(&parsedInput)
    if err != nil {
        //parsing error
        return nil, err
    }

    //******* This is the line that generates the error "invalid indirect of parsedInput (type reflect.Value)" *******
    contentValues := reflect.ValueOf(*parsedInput)
    for i := 0; i < contentValues.NumField(); i++ {
        //do stuff with each field
        fmt.Printf("Field name was: %s\n", reflect.TypeOf(parsedInput).Elem().Field(i).Name)
    }
    return parsedInput, nil
}


func main() {
    inputData := strings.NewReader("{\"FirstName\":\"John\", \"LastName\":\"Smith\"}")
    exampleObject := new(Test)
    processedData, err := genericParser(inputData, exampleObject)
    if err != nil {
        fmt.Println("Parsing error")
    } else {
        fmt.Printf("Success: %v", processedData)
    }
}

If I can't create a replica of the object, then a way of updating / returning the one supplied would be feasible. The key thing is that this function must be completely agnostic to the different structures available.

标签: gotypes

解决方案


reflect.New isn't a direct analog to new, as it can't return a specific type, it only can return a reflect.Value. This means that you are attempting to unmarshal into a *reflect.Value, which obviously isn't going to work (even if it did, your code would have passed in **Type, which isn't what you want either).

Use parsedInput.Interface() to get the underlying value after creating the new value to unmarshal into. You then don't need to reflect on the same value a second time, as that would be a reflect.Value of a reflect.Value, which again isn't going to do anything useful.

Finally, you need to use parsedInput.Interface() before you return, otherwise you are returning the reflect.Value rather than the value of the input type.

For example:

func genericParser(incomingData io.Reader, inputStructure interface{}) (interface{}, error) {
    parsedInput := reflect.New(reflect.TypeOf(inputStructure).Elem())

    decoder := json.NewDecoder(incomingData)
    err := decoder.Decode(parsedInput.Interface())
    if err != nil {
        return nil, err
    }

    for i := 0; i < parsedInput.Elem().NumField(); i++ {
        fmt.Printf("Field name was: %s\n", parsedInput.Type().Elem().Field(i).Name)
    }
    return parsedInput.Interface(), nil
}

https://play.golang.org/p/CzDrj6sgQNt


推荐阅读