如何在Unmarshal中使用仿制药(GO 1.18)

发布于 2025-01-22 22:27:22 字数 3081 浏览 3 评论 0原文

我是Golang Generics的新手,并拥有以下设置。

  1. 我收集了许多不同类型的报告。
  2. 每个报告都包含字段
  3. ,因此我将其包装在reportContainerimpl

我使用的类型参数[t efortable] 中,其中定义了efortboble如下所示,

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"`
}

我使用歧视器reportTypeunmarshal时确定具体类型。

type ReportType string

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

由于 go 不支持 struct (仅 interfaces )的类型断言,因此当unmarshal)不可能施放类型>。另外, go 不支持指向“原始” 通用类型的指针。因此,我创建了一个接口reportContainerimpl实施。

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

然后我得到的问题是,我无法对任何形式或形状的返回类型进行键入约束,然后在上回到 上的getbody()函数到当完成unmarshal时,允许类型断言。

    container, err := UnmarshalReportContainer(data)

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

也许我弄错了? - 但是我这样做,我总是最终到达某个地方需要一个接口{}或在 exact eact type之前unmarshal

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

干杯, Mario :)

为了完整性,我在此处添加umarshalreportContainer

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

      // ...
    }
}

I'm new to golang generics and have the following setup.

  1. I've gathered loads of different kinds of reports.
  2. Each report has enclosing fields
  3. So I wrapped it in a ReportContainerImpl

I've used a type argument of [T Reportable] where the Reportable is defined as follows

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

Each of the type in the type constraint is structs that is to be embedded in the container.

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

I use a discriminator ReportType to determine the concrete type when Unmarshal.

type ReportType string

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

Since go does not support type assertion for struct (only interfaces) it is not possible to cast the type when Unmarshal. Also go does not support pointer to the "raw" generic type. Hence, I've created a interface that the ReportContainerImpl implements.

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

The problem I then get is that I cannot do type constrains on the return type in any form or shape and am back at "freetext semantics" on the GetBody() function to allow for type assertion when Unmarshal is done.

    container, err := UnmarshalReportContainer(data)

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

Maybe I'm getting this wrong? - but however I do this, I always end up with somewhere needs a interface{} or to know the exact type before Unmarshal

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

Cheers,
Mario :)

For completeness I add the UnmarshalReportContainer here

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

      // ...
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

箜明 2025-01-29 22:27:22

但是,但是我这样做,最终我总是需要一个接口{}或在unmarshal

之前知道确切的类型

精确地知道unmarshal之前的确切类型。

实例化某些通用类型或功能所需的具体类型,例如ReportContainerimplunmarshalreportContainer必须在编译时(编写代码)在编译时知道。相反,当您将字节切片带有实际数据时,JSON UNMARSHALLING会发生在运行时。

要基于某些歧视价值来解开“动态JSON”,您仍然需要switch

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

仅仅是放弃参数多态性。这里不合适。保留您现在使用json.rawmessage的代码,在switch中有条件地删除动态数据,然后返回实现report> Report> ReportContainer接口的混凝土结构。


作为一般解决方案 - 如果并且只有在您可以克服这个鸡和蛋的问题并在编译时间已知的类型参数时,您可以写出这样的最小通用的透明函数:

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已经接受任何类型,因此,如果您的通用函数实际上没有执行任何操作“在unmarshalany不存在”整个过程中“插入”。

v, err := unmarshalAny[SomeType](src)

在功能上等效,就好像

out := &SomeType{}
err := json.Unmarshal(bytes, out)

您打算在umarshalany中放置更多逻辑一样,可能需要使用其用法。您的里程可能会有所不同;通常,在实际上不是必需的情况下,请勿使用类型参数。

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

Precisely.

The concrete types needed to instantiate some generic type or function like ReportContainerImpl or UnmarshalReportContainer must be known at compile time, when you write the code. JSON unmarshalling instead occurs at run-time, when you have the byte slice populated with the actual data.

To unmarshal dynamic JSON based on some discriminatory value, you still need a switch.

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

Just forgo parametric polymorphism. It's not a good fit here. Keep the code you have now with json.RawMessage, unmarshal the dynamic data conditionally in the switch and return the concrete structs that implement ReportContainer interface.


As a general solution — if, and only if, you can overcome this chicken-and-egg problem and make type parameters known at compile time, you can write a minimal generic unmarshal function like this:

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
}

This is only meant to illustrate the principle. Note that json.Unmarshal already accepts any type, so if your generic function actually does nothing except new(T) and return, like in my example, it is no different than "inlining" the entire thing as if unmarshalAny didn't exist.

v, err := unmarshalAny[SomeType](src)

functionally equivalent as

out := &SomeType{}
err := json.Unmarshal(bytes, out)

If you plan to put more logic in unmarshalAny, its usage may be warranted. Your mileage may vary; in general, don't use type parameters when it's not actually necessary.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文