1 回答

TA貢獻(xiàn)1805條經(jīng)驗(yàn) 獲得超10個(gè)贊
在我們開(kāi)始之前
一個(gè)小的免責(zé)聲明:我在腦海中寫(xiě)下了下面的所有代碼片段,沒(méi)有校對(duì)或任何類似的東西。該代碼尚未準(zhǔn)備好復(fù)制粘貼。這個(gè)答案的重點(diǎn)是為您提供一些方法,讓您可以按照您的要求進(jìn)行操作,解釋為什么選擇給定選項(xiàng)可能是個(gè)好主意,等等……第三種方法絕對(duì)是更好的方法,但鑒于信息有限(沒(méi)有具體 WRT 您要解決的問(wèn)題),您可能需要做更多的挖掘才能獲得最終解決方案。
接下來(lái),我必須問(wèn)你為什么要嘗試做這樣的事情。如果您想要一種可用于解組不同有效負(fù)載的單一類型,我認(rèn)為您會(huì)引入很多代碼味道。如果有效負(fù)載不同,則它們必須代表不同的數(shù)據(jù)。想要對(duì)多個(gè)數(shù)據(jù)集使用單一的包羅萬(wàn)象的類型 IMO 只是在自找麻煩。我將提供幾種方法,但我想在開(kāi)始之前非常清楚這一點(diǎn):
盡管這是可能的,但這是個(gè)壞主意
一個(gè)較小的問(wèn)題,但我必須指出:您包括這樣的示例類型:
interface User struct {
Id int64
}
這是完全錯(cuò)誤的。具有字段的結(jié)構(gòu)不是接口,因此我將假設(shè)兩件事向前發(fā)展。一種是您需要專用的用戶類型,例如:
type Employee struct {
Id int64
}
type Employer struct {
Id int64
}
和一個(gè):
type User interface {
ID() int64
}
解組這些東西
因此,您可以通過(guò)多種方式完成您想要做的事情。凌亂但簡(jiǎn)單的方法是擁有一個(gè)包含字段所有可能排列的單一類型:
type AllUser struct {
UID int64 `json:"user_id"`
EID int64 `json:"employee_id"`
}
這可確保user_id employee_id您的 JSON 輸入中的兩個(gè)字段都能找到歸宿,并填充 ID 字段。但是,當(dāng)您想要實(shí)現(xiàn)User接口時(shí),真正的混亂很快就會(huì)變得明顯:
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?
}
對(duì)于 getter 來(lái)說(shuō),這只是很多樣板代碼,但是 setter 呢?該字段可能尚未設(shè)置。您需要找出一種方法來(lái)從單個(gè)設(shè)置器中設(shè)置正確的 ID 字段。傳入一個(gè)枚舉/常量來(lái)指定您要設(shè)置的字段,乍一看似乎是一種合理的方法,但仔細(xì)想想:它首先違背了擁有接口的目的。你會(huì)失去所有的抽象。所以這種做法是相當(dāng)有缺陷的。
此外,如果您設(shè)置了員工 ID,則其他 ID 字段將默認(rèn)為它們的 nil 值(0 表示 int64)。再次編組類型將導(dǎo)致 JSON 輸出如下:
{
"employee_id": 123,
"user_id": 0,
"employer_id": 0,
}
您可以通過(guò)將類型更改為使用指針來(lái)解決此問(wèn)題,并添加omitempty以跳過(guò)nilJSON 輸出中的字段:
type AllUser struct {
EID *int64 `json:"employee_id,omitempty"`
UID *int64 `json:"user_id,omitempty"`
}
同樣,這是一件令人討厭的事情,將導(dǎo)致您不得不在整個(gè)代碼中處理指針字段(在不同的時(shí)間點(diǎn)可能為 nil,也可能不為 nil)。這并不難做到,但它增加了很多噪音,使代碼更容易出現(xiàn)錯(cuò)誤,并且是一個(gè)全面的 PITA,你應(yīng)該盡可能避免。你可以很容易地避免它。
自定義編組
更好的方法是創(chuàng)建一個(gè)嵌入數(shù)據(jù)特定類型的基類型。假設(shè)我們已經(jīng)創(chuàng)建了我們的Employeeand EmployerorCustomer類型。這些類型都有一個(gè)ID字段,帶有自己的標(biāo)簽,如下所示:
type Employee struct {
ID int64 `json:"employee_id"`
}
type FooUser struct {
ID int64 `json:"foo_id"`
}
接下來(lái)要做的是創(chuàng)建一個(gè)嵌入所有特定用戶類型的半通用類型。name可以在此基本類型上添加共享字段(例如,如果所有數(shù)據(jù)集都有一個(gè)字段)。接下來(lái)您要做的是將此復(fù)合類型嵌入到另一種實(shí)現(xiàn)自定義編組/解組的類型中。這將允許您設(shè)置一些字段(就像我在此處的示例中包含的:例如,指定您正在處理的用戶類型的字段)。
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
}
現(xiàn)在要實(shí)現(xiàn)User接口,你可以在WrappedUser類型上實(shí)現(xiàn)它(BaseUser嵌入它,所以方法可以通過(guò)任何一種方式訪問(wèn)),你現(xiàn)在準(zhǔn)確地知道你需要獲取/設(shè)置哪些字段,因?yàn)槟阌蓄愋蜆?biāo)志來(lái)告訴你:
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
}
}
使用像這樣的自定義編組和嵌入類型稍微好一些,但正如您可能通過(guò)查看這個(gè)非常簡(jiǎn)單的示例可以看出的那樣,處理/維護(hù)它很快就會(huì)變得非常麻煩。
翻轉(zhuǎn)腳本
現(xiàn)在我假設(shè)您希望能夠?qū)⒉煌挠行ж?fù)載解組為單一類型,因?yàn)楹芏嘧侄问枪蚕淼?,但?ID 字段之類的東西可能不同(user_id與employee_id本例相比)。這是完全正常的。您在問(wèn)如何使用單一的包羅萬(wàn)象的類型。這是一個(gè) XY 問(wèn)題。與其詢問(wèn)如何為所有特定數(shù)據(jù)集使用單一類型,不如簡(jiǎn)單地為共享字段創(chuàng)建一個(gè)類型,然后依次將其包含到特定類型中?它與自定義編組的方法非常相似,但要簡(jiǎn)單大約 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在類型上實(shí)現(xiàn)接口的所有方法BaseUser,只在特定類型上實(shí)現(xiàn)ID getter/setter,就大功告成了。如果您需要覆蓋一個(gè)字段,就像我Name在FooUser類型上所做的那樣,那么您只需覆蓋該單一類型上該字段的 getter/setter:
func (f FooUser) Name() string {
return f.Name
}
func (f *FooUser) SetName(n string) {
f.Name = n
}
這就是您需要做的全部。好,易于。您正在使用 JSON 數(shù)據(jù)。這意味著您正在從某個(gè)地方獲取該數(shù)據(jù)(API,或者作為對(duì)某種數(shù)據(jù)存儲(chǔ)的查詢的響應(yīng))。如果您正在處理您請(qǐng)求的數(shù)據(jù),您至少應(yīng)該知道您期望什么樣的響應(yīng)數(shù)據(jù)。API 是契約:我調(diào)用 X,服務(wù)響應(yīng)我以給定格式請(qǐng)求的數(shù)據(jù)或錯(cuò)誤。我從商店查詢數(shù)據(jù)集 Y,要么得到請(qǐng)求的數(shù)據(jù),要么什么也得不到(可能會(huì)出錯(cuò))。
如果您從文件或某些服務(wù)中提取數(shù)據(jù),并且無(wú)法預(yù)測(cè)返回的內(nèi)容,則需要修復(fù)數(shù)據(jù)源。您不應(yīng)該嘗試圍繞更基本的問(wèn)題進(jìn)行編碼。必須,我會(huì)花一些時(shí)間編寫(xiě)一個(gè)小程序,例如,讀取源文件,將其解組為像map[string]interface{}. ,按類型分組,因此我可以以更理智的方式攝取數(shù)據(jù)。
- 1 回答
- 0 關(guān)注
- 155 瀏覽
添加回答
舉報(bào)