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

发布于 2025-01-18 12:22:36 字数 3010 浏览 3 评论 0原文

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

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

type ReportType string

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

由于 go 不支持 struct 的类型断言(仅接口),因此在 Unmarshal。另外,go 不支持指向“raw”泛型类型的指针。因此,我创建了一个由 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...
    }

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

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

干杯, Mario :)

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

      // ...
    }
}

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-25 12:22:36

但是,但是我这样做,最终我总是需要一个接口{}或在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 和您的相关数据。
原文