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

How to use generics in Unmarshal (go 1.18)

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

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

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

type Reportable interface {
    ExportDataPointReport | ImportDataPointReport | MissingDataPointReport | SensorThresoldReport
}

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

type ReportContainerImpl[T Reportable] struct {
    LocationID string `json:"lid"`
    Provider string `json:"pn"`
    ReportType ReportType `json:"m"`
    Body T `json:"body"`
}

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

type ReportType string

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

因为 go 不支持 struct 的类型断言(仅 interfaces)它是Unmarshal 时无法转换类型。此外 go 不支持指向 "raw" 通用类型的指针。因此,我创建了 接口 ReportContainerImpl 实现。

type ReportContainer interface {
    GetLocationID() string
    GetProvider() string
    GetReportType() ReportType
    GetBody() interface{}
}

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

    container, err := UnmarshalReportContainer(data)

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

也许我理解错了? - 但是无论我这样做,我总是在某处需要 interface{} 或在 Unmarshal

之前知道 exact 类型

干杯, 马里奥 :)

为了完整起见,我在此处添加 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

      // ...
    }
}

but however I do this, I always end up with somewhere needs a interface{} or to know the exact type before Unmarshal

没错。

实例化某些泛型类型或函数(如 ReportContainerImplUnmarshalReportContainer 所需的具体类型必须在编写代码时的编译时已知。 JSON 解组发生在 run-time,当你用实际数据填充字节片时。

要根据某些区分值解组动态 JSON,您仍然需要 switch

Do you have a better suggestion how to solve this in a type (safer) way?

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


附带说明一下,如果您发现自己使用多种结构类型编写联合,这也表明您可能需要使用普通的旧接口而不是类型参数。