首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何在解组中使用泛型(go 1.18)

如何在解组中使用泛型(go 1.18)
EN

Stack Overflow用户
提问于 2022-04-21 13:12:43
回答 1查看 3.2K关注 0票数 9

我是新来的金刚泛型,并有以下设置。

  1. 我收集了很多不同类型的报告。
  2. 每个报表都包含有
  3. 字段,所以我将其包装在一个

我使用了[T Reportable]的类型参数,其中Reportable定义如下

代码语言:javascript
复制
type Reportable interface {
    ExportDataPointReport | ImportDataPointReport | MissingDataPointReport | SensorThresoldReport
}

类型约束中的每个类型都是要嵌入到容器中的结构。

代码语言:javascript
复制
type ReportContainerImpl[T Reportable] struct {
    LocationID string `json:"lid"`
    Provider string `json:"pn"`
    ReportType ReportType `json:"m"`
    Body T `json:"body"`
}

我使用鉴别器ReportType来确定Unmarshal时的具体类型。

代码语言:javascript
复制
type ReportType string

const (
    ReportTypeExportDataPointReport ReportType = "ExportDataPointReport"
    ReportTypeImportDataPointReport ReportType = "ImportDataPointReport"
    ReportTypeMissingDataPointReport ReportType = "MissingDataPointReport"
    ReportTypeSensorThresoldReport ReportType = "SensorThresoldReport"
)

由于go不支持struct的类型断言(仅支持接口),所以当Unmarshal时不可能转换该类型。此外,go不支持指向"raw“泛型的指针。因此,我创建了一个ReportContainerImpl实现的接口。

代码语言:javascript
复制
type ReportContainer interface {
    GetLocationID() string
    GetProvider() string
    GetReportType() ReportType
    GetBody() interface{}
}

然后我遇到的问题是,我不能以任何形式或形状对返回类型进行类型约束,并且返回到GetBody()函数的"freetext语义“,以便在Unmarshal完成时允许类型断言。

代码语言:javascript
复制
    container, err := UnmarshalReportContainer(data)

    if rep, ok := container.GetBody().(ExportDataPointReport); ok {
      // Use the ReportContainerImpl[ExportDataPointReport] here...
    }

也许我搞错了?-但不管我怎么做,我总是在需要一个interface{}的地方,或者在Unmarshal之前知道确切的类型。

  • 你有更好的建议如何以一种更安全的方式解决这个问题吗?

干杯,马里奥:)

为了完整起见,我在这里添加了UnmarshalReportContainer

代码语言:javascript
复制
func UnmarshalReportContainer(data []byte) (ReportContainer, error) {

    type Temp struct {
        LocationID string `json:"lid"`
        Provider string `json:"pn"`
        ReportType ReportType `json:"m"`
        Body *json.RawMessage `json:"body"`
    }

    var temp Temp
    err := json.Unmarshal(data, &temp)
    if err != nil {
        return nil, err
    }

    switch temp.ReportType {
    case ReportTypeExportDataPointReport:
        var report ExportDataPointReport
        err := json.Unmarshal(*temp.Body, &report)
        return &ReportContainerImpl[ExportDataPointReport]{
            LocationID: temp.LocationID,
            Provider:   temp.Provider,
            ReportType: temp.ReportType,
            Body:       report,
        }, err

      // ...
    }
}
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2022-04-21 13:35:57

--但是不管我怎么做,我最终总是需要一个接口{},或者在解组之前知道确切的类型

确切地说。

在编写代码时,必须在编译时知道实例化某些泛型类型或函数(如ReportContainerImplUnmarshalReportContainer )所需的具体类型。相反,JSON解编组发生在运行时,当字节片填充实际数据时。

要基于某些歧视性值对动态JSON进行封送,仍然需要一个switch

您有更好的建议如何以一种类型(更安全)的方式解决这个问题吗?

放弃参数多态性。这里不太合身。保留现在使用json.RawMessage的代码,在switch中有条件地解除动态数据的封送,并返回实现ReportContainer接口的具体结构。

作为一种通用解决方案--如果并且只有在这样的情况下,您才能克服这个鸡和蛋问题,并在编译时使类型参数为人所知,您可以编写一个最小的泛型解组函数,如下所示:

代码语言:javascript
复制
func unmarshalAny[T any](bytes []byte) (*T, error) {
    out := new(T)
    if err := json.Unmarshal(bytes, out); err != nil {
        return nil, err
    }
    return out, nil
}

这只是为了说明这一原则。请注意,json.Unmarshal已经接受任何类型,因此如果泛型函数实际上只执行new(T)并返回(如我的示例中所示),它与“内联”整件事情没有什么区别,就好像unmarshalAny不存在一样。

代码语言:javascript
复制
v, err := unmarshalAny[SomeType](src)

功能等价于

代码语言:javascript
复制
out := &SomeType{}
err := json.Unmarshal(bytes, out)

如果您计划在unmarshalAny中添加更多的逻辑,则可能需要使用它。您的里程可能会有所不同;一般来说,在没有必要的情况下,不要使用类型参数。

票数 9
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/71955113

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档