为了账号安全,请及时绑定邮箱和手机立即绑定

如何匹配字符串golang struct标签

如何匹配字符串golang struct标签

Go
UYOU 2022-11-23 14:00:59

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


{

  "id_employee": 123

}

或者


{

  "id_user": 321

}

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


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


interface User struct {

   Id int64 .....

}


查看完整描述

1 回答

?
holdtom

TA贡献1512条经验 获得超10个赞

在我们开始之前

一个小的免责声明:我在脑海中写下了下面的所有代码片段,没有校对或任何类似的东西。该代码尚未准备好复制粘贴。这个答案的重点是为您提供一些方法,让您可以按照您的要求进行操作,解释为什么选择给定选项可能是个好主意,等等……第三种方法绝对是更好的方法,但鉴于信息有限(没有具体 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 int


const (

    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 share

type BaseUser struct {

    Name   string `json:"name"`

    Active bool   `json:"active"`

    // etc...

}


// Employee is a user, that happens to be an employee

type 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{}. ,按类型分组,因此我可以以更理智的方式摄取数据。


查看完整回答
反对 回复 5天前

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信