1个interface、2个packages场景下,能否用接口式Balance struct实现统一unmarshalling?
刚好之前在项目里处理过类似的多API JSON适配问题,核心是要让同一个业务实体(Balance)能兼容不同的JSON序列化规则,同时复用你的接口定义。下面给你几个实用的解决方案,从简单到灵活都有:
方案1:自定义
UnmarshalJSON实现多字段兼容 这是最直接的方案——定义一个全局的GlobalBalance结构体,让它实现json.Unmarshaler接口,在自定义的反序列化方法里兼容不同API返回的JSON格式。
package main import ( "encoding/json" "fmt" ) // 你的全局Balance接口 type Balance interface { GetAmount() float64 GetCurrency() string } // 全局通用的Balance结构体,实现Balance接口 type GlobalBalance struct { Amount float64 `json:"-"` // 用"-"避免默认JSON序列化/反序列化 Currency string `json:"-"` } func (b *GlobalBalance) GetAmount() float64 { return b.Amount } func (b *GlobalBalance) GetCurrency() string { return b.Currency } // 自定义UnmarshalJSON,兼容两种不同的JSON格式 func (b *GlobalBalance) UnmarshalJSON(data []byte) error { // 定义两个临时结构体,分别对应不同API的JSON字段规则 type api1Balance struct { Amount float64 `json:"balance"` Currency string `json:"currency"` } type api2Balance struct { Amount float64 `json:"available_balance"` Currency string `json:"curr"` } // 先尝试解析API1的格式 var api1 api1Balance if err := json.Unmarshal(data, &api1); err == nil { b.Amount = api1.Amount b.Currency = api1.Currency return nil } // 解析失败则尝试API2的格式 var api2 api2Balance if err := json.Unmarshal(data, &api2); err == nil { b.Amount = api2.Amount b.Currency = api2.Currency return nil } return fmt.Errorf("无法解析Balance的JSON格式:%s", string(data)) }
这样不管是哪种API返回的JSON,都能直接反序列化成GlobalBalance,而它又实现了你的全局Balance接口,完全不用再维护两个包里的重复结构体。
方案2:泛型+结构体嵌入(Go 1.18+)
如果你的API字段差异有规律,且需要复用更多业务逻辑,可以用泛型来封装不同的字段标签结构体,再嵌入到全局结构体中:
// 全局Balance接口不变 type Balance interface { GetAmount() float64 GetCurrency() string } // 定义约束:所有字段结构体必须实现BalanceGetter接口 type BalanceGetter interface { GetAmount() float64 GetCurrency() string } // 全局通用结构体,嵌入泛型字段结构体 type GlobalBalance[T BalanceGetter] struct { Inner T } // 实现Balance接口,直接委托给Inner func (b *GlobalBalance[T]) GetAmount() float64 { return b.Inner.GetAmount() } func (b *GlobalBalance[T]) GetCurrency() string { return b.Inner.GetCurrency() } // API1对应的字段结构体 type AP1BalanceFields struct { Amount float64 `json:"balance"` Currency string `json:"currency"` } func (f *AP1BalanceFields) GetAmount() float64 { return f.Amount } func (f *AP1BalanceFields) GetCurrency() string { return f.Currency } // API2对应的字段结构体 type AP2BalanceFields struct { Amount float64 `json:"available_balance"` Currency string `json:"curr"` } func (f *AP2BalanceFields) GetAmount() float64 { return f.Amount } func (f *AP2BalanceFields) GetCurrency() string { return f.Currency }
这种方式既保留了不同API的JSON标签规则,又通过泛型和接口复用了所有业务逻辑,适合复杂嵌套结构的场景——嵌套的子结构体也可以用同样的模式处理。
方案3:基于
map[string]interface{}的灵活适配 如果API的JSON结构差异极大(比如字段名完全无规律、嵌套层级不同),可以先把JSON解析成原始map,再根据键的存在情况手动赋值:
func (b *GlobalBalance) UnmarshalJSON(data []byte) error { var rawMap map[string]interface{} if err := json.Unmarshal(data, &rawMap); err != nil { return err } // 适配不同的金额字段名 if amountVal, ok := rawMap["balance"]; ok { b.Amount, _ = amountVal.(float64) } else if amountVal, ok := rawMap["available_balance"]; ok { b.Amount, _ = amountVal.(float64) } else { return fmt.Errorf("未找到金额字段") } // 适配不同的货币字段名 if currVal, ok := rawMap["currency"]; ok { b.Currency, _ = currVal.(string) } else if currVal, ok := rawMap["curr"]; ok { b.Currency, _ = currVal.(string) } else { return fmt.Errorf("未找到货币字段") } return nil }
这种方式灵活性最高,但需要处理类型断言的安全问题,适合字段规则极不统一的场景。
内容的提问来源于stack exchange,提问作者lixid
相关产品推荐
相关产品推荐

