如何匹配字符串golang struct标签

我有一个带有动态键的 json 数据,例如:


{

  "id_employee": 123

}

或者


{

  "id_user": 321

}

我正在尝试将数据解组为结构。


在解组数据后,如何创建结构标签以匹配示例中的所有这两个键“id_user”和“id_employee”?


interface User struct {

   Id int64 .....

}


UYOU
浏览 103回答 1
1回答

holdtom

在我们开始之前一个小的免责声明:我在脑海中写下了下面的所有代码片段,没有校对或任何类似的东西。该代码尚未准备好复制粘贴。这个答案的重点是为您提供一些方法,让您可以按照您的要求进行操作,解释为什么选择给定选项可能是个好主意,等等……第三种方法绝对是更好的方法,但鉴于信息有限(没有具体 WRT 您要解决的问题),您可能需要做更多的挖掘才能获得最终解决方案。接下来,我必须问你为什么要尝试做这样的事情。如果您想要一种可用于解组不同有效负载的单一类型,我认为您会引入很多代码味道。如果有效负载不同,则它们必须代表不同的数据。想要对多个数据集使用单一的包罗万象的类型 IMO 只是在自找麻烦。我将提供几种方法,但我想在开始之前非常清楚这一点:尽管这是可能的,但这是个坏主意一个较小的问题,但我必须指出:您包括这样的示例类型:interface User struct {    Id int64}这是完全错误的。具有字段的结构不是接口,因此我将假设两件事向前发展。一种是您需要专用的用户类型,例如:type Employee struct {    Id int64}type Employer struct {    Id int64}和一个:type User interface {   ID() int64}解组这些东西因此,您可以通过多种方式完成您想要做的事情。凌乱但简单的方法是拥有一个包含字段所有可能排列的单一类型:type AllUser struct {    UID int64 `json:"user_id"`    EID int64 `json:"employee_id"`}这可确保user_id employee_id您的 JSON 输入中的两个字段都能找到归宿,并填充 ID 字段。但是,当您想要实现User接口时,真正的混乱很快就会变得明显:func (a AllUser) ID() int64 {    if a.UID != 0 {        return a.UID    }    if a.EID != 0 {        return a.EID    }    // and so on    return 0 // probably an error?}对于 getter 来说,这只是很多样板代码,但是 setter 呢?该字段可能尚未设置。您需要找出一种方法来从单个设置器中设置正确的 ID 字段。传入一个枚举/常量来指定您要设置的字段,乍一看似乎是一种合理的方法,但仔细想想:它首先违背了拥有接口的目的。你会失去所有的抽象。所以这种做法是相当有缺陷的。此外,如果您设置了员工 ID,则其他 ID 字段将默认为它们的 nil 值(0 表示 int64)。再次编组类型将导致 JSON 输出如下:{    "employee_id": 123,    "user_id": 0,    "employer_id": 0,}您可以通过将类型更改为使用指针来解决此问题,并添加omitempty以跳过nilJSON 输出中的字段:type AllUser struct {    EID *int64 `json:"employee_id,omitempty"`    UID *int64 `json:"user_id,omitempty"`}同样,这是一件令人讨厌的事情,将导致您不得不在整个代码中处理指针字段(在不同的时间点可能为 nil,也可能不为 nil)。这并不难做到,但它增加了很多噪音,使代码更容易出现错误,并且是一个全面的 PITA,你应该尽可能避免。你可以很容易地避免它。自定义编组更好的方法是创建一个嵌入数据特定类型的基类型。假设我们已经创建了我们的Employeeand EmployerorCustomer类型。这些类型都有一个ID字段,带有自己的标签,如下所示:type Employee struct {    ID int64 `json:"employee_id"`}type FooUser struct {    ID int64 `json:"foo_id"`}接下来要做的是创建一个嵌入所有特定用户类型的半通用类型。name可以在此基本类型上添加共享字段(例如,如果所有数据集都有一个字段)。接下来您要做的是将此复合类型嵌入到另一种实现自定义编组/解组的类型中。这将允许您设置一些字段(就像我在此处的示例中包含的:例如,指定您正在处理的用户类型的字段)。type UserType intconst (    EmployeeUserType UserType = iota    FooUserType    // go-style enum values for all user-types)type BaseUser struct {    WrappedUser}type WrappedUser struct {    *Employee // embed pointers to these types    *FooUser    Name       string   `json:"name"`    Type       UserType `json:"-"` // ignore this in JSON unmarshalling}func (b *BaseUser) UnmarshalJSON(data []byte) error {    if err := json.Unmarshal(data, &b.WrappedUser); err != nil {        return err    }    if b.Employee != nil {        b.Type = EmployeeUserType // set the user-type flag    }    if b.FooUser != nil {        b.Type = FooUserType    }    return nil}func (b BaseUser) MarshalJSON() ([]byte, error) {    return json.Marshal(b.WrappedUser) // wrapped user doesn't have any custom handling}现在要实现User接口,你可以在WrappedUser类型上实现它(BaseUser嵌入它,所以方法可以通过任何一种方式访问),你现在准确地知道你需要获取/设置哪些字段,因为你有类型标志来告诉你:func (w WrappedUser) ID() int64 {    switch w.Type {    case EmployeeUserType:        return w.Employee.ID    case FooUserType:        return w.FooUser.ID    }    return 0}setter 也可以这样做:func (w *WrappedUser) SetID(id int64) {    switch w.Type {    case EmployeeUserType:        if w.Employee == nil {            w.Employee = &Employee{}        }        w.Employee.ID = id    case FooUserType:        if w.FooUser == nil {            w.FooUser = &FooUser{}        }        w.FooUser.ID = id    }}使用像这样的自定义编组和嵌入类型稍微好一些,但正如您可能通过查看这个非常简单的示例可以看出的那样,处理/维护它很快就会变得非常麻烦。翻转脚本现在我假设您希望能够将不同的有效负载解组为单一类型,因为很多字段是共享的,但是 ID 字段之类的东西可能不同(user_id与employee_id本例相比)。这是完全正常的。您在问如何使用单一的包罗万象的类型。这是一个 XY 问题。与其询问如何为所有特定数据集使用单一类型,不如简单地为共享字段创建一个类型,然后依次将其包含到特定类型中?它与自定义编组的方法非常相似,但要简单大约 1,000,000 倍:// BaseUser contains all fields all specific user-types sharetype BaseUser struct {    Name   string `json:"name"`    Active bool   `json:"active"`    // etc...}// Employee is a user, that happens to be an employeetype Employee struct {    ID int64 `json:"employee_id"`    BaseUser // embed the other fields that all users share here}type FooUser struct {    ID int64 `json:"foo_id"`    BaseUser    Name string `json:"foo_user"` // override the name field of BaseUser}User在类型上实现接口的所有方法BaseUser,只在特定类型上实现ID getter/setter,就大功告成了。如果您需要覆盖一个字段,就像我Name在FooUser类型上所做的那样,那么您只需覆盖该单一类型上该字段的 getter/setter:func (f FooUser) Name() string {    return f.Name}func (f *FooUser) SetName(n string) {    f.Name = n}这就是您需要做的全部。好,易于。您正在使用 JSON 数据。这意味着您正在从某个地方获取该数据(API,或者作为对某种数据存储的查询的响应)。如果您正在处理您请求的数据,您至少应该知道您期望什么样的响应数据。API 是契约:我调用 X,服务响应我以给定格式请求的数据或错误。我从商店查询数据集 Y,要么得到请求的数据,要么什么也得不到(可能会出错)。如果您从文件或某些服务中提取数据,并且无法预测返回的内容,则需要修复数据源。您不应该尝试围绕更基本的问题进行编码。必须,我会花一些时间编写一个小程序,例如,读取源文件,将其解组为像map[string]interface{}. ,按类型分组,因此我可以以更理智的方式摄取数据。
打开App,查看更多内容
随时随地看视频慕课网APP