如何在 Unmarshal 中使用泛型(转到 1.18)

Mar*_*fia 10 generics marshalling go unmarshalling

我是 golang 泛型的新手,并且有以下设置。

  1. 我收集了大量不同类型的报告。
  2. 每个报告都有封闭字段
  3. 所以我把它包裹在一个ReportContainerImpl

我使用了一个类型参数, [T Reportable]Reportable定义如下

type Reportable interface {
    ExportDataPointReport | ImportDataPointReport | MissingDataPointReport | SensorThresoldReport
}
Run Code Online (Sandbox Code Playgroud)

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

type ReportContainerImpl[T Reportable] struct {
    LocationID string `json:"lid"`
    Provider string `json:"pn"`
    ReportType ReportType `json:"m"`
    Body T `json:"body"`
}
Run Code Online (Sandbox Code Playgroud)

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

type ReportType string

const (
    ReportTypeExportDataPointReport ReportType = "ExportDataPointReport"
    ReportTypeImportDataPointReport ReportType = "ImportDataPointReport"
    ReportTypeMissingDataPointReport ReportType = "MissingDataPointReport"
    ReportTypeSensorThresoldReport ReportType = "SensorThresoldReport"
)
Run Code Online (Sandbox Code Playgroud)

由于go不支持struct的类型断言(仅支持接口),因此无法在 when 时强制转换类型Unmarshal。另外,go不支持指向“原始”泛型类型的指针。因此,我创建了一个实现的接口ReportContainerImpl

type ReportContainer interface {
    GetLocationID() string
    GetProvider() string
    GetReportType() ReportType
    GetBody() interface{}
}
Run Code Online (Sandbox Code Playgroud)

然后我遇到的问题是,我无法以任何形式或形状对返回类型进行类型约束,并且回到函数上的“自由文本语义”GetBody()以允许在Unmarshal完成时进行类型断言。

    container, err := UnmarshalReportContainer(data)

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

也许我理解错了?- 但无论我这样做,我总是会在某个地方需要 a或之前interface{}知道确切的类型Unmarshal

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

干杯,马里奥:)

UnmarshalReportContainer为了完整起见,我在此处添加

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

      // ...
    }
}
Run Code Online (Sandbox Code Playgroud)

bla*_*een 10

\n

但无论我这样做,我总是会在某个地方需要一个接口{}或在 Unmarshal 之前知道确切的类型

\n
\n

恰恰。

\n

ReportContainerImpl当您编写代码时,实例化某些泛型类型或函数(例如或 )所需的具体类型UnmarshalReportContainer必须在编译时已知。当您使用实际数据填充字节切片时,JSON 解组会在运行时发生。

\n

要根据某些歧视性值解组动态 JSON,您仍然需要一个switch.

\n
\n

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

\n
\n

只是放弃参数多态性。这里不太适合。保留现在的代码json.RawMessage,有条件地解组动态数据switch并返回实现接口的具体结构ReportContainer

\n
\n

作为通用解决方案 \xe2\x80\x94 当且仅当您可以克服这个先有鸡还是先有蛋的问题并在编译时已知类型参数时,您可以编写一个最小的通用解组函数,如下所示:

\n
func unmarshalAny[T any](bytes []byte) (*T, error) {\n    out := new(T)\n    if err := json.Unmarshal(bytes, out); err != nil {\n        return nil, err\n    }\n    return out, nil\n}\n
Run Code Online (Sandbox Code Playgroud)\n

这只是为了说明原理。请注意,它json.Unmarshal已经接受任何类型,因此如果您的泛型函数实际上除了new(T)返回之外什么都不做,就像在我的示例中一样,它与“内联”整个事物没有什么不同,就好像unmarshalAny不存在一样。

\n
v, err := unmarshalAny[SomeType](src)\n
Run Code Online (Sandbox Code Playgroud)\n

功能上等同于

\n
out := &SomeType{}\nerr := json.Unmarshal(bytes, out)\n
Run Code Online (Sandbox Code Playgroud)\n

如果您计划在 中放入更多逻辑unmarshalAny,则可能有必要使用它。你的旅费可能会改变; 一般来说,当实际上没有必要时不要使用类型参数。

\n