1 回答
TA贡献1805条经验 获得超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{}. ,按类型分组,因此我可以以更理智的方式摄取数据。
- 1 回答
- 0 关注
- 122 浏览
添加回答
举报