我有一個帶有動態鍵的 json 資料,例如:
{
"id_employee": 123
}
要么
{
"id_user": 321
}
我正在嘗試將資料解組為結構。
在解組資料后,如何創建一個結構標簽來匹配示例中的所有這兩個鍵“id_user”和“id_employee”?
interface User struct {
Id int64 .....
}
uj5u.com熱心網友回復:
在我們開始之前
一個小小的免責宣告:我寫了下面所有的代碼片段,沒有校對,或者類似的東西。該代碼尚未準備好復制粘貼。這個答案的重點是為您提供一些方法,讓您可以按照您的要求去做,解釋為什么選擇給定選項可能是一個好主意,也可能不是一個好主意,等等......第三種方法絕對是最好的方法,但是鑒于資訊有限(沒有具體說明您要解決的問題),您可能需要進行更多挖掘才能找到最終解決方案。
接下來,我不得不問你為什么要嘗試做這樣的事情。如果您想要一種可以用來解組不同有效負載的單一型別,我認為您會引入很多代碼異味。如果有效載荷不同,它們必須代表不同的資料。想要對多個資料集使用單一的包羅萬象的型別 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?
}
For getters, that's just a lot of boilerplate to get through, but what about setters? The field may not have been set yet. You'd need to figure out a way to set the correct ID field from a single setter. Passing in an enum/constant to specify what field you're looking to set may at first seem like a reasonable approach, but think about it: it kind of defeats the purpose of having an interface in the first place. You'd lose any and all abstraction. So this approach is quite flawed.
Furthermore, if you have an employee ID set, the other ID fields will default to their nil values (0 for int64). Marshalling the type again will result in JSON output like this:
{
"employee_id": 123,
"user_id": 0,
"employer_id": 0,
}
You can address this issue by changing your type to use pointers, and add omitempty to skip nil fields from the JSON output:
type AllUser struct {
EID *int64 `json:"employee_id,omitempty"`
UID *int64 `json:"user_id,omitempty"`
}
Again, this is nasty business, and will result in you having to deal with pointer fields (which may or may not be nil at different points in time) throughout the code. It's not that difficult to do, but it adds a lot of noise, makes the code more prone to bugs, and is just all-round a PITA that you should avoid if you can. And you can avoid it quite easily.
Custom marshalling
A better approach would be to create a base type that embeds data-specific types. Assuming we have created our Employee and Employer or Customer types. These types all have an ID field, with their own tags, like this:
type Employee struct {
ID int64 `json:"employee_id"`
}
type FooUser struct {
ID int64 `json:"foo_id"`
}
The next thing to do is to create a semi-generic type that embeds all specific user types. Shared fields (e.g. if all data-sets have a name field) can be added on this base type. The next thing you'll have to do is embed this composite type into yet another type that implements custom marshal/unmarshalling. This will allow you to set some fields (like I've included in the example here: a field that specifies that type of user you're dealing with, for example).
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
}
To implement the User interface now, you can implement it on the WrappedUser type (the BaseUser embeds it, so the methods will be accessible either way), and you now know precisely what fields you need to get/set because you have the type flag to tell you:
func (w WrappedUser) ID() int64 {
switch w.Type {
case EmployeeUserType:
return w.Employee.ID
case FooUserType:
return w.FooUser.ID
}
return 0
}
The same can be done with setters:
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
}
}
Using custom marshalling and embedding types like this is slightly better, but as you probably can tell by looking at this one, pretty simple example already, it quickly becomes quite cumbersome to handle/maintain.
Flipping the script
Now I'm assuming that you want to be able to unmarshal different payloads into a single type because a lot of fields are shared, but things like the ID field might be different (user_id vs employee_id in this case). That's perfectly normal. You're asking how you can use a single catch-all type. That's kind of an X-Y problem. Instead of asking how you can use a single type for all specific data-sets, why not simply create a type for the shared fields, and include that into specific types in turn? It's very similar to the approach with the custom marshalling, but it's ~1,000,000 times simpler:
// 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
}
Implement all methods for the User interface of on the BaseUser type, and just implement the ID getter/setter on the specific types, and you're done. If you need to override a field, like I did for Name on the FooUser type, then you just override the getter/setter for that field on that single type:
func (f FooUser) Name() string {
return f.Name
}
func (f *FooUser) SetName(n string) {
f.Name = n
}
That's all you need to do. Nice and easy. You're consuming JSON data. That implies you're getting that data from somewhere (either an API, or as a response from a query to some kind of data store). If you're processing data you requested, you should at the very least know what kind of response data you expect. API's are contracts: I make call X, and the service responds with either the data I request in a given format, or an error. I query data-set Y from a store, and I either get the requested data, or I don't get anything (potentially, I get an error).
If you're ingesting data from a file, or from some service, and you can't predict what you're getting back, you need to fix your data-source. You shouldn't be trying to code around a more fundamental problem. Needs must, I'd spend some time writing a small program that, for instance, reads the source file, unmarshals it into something as crude as a map[string]interface{}, check what keys each object contains, and I'd write the data out into distinct files, grouped by type, so I can ingest the data in a more sane way.
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/444030.html
標籤:走
