假设我有以下代码:

type Type1 struct {
    Name string `json:"name,omitempty"`
    Path string `json:"path"`
    File string `json:"file"`
    Tag  int    `json:"tag"`
    Num  int    `json:"num"`
}

func LoadConfiguration(data []byte) (*Type1, error) {
    config, err := loadConf1(data)
    if err != nil {
        return nil, err
    }
    confOther, err := loadConfOther1()
    if err != nil {
        return nil, err
    }
    // do something with confOther
    fmt.Println("confOther", confOther)
    if confOther.Tag == 0 {
        config.Num = 5
    }

    // do something with config attributes of type1
    if config.Tag == 0 {
        config.Tag = 5
    }
    if config.Num == 0 {
        config.Num = 4
    }

    return config, nil
}

func loadConf1(bytes []byte) (*Type1, error) {
    config := &Type1{}
    if err := json.Unmarshal(bytes, config); err != nil {
        return nil, fmt.Errorf("cannot load config: %v", err)
    }

    return config, nil
}

func loadConfOther1() (*Type1, error) {
    // return value of this specific type
    flatconfig := &Type1{}

    // read a file as []byte
    // written as a fixed array to simplify this example
    fileContent := []byte{10, 22, 33, 44, 55}

    if err := json.Unmarshal(fileContent, flatconfig); err != nil {
        return nil, fmt.Errorf("cannot read config %v", err)
    }

    return flatconfig, nil
}

唯一的公共功能是LoadConfiguration.

它基于真实代码,用于将json数据作为特定 struct 读取.如果有些东西看起来没用,那是因为我简化了原始代码.

上面的代码还可以,但现在我想创建另一个名为"Type2"的 struct 类型,并重新使用相同的方法将数据读入Type2,而无需复制和粘贴所有内容.

type Type2 struct {
    Name  string                  `json:"name,omitempty"`
    Path  string                  `json:"path"`
    Map   *map[string]interface{} `json:"map"`
    Other string                  `json:"other"`
}

基本上,我想打LoadConfiguration也能得到Type2.我可以接受调用像LoadConfiguration2这样的特定方法,但我不想同时复制和粘贴loadConf1loadConfOther1.

推荐答案

实际上,问题中显示的代码只不过是将一个类型传递到json.Unmarshal并格式化一个错误,这样您就可以重写您的函数,使其行为与之类似:

func LoadConfiguration(data []byte) (*Type1, error) {
    config := &Type1{}
    if err := loadConf(data, config); err != nil {
        return nil, err
    }
    // ...
}

// "magically" accepts any type
// you could actually get rid of the intermediate function altogether
func loadConf(bytes []byte, config any) error {
    if err := json.Unmarshal(bytes, config); err != nil {
        return fmt.Errorf("cannot load config: %v", err)
    }
    return nil
}

如果代码实际上不仅仅是将指针传递到json.Unmarshal,它还可以受益于类型参数.

type Configurations interface {
    Type1 | Type2
}

func loadConf[T Configurations](bytes []byte) (*T, error) {
    config := new(T)
    if err := json.Unmarshal(bytes, config); err != nil {
        return nil, fmt.Errorf("cannot load config: %v", err)
    }
    return config, nil
}

func loadConfOther[T Configurations]() (*T, error) {
    flatconfig := new(T)
    // ... code
    return flatconfig, nil
}

在这些情况下,您可以创建一个具有new(T)的任意类型的新指针,然后json.Unmarshal将负责将字节片或文件的内容反序列化到其中-前提是JSON实际上可以解组到任意一个 struct 中.

顶级函数中特定于类型的代码应该仍然不同,特别是因为您希望用显式具体类型实例化泛型函数.所以我建议保留LoadConfiguration1LoadConfiguration2.

func LoadConfiguration1(data []byte) (*Type1, error) {
    config, err := loadConf[Type1](data)
    if err != nil {
        return nil, err
    }
    confOther, err := loadConfOther[Type1]()
    if err != nil {
        return nil, err
    }

    // ... type specific code

    return config, nil
}

然而,如果特定类型的代码只是其中的一小部分,那么您可能可以为特定部分使用类型switch ,尽管在您的情况下,这似乎不是一个可行的 Select .我看起来像:

func LoadConfiguration[T Configuration](data []byte) (*T, error) {
    config, err := loadConf[T](data)
    if err != nil {
        return nil, err
    }
    // let's pretend there's only one value of type parameter type
    // type-specific code
    switch t := config.(type) {
        case *Type1:
            // ... some *Type1 specific code
        case *Type2:
            // ... some *Type2 specific code
        default:
            // can't really happen because T is restricted to Configuration but helps catch errors if you extend the union and forget to add a corresponding case
            panic("invalid type")
    }

    return config, nil
}

最小示例操场:https://go.dev/play/p/-rhIgoxINTZ

Go相关问答推荐

go grpc:无法导入github.com/golang/protobuf/proto(没有所需的模块提供包github.com/gorang/protobuf-proto)

格式化 Azure SDK 的 golang 时间

go aws-lambda 与 terraform 中的 exec 格式错误

Go 1.20 中如何计算连接错误?

不接受来自 stdin 的重复输入

Go 是否提供了标准或事实上的方法来处理单个语句中的错误(即内联错误处理)?

从 wincrypt API 到 Go 的 RC2 解密

如何绕过深层 xml,没有嵌套循环?

Exchange Web 服务 - 使用 soap xml 请求查找所有未读邮件

当填充通道的函数调用未嵌入 goroutine 时,为什么我会遇到死锁?

golang中如何声明多个接口约束?

在两个单独的速率受限端点之间同步请求

使用 unsafe.Pointer 将 struct point直接转换为另一个 struct 是否安全?

使用 AppID 在 Windows 中启动应用程序并获取 pid

Gorm 在保存/创建时序列化 struct

如何在 GORM 中迭代一个 int 数组

Go:为一组单个结果实现 ManyDecode

Go generics:我会在哪里使用 any 而不是 interface{}?

为什么在 unsafe.Sizeof() 中取消引用 nil 指针不会导致panic ?

如何在程序退出时使用 golang 删除文件?